Extends the Chrome Developer Tools, adding a new Network Panel in the Developer Tools window with better searching and response previews. https://leviolson.com/posts/chrome-ext-better-network-panel
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

514 lines
18 KiB

4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. function Console() {}
  2. Console.Type = {
  3. LOG: "log",
  4. DEBUG: "debug",
  5. INFO: "info",
  6. WARN: "warn",
  7. ERROR: "error",
  8. GROUP: "group",
  9. GROUP_COLLAPSED: "groupCollapsed",
  10. GROUP_END: "groupEnd"
  11. };
  12. Console.addMessage = function (type, format, args) {
  13. chrome.runtime.sendMessage({
  14. command: "sendToConsole",
  15. tabId: chrome.devtools.inspectedWindow.tabId,
  16. args: escape(JSON.stringify(Array.prototype.slice.call(arguments, 0)))
  17. });
  18. };
  19. // Generate Console output methods, i.e. Console.log(), Console.debug() etc.
  20. (function () {
  21. var console_types = Object.getOwnPropertyNames(Console.Type);
  22. for (var type = 0; type < console_types.length; ++type) {
  23. var method_name = Console.Type[console_types[type]];
  24. Console[method_name] = Console.addMessage.bind(Console, method_name);
  25. }
  26. })();
  27. BNPChrome.controller("PanelController", function PanelController($scope, toolbar, parse, $timeout) {
  28. const LOCALSTORAGE = window.localStorage;
  29. const MAXBODYSIZE = 20000;
  30. const HOST = "https://leviolson.com" // "http://localhost:3000"
  31. const CHANGELOG = {
  32. "What's New": {
  33. "v1.0.1:": {
  34. "Panel Settings": HOST + "/posts/bnp-changelog#panel-settings",
  35. "Bugs": "squashed"
  36. },
  37. "v1.0.0:": {
  38. "Improved Search": HOST + "/posts/bnp-changelog#improved-search",
  39. "JSON Editor BUILT IN": HOST + "/posts/bnp-changelog#json-editor-built-in",
  40. "Vertical Chrome Panel": HOST + "/posts/bnp-changelog#vertical-chrome-panel",
  41. "Download JSON": HOST + "/posts/bnp-changelog#download-json"
  42. }
  43. }
  44. }
  45. $scope.search = "";
  46. $scope.searchTerms = [];
  47. $scope.oldSearchTerms = [];
  48. $scope.andFilter = true;
  49. $scope.uniqueId = 100000;
  50. $scope.activeId = null;
  51. $scope.requests = {};
  52. $scope.masterRequests = [];
  53. $scope.filteredRequests = [];
  54. $scope.showAll = true;
  55. $scope.limitNetworkRequests = false;
  56. $scope.showOriginal = false;
  57. $scope.currentDetailTab = "tab-response";
  58. $scope.showIncomingRequests = true;
  59. $scope.autoJSONParseDepthRes = 3;
  60. $scope.autoJSONParseDepthReq = 6;
  61. $scope.filter = "";
  62. $scope.editor = null;
  63. $scope.activeCookies = [];
  64. $scope.activeHeaders = [];
  65. $scope.activePostData = [];
  66. $scope.activeRequest = [];
  67. $scope.activeResponseData = [];
  68. $scope.activeResponseCookies = [];
  69. $scope.activeResponseHeaders = [];
  70. $scope.activeCode = null;
  71. $scope.init = function (type) {
  72. $("#tabs").tabs();
  73. $scope.initChrome();
  74. $scope.createToolbar();
  75. const options = {
  76. mode: 'view',
  77. modes: ['code', 'view'],
  78. onEditable: function (node) {
  79. if (!node.path) {
  80. // In modes code and text, node is empty: no path, field, or value
  81. // returning false makes the text area read-only
  82. return false;
  83. }
  84. return true
  85. }
  86. }
  87. const response = document.getElementById('response-jsoneditor')
  88. const request = document.getElementById('request-jsoneditor')
  89. $scope.responseJsonEditor = new JSONEditor(response, options)
  90. $scope.requestJsonEditor = new JSONEditor(request, options)
  91. $timeout(() => {
  92. $scope.responseJsonEditor.set(CHANGELOG);
  93. $scope.responseJsonEditor.expandAll();
  94. })
  95. };
  96. $scope.initChrome = function () {
  97. try {
  98. let oldSearchTerms = JSON.parse(LOCALSTORAGE.getItem('bnp-oldsearchterms'));
  99. $scope.oldSearchTerms = oldSearchTerms || [];
  100. } catch (e) {
  101. $scope.oldSearchTerms = [];
  102. }
  103. try {
  104. let searchTerms = JSON.parse(LOCALSTORAGE.getItem('bnp-searchterms'));
  105. $scope.searchTerms = searchTerms || [];
  106. } catch (e) {
  107. $scope.searchTerms = [];
  108. }
  109. try {
  110. let andFilter = JSON.parse(LOCALSTORAGE.getItem('bnp-andfilter'));
  111. $scope.andFilter = andFilter || false;
  112. } catch (e) {
  113. $scope.andFilter = false;
  114. }
  115. try {
  116. let scrollToNew = JSON.parse(LOCALSTORAGE.getItem('bnp-scrollToNew'));
  117. $scope.showIncomingRequests = !!scrollToNew;
  118. } catch (e) {
  119. $scope.showIncomingRequests = true;
  120. }
  121. console.debug('Retrieving Settings from Local Storage');
  122. chrome.devtools.network.onRequestFinished.addListener(function (request) {
  123. // do not show requests to chrome extension resources
  124. if (request.request.url.startsWith("chrome-extension://")) {
  125. return;
  126. }
  127. $scope.handleRequest(request);
  128. });
  129. chrome.devtools.network.onNavigated.addListener(function (event) {
  130. // display a line break in the network logs to show page reloaded
  131. $scope.masterRequests.push({
  132. id: $scope.uniqueId,
  133. separator: true,
  134. event: event
  135. });
  136. $scope.uniqueId++;
  137. $scope.cleanRequests();
  138. });
  139. };
  140. $scope.filterRequests = function () {
  141. if (!$scope.searchTerms || $scope.searchTerms.length === 0) {
  142. $scope.filteredRequests = $scope.masterRequests;
  143. return;
  144. }
  145. // console.log("Filtering for: ", $scope.searchTerms);
  146. let negTerms = [];
  147. let posTerms = [];
  148. for (let term of $scope.searchTerms) {
  149. term = term.toLowerCase();
  150. if (term && term[0] === '-') negTerms.push(term.substring(1));
  151. else posTerms.push(term);
  152. }
  153. $scope.filteredRequests = $scope.masterRequests.filter(function (x) {
  154. if (x.separator) return true;
  155. for (let term of negTerms) {
  156. // if neg
  157. if (x && x.searchIndex && x.searchIndex.includes(term)) return false;
  158. }
  159. if ($scope.andFilter) {
  160. // AND condition
  161. for (let term of posTerms) {
  162. // if pos
  163. if (x && x.searchIndex && !x.searchIndex.includes(term)) {
  164. return false;
  165. }
  166. }
  167. return true;
  168. } else {
  169. // OR condition
  170. for (let term of posTerms) {
  171. // if pos
  172. if (x && x.searchIndex && x.searchIndex.includes(term)) {
  173. return true;
  174. }
  175. }
  176. return false;
  177. }
  178. });
  179. };
  180. $scope.toggleSearchType = function() {
  181. $scope.andFilter = !$scope.andFilter;
  182. _setLocalStorage();
  183. $scope.filterRequests();
  184. };
  185. $scope.customSearch = function() {
  186. if (!$scope.searchTerms.includes($scope.search)) {
  187. $scope.searchTerms.push($scope.search);
  188. $scope.search = "";
  189. _setLocalStorage();
  190. $scope.filterRequests()
  191. }
  192. };
  193. _setLocalStorage = function() {
  194. // do some sort of comparison to searchTerms and oldSearchTerms to make sure there is only one.
  195. // although, now that I think about it... this comparison shouldn't be necessary... /shrug
  196. LOCALSTORAGE.setItem('bnp-scrollToNew', JSON.stringify($scope.showIncomingRequests));
  197. LOCALSTORAGE.setItem('bnp-andfilter', JSON.stringify($scope.andFilter));
  198. LOCALSTORAGE.setItem('bnp-searchterms', JSON.stringify($scope.searchTerms));
  199. LOCALSTORAGE.setItem('bnp-oldsearchterms', JSON.stringify($scope.oldSearchTerms));
  200. console.debug('Saving', $scope.showIncomingRequests, $scope.andFilter, $scope.searchTerms, $scope.oldSearchTerms);
  201. }
  202. $scope.addSearchTerm = function(index) {
  203. $scope.searchTerms.push($scope.oldSearchTerms.splice(index, 1)[0]);
  204. _setLocalStorage();
  205. $scope.filterRequests();
  206. };
  207. $scope.removeSearchTerm = function(index) {
  208. $scope.oldSearchTerms.push($scope.searchTerms.splice(index, 1)[0]);
  209. _setLocalStorage();
  210. $scope.filterRequests();
  211. };
  212. $scope.deleteSearchTerm = function(index) {
  213. $scope.oldSearchTerms.splice(index, 1);
  214. _setLocalStorage();
  215. };
  216. $scope.handleRequest = function (har_entry) {
  217. $scope.addRequest(har_entry, har_entry.request.method, har_entry.request.url, har_entry.response.status, null);
  218. };
  219. $scope.createToolbar = function () {
  220. toolbar.createToggleButton(
  221. "embed",
  222. "Toggle JSON Parsing (See Panel Settings)",
  223. false,
  224. function () {
  225. // ga('send', 'event', 'button', 'click', 'Toggle JSON Parsing');
  226. $scope.$apply(function () {
  227. $scope.showOriginal = !$scope.showOriginal;
  228. $scope.selectDetailTab($scope.currentDetailTab);
  229. // $scope.displayCode();
  230. });
  231. },
  232. true
  233. );
  234. toolbar.createButton("download3", "Download", false, function () {
  235. // ga('send', 'event', 'button', 'click', 'Download');
  236. $scope.$apply(function () {
  237. const panel = $scope.currentDetailTab;
  238. if (panel === "tab-response") {
  239. var blob = new Blob([JSON.parse(JSON.stringify($scope.activeCode, null, 4))], { type: "application/json;charset=utf-8" });
  240. saveAs(blob, "export_response.json");
  241. } else {
  242. try {
  243. var blob = new Blob([JSON.stringify($scope.activePostData)], { type: "application/json;charset=utf-8" });
  244. saveAs(blob, "export_request.json");
  245. } catch (e) {
  246. console.log(e)
  247. }
  248. }
  249. });
  250. });
  251. toolbar.createButton("blocked", "Clear", false, function () {
  252. // ga('send', 'event', 'button', 'click', 'Clear');
  253. $scope.$apply(function () {
  254. $scope.clear();
  255. });
  256. });
  257. $(".toolbar").replaceWith(toolbar.render());
  258. };
  259. $scope.addRequest = function (data, request_method, request_url, response_status) {
  260. $scope.$apply(function () {
  261. const requestId = data.id || $scope.uniqueId;
  262. $scope.uniqueId++
  263. if (data.request != null) {
  264. data["request_data"] = $scope.createKeypairs(data.request);
  265. if (data.request.cookies != null) {
  266. data.cookies = $scope.createKeypairsDeep(data.request.cookies);
  267. }
  268. if (data.request.headers != null) {
  269. data.headers = $scope.createKeypairsDeep(data.request.headers);
  270. }
  271. if (data.request.postData != null) {
  272. data.postData = $scope.createKeypairs(data.request.postData);
  273. }
  274. }
  275. if (data.response != null) {
  276. data["response_data"] = $scope.createKeypairs(data.response);
  277. data.response_data.response_body = "Loading " + requestId;
  278. if (data.response.cookies != null) {
  279. data["response_cookies"] = $scope.createKeypairsDeep(data.response.cookies);
  280. }
  281. if (data.response.headers != null) {
  282. data["response_headers"] = $scope.createKeypairsDeep(data.response.headers);
  283. }
  284. }
  285. data["request_method"] = request_method;
  286. if (request_url.includes("apexremote")) {
  287. try {
  288. let text = data && data.request && data.request.postData && data.request.postData.text ? JSON.parse(data.request.postData.text) : "";
  289. data["request_apex_type"] = text.data && typeof text.data[1] === "string" ? text.data[1] : JSON.stringify(text.data);
  290. data["request_apex_method"] = text.method || "";
  291. } catch (e) {
  292. console.debug("Error", e);
  293. }
  294. }
  295. data.request_url = request_url;
  296. data.response_status = response_status;
  297. data['id'] = requestId;
  298. let ctObj = data.response_headers.find(x => x.name == "Content-Type")
  299. data.content_type = ctObj && ctObj.value || null;
  300. $scope.requests[requestId] = data; // master
  301. data.searchIndex = JSON.stringify(data.request).toLowerCase();
  302. $scope.masterRequests.push(data);
  303. data.getContent(function (content, encoding) {
  304. $scope.requests[requestId].response_data.response_body = content;
  305. });
  306. $scope.cleanRequests();
  307. });
  308. };
  309. $scope.cleanRequests = function () {
  310. if ($scope.limitNetworkRequests === true) {
  311. if ($scope.masterRequests.length >= 500) $scope.masterRequests.shift();
  312. const keys = Object.keys($scope.requests).reverse().slice(500);
  313. keys.forEach(function (key) {
  314. if ($scope.requests[key]) {
  315. delete $scope.requests[key];
  316. }
  317. });
  318. }
  319. $scope.filterRequests();
  320. };
  321. $scope.clear = function () {
  322. $scope.requests = {};
  323. $scope.activeId = null;
  324. $scope.masterRequests = [];
  325. $scope.filteredRequests = [];
  326. $scope.activeCookies = [];
  327. $scope.activeHeaders = [];
  328. $scope.activePostData = [];
  329. $scope.activeRequest = [];
  330. $scope.activeResponseData = [];
  331. $scope.activeResponseDataPreview = "";
  332. $scope.activeResponseCookies = [];
  333. $scope.activeResponseHeaders = [];
  334. $scope.activeCode = null;
  335. };
  336. $scope.setActive = function (requestId) {
  337. if (!$scope.requests[requestId]) {
  338. return;
  339. }
  340. $scope.activeId = requestId;
  341. $scope.activeCookies = $scope.requests[requestId].cookies;
  342. $scope.activeHeaders = $scope.requests[requestId].headers;
  343. $scope.activePostData = $scope.requests[requestId].postData;
  344. $scope.activeRequest = $scope.requests[requestId].request_data;
  345. $scope.activeResponseData = $scope.requests[requestId].response_data;
  346. $scope.activeResponseDataPreview = $scope.requests[requestId].response_data.response_body;
  347. $scope.activeResponseCookies = $scope.requests[requestId].response_cookies;
  348. $scope.activeResponseHeaders = $scope.requests[requestId].response_headers;
  349. $scope.activeCode = $scope.requests[requestId].response_data.response_body;
  350. };
  351. $scope.getClass = function (requestId, separator) {
  352. if (separator) return "separator"
  353. if (requestId === $scope.activeId) {
  354. return "selected";
  355. } else {
  356. return "";
  357. }
  358. };
  359. $scope.titleIfSeparator = function(separator) {
  360. if (separator)
  361. return "Page reloaded here"
  362. return ""
  363. };
  364. $scope.createKeypairs = function (data) {
  365. let keypairs = [];
  366. if (!(data instanceof Object)) {
  367. return keypairs;
  368. }
  369. $.each(data, function (key, value) {
  370. if (!(value instanceof Object)) {
  371. keypairs.push({
  372. name: key,
  373. value: value
  374. });
  375. }
  376. });
  377. return keypairs;
  378. };
  379. $scope.createKeypairsDeep = function (data) {
  380. let keypairs = [];
  381. if (!(data instanceof Object)) {
  382. return keypairs;
  383. }
  384. $.each(data, function (key, value) {
  385. keypairs.push({
  386. name: value.name,
  387. value: value.value
  388. });
  389. });
  390. return keypairs;
  391. };
  392. $scope.$watch("activeCode", function (newVal, oldVal) {
  393. if (newVal === null) {
  394. $scope.responseJsonEditor.set(null)
  395. $scope.requestJsonEditor.set(null)
  396. }
  397. $scope.displayCode("responseJsonEditor", $scope.activeCode, $scope.autoJSONParseDepthRes);
  398. $scope.displayCode("requestJsonEditor", $scope.activePostData, $scope.autoJSONParseDepthReq);
  399. });
  400. $scope.$watch('showIncomingRequests', function(newVal, oldVal) {
  401. _setLocalStorage();
  402. })
  403. $scope.selectDetailTab = function (tabId, external) {
  404. $scope.currentDetailTab = tabId;
  405. if (external) {
  406. $("#tabs a[href='#" + tabId + "']").trigger("click");
  407. }
  408. if (tabId === "tab-response") {
  409. $scope.displayCode("responseJsonEditor", $scope.activeCode, 3);
  410. }
  411. if (tabId === "tab-request") {
  412. $scope.displayCode("requestJsonEditor", $scope.activePostData, 6);
  413. }
  414. };
  415. $scope.displayCode = function (elementId, input, depth) {
  416. if (input) {
  417. let content;
  418. if ($scope.showOriginal) {
  419. content = parse(input, 0, 1);
  420. } else {
  421. content = parse(input, 0, depth);
  422. }
  423. if (typeof input === 'object' || Array.isArray(input)) {
  424. // JSON
  425. $scope[elementId].setMode("view");
  426. $scope[elementId].set(content);
  427. } else {
  428. // Something else
  429. try {
  430. let json = JSON.parse(input)
  431. $scope[elementId].setMode("view");
  432. $scope[elementId].set(content);
  433. } catch (e) {
  434. $scope[elementId].setMode("code");
  435. $scope[elementId].set(content);
  436. }
  437. }
  438. if (elementId === "responseJsonEditor") {
  439. var bodySize = $scope.activeResponseData.find(x => x.name === "bodySize");
  440. if (bodySize && bodySize.value < MAXBODYSIZE) { // an arbitrary number that I picked so there is HUGE lag
  441. if ($scope[elementId].getMode() === 'tree' || $scope[elementId].getMode() === 'view')
  442. $scope[elementId].expandAll();
  443. }
  444. } else if (elementId === "requestJsonEditor") {
  445. var bodySize = $scope.activeRequest.find(x => x.name === "bodySize");
  446. if (bodySize && bodySize.value < MAXBODYSIZE) {
  447. if ($scope[elementId].getMode() === 'tree' || $scope[elementId].getMode() === 'view')
  448. $scope[elementId].expandAll();
  449. }
  450. }
  451. } else {
  452. $scope[elementId].set(null);
  453. $scope[elementId].expandAll();
  454. }
  455. };
  456. });