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.

433 lines
15 KiB

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, $http, toolbar) {
  28. $scope.uniqueid = 1000000;
  29. $scope.activeId = null;
  30. $scope.requests = {};
  31. $scope.masterRequests = [];
  32. $scope.filteredRequests = [];
  33. $scope.showAll = true;
  34. $scope.limitNetworkRequests = true;
  35. $scope.showOriginal = true;
  36. $scope.currentDetailTab = "tab-code";
  37. $scope.myCodeMirror = null;
  38. $scope.activeCookies = [];
  39. $scope.activeHeaders = [];
  40. $scope.activePostData = [];
  41. $scope.activeRequest = [];
  42. $scope.activeResponseData = [];
  43. $scope.activeResponseCookies = [];
  44. $scope.activeResponseHeaders = [];
  45. $scope.activeCode = null;
  46. $scope.filter = "";
  47. $scope.showIncomingRequests = true;
  48. $scope.init = function(type) {
  49. $('#tabs').tabs();
  50. $scope.initChrome();
  51. this.createToolbar();
  52. };
  53. $scope.initChrome = function() {
  54. key('⌘+k, ctrl+l', function() {
  55. $scope.$apply(function() {
  56. $scope.clear();
  57. });
  58. });
  59. chrome.devtools.network.onRequestFinished.addListener(function(request) {
  60. // do not show requests to chrome extension resources
  61. if (request.request.url.startsWith("chrome-extension://")) {
  62. return;
  63. }
  64. $scope.handleRequest(request);
  65. });
  66. };
  67. $scope.filterRequests = function() {
  68. // console.debug('Search Filter: ', $scope.filter, $scope.filteredRequests.length, $scope.masterRequests.length);
  69. // console.debug("Request", $scope.masterRequests[0]);
  70. // console.debug('Requests', $scope.requests);
  71. $scope.filteredRequests = $scope.masterRequests.filter(function(x) {
  72. if (!$scope.filter) return true;
  73. if (x && JSON.stringify(x).toLowerCase().includes($scope.filter.toLowerCase())) return true;
  74. });
  75. }
  76. $scope.handleRequest = function(har_entry) {
  77. const request_method = har_entry.request.method;
  78. const request_url = har_entry.request.url;
  79. const response_status = har_entry.response.status;
  80. $scope.addRequest(har_entry, request_method, request_url, response_status, null);
  81. };
  82. $scope.createToolbar = function() {
  83. toolbar.createButton('search', 'Search Code', false, function() {
  84. // ga('send', 'event', 'button', 'click', 'Search Code');
  85. $scope.$apply(function() {
  86. if ($scope.myCodeMirror) {
  87. $scope.myCodeMirror.execCommand("find");
  88. }
  89. });
  90. });
  91. toolbar.createToggleButton('embed', 'JSON Parsing', false, function() {
  92. // ga('send', 'event', 'button', 'click', 'Toggle JSON Parsing');
  93. $scope.$apply(function() {
  94. $scope.showOriginal = !$scope.showOriginal;
  95. $scope.displayCode();
  96. });
  97. }, false);
  98. toolbar.createButton('download3', 'Download', false, function() {
  99. // ga('send', 'event', 'button', 'click', 'Download');
  100. $scope.$apply(function() {
  101. var blob = new Blob([JSON.stringify($scope.requests)], {type: "application/json;charset=utf-8"});
  102. saveAs(blob, "BNPChromeExport.json");
  103. });
  104. });
  105. toolbar.createButton('upload3', 'Import', true, function() {
  106. // ga('send', 'event', 'button', 'click', 'Import');
  107. $scope.$apply(function() {
  108. $('#ImportInput').click();
  109. });
  110. });
  111. toolbar.createToggleButton('meter', 'Limit network requests to 500', false, function() {
  112. // ga('send', 'event', 'button', 'click', 'Toggle Limit Network Request');
  113. $scope.$apply(function() {
  114. $scope.limitNetworkRequests = !$scope.limitNetworkRequests;
  115. });
  116. }, true);
  117. toolbar.createButton('blocked', 'Clear', false, function() {
  118. // ga('send', 'event', 'button', 'click', 'Clear');
  119. $scope.$apply(function() {
  120. $scope.clear();
  121. });
  122. });
  123. $('.toolbar').replaceWith(toolbar.render());
  124. //clears the input value so you can reload the same file
  125. document.getElementById('ImportInput').addEventListener('click', function() {this.value=null;}, false);
  126. document.getElementById('ImportInput').addEventListener('change', readFile, false);
  127. function readFile (evt) {
  128. const files = evt.target.files;
  129. const file = files[0];
  130. const reader = new FileReader();
  131. reader.onload = function() {
  132. $scope.importFile(this.result);
  133. }
  134. reader.readAsText(file)
  135. }
  136. };
  137. $scope.importFile = function(data) {
  138. $scope.$apply(function() {
  139. const importHar = JSON.parse(data);
  140. for (i in importHar)
  141. {
  142. $scope.handleRequest(importHar[i]);
  143. }
  144. });
  145. }
  146. $scope.addRequest = function(data, request_method, request_url, response_status) {
  147. $scope.$apply(function() {
  148. const requestId = $scope.uniqueid;
  149. $scope.uniqueid = $scope.uniqueid + 1;
  150. if (data.request != null) {
  151. data["request_data"] = $scope.createKeypairs(data.request);
  152. if (data.request.cookies != null) {
  153. data.cookies = $scope.createKeypairsDeep(data.request.cookies);
  154. }
  155. if (data.request.headers != null) {
  156. data.headers = $scope.createKeypairsDeep(data.request.headers);
  157. }
  158. if (data.request.postData != null) {
  159. data.postData = $scope.createKeypairs(data.request.postData);
  160. }
  161. }
  162. if (data.response != null) {
  163. data["response_data"] = $scope.createKeypairs(data.response);
  164. data.response_data.response_body = "Loading " + requestId;
  165. if (data.response.cookies != null) {
  166. data["response_cookies"] = $scope.createKeypairsDeep(data.response.cookies);
  167. }
  168. if (data.response.headers != null) {
  169. data["response_headers"] = $scope.createKeypairsDeep(data.response.headers);
  170. }
  171. }
  172. data["request_method"] = request_method;
  173. if (request_url.includes('apexremote')) {
  174. try {
  175. let text = (data && data.request && data.request.postData && data.request.postData.text) ? JSON.parse(data.request.postData.text) : '';
  176. data["request_apex_type"] = (text.data && typeof text.data[1] === 'string') ? text.data[1] : JSON.stringify(text.data);
  177. data["request_apex_method"] = text.method || '';
  178. } catch (e) {console.debug('Error', e)}
  179. }
  180. data["request_url"] = request_url;
  181. data["response_status"] = response_status;
  182. data["id"] = requestId;
  183. $scope.requests[requestId] = data; // master
  184. $scope.masterRequests.push(data);
  185. $scope.filteredRequests.push(data);
  186. data.getContent(function (content, encoding) {
  187. try {
  188. $scope.requests[requestId].response_data.response_body = JSON.stringify(JSON.parse(content), null, 4);
  189. } catch (e) {}
  190. });
  191. $scope.cleanRequests();
  192. });
  193. };
  194. $scope.cleanRequests = function() {
  195. if ($scope.limitNetworkRequests === true) {
  196. if ($scope.masterRequests.length >= 500 ) $scope.masterRequests.shift();
  197. const keys = Object.keys($scope.requests).reverse().slice(500);
  198. keys.forEach(function(key) {
  199. if ($scope.requests[key]) {
  200. delete $scope.requests[key];
  201. }
  202. });
  203. }
  204. $scope.filterRequests();
  205. }
  206. $scope.clear = function() {
  207. $scope.requests = {};
  208. $scope.activeId = null;
  209. $scope.masterRequests = [];
  210. $scope.filteredRequests = [];
  211. $scope.activeCookies = [];
  212. $scope.activeHeaders = [];
  213. $scope.activePostData = [];
  214. $scope.activeRequest = [];
  215. $scope.activeResponseData = [];
  216. $scope.activeResponseDataPreview = "";
  217. $scope.activeResponseCookies = [];
  218. $scope.activeResponseHeaders = [];
  219. $scope.activeCode = null;
  220. $scope.showIncomingRequests = true;
  221. document.getElementById("tab-code-codemirror").style.visibility = "hidden";
  222. };
  223. $scope.setActive = function(requestId) {
  224. if (!$scope.requests[requestId]) {
  225. return;
  226. }
  227. $scope.activeId = requestId;
  228. $scope.activeCookies = $scope.requests[requestId].cookies;
  229. $scope.activeHeaders = $scope.requests[requestId].headers;
  230. $scope.activePostData = $scope.requests[requestId].postData;
  231. $scope.activeRequest = $scope.requests[requestId].request_data;
  232. $scope.activeResponseData = $scope.requests[requestId].response_data;
  233. $scope.activeResponseDataPreview = $scope.requests[requestId].response_data.response_body;
  234. $scope.activeResponseCookies = $scope.requests[requestId].response_cookies;
  235. $scope.activeResponseHeaders = $scope.requests[requestId].response_headers;
  236. $scope.activeCode = $scope.requests[requestId].response_data.response_body;
  237. };
  238. $scope.getClass = function(requestId) {
  239. if (requestId === $scope.activeId) {
  240. return 'selected';
  241. } else {
  242. return '';
  243. }
  244. };
  245. $scope.createKeypairs = function(data) {
  246. let keypairs = [];
  247. if (!(data instanceof Object)) {
  248. return keypairs;
  249. }
  250. $.each(data, function(key, value) {
  251. if (!(value instanceof Object)) {
  252. keypairs.push({
  253. name: key,
  254. value: value
  255. });
  256. }
  257. });
  258. return keypairs;
  259. };
  260. $scope.createKeypairsDeep = function(data) {
  261. let keypairs = [];
  262. if (!(data instanceof Object)) {
  263. return keypairs;
  264. }
  265. $.each(data, function(key, value) {
  266. keypairs.push({
  267. name: value.name,
  268. value: value.value
  269. });
  270. });
  271. return keypairs;
  272. };
  273. $scope.$watch('activeCode', function() {
  274. $scope.displayCode();
  275. });
  276. $scope.selectDetailTab = function(tabId) {
  277. $scope.currentDetailTab = tabId;
  278. if (tabId === "tab-code") {
  279. $scope.displayCode();
  280. }
  281. }
  282. $scope.displayCode = function() {
  283. if ($scope.activeCode != null) {
  284. document.getElementById("tab-code-codemirror").style.visibility = "visible";
  285. let content = $scope.activeCode;
  286. // if (!$scope.showOriginal) {
  287. // content = $scope.getPretty(content);
  288. // } else {
  289. content = JSON.stringify($scope.parse(content), null, 4);
  290. // }
  291. if ($scope.myCodeMirror) {
  292. $scope.myCodeMirror.getDoc().setValue(content);
  293. $scope.myCodeMirror.refresh();
  294. return;
  295. }
  296. document.getElementById("tab-code-codemirror").innerHTML = "";
  297. const myCodeMirror = CodeMirror(document.getElementById("tab-code-codemirror"), {
  298. value: content,
  299. mode: "application/json",
  300. theme: "neat",
  301. lineNumbers: true,
  302. lineWrapping: false,
  303. readOnly: true,
  304. foldGutter: true,
  305. gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
  306. });
  307. myCodeMirror.setOption("extraKeys", {
  308. "Ctrl-F": function(cm) {
  309. cm.execCommand('findPersistent');
  310. },
  311. "Ctrl-G": function(cm) {
  312. cm.execCommand('findPersistentNext');
  313. },
  314. "Shift-Ctrl-G": function(cm) {
  315. cm.execCommand('findPersistentPrev');
  316. }
  317. });
  318. $scope.myCodeMirror = myCodeMirror;
  319. }
  320. }
  321. $scope.getPretty = function(source) {
  322. let code = $scope.parse(source);
  323. const options = {
  324. source: code,
  325. mode: "beautify", // beautify, diff, minify, parse
  326. lang: "auto",
  327. inchar: " ", // indent character
  328. };
  329. const pd = prettydiff(options); // returns and array: [beautified, report]
  330. const pretty = pd[0];
  331. return pretty;
  332. }
  333. $scope.parse = function(input) {
  334. try {
  335. // console.warn('Parse Type', typeof input);
  336. if (typeof input === 'boolean') return input;
  337. if (typeof input === 'number') return input;
  338. if (!input) return input;
  339. if (typeof input === 'string') {
  340. // if string, try to parse
  341. // returns the original string if this fails
  342. input = JSON.parse(input);
  343. // console.debug('Parse String', input);
  344. return $scope.parse(input);
  345. }
  346. if (Array.isArray(input)) {
  347. for (let i = 0; i < input.length; i++) {
  348. // console.debug('Parse Inner Array', i, input[i]);
  349. input[i] = $scope.parse(input[i]);
  350. }
  351. }
  352. if (typeof input === 'object') {
  353. // console.debug('Parse Object', input);
  354. Object.entries(input).forEach(function([key, value]) {
  355. // console.debug('Parse Inner Object', key, value);
  356. if (key === "result")
  357. input[key] = $scope.parse(value);
  358. })
  359. }
  360. } catch (e) {
  361. // console.info('Error parsing', e, typeof input, input)
  362. // console.debug('Parse String Failed', input);
  363. return input
  364. }
  365. // console.debug('Returning', input);
  366. return input;
  367. }
  368. });