background.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939
  1. /**
  2. * FeJson后台运行程序
  3. * @author zhaoxianlie
  4. */
  5. import MSG_TYPE from '../static/js/common.js';
  6. import Settings from '../options/settings.js';
  7. import Menu from './menu.js';
  8. import Awesome from './awesome.js';
  9. import InjectTools from './inject-tools.js';
  10. import Monkey from './monkey.js';
  11. import Statistics from './statistics.js';
  12. let BgPageInstance = (function () {
  13. let FeJson = {
  14. notifyTimeoutId: -1
  15. };
  16. // 黑名单页面
  17. let blacklist = [
  18. /^https:\/\/chrome\.google\.com/
  19. ];
  20. const FeHelperBg = {};
  21. // 全局缓存最新的客户端信息
  22. let FH_CLIENT_INFO = {};
  23. /**
  24. * 文本格式,可以设置一个图标和标题
  25. * @param {Object} options
  26. * @config {string} type notification的类型,可选值:html、text
  27. * @config {string} icon 图标
  28. * @config {string} title 标题
  29. * @config {string} message 内容
  30. */
  31. let notifyText = function (options) {
  32. let notifyId = 'FeJson-notify-id';
  33. clearTimeout(FeJson.notifyTimeoutId);
  34. if (options.closeImmediately) {
  35. return chrome.notifications.clear(notifyId);
  36. }
  37. if (!options.icon) {
  38. options.icon = "static/img/fe-48.png";
  39. }
  40. if (!options.title) {
  41. options.title = "温馨提示";
  42. }
  43. chrome.notifications.create(notifyId, {
  44. type: 'basic',
  45. title: options.title,
  46. iconUrl: chrome.runtime.getURL(options.icon),
  47. message: options.message
  48. });
  49. FeJson.notifyTimeoutId = setTimeout(() => {
  50. chrome.notifications.clear(notifyId);
  51. }, parseInt(options.autoClose || 3000, 10));
  52. };
  53. // 像页面注入css脚本
  54. let _injectContentCss = function(tabId,toolName,isDevTool){
  55. if(isDevTool){
  56. Awesome.getContentScript(toolName, true)
  57. .then(css => {
  58. InjectTools.inject(tabId, { css })
  59. });
  60. }else{
  61. InjectTools.inject(tabId, {files: [`${toolName}/content-script.css`]});
  62. }
  63. };
  64. let _getContentScriptFiles = function (tool) {
  65. let files = [];
  66. switch (tool) {
  67. case 'json-format':
  68. files.push(
  69. 'json-format/json-bigint.js',
  70. 'json-format/format-lib.js',
  71. 'json-format/json-abc.js',
  72. 'json-format/json-decode.js'
  73. );
  74. break;
  75. }
  76. files.push(`${tool}/content-script.js`);
  77. return files;
  78. };
  79. // 往当前页面直接注入脚本,不再使用content-script的配置了
  80. let _injectContentScripts = function (tabId) {
  81. // FH工具脚本注入
  82. Awesome.getInstalledTools().then(tools => {
  83. // 注入js
  84. let jsTools = Object.keys(tools)
  85. .filter(tool => !tools[tool]._devTool
  86. && (tools[tool].contentScriptJs || tools[tool].contentScript));
  87. let initFuncNames = jsTools.map(t => `${t.replace(/-/g, '')}ContentScript`);
  88. let jsFiles = jsTools.reduce((files, tool) => files.concat(_getContentScriptFiles(tool)), []);
  89. InjectTools.inject(tabId, {
  90. files: jsFiles,
  91. func: (names) => { names.forEach(n => { try { let fn = window[n]; fn && fn(); } catch(e) {} }); },
  92. args: [initFuncNames]
  93. });
  94. });
  95. // 其他开发者自定义工具脚本注入======For FH DevTools
  96. Awesome.getInstalledTools().then(tools => {
  97. let list = Object.keys(tools).filter(tool => tools[tool]._devTool);
  98. // 注入js脚本
  99. list.filter(tool => (tools[tool].contentScriptJs || tools[tool].contentScript))
  100. .map(tool => Awesome.getContentScript(tool).then(js => {
  101. InjectTools.inject(tabId, { js });
  102. }));
  103. });
  104. };
  105. /**
  106. * 打开打赏弹窗
  107. * @param {string} toolName - 工具名称
  108. */
  109. FeHelperBg.gotoDonateModal = function (toolName) {
  110. chrome.tabs.query({currentWindow: true}, function (tabs) {
  111. Settings.getOptions((opts) => {
  112. let isOpened = false;
  113. let tabId;
  114. let reg = new RegExp("^chrome.*\\/options\\/index.html\\?donate_from=" + toolName + "$", "i");
  115. for (let i = 0, len = tabs.length; i < len; i++) {
  116. if (reg.test(tabs[i].url)) {
  117. isOpened = true;
  118. tabId = tabs[i].id;
  119. break;
  120. }
  121. }
  122. if (!isOpened) {
  123. let url = `/options/index.html?donate_from=${toolName}`;
  124. chrome.tabs.create({ url,active: true });
  125. } else {
  126. chrome.tabs.update(tabId, {highlighted: true}).then(tab => {
  127. chrome.tabs.reload(tabId);
  128. });
  129. }
  130. // 记录工具使用
  131. Statistics.recordToolUsage('donate',{from: toolName});
  132. });
  133. });
  134. };
  135. /**
  136. * 动态运行工具
  137. * @param configs
  138. * @config tool 工具名称
  139. * @config withContent 默认携带的内容
  140. * @config query 请求参数
  141. * @config noPage 无页面模式
  142. * @constructor
  143. */
  144. FeHelperBg.DynamicToolRunner = async function (configs) {
  145. if (!configs || typeof configs !== 'object') return;
  146. let tool = configs.tool || configs.page;
  147. if (!tool) return;
  148. let withContent = configs.withContent;
  149. let activeTab = null;
  150. let query = configs.query;
  151. if (configs.noPage) {
  152. let toolFunc = tool.replace(/-/g, '');
  153. chrome.tabs.query({active: true, currentWindow: true}, tabs => {
  154. let found = tabs.some(tab => {
  155. if (/^(http(s)?|file):\/\//.test(tab.url) && blacklist.every(reg => !reg.test(tab.url))) {
  156. let funcName = toolFunc + 'NoPage';
  157. InjectTools.inject(tab.id, {
  158. func: (fn, t) => { window[fn] && window[fn](t); },
  159. args: [funcName, tab]
  160. });
  161. return true;
  162. }
  163. return false;
  164. });
  165. if (!found) {
  166. notifyText({
  167. message: '抱歉,此工具无法在当前页面使用!'
  168. });
  169. }
  170. });
  171. return;
  172. }
  173. chrome.tabs.query({currentWindow: true}, function (tabs) {
  174. activeTab = tabs.filter(tab => tab.active)[0];
  175. // 如果是二维码工具,且没有传入内容,则使用当前页面的URL
  176. if (tool === 'qr-code' && !withContent && activeTab) {
  177. withContent = activeTab.url;
  178. }
  179. Settings.getOptions((opts) => {
  180. let isOpened = false;
  181. let tabId;
  182. // 允许在新窗口打开
  183. if (String(opts['FORBID_OPEN_IN_NEW_TAB']) === 'true') {
  184. let reg = new RegExp("^chrome.*\\/" + tool + "\\/index.html" + (query ? "\\?" + query : '') + "$", "i");
  185. for (let i = 0, len = tabs.length; i < len; i++) {
  186. if (reg.test(tabs[i].url)) {
  187. isOpened = true;
  188. tabId = tabs[i].id;
  189. break;
  190. }
  191. }
  192. }
  193. if (!isOpened) {
  194. let url = `/${tool}/index.html` + (query ? "?" + query : '');
  195. chrome.tabs.create({
  196. url,
  197. active: true
  198. }).then(tab => {
  199. FeJson[tab.id] = { content: withContent };
  200. }).catch(() => {});
  201. } else {
  202. chrome.tabs.update(tabId, {highlighted: true}).then(tab => {
  203. FeJson[tab.id] = { content: withContent };
  204. chrome.tabs.reload(tabId);
  205. }).catch(() => {});
  206. }
  207. });
  208. });
  209. };
  210. /**
  211. * 动态在icon处显示提示
  212. * @param tips
  213. * @private
  214. */
  215. let _animateTips = (tips) => {
  216. setTimeout(() => {
  217. chrome.action.setBadgeText({text: tips});
  218. setTimeout(() => {
  219. chrome.action.setBadgeText({text: ''});
  220. }, 2000);
  221. }, 3000);
  222. };
  223. /**
  224. * 插件图标点击后的默认动作
  225. * @param request
  226. * @param sender
  227. * @param callback
  228. */
  229. let browserActionClickedHandler = function (request, sender, callback) {
  230. // 获取当前唯一安装的工具并直接打开
  231. Awesome.getInstalledTools().then(tools => {
  232. const installedTools = Object.keys(tools).filter(tool => tools[tool].installed);
  233. if (installedTools.length === 1) {
  234. const singleTool = installedTools[0];
  235. FeHelperBg.DynamicToolRunner({
  236. tool: singleTool,
  237. noPage: !!tools[singleTool].noPage
  238. });
  239. // 记录工具使用
  240. Statistics.recordToolUsage(singleTool);
  241. } else {
  242. // 备用方案:如果检测失败,打开JSON格式化工具
  243. FeHelperBg.DynamicToolRunner({
  244. tool: MSG_TYPE.JSON_FORMAT
  245. });
  246. // 记录工具使用
  247. Statistics.recordToolUsage(MSG_TYPE.JSON_FORMAT);
  248. }
  249. }).catch(error => {
  250. console.error('获取工具列表失败,使用默认工具:', error);
  251. // 出错时的备用方案
  252. FeHelperBg.DynamicToolRunner({
  253. tool: MSG_TYPE.JSON_FORMAT
  254. });
  255. // 记录工具使用
  256. Statistics.recordToolUsage(MSG_TYPE.JSON_FORMAT);
  257. });
  258. };
  259. /**
  260. * 更新browser action的点击动作
  261. * @param action install / upgrade / offload
  262. * @param showTips 是否notify
  263. * @param menuOnly 只管理Menu
  264. * @private
  265. */
  266. let _updateBrowserAction = function (action, showTips, menuOnly) {
  267. if (!menuOnly) {
  268. // 对于卸载操作,添加一个小延迟确保存储操作完成
  269. const delay = action === 'offload' ? 100 : 0;
  270. setTimeout(() => {
  271. // 如果有安装过工具,则显示Popup模式
  272. Awesome.getInstalledTools().then(tools => {
  273. // 计算已安装的工具数量
  274. const installedTools = Object.keys(tools).filter(tool => tools[tool].installed);
  275. const installedCount = installedTools.length;
  276. if (installedCount > 1) {
  277. // 多个工具:显示popup
  278. chrome.action.setPopup({ popup: '/popup/index.html' });
  279. // 移除点击监听器(如果存在)
  280. if (chrome.action.onClicked.hasListener(browserActionClickedHandler)) {
  281. chrome.action.onClicked.removeListener(browserActionClickedHandler);
  282. }
  283. } else if (installedCount === 1) {
  284. // 只有一个工具:直接打开工具,不显示popup
  285. chrome.action.setPopup({ popup: '' });
  286. // 添加点击监听器
  287. if (!chrome.action.onClicked.hasListener(browserActionClickedHandler)) {
  288. chrome.action.onClicked.addListener(browserActionClickedHandler);
  289. }
  290. } else {
  291. // 没有安装任何工具:显示popup(让用户去安装工具)
  292. chrome.action.setPopup({ popup: '/popup/index.html' });
  293. // 移除点击监听器(如果存在)
  294. if (chrome.action.onClicked.hasListener(browserActionClickedHandler)) {
  295. chrome.action.onClicked.removeListener(browserActionClickedHandler);
  296. }
  297. }
  298. });
  299. }, delay);
  300. if (action === 'offload') {
  301. _animateTips('-1');
  302. } else if(!!action) {
  303. _animateTips('+1');
  304. }
  305. } else {
  306. // 重绘菜单
  307. Menu.rebuild();
  308. }
  309. if (showTips) {
  310. let actionTxt = '';
  311. switch (action) {
  312. case 'install':
  313. actionTxt = '工具已「安装」成功,并已添加到弹出下拉列表,点击FeHelper图标可正常使用!';
  314. break;
  315. case 'offload':
  316. actionTxt = '工具已「卸载」成功,并已从弹出下拉列表中移除!';
  317. break;
  318. case 'menu-install':
  319. actionTxt = '已将此工具快捷方式加入到「右键菜单」中!';
  320. break;
  321. case 'menu-offload':
  322. actionTxt = '已将此工具快捷方式从「右键菜单」中移除!';
  323. break;
  324. default:
  325. actionTxt = '恭喜,操作成功!';
  326. }
  327. notifyText({
  328. message: actionTxt,
  329. autoClose: 2500
  330. });
  331. }
  332. };
  333. // 捕获当前页面可视区域
  334. let _captureVisibleTab = function (callback) {
  335. chrome.tabs.captureVisibleTab(null, {format: 'png', quality: 100}, uri => {
  336. callback && callback(uri);
  337. });
  338. };
  339. let _addScreenShotByPages = function(params,callback){
  340. chrome.tabs.captureVisibleTab(null, {format: 'png', quality: 100}, uri => {
  341. callback({ params, uri });
  342. });
  343. };
  344. let _showScreenShotResult = function(data){
  345. // 确保截图数据完整有效
  346. if (!data || !data.screenshots || !data.screenshots.length) {
  347. return;
  348. }
  349. FeHelperBg.DynamicToolRunner({
  350. tool: 'screenshot',
  351. withContent: data
  352. });
  353. };
  354. let _colorPickerCapture = function(params) {
  355. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  356. if (!tabs || !tabs.length) return;
  357. chrome.tabs.captureVisibleTab(null, {format: 'png'}, function (dataUrl) {
  358. if (chrome.runtime.lastError || !dataUrl) return;
  359. let pickerParams = { setPickerImage: true, pickerImage: dataUrl };
  360. InjectTools.inject(tabs[0].id, {
  361. func: (p) => { window.colorpickerNoPage && window.colorpickerNoPage(p); },
  362. args: [pickerParams]
  363. });
  364. });
  365. });
  366. };
  367. let _codeBeautify = function(params, sender){
  368. let tabId = params.tabId || (sender && sender.tab && sender.tab.id);
  369. if (!tabId) return;
  370. let fileType = params.fileType;
  371. if (!['javascript', 'css'].includes(fileType)) {
  372. fileType = 'javascript';
  373. }
  374. Awesome.StorageMgr.get('JS_CSS_PAGE_BEAUTIFY').then(val => {
  375. if(val !== '0') {
  376. let filesToInject = [];
  377. if (fileType === 'javascript') {
  378. filesToInject.push('code-beautify/beautify.js');
  379. }
  380. let triggerDetect = () => {
  381. InjectTools.inject(tabId, {
  382. func: (ft) => { window._codebutifydetect_ && window._codebutifydetect_(ft); },
  383. args: [fileType]
  384. });
  385. };
  386. if (filesToInject.length) {
  387. chrome.scripting.executeScript({
  388. target: { tabId },
  389. files: filesToInject
  390. }, triggerDetect);
  391. } else {
  392. triggerDetect();
  393. }
  394. Statistics.recordToolUsage('code-beautify');
  395. }
  396. });
  397. };
  398. /**
  399. * 接收来自content_scripts发来的消息
  400. */
  401. let _addExtensionListener = function () {
  402. _updateBrowserAction();
  403. chrome.runtime.onMessage.addListener(function (request, sender, callback) {
  404. // 如果发生了错误,就啥都别干了
  405. if (chrome.runtime.lastError) {
  406. return true;
  407. }
  408. // 动态安装工具或者卸载工具,需要更新browserAction
  409. if (request.type === MSG_TYPE.DYNAMIC_TOOL_INSTALL_OR_OFFLOAD) {
  410. _updateBrowserAction(request.action, request.showTips, request.menuOnly);
  411. callback && callback();
  412. }
  413. // 截屏
  414. else if (request.type === MSG_TYPE.CAPTURE_VISIBLE_PAGE) {
  415. _captureVisibleTab(callback);
  416. // 记录工具使用
  417. Statistics.recordToolUsage('screenshot');
  418. }
  419. // 直接处理content-script.js中的截图请求
  420. else if (request.type === 'fh-screenshot-capture-visible') {
  421. _captureVisibleTab(callback);
  422. // 记录工具使用
  423. Statistics.recordToolUsage('screenshot');
  424. }
  425. // 打开动态工具页面
  426. else if (request.type === MSG_TYPE.OPEN_DYNAMIC_TOOL) {
  427. FeHelperBg.DynamicToolRunner(request);
  428. // 记录工具使用
  429. if (request.page) {
  430. Statistics.recordToolUsage(request.page);
  431. }
  432. callback && callback();
  433. }
  434. // 打开其他页面
  435. else if (request.type === MSG_TYPE.OPEN_PAGE) {
  436. FeHelperBg.DynamicToolRunner({
  437. tool: request.page
  438. });
  439. // 记录工具使用
  440. if (request.page) {
  441. Statistics.recordToolUsage(request.page);
  442. }
  443. callback && callback();
  444. }
  445. // 任何事件,都可以通过这个钩子来完成
  446. else if (request.type === MSG_TYPE.DYNAMIC_ANY_THING) {
  447. switch(request.thing){
  448. // 插件选项保存成功提示
  449. case 'save-options':
  450. notifyText({
  451. message: '配置修改已生效,请继续使用!',
  452. autoClose: 2000
  453. });
  454. break;
  455. // 触发网页截图功能
  456. case 'trigger-screenshot':
  457. handleTriggerScreenshot(request.tabId);
  458. break;
  459. // 获取JSON格式化工具的配置选项
  460. case 'request-jsonformat-options':
  461. requestJsonformatOptions(request.params, callback);
  462. return true; // 这个返回true是非常重要的!!!要不然callback会拿不到结果
  463. // 保存JSON格式化工具的配置选项
  464. case 'save-jsonformat-options':
  465. saveJsonformatOptions(request.params, callback);
  466. return true;
  467. // 切换JSON格式化工具栏显示状态
  468. case 'toggle-jsonformat-options':
  469. toggleJsonformatOptions(callback);
  470. return true; // 这个返回true是非常重要的!!!要不然callback会拿不到结果
  471. // 代码美化功能
  472. case 'code-beautify':
  473. _codeBeautify(request.params, sender);
  474. break;
  475. // 关闭代码美化功能
  476. case 'close-beautify':
  477. handleCloseBeautify();
  478. break;
  479. // 二维码解码功能
  480. case 'qr-decode':
  481. handleQrDecode(request.params.uri);
  482. break;
  483. // 请求页面内容数据
  484. case 'request-page-content':
  485. handleRequestPageContent(request);
  486. break;
  487. // 设置页面性能时序数据
  488. case 'set-page-timing-data':
  489. handleSetPageTimingData(request.wpoInfo);
  490. break;
  491. // 颜色拾取器截图功能
  492. case 'color-picker-capture':
  493. _colorPickerCapture(request.params);
  494. // 记录工具使用
  495. Statistics.recordToolUsage('color-picker');
  496. break;
  497. // 分页截图功能
  498. case 'add-screen-shot-by-pages':
  499. _addScreenShotByPages(request.params,callback);
  500. // 记录工具使用
  501. Statistics.recordToolUsage('screenshot');
  502. return true;
  503. // 页面截图完成处理
  504. case 'page-screenshot-done':
  505. _showScreenShotResult(request.params);
  506. break;
  507. // 启动页面脚本注入(油猴功能)
  508. case 'request-monkey-start':
  509. Monkey.start(request.params);
  510. break;
  511. // 注入内容脚本CSS样式
  512. case 'inject-content-css':
  513. _injectContentCss(sender.tab.id,request.tool,!!request.devTool);
  514. break;
  515. // 打开插件选项页面
  516. case 'open-options-page':
  517. chrome.runtime.openOptionsPage();
  518. break;
  519. // 打开打赏弹窗
  520. case 'open-donate-modal':
  521. FeHelperBg.gotoDonateModal(request.params.toolName);
  522. break;
  523. // 通过 chrome.scripting.executeScript 注入脚本文件(CSP安全)
  524. case 'inject-scripts-to-tab':
  525. if (sender.tab && sender.tab.id && request.files) {
  526. chrome.scripting.executeScript({
  527. target: { tabId: sender.tab.id },
  528. files: request.files
  529. }, () => { callback && callback(true); });
  530. } else {
  531. callback && callback(false);
  532. }
  533. return true;
  534. // 加载本地脚本文件
  535. case 'load-local-script':
  536. loadLocalScript(request.script, callback);
  537. return true; // 异步响应需要返回true
  538. // 工具使用统计埋点
  539. case 'statistics-tool-usage':
  540. // 埋点:自动触发json-format-auto
  541. Statistics.recordToolUsage(request.params.tool_name,request.params);
  542. break;
  543. // 获取热修复脚本
  544. case 'fetch-hotfix-json':
  545. fetchHotfixJson(callback);
  546. return true; // 异步响应必须返回true
  547. // 获取插件补丁数据
  548. case 'fetch-fehelper-patchs':
  549. fetchFehelperPatchs(callback);
  550. return true;
  551. // 获取指定工具的补丁
  552. case 'fh-get-tool-patch':
  553. getToolPatch(request.toolName, callback);
  554. return true;
  555. }
  556. callback && callback(request.params);
  557. } else {
  558. callback && callback();
  559. }
  560. return true;
  561. });
  562. // 每开一个窗口,都向内容脚本注入一个js,绑定tabId
  563. chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
  564. if (String(changeInfo.status).toLowerCase() === "complete") {
  565. if(/^(http(s)?|file):\/\//.test(tab.url) && blacklist.every(reg => !reg.test(tab.url))){
  566. InjectTools.inject(tabId, { func: (id) => { window.__FH_TAB_ID__ = id; }, args: [tabId] });
  567. _injectContentScripts(tabId);
  568. }
  569. }
  570. });
  571. // 安装与更新
  572. chrome.runtime.onInstalled.addListener(({reason, previousVersion}) => {
  573. switch (reason) {
  574. case 'install':
  575. chrome.runtime.openOptionsPage();
  576. Statistics.recordInstallation();
  577. break;
  578. case 'update':
  579. _animateTips('+++1');
  580. Statistics.recordUpdate(previousVersion);
  581. if (previousVersion === '2019.12.2415') {
  582. notifyText({
  583. message: '历尽千辛万苦,FeHelper已升级到最新版本,可以到插件设置页去安装旧版功能了!',
  584. autoClose: 5000
  585. });
  586. }
  587. break;
  588. }
  589. Menu.rebuild();
  590. });
  591. // MV3: Service Worker 每次重新启动时确保菜单可用
  592. chrome.runtime.onStartup.addListener(() => {
  593. Menu.rebuild();
  594. });
  595. // 卸载
  596. chrome.runtime.setUninstallURL(chrome.runtime.getManifest().homepage_url);
  597. };
  598. let runUpdateCheck = function () {
  599. if (chrome.runtime.requestUpdateCheck && navigator.userAgent.indexOf("Firefox") === -1) {
  600. chrome.runtime.requestUpdateCheck((status) => {
  601. if (status === "update_available") {
  602. chrome.runtime.reload();
  603. }
  604. });
  605. }
  606. };
  607. chrome.alarms.onAlarm.addListener((alarm) => {
  608. if (alarm.name === 'fehelper-check-update') {
  609. runUpdateCheck();
  610. }
  611. });
  612. /**
  613. * 检查插件更新
  614. * @private
  615. */
  616. let _checkUpdate = function () {
  617. chrome.alarms.create('fehelper-check-update', { periodInMinutes: 1440 });
  618. runUpdateCheck();
  619. };
  620. /**
  621. * 初始化
  622. */
  623. let _init = function () {
  624. _checkUpdate();
  625. _addExtensionListener();
  626. Statistics.init();
  627. Menu.rebuild();
  628. setTimeout(() => {
  629. Awesome.gcLocalFiles();
  630. }, 1000 * 10);
  631. };
  632. /**
  633. * 触发截图工具的执行
  634. * @param {number} tabId - 标签页ID
  635. */
  636. function _triggerScreenshotTool(tabId) {
  637. // 先尝试直接发送消息给content script
  638. chrome.tabs.sendMessage(tabId, {
  639. type: 'fh-screenshot-start'
  640. }).then(() => {
  641. // 成功触发
  642. }).catch(() => {
  643. // 如果发送消息失败,使用noPage模式
  644. FeHelperBg.DynamicToolRunner({
  645. tool: 'screenshot',
  646. noPage: true
  647. });
  648. });
  649. }
  650. // 监听options页面传递的客户端信息
  651. chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  652. if (request && request.type === 'clientInfo' && request.data) {
  653. FH_CLIENT_INFO = request.data;
  654. }
  655. });
  656. // 处理从popup触发的截图请求
  657. function handleTriggerScreenshot(tabId) {
  658. if (tabId) {
  659. _triggerScreenshotTool(tabId);
  660. } else {
  661. FeHelperBg.DynamicToolRunner({
  662. tool: 'screenshot',
  663. noPage: true
  664. });
  665. }
  666. // 记录工具使用
  667. Statistics.recordToolUsage('screenshot');
  668. }
  669. // 请求JSON格式化选项配置
  670. function requestJsonformatOptions(params, callback) {
  671. Awesome.StorageMgr.get(params).then(result => {
  672. Object.keys(result).forEach(key => {
  673. if (['MAX_JSON_KEYS_NUMBER', 'JSON_FORMAT_THEME'].includes(key)) {
  674. result[key] = parseInt(result[key]);
  675. } else {
  676. result[key] = (""+result[key] !== 'false');
  677. }
  678. });
  679. callback && callback(result);
  680. });
  681. }
  682. // 保存JSON格式化选项配置
  683. function saveJsonformatOptions(params, callback) {
  684. Awesome.StorageMgr.set(params).then(() => {
  685. callback && callback();
  686. });
  687. // 记录工具使用
  688. Statistics.recordToolUsage('save-jsonformat-options');
  689. }
  690. // 切换JSON格式化选项显示状态
  691. function toggleJsonformatOptions(callback) {
  692. Awesome.StorageMgr.get('JSON_TOOL_BAR_ALWAYS_SHOW').then(result => {
  693. let show = result !== false;
  694. Awesome.StorageMgr.set('JSON_TOOL_BAR_ALWAYS_SHOW',!show).then(() => {
  695. callback && callback(!show);
  696. });
  697. });
  698. // 记录工具使用
  699. Statistics.recordToolUsage('json-format');
  700. }
  701. // 关闭代码美化功能
  702. function handleCloseBeautify() {
  703. Awesome.StorageMgr.set('JS_CSS_PAGE_BEAUTIFY',0);
  704. // 记录工具使用
  705. Statistics.recordToolUsage('code-beautify-close');
  706. }
  707. // 处理二维码解码
  708. function handleQrDecode(uri) {
  709. FeHelperBg.DynamicToolRunner({
  710. withContent: uri,
  711. tool: 'qr-code',
  712. query: `mode=decode`
  713. });
  714. // 记录工具使用
  715. Statistics.recordToolUsage('qr-code');
  716. }
  717. // 处理页面内容请求
  718. function handleRequestPageContent(request) {
  719. request.params = FeJson[request.tabId];
  720. delete FeJson[request.tabId];
  721. }
  722. // 处理页面性能数据设置
  723. function handleSetPageTimingData(wpoInfo) {
  724. FeHelperBg.DynamicToolRunner({
  725. tool: 'page-timing',
  726. withContent: wpoInfo
  727. });
  728. // 记录工具使用
  729. Statistics.recordToolUsage('page-timing');
  730. }
  731. // 获取指定工具的补丁(css/js)
  732. function getToolPatch(toolName, callback) {
  733. // 如果没有提供toolName,直接返回空补丁
  734. if (!toolName) {
  735. callback && callback({ css: '', js: '' });
  736. return;
  737. }
  738. let version = String(chrome.runtime.getManifest().version).split('.').map(n => parseInt(n)).join('.');
  739. const storageKey = `FH_PATCH_HOTFIX_${version}`;
  740. chrome.storage.local.get(storageKey, result => {
  741. const patchs = result[storageKey];
  742. if (patchs && patchs[toolName]) {
  743. const { css, js } = patchs[toolName];
  744. callback && callback({ css, js });
  745. } else {
  746. callback && callback({ css: '', js: '' });
  747. }
  748. });
  749. }
  750. // 加载本地脚本,处理加载JSON格式化相关脚本的请求
  751. function loadLocalScript(scriptUrl, callback) {
  752. fetch(scriptUrl)
  753. .then(response => response.text())
  754. .then(scriptContent => {
  755. callback && callback(scriptContent);
  756. })
  757. .catch(error => {
  758. console.error('加载脚本失败:', error);
  759. callback && callback(null);
  760. });
  761. }
  762. // 获取热修复脚本,代理请求 hotfix.json,解决CORS问题
  763. function fetchHotfixJson(callback) {
  764. fetch('https://fehelper.com/static/js/hotfix.json?v=' + Date.now())
  765. .then(response => response.text())
  766. .then(scriptContent => {
  767. callback && callback({ success: true, content: scriptContent });
  768. })
  769. .catch(error => {
  770. callback && callback({ success: false, error: error.message });
  771. });
  772. }
  773. // 检查并获取补丁(带频率控制)
  774. function checkAndFetchPatchs() {
  775. const PATCH_CHECK_INTERVAL = 5 * 60 * 1000; // 5min
  776. const STORAGE_KEY = 'FH_LAST_PATCH_CHECK';
  777. chrome.storage.local.get(STORAGE_KEY, (result) => {
  778. const lastCheck = result[STORAGE_KEY] || 0;
  779. const now = Date.now();
  780. if (now - lastCheck > PATCH_CHECK_INTERVAL) {
  781. console.log(`[FeHelper] 距离上次检查已超过5min,开始检查热更新...`);
  782. fetchFehelperPatchs((result) => {
  783. if (result && result.success) {
  784. console.log(`[FeHelper] 自动热更新成功,版本: v${result.version}`);
  785. } else if (result && result.notFound) {
  786. console.log(`[FeHelper] 当前版本暂无热更新补丁`);
  787. } else {
  788. console.log(`[FeHelper] 自动热更新检查失败:`, result?.error);
  789. }
  790. // 更新最后检查时间
  791. chrome.storage.local.set({ [STORAGE_KEY]: now });
  792. });
  793. } else {
  794. const nextCheck = new Date(lastCheck + PATCH_CHECK_INTERVAL);
  795. console.log(`[FeHelper] 距离上次检查不足5min,下次检查时间: ${nextCheck.toLocaleString()}`);
  796. }
  797. });
  798. }
  799. // 获取FeHelper热修复补丁
  800. function fetchFehelperPatchs(callback) {
  801. let version = String(chrome.runtime.getManifest().version).split('.').map(n => parseInt(n)).join('.');
  802. let patchUrl = `https://fehelper.com/v1/fh-patchs/v${version}.json`;
  803. // 先检测文件是否存在(使用HEAD请求)
  804. fetch(patchUrl, { method: 'HEAD' })
  805. .then(response => {
  806. if (response.ok) {
  807. // 文件存在,进行正常的fetch操作
  808. return fetch(`${patchUrl}?t=${Date.now()}`)
  809. .then(resp => {
  810. if (!resp.ok) {
  811. throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
  812. }
  813. return resp.json();
  814. })
  815. .then(data => {
  816. const patchs = data.patchs || data;
  817. const storageData = {};
  818. storageData[`FH_PATCH_HOTFIX_${version}`] = patchs;
  819. chrome.storage.local.set(storageData, () => {
  820. console.log(`[FeHelper] 成功获取版本 v${version} 的热修复补丁`);
  821. callback && callback({ success: true, version });
  822. });
  823. });
  824. } else {
  825. // 文件不存在
  826. console.log(`[FeHelper] 服务器上不存在版本 v${version} 的补丁文件`);
  827. callback && callback({ success: false, error: '补丁文件不存在', notFound: true });
  828. }
  829. })
  830. .catch(e => {
  831. callback && callback({ success: false, error: '没有需要修复的补丁' });
  832. });
  833. }
  834. globalThis.FeHelperBg = FeHelperBg;
  835. return {
  836. pageCapture: _captureVisibleTab,
  837. init: _init
  838. };
  839. })();
  840. BgPageInstance.init();