index.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /**
  2. * FeHelper 简易版Postman
  3. */
  4. const JSON_SORT_TYPE_KEY = 'json_sort_type_key';
  5. new Vue({
  6. el: '#pageContainer',
  7. data: {
  8. urlContent: '',
  9. methodContent: 'GET',
  10. resultContent: '',
  11. funcName: '',
  12. paramContent: '',
  13. responseHeaders: [],
  14. jfCallbackName_start: '',
  15. jfCallbackName_end: '',
  16. errorMsgForJson: '',
  17. originalJsonStr: '',
  18. headerList: [new Date() * 1],
  19. urlencodedDefault: 1,
  20. urlParams: [],
  21. paramMode:'kv' // kv、json
  22. },
  23. computed: {
  24. // 计算属性:根据当前参数内容格式返回按钮文字
  25. paramModeText() {
  26. return this.detectParamFormat() === 'kv' ? 'JSON' : 'URL-KV';
  27. }
  28. },
  29. watch: {
  30. urlContent: function (val) {
  31. let url = val;
  32. let reg = /[?&]([^?&#]+)=([^?&#]*)/g;
  33. let params = [];
  34. let ret = reg.exec(url);
  35. while (ret) {
  36. params.push({
  37. key: ret[1],
  38. value: ret[2],
  39. });
  40. ret = reg.exec(url);
  41. }
  42. const originStr = this.urlParams2String(params);
  43. const newStr = this.urlParams2String(this.urlParams);
  44. if (originStr !== newStr) {
  45. this.urlParams = params;
  46. }
  47. },
  48. urlParams: {
  49. handler(val) {
  50. this.urlContent =
  51. this.urlContent.substr(0, this.urlContent.indexOf("?") + 1) +
  52. val.map((item) => `${item.key}=${item.value}`).join("&");
  53. },
  54. deep: true,
  55. },
  56. // 监听参数内容变化,自动更新按钮文字
  57. paramContent: function() {
  58. // 触发计算属性重新计算
  59. this.$forceUpdate();
  60. }
  61. },
  62. mounted: function () {
  63. this.$refs.url.focus();
  64. this.loadPatchHotfix();
  65. this.initMockServer();
  66. },
  67. methods: {
  68. loadPatchHotfix() {
  69. // 页面加载时自动获取并注入页面的补丁
  70. chrome.runtime.sendMessage({
  71. type: 'fh-dynamic-any-thing',
  72. thing: 'fh-get-tool-patch',
  73. toolName: 'postman'
  74. }, patch => {
  75. if (patch) {
  76. if (patch.css) {
  77. const style = document.createElement('style');
  78. style.textContent = patch.css;
  79. document.head.appendChild(style);
  80. }
  81. if (patch.js) {
  82. try {
  83. if (window.evalCore && window.evalCore.getEvalInstance) {
  84. window.evalCore.getEvalInstance(window)(patch.js);
  85. }
  86. } catch (e) {
  87. console.error('postman补丁JS执行失败', e);
  88. }
  89. }
  90. }
  91. });
  92. },
  93. initMockServer() {
  94. // 注册Service Worker用于Mock服务器
  95. if ('serviceWorker' in navigator) {
  96. window.addEventListener('load', () => {
  97. navigator.serviceWorker.register('./sw.js')
  98. .then((registration) => {
  99. console.log('✅ FeHelper Mock Server 已注册:', registration.scope);
  100. // 通知Vue组件Mock服务器已就绪
  101. window.dispatchEvent(new CustomEvent('mockServerReady'));
  102. })
  103. .catch((error) => {
  104. console.warn('❌ Mock Server 注册失败:', error);
  105. });
  106. });
  107. } else {
  108. console.warn('❌ 当前浏览器不支持Service Worker');
  109. }
  110. // 监听Mock服务器就绪事件
  111. window.addEventListener('mockServerReady', () => {
  112. console.log('🎉 Mock服务器已就绪,可以测试POST请求了!');
  113. // 可以在这里添加一些UI提示
  114. });
  115. },
  116. postman: function () {
  117. this.$nextTick(() => {
  118. this.sendRequest(this.urlContent, this.methodContent, this.paramContent);
  119. });
  120. },
  121. sendRequest: function (url, method, body) {
  122. let xhr = new XMLHttpRequest();
  123. xhr.addEventListener("readystatechange", (resp) => {
  124. let result = 'Loading...';
  125. switch (resp.target.readyState) {
  126. case resp.target.OPENED:
  127. result = 'Senting...';
  128. break;
  129. case resp.target.HEADERS_RECEIVED:
  130. result = 'Headers received';
  131. this.responseHeaders = resp.target.getAllResponseHeaders().trim().split('\n').map(item => {
  132. return item.split(': ').map(x => x.trim())
  133. });
  134. break;
  135. case resp.target.LOADING:
  136. result = 'Loading...';
  137. break;
  138. case resp.target.DONE:
  139. try {
  140. result = JSON.stringify(JSON.parse(resp.target.responseText), null, 4);
  141. } catch (e) {
  142. result = resp.target.responseText;
  143. }
  144. this.jsonFormat(result);
  145. this.renderTab();
  146. break;
  147. }
  148. this.resultContent = result || '无数据';
  149. });
  150. xhr.open(method, url);
  151. let isPost = false;
  152. if (method.toLowerCase() === 'post') {
  153. isPost = true;
  154. this.urlencodedDefault && xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  155. }
  156. // 设置请求头:Header
  157. this.headerList.forEach(id => {
  158. let headerKey = $(`#header_key_${id}`).val();
  159. let headerVal = $(`#header_value_${id}`).val();
  160. if (headerKey && headerVal) {
  161. xhr.setRequestHeader(headerKey, headerVal);
  162. }
  163. });
  164. xhr.send(isPost && body);
  165. },
  166. addHeader() {
  167. this.headerList.push(new Date() * 1);
  168. },
  169. deleteHeader(event) {
  170. event.target.parentNode.remove();
  171. },
  172. // 检测当前参数内容的数据格式
  173. detectParamFormat() {
  174. if (!this.paramContent || !this.paramContent.trim()) {
  175. return 'kv'; // 默认为KV格式
  176. }
  177. const content = this.paramContent.trim();
  178. // 检测是否为JSON格式
  179. try {
  180. JSON.parse(content);
  181. return 'json';
  182. } catch (e) {
  183. // 检测是否为KV格式(包含=号且用&分隔)
  184. if (content.includes('=') && (content.includes('&') || content.split('=').length === 2)) {
  185. return 'kv';
  186. }
  187. // 如果都不符合,默认为KV格式
  188. return 'kv';
  189. }
  190. },
  191. transParamMode(){
  192. // 先检测当前格式
  193. const currentFormat = this.detectParamFormat();
  194. if(currentFormat === 'kv') {
  195. this.paramMode = 'json';
  196. let objParam = {};
  197. // 检查是否有内容
  198. if (this.paramContent && this.paramContent.trim()) {
  199. this.paramContent.split('&').forEach(p => {
  200. if (p.trim()) {
  201. let x = p.split('=');
  202. if (x.length >= 2) {
  203. objParam[x[0].trim()] = x[1].trim();
  204. }
  205. }
  206. });
  207. }
  208. // 如果没有任何参数,提供默认示例
  209. if (Object.keys(objParam).length === 0) {
  210. objParam = {
  211. "key1": "value1",
  212. "key2": "value2",
  213. "key3": "value3"
  214. };
  215. }
  216. this.paramContent = JSON.stringify(objParam, null, 4);
  217. }else{
  218. this.paramMode = 'kv';
  219. try {
  220. if (this.paramContent && this.paramContent.trim()) {
  221. let obj = JSON.parse(this.paramContent);
  222. this.paramContent = Object.keys(obj).map(k => {
  223. let v = JSON.stringify(obj[k]).replace(/"/g,'');
  224. return `${k}=${v}`;
  225. }).join('&');
  226. } else {
  227. // 如果JSON为空,提供默认示例
  228. this.paramContent = 'key1=value1&key2=value2&key3=value3';
  229. }
  230. } catch (e) {
  231. // JSON解析失败时,提供默认示例
  232. this.paramContent = 'key1=value1&key2=value2&key3=value3';
  233. }
  234. }
  235. },
  236. renderTab: function () {
  237. jQuery('#tabs').tabs({
  238. show: (event, ui) => {
  239. }
  240. });
  241. this.$refs.resultContainer.classList.remove('hide');
  242. },
  243. jsonFormat: function (source) {
  244. this.errorMsgForJson = '';
  245. this.jfCallbackName_start = '';
  246. this.jfCallbackName_end = '';
  247. if (!source) {
  248. return false;
  249. }
  250. // json对象
  251. let jsonObj = null;
  252. // 下面校验给定字符串是否为一个合法的json
  253. try {
  254. this.funcName = '';
  255. // 再看看是不是jsonp的格式
  256. let reg = /^([\w\.]+)\(\s*([\s\S]*)\s*\)$/igm;
  257. let matches = reg.exec(source);
  258. if (matches != null) {
  259. this.funcName = matches[1];
  260. source = matches[2];
  261. }
  262. // 这里可能会throw exception
  263. jsonObj = JSON.parse(source);
  264. } catch (ex) {
  265. // new Function的方式,能自动给key补全双引号,但是不支持bigint,所以是下下策,放在try-catch里搞
  266. try {
  267. jsonObj = new Function("return " + source)();
  268. } catch (exx) {
  269. try {
  270. // 再给你一次机会,是不是下面这种情况: "{\"ret\":\"0\", \"msg\":\"ok\"}"
  271. jsonObj = new Function("return '" + source + "'")();
  272. if (typeof jsonObj === 'string') {
  273. // 最后给你一次机会,是个字符串,老夫给你再转一次
  274. jsonObj = new Function("return " + jsonObj)();
  275. }
  276. } catch (exxx) {
  277. this.errorMsgForJson = exxx.message;
  278. }
  279. }
  280. }
  281. // 是json格式,可以进行JSON自动格式化
  282. if (jsonObj != null && typeof jsonObj === "object" && !this.errorMsgForJson.length) {
  283. try {
  284. // 要尽量保证格式化的东西一定是一个json,所以需要把内容进行JSON.stringify处理
  285. source = JSON.stringify(jsonObj);
  286. } catch (ex) {
  287. // 通过JSON反解不出来的,一定有问题
  288. this.errorMsgForJson = ex.message;
  289. }
  290. if (!this.errorMsgForJson.length) {
  291. this.originalJsonStr = source;
  292. // 获取上次记录的排序方式
  293. let curSortType = parseInt(localStorage.getItem(JSON_SORT_TYPE_KEY) || 0);
  294. this.didFormat(curSortType);
  295. // 排序选项初始化
  296. $('[name=jsonsort][value=' + curSortType + ']').attr('checked', 1);
  297. let that = this;
  298. $('[name=jsonsort]').click(function (e) {
  299. let sortType = parseInt(this.value);
  300. if (sortType !== curSortType) {
  301. that.didFormat(sortType);
  302. curSortType = sortType;
  303. }
  304. localStorage.setItem(JSON_SORT_TYPE_KEY, sortType);
  305. });
  306. }
  307. }
  308. // 不是json,都格式化不了,一定会出错
  309. if (this.errorMsgForJson) {
  310. let el = document.querySelector('#optionBar');
  311. el && (el.style.display = 'none');
  312. }
  313. },
  314. didFormat: function (sortType) {
  315. sortType = sortType || 0;
  316. let source = this.originalJsonStr;
  317. if (sortType !== 0) {
  318. let jsonObj = JsonABC.sortObj(JSON.parse(this.originalJsonStr), parseInt(sortType), true);
  319. source = JSON.stringify(jsonObj);
  320. }
  321. Formatter.format(source);
  322. $('.x-toolbar').fadeIn(500);
  323. // 如果是JSONP格式的,需要把方法名也显示出来
  324. if (this.funcName) {
  325. $('#jfCallbackName_start').html(this.funcName + '(');
  326. $('#jfCallbackName_end').html(')');
  327. } else {
  328. this.jfCallbackName_start = '';
  329. this.jfCallbackName_end = '';
  330. }
  331. },
  332. setDemo: function (type) {
  333. if (type === 1) {
  334. // GET示例
  335. this.urlContent = 'http://t.weather.sojson.com/api/weather/city/101030100';
  336. this.methodContent = 'GET';
  337. this.paramContent = '';
  338. this.headerList = [new Date() * 1];
  339. } else if (type === 2) {
  340. // 基础Mock API
  341. this.urlContent = window.location.origin + '/api/mock';
  342. this.methodContent = 'POST';
  343. this.paramContent = JSON.stringify({
  344. username: 'fehelper_user',
  345. password: '123456',
  346. email: '[email protected]',
  347. action: 'login',
  348. timestamp: new Date().toISOString()
  349. }, null, 2);
  350. // 自动设置Content-Type为application/json
  351. this.headerList = [new Date() * 1];
  352. this.$nextTick(() => {
  353. $(`#header_key_${this.headerList[0]}`).val('Content-Type');
  354. $(`#header_value_${this.headerList[0]}`).val('application/json');
  355. });
  356. } else if (type === 3) {
  357. // Mock登录API
  358. this.urlContent = window.location.origin + '/api/user/login';
  359. this.methodContent = 'POST';
  360. this.paramContent = JSON.stringify({
  361. username: 'admin',
  362. password: 'admin123',
  363. remember: true
  364. }, null, 2);
  365. this.headerList = [new Date() * 1];
  366. this.$nextTick(() => {
  367. $(`#header_key_${this.headerList[0]}`).val('Content-Type');
  368. $(`#header_value_${this.headerList[0]}`).val('application/json');
  369. });
  370. } else if (type === 4) {
  371. // Mock数据创建API
  372. this.urlContent = window.location.origin + '/api/data/create';
  373. this.methodContent = 'POST';
  374. this.paramContent = JSON.stringify({
  375. title: '测试数据',
  376. content: '这是一个通过FeHelper Mock服务器创建的测试数据',
  377. category: 'test',
  378. tags: ['mock', 'test', 'fehelper']
  379. }, null, 2);
  380. this.headerList = [new Date() * 1];
  381. this.$nextTick(() => {
  382. $(`#header_key_${this.headerList[0]}`).val('Content-Type');
  383. $(`#header_value_${this.headerList[0]}`).val('application/json');
  384. });
  385. }
  386. },
  387. urlParams2String: function (params) {
  388. return params.map((param) => `${param.key}=${param.value}`).join("&")
  389. },
  390. openDonateModal: function(event) {
  391. event.preventDefault();
  392. event.stopPropagation();
  393. chrome.runtime.sendMessage({
  394. type: 'fh-dynamic-any-thing',
  395. thing: 'open-donate-modal',
  396. params: { toolName: 'postman' }
  397. });
  398. },
  399. openOptionsPage: function(event) {
  400. event.preventDefault();
  401. event.stopPropagation();
  402. chrome.runtime.openOptionsPage();
  403. }
  404. }
  405. });