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.

496 lines
18 KiB

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