background.js 33 KB

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