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.

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