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.

497 lines
18 KiB

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