diff --git a/src/app/cache.ts b/src/app/cache.ts index 3d3853f..5f44f68 100644 --- a/src/app/cache.ts +++ b/src/app/cache.ts @@ -134,4 +134,27 @@ export default class Cache { public list(): Promise { return this.storage.list(); } + + private txPromise: Map> = new Map(); + + // 事务处理,如果有事务正在进行,则等待 + public async tx(key: string, set: (result: any) => Promise): Promise { + let promise = this.txPromise.get(key); + if (promise) { + await promise; + } + + promise = this.get(key) + .then((result) => set(result)) + .then((value) => { + console.log("tx", key, value); + if (value) { + return this.set(key, value); + } + return Promise.resolve(); + }); + this.txPromise.set(key, promise); + await promise; + this.txPromise.delete(key); + } } diff --git a/src/app/service/service_worker/popup.ts b/src/app/service/service_worker/popup.ts index 3274e2e..95adc32 100644 --- a/src/app/service/service_worker/popup.ts +++ b/src/app/service/service_worker/popup.ts @@ -103,45 +103,49 @@ export class PopupService { async registerMenuCommand(message: ScriptMenuRegisterCallbackValue) { // 给脚本添加菜单 - const data = await this.getScriptMenu(message.tabId); - const script = data.find((item) => item.uuid === message.uuid); - if (script) { - const menu = script.menus.find((item) => item.id === message.id); - if (!menu) { - script.menus.push({ - id: message.id, - name: message.name, - accessKey: message.accessKey, - tabId: message.tabId, - frameId: message.frameId, - documentId: message.documentId, - }); - } else { - menu.name = message.name; - menu.accessKey = message.accessKey; - menu.tabId = message.tabId; + return this.txUpdateScriptMenu(message.tabId, async (data) => { + console.log("register menu", message, data); + const script = data.find((item) => item.uuid === message.uuid); + if (script) { + const menu = script.menus.find((item) => item.id === message.id); + if (!menu) { + script.menus.push({ + id: message.id, + name: message.name, + accessKey: message.accessKey, + tabId: message.tabId, + frameId: message.frameId, + documentId: message.documentId, + }); + } else { + menu.name = message.name; + menu.accessKey = message.accessKey; + menu.tabId = message.tabId; + menu.frameId = message.frameId; + menu.documentId = message.documentId; + } } - } - console.log("set menu", data); - await Cache.getInstance().set("tabScript:" + message.tabId, data); - console.log("update menu"); - this.updateScriptMenu(); + this.updateScriptMenu(); + return data; + }); } async unregisterMenuCommand({ id, uuid, tabId }: { id: number; uuid: string; tabId: number }) { - const data = await this.getScriptMenu(tabId); - // 删除脚本菜单 - const script = data.find((item) => item.uuid === uuid); - if (script) { - script.menus = script.menus.filter((item) => item.id !== id); - } - await Cache.getInstance().set("tabScript:" + tabId, data); - this.updateScriptMenu(); + return this.txUpdateScriptMenu(tabId, async (data) => { + // 删除脚本菜单 + const script = data.find((item) => item.uuid === uuid); + if (script) { + script.menus = script.menus.filter((item) => item.id !== id); + } + console.log("unregister menu", data); + this.updateScriptMenu(); + return data; + }); } updateScriptMenu() { // 获取当前页面并更新菜单 - chrome.tabs.query({ active: true }, (tabs) => { + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { console.log("query", tabs); if (!tabs.length) { return; @@ -192,6 +196,13 @@ export class PopupService { return ((await Cache.getInstance().get("tabScript:" + tabId)) || []) as ScriptMenu[]; } + // 事务更新脚本菜单 + txUpdateScriptMenu(tabId: number, callback: (menu: ScriptMenu[]) => Promise) { + return Cache.getInstance().tx("tabScript:" + tabId, async (menu) => { + return callback(menu || []); + }); + } + async addScriptRunNumber({ tabId, frameId, @@ -201,30 +212,28 @@ export class PopupService { frameId: number; scripts: ScriptMatchInfo[]; }) { - if (frameId === undefined) { - // 清理数据 - await Cache.getInstance().del("tabScript:" + tabId); - } // 设置数据 - const data = await this.getScriptMenu(tabId); - // 设置脚本运行次数 - scripts.forEach((script) => { - const scriptMenu = data.find((item) => item.uuid === script.uuid); - if (scriptMenu) { - scriptMenu.runNum = (scriptMenu.runNum || 0) + 1; - if (frameId) { - scriptMenu.runNumByIframe = (scriptMenu.runNumByIframe || 0) + 1; + return this.txUpdateScriptMenu(tabId, async (data) => { + data = []; + // 设置脚本运行次数 + scripts.forEach((script) => { + const scriptMenu = data.find((item) => item.uuid === script.uuid); + if (scriptMenu) { + scriptMenu.runNum = (scriptMenu.runNum || 0) + 1; + if (frameId) { + scriptMenu.runNumByIframe = (scriptMenu.runNumByIframe || 0) + 1; + } + } else { + const item = this.scriptToMenu(script); + item.runNum = 1; + if (frameId) { + item.runNumByIframe = 1; + } + data.push(item); } - } else { - const item = this.scriptToMenu(script); - item.runNum = 1; - if (frameId) { - item.runNumByIframe = 1; - } - data.push(item); - } + }); + return data; }); - Cache.getInstance().set("tabScript:" + tabId, data); } dealBackgroundScriptInstall() { @@ -233,21 +242,17 @@ export class PopupService { if (script.type === SCRIPT_TYPE_NORMAL) { return; } - const menu = await this.getScriptMenu(-1); - const scriptMenu = menu.find((item) => item.uuid === script.uuid); - if (script.status === SCRIPT_STATUS_ENABLE) { - // 加入菜单 - if (!scriptMenu) { - const item = this.scriptToMenu(script); - menu.push(item); + return this.txUpdateScriptMenu(-1, async (menu) => { + const scriptMenu = menu.find((item) => item.uuid === script.uuid); + if (script.status === SCRIPT_STATUS_ENABLE) { + // 加入菜单 + if (!scriptMenu) { + const item = this.scriptToMenu(script); + menu.push(item); + } } - } else { - // 移出菜单 - if (scriptMenu) { - menu.splice(menu.indexOf(scriptMenu), 1); - } - } - Cache.getInstance().set("tabScript:" + -1, menu); + return menu; + }); }); subscribeScriptEnable(this.mq, async ({ uuid }) => { const script = await this.scriptDAO.get(uuid); @@ -257,37 +262,42 @@ export class PopupService { if (script.type === SCRIPT_TYPE_NORMAL) { return; } - const menu = await this.getScriptMenu(-1); - const scriptMenu = menu.find((item) => item.uuid === uuid); - if (script.status === SCRIPT_STATUS_ENABLE) { - // 加入菜单 - if (!scriptMenu) { - const item = this.scriptToMenu(script); - menu.push(item); + return this.txUpdateScriptMenu(-1, async (menu) => { + const scriptMenu = menu.find((item) => item.uuid === uuid); + if (script.status === SCRIPT_STATUS_ENABLE) { + // 加入菜单 + if (!scriptMenu) { + const item = this.scriptToMenu(script); + menu.push(item); + } + } else { + // 移出菜单 + if (scriptMenu) { + menu.splice(menu.indexOf(scriptMenu), 1); + } } - } else { - // 移出菜单 - if (scriptMenu) { - menu.splice(menu.indexOf(scriptMenu), 1); - } - } - Cache.getInstance().set("tabScript:" + -1, menu); + return menu; + }); }); subscribeScriptDelete(this.mq, async ({ uuid }) => { - const menu = await this.getScriptMenu(-1); - const scriptMenu = menu.find((item) => item.uuid === uuid); - if (scriptMenu) { - menu.splice(menu.indexOf(scriptMenu), 1); - Cache.getInstance().set("tabScript:" + -1, menu); - } + return this.txUpdateScriptMenu(-1, async (menu) => { + const scriptMenu = menu.find((item) => item.uuid === uuid); + if (scriptMenu) { + menu.splice(menu.indexOf(scriptMenu), 1); + return menu; + } + return null; + }); }); subscribeScriptRunStatus(this.mq, async ({ uuid, runStatus }) => { - const menu = await this.getScriptMenu(-1); - const scriptMenu = menu.find((item) => item.uuid === uuid); - if (scriptMenu) { - scriptMenu.runStatus = runStatus; - Cache.getInstance().set("tabScript:" + -1, menu); - } + return this.txUpdateScriptMenu(-1, async (menu) => { + const scriptMenu = menu.find((item) => item.uuid === uuid); + if (scriptMenu) { + scriptMenu.runStatus = runStatus; + return menu; + } + return null; + }); }); } @@ -305,7 +315,6 @@ export class PopupService { documentId: string; }) { // 菜单点击事件 - console.log("click menu", uuid, id, tabId, frameId, documentId); this.runtime.sendMessageToTab( tabId, "menuClick", @@ -333,7 +342,9 @@ export class PopupService { // 监听tab开关 chrome.tabs.onRemoved.addListener((tabId) => { // 清理数据 - Cache.getInstance().del("tabScript:" + tabId); + this.txUpdateScriptMenu(tabId, async () => { + return []; + }); }); // 监听页面切换加载菜单 chrome.tabs.onActivated.addListener((activeInfo) => { diff --git a/src/app/service/service_worker/runtime.ts b/src/app/service/service_worker/runtime.ts index 5432edf..420e480 100644 --- a/src/app/service/service_worker/runtime.ts +++ b/src/app/service/service_worker/runtime.ts @@ -212,6 +212,7 @@ export class RuntimeService { return runScript(this.sender, res); } + // 注册inject.js registerInjectScript() { chrome.userScripts.getScripts({ ids: ["scriptcat-inject"] }).then((res) => { if (res.length == 0) { diff --git a/src/inject.ts b/src/inject.ts index 46815b0..704e0f8 100644 --- a/src/inject.ts +++ b/src/inject.ts @@ -17,6 +17,7 @@ const server = new Server("inject", msg); server.on("pageLoad", (data: { scripts: ScriptRunResouce[] }) => { logger.logger().debug("inject start"); - const runtime = new InjectRuntime(msg, data.scripts); + // 监听事件 + const runtime = new InjectRuntime(server, msg, data.scripts); runtime.start(); }); diff --git a/src/runtime/content/content.ts b/src/runtime/content/content.ts index cf10fc8..9e0c4c6 100644 --- a/src/runtime/content/content.ts +++ b/src/runtime/content/content.ts @@ -1,5 +1,5 @@ import { ScriptRunResouce } from "@App/app/repo/scripts"; -import { Client } from "@Packages/message/client"; +import { Client, sendMessage } from "@Packages/message/client"; import { forwardMessage, Message, MessageSend, Server } from "@Packages/message/server"; // content页的处理 @@ -7,49 +7,55 @@ export default class ContentRuntime { constructor( private extServer: Server, private server: Server, - private send: MessageSend, + private extSend: MessageSend, private msg: Message ) {} start(scripts: ScriptRunResouce[]) { - this.extServer.on("runtime/menuClick", (action, data) => { - // gm菜单点击 - console.log("runtime/menuClick", action, data); + this.extServer.on("runtime/menuClick", (data) => { + // 转发给inject + return sendMessage(this.msg, "inject/runtime/menuClick", data); }); - this.extServer.on("runtime/valueUpdate", (action, data) => { - // gm value变化 - console.log(action, data); + this.extServer.on("runtime/valueUpdate", (data) => { + // 转发给inject + return sendMessage(this.msg, "inject/runtime/valueUpdate", data); }); - forwardMessage("serviceWorker", "runtime/gmApi", this.server, this.send, (data: { api: string; params: any }) => { - // 拦截关注的api - switch (data.api) { - case "CAT_createBlobUrl": { - const file = data.params[0] as File; - const url = URL.createObjectURL(file); - setTimeout(() => { - URL.revokeObjectURL(url); - }, 60 * 1000); - return Promise.resolve(url); - } - case "CAT_fetchBlob": { - return fetch(data.params[0]).then((res) => res.blob()); - } - case "CAT_fetchDocument": { - return new Promise((resolve) => { - const xhr = new XMLHttpRequest(); - xhr.responseType = "document"; - xhr.open("GET", data.params[0]); - xhr.onload = () => { - resolve({ - relatedTarget: xhr.response, - }); - }; - xhr.send(); - }); + forwardMessage( + "serviceWorker", + "runtime/gmApi", + this.server, + this.extSend, + (data: { api: string; params: any }) => { + // 拦截关注的api + switch (data.api) { + case "CAT_createBlobUrl": { + const file = data.params[0] as File; + const url = URL.createObjectURL(file); + setTimeout(() => { + URL.revokeObjectURL(url); + }, 60 * 1000); + return Promise.resolve(url); + } + case "CAT_fetchBlob": { + return fetch(data.params[0]).then((res) => res.blob()); + } + case "CAT_fetchDocument": { + return new Promise((resolve) => { + const xhr = new XMLHttpRequest(); + xhr.responseType = "document"; + xhr.open("GET", data.params[0]); + xhr.onload = () => { + resolve({ + relatedTarget: xhr.response, + }); + }; + xhr.send(); + }); + } } + return Promise.resolve(false); } - return Promise.resolve(false); - }); + ); const client = new Client(this.msg, "inject"); client.do("pageLoad", { scripts }); } diff --git a/src/runtime/content/exec_script.ts b/src/runtime/content/exec_script.ts index 04a03ef..f35758f 100644 --- a/src/runtime/content/exec_script.ts +++ b/src/runtime/content/exec_script.ts @@ -72,6 +72,10 @@ export default class ExecScript { this.sandboxContent?.valueUpdate(data); } + menuClick(id: number) { + this.sandboxContent?.menuClick(id); + } + exec() { this.logger.debug("script start"); return this.scriptFunc.apply(this.proxyContent, [this.proxyContent, this.GM_info]); diff --git a/src/runtime/content/gm_api.ts b/src/runtime/content/gm_api.ts index 6ccd001..f5c3738 100644 --- a/src/runtime/content/gm_api.ts +++ b/src/runtime/content/gm_api.ts @@ -7,6 +7,7 @@ import { Message, MessageConnect } from "@Packages/message/server"; import { CustomEventMessage } from "@Packages/message/custom_event_message"; import LoggerCore from "@App/app/logger/core"; import { connect, sendMessage } from "@Packages/message/client"; +import EventEmitter from "eventemitter3"; interface ApiParam { depend?: string[]; @@ -106,6 +107,10 @@ export default class GMApi { } } + menuClick(id: number) { + this.EE.emit("menuClick" + id); + } + // 获取脚本信息和管理器信息 static GM_info(script: ScriptRunResouce) { const metadataStr = getMetadataStr(script.code); @@ -202,18 +207,21 @@ export default class GMApi { menuMap: Map | undefined; + EE: EventEmitter = new EventEmitter(); + @GMContext.API() GM_registerMenuCommand(name: string, listener: () => void, accessKey?: string): number { if (!this.menuMap) { this.menuMap = new Map(); } let flag = 0; - this.menuMap.forEach((val, key) => { + this.menuMap.forEach((val, menuId) => { if (val === name) { - flag = key; + flag = menuId; } }); if (flag) { + this.EE.addListener("menuClick" + flag, listener); return flag; } if (!this.menuId) { @@ -222,18 +230,9 @@ export default class GMApi { this.menuId += 1; } const id = this.menuId; - this.sendMessage("GM_registerMenuCommand", [id, name, accessKey]); - // .then((con) => { - // con.onMessage((data: { action: string; data: any }) => { - // if (data.action === "onClick") { - // listener(); - // } - // }); - // con.onDisconnect(() => { - // this.menuMap?.delete(id); - // }); - // }); this.menuMap.set(id, name); + this.EE.addListener("menuClick" + id, listener); + this.sendMessage("GM_registerMenuCommand", [id, name, accessKey]); return id; } @@ -243,6 +242,7 @@ export default class GMApi { this.menuMap = new Map(); } this.menuMap.delete(id); + this.EE.removeAllListeners("menuClick" + id); this.sendMessage("GM_unregisterMenuCommand", [id]); } diff --git a/src/runtime/content/inject.ts b/src/runtime/content/inject.ts index 08912eb..dbbac26 100644 --- a/src/runtime/content/inject.ts +++ b/src/runtime/content/inject.ts @@ -1,5 +1,5 @@ import { ScriptRunResouce } from "@App/app/repo/scripts"; -import { Message } from "@Packages/message/server"; +import { Message, Server } from "@Packages/message/server"; import ExecScript from "./exec_script"; import { addStyle, ScriptFunc } from "./utils"; @@ -7,6 +7,7 @@ export class InjectRuntime { execList: ExecScript[] = []; constructor( + private server: Server, private msg: Message, private scripts: ScriptRunResouce[] ) {} @@ -27,6 +28,19 @@ export class InjectRuntime { }); } }); + this.server.on("runtime/menuClick", (data: { id: number; uuid: string }) => { + // 转发给脚本 + const exec = this.execList.find((val) => val.scriptRes.uuid === data.uuid); + if (exec) { + exec.menuClick(data.id); + } + }); + this.server.on("runtime/valueUpdate", (data: { uuid: string; key: string; value: any }) => { + const exec = this.execList.find((val) => val.scriptRes.uuid === data.uuid); + if (exec) { + // exec.valueUpdate(data.key,); + } + }); } execScript(script: ScriptRunResouce, scriptFunc: ScriptFunc) { diff --git a/src/runtime/content/utils.ts b/src/runtime/content/utils.ts index 24db6cf..f11bd12 100644 --- a/src/runtime/content/utils.ts +++ b/src/runtime/content/utils.ts @@ -3,6 +3,7 @@ import { v4 as uuidv4 } from "uuid"; import GMApi, { ApiValue, GMContext } from "./gm_api"; import { has } from "@App/pkg/utils/lodash"; import { Message } from "@Packages/message/server"; +import EventEmitter from "eventemitter3"; // 构建脚本运行代码 export function compileScriptCode(scriptRes: ScriptRunResouce): string { @@ -64,6 +65,8 @@ export function createContext(scriptRes: ScriptRunResouce, GMInfo: any, envPrefi connect: GMApi.prototype.connect, runFlag: uuidv4(), valueUpdate: GMApi.prototype.valueUpdate, + menuClick: GMApi.prototype.menuClick, + EE: new EventEmitter(), GM: { Info: GMInfo }, GM_info: GMInfo, };