gm 菜单响应
Some checks failed
test / Run tests (push) Failing after 8s
build / Build (push) Failing after 10s

This commit is contained in:
王一之 2025-04-08 13:59:08 +08:00
parent f26aecd10f
commit 42975d47cf
9 changed files with 208 additions and 145 deletions

View File

@ -134,4 +134,27 @@ export default class Cache {
public list(): Promise<string[]> { public list(): Promise<string[]> {
return this.storage.list(); return this.storage.list();
} }
private txPromise: Map<string, Promise<any>> = new Map();
// 事务处理,如果有事务正在进行,则等待
public async tx(key: string, set: (result: any) => Promise<any>): Promise<void> {
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);
}
} }

View File

@ -103,7 +103,8 @@ export class PopupService {
async registerMenuCommand(message: ScriptMenuRegisterCallbackValue) { async registerMenuCommand(message: ScriptMenuRegisterCallbackValue) {
// 给脚本添加菜单 // 给脚本添加菜单
const data = await this.getScriptMenu(message.tabId); return this.txUpdateScriptMenu(message.tabId, async (data) => {
console.log("register menu", message, data);
const script = data.find((item) => item.uuid === message.uuid); const script = data.find((item) => item.uuid === message.uuid);
if (script) { if (script) {
const menu = script.menus.find((item) => item.id === message.id); const menu = script.menus.find((item) => item.id === message.id);
@ -120,28 +121,31 @@ export class PopupService {
menu.name = message.name; menu.name = message.name;
menu.accessKey = message.accessKey; menu.accessKey = message.accessKey;
menu.tabId = message.tabId; 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 }) { async unregisterMenuCommand({ id, uuid, tabId }: { id: number; uuid: string; tabId: number }) {
const data = await this.getScriptMenu(tabId); return this.txUpdateScriptMenu(tabId, async (data) => {
// 删除脚本菜单 // 删除脚本菜单
const script = data.find((item) => item.uuid === uuid); const script = data.find((item) => item.uuid === uuid);
if (script) { if (script) {
script.menus = script.menus.filter((item) => item.id !== id); script.menus = script.menus.filter((item) => item.id !== id);
} }
await Cache.getInstance().set("tabScript:" + tabId, data); console.log("unregister menu", data);
this.updateScriptMenu(); this.updateScriptMenu();
return data;
});
} }
updateScriptMenu() { updateScriptMenu() {
// 获取当前页面并更新菜单 // 获取当前页面并更新菜单
chrome.tabs.query({ active: true }, (tabs) => { chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
console.log("query", tabs); console.log("query", tabs);
if (!tabs.length) { if (!tabs.length) {
return; return;
@ -192,6 +196,13 @@ export class PopupService {
return ((await Cache.getInstance().get("tabScript:" + tabId)) || []) as ScriptMenu[]; return ((await Cache.getInstance().get("tabScript:" + tabId)) || []) as ScriptMenu[];
} }
// 事务更新脚本菜单
txUpdateScriptMenu(tabId: number, callback: (menu: ScriptMenu[]) => Promise<any>) {
return Cache.getInstance().tx("tabScript:" + tabId, async (menu) => {
return callback(menu || []);
});
}
async addScriptRunNumber({ async addScriptRunNumber({
tabId, tabId,
frameId, frameId,
@ -201,12 +212,9 @@ export class PopupService {
frameId: number; frameId: number;
scripts: ScriptMatchInfo[]; scripts: ScriptMatchInfo[];
}) { }) {
if (frameId === undefined) {
// 清理数据
await Cache.getInstance().del("tabScript:" + tabId);
}
// 设置数据 // 设置数据
const data = await this.getScriptMenu(tabId); return this.txUpdateScriptMenu(tabId, async (data) => {
data = [];
// 设置脚本运行次数 // 设置脚本运行次数
scripts.forEach((script) => { scripts.forEach((script) => {
const scriptMenu = data.find((item) => item.uuid === script.uuid); const scriptMenu = data.find((item) => item.uuid === script.uuid);
@ -224,7 +232,8 @@ export class PopupService {
data.push(item); data.push(item);
} }
}); });
Cache.getInstance().set("tabScript:" + tabId, data); return data;
});
} }
dealBackgroundScriptInstall() { dealBackgroundScriptInstall() {
@ -233,7 +242,7 @@ export class PopupService {
if (script.type === SCRIPT_TYPE_NORMAL) { if (script.type === SCRIPT_TYPE_NORMAL) {
return; return;
} }
const menu = await this.getScriptMenu(-1); return this.txUpdateScriptMenu(-1, async (menu) => {
const scriptMenu = menu.find((item) => item.uuid === script.uuid); const scriptMenu = menu.find((item) => item.uuid === script.uuid);
if (script.status === SCRIPT_STATUS_ENABLE) { if (script.status === SCRIPT_STATUS_ENABLE) {
// 加入菜单 // 加入菜单
@ -241,13 +250,9 @@ export class PopupService {
const item = this.scriptToMenu(script); const item = this.scriptToMenu(script);
menu.push(item); menu.push(item);
} }
} else {
// 移出菜单
if (scriptMenu) {
menu.splice(menu.indexOf(scriptMenu), 1);
} }
} return menu;
Cache.getInstance().set("tabScript:" + -1, menu); });
}); });
subscribeScriptEnable(this.mq, async ({ uuid }) => { subscribeScriptEnable(this.mq, async ({ uuid }) => {
const script = await this.scriptDAO.get(uuid); const script = await this.scriptDAO.get(uuid);
@ -257,7 +262,7 @@ export class PopupService {
if (script.type === SCRIPT_TYPE_NORMAL) { if (script.type === SCRIPT_TYPE_NORMAL) {
return; return;
} }
const menu = await this.getScriptMenu(-1); return this.txUpdateScriptMenu(-1, async (menu) => {
const scriptMenu = menu.find((item) => item.uuid === uuid); const scriptMenu = menu.find((item) => item.uuid === uuid);
if (script.status === SCRIPT_STATUS_ENABLE) { if (script.status === SCRIPT_STATUS_ENABLE) {
// 加入菜单 // 加入菜单
@ -271,23 +276,28 @@ export class PopupService {
menu.splice(menu.indexOf(scriptMenu), 1); menu.splice(menu.indexOf(scriptMenu), 1);
} }
} }
Cache.getInstance().set("tabScript:" + -1, menu); return menu;
});
}); });
subscribeScriptDelete(this.mq, async ({ uuid }) => { subscribeScriptDelete(this.mq, async ({ uuid }) => {
const menu = await this.getScriptMenu(-1); return this.txUpdateScriptMenu(-1, async (menu) => {
const scriptMenu = menu.find((item) => item.uuid === uuid); const scriptMenu = menu.find((item) => item.uuid === uuid);
if (scriptMenu) { if (scriptMenu) {
menu.splice(menu.indexOf(scriptMenu), 1); menu.splice(menu.indexOf(scriptMenu), 1);
Cache.getInstance().set("tabScript:" + -1, menu); return menu;
} }
return null;
});
}); });
subscribeScriptRunStatus(this.mq, async ({ uuid, runStatus }) => { subscribeScriptRunStatus(this.mq, async ({ uuid, runStatus }) => {
const menu = await this.getScriptMenu(-1); return this.txUpdateScriptMenu(-1, async (menu) => {
const scriptMenu = menu.find((item) => item.uuid === uuid); const scriptMenu = menu.find((item) => item.uuid === uuid);
if (scriptMenu) { if (scriptMenu) {
scriptMenu.runStatus = runStatus; scriptMenu.runStatus = runStatus;
Cache.getInstance().set("tabScript:" + -1, menu); return menu;
} }
return null;
});
}); });
} }
@ -305,7 +315,6 @@ export class PopupService {
documentId: string; documentId: string;
}) { }) {
// 菜单点击事件 // 菜单点击事件
console.log("click menu", uuid, id, tabId, frameId, documentId);
this.runtime.sendMessageToTab( this.runtime.sendMessageToTab(
tabId, tabId,
"menuClick", "menuClick",
@ -333,7 +342,9 @@ export class PopupService {
// 监听tab开关 // 监听tab开关
chrome.tabs.onRemoved.addListener((tabId) => { chrome.tabs.onRemoved.addListener((tabId) => {
// 清理数据 // 清理数据
Cache.getInstance().del("tabScript:" + tabId); this.txUpdateScriptMenu(tabId, async () => {
return [];
});
}); });
// 监听页面切换加载菜单 // 监听页面切换加载菜单
chrome.tabs.onActivated.addListener((activeInfo) => { chrome.tabs.onActivated.addListener((activeInfo) => {

View File

@ -212,6 +212,7 @@ export class RuntimeService {
return runScript(this.sender, res); return runScript(this.sender, res);
} }
// 注册inject.js
registerInjectScript() { registerInjectScript() {
chrome.userScripts.getScripts({ ids: ["scriptcat-inject"] }).then((res) => { chrome.userScripts.getScripts({ ids: ["scriptcat-inject"] }).then((res) => {
if (res.length == 0) { if (res.length == 0) {

View File

@ -17,6 +17,7 @@ const server = new Server("inject", msg);
server.on("pageLoad", (data: { scripts: ScriptRunResouce[] }) => { server.on("pageLoad", (data: { scripts: ScriptRunResouce[] }) => {
logger.logger().debug("inject start"); logger.logger().debug("inject start");
const runtime = new InjectRuntime(msg, data.scripts); // 监听事件
const runtime = new InjectRuntime(server, msg, data.scripts);
runtime.start(); runtime.start();
}); });

View File

@ -1,5 +1,5 @@
import { ScriptRunResouce } from "@App/app/repo/scripts"; 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"; import { forwardMessage, Message, MessageSend, Server } from "@Packages/message/server";
// content页的处理 // content页的处理
@ -7,20 +7,25 @@ export default class ContentRuntime {
constructor( constructor(
private extServer: Server, private extServer: Server,
private server: Server, private server: Server,
private send: MessageSend, private extSend: MessageSend,
private msg: Message private msg: Message
) {} ) {}
start(scripts: ScriptRunResouce[]) { start(scripts: ScriptRunResouce[]) {
this.extServer.on("runtime/menuClick", (action, data) => { this.extServer.on("runtime/menuClick", (data) => {
// gm菜单点击 // 转发给inject
console.log("runtime/menuClick", action, data); return sendMessage(this.msg, "inject/runtime/menuClick", data);
}); });
this.extServer.on("runtime/valueUpdate", (action, data) => { this.extServer.on("runtime/valueUpdate", (data) => {
// gm value变化 // 转发给inject
console.log(action, data); return sendMessage(this.msg, "inject/runtime/valueUpdate", data);
}); });
forwardMessage("serviceWorker", "runtime/gmApi", this.server, this.send, (data: { api: string; params: any }) => { forwardMessage(
"serviceWorker",
"runtime/gmApi",
this.server,
this.extSend,
(data: { api: string; params: any }) => {
// 拦截关注的api // 拦截关注的api
switch (data.api) { switch (data.api) {
case "CAT_createBlobUrl": { case "CAT_createBlobUrl": {
@ -49,7 +54,8 @@ export default class ContentRuntime {
} }
} }
return Promise.resolve(false); return Promise.resolve(false);
}); }
);
const client = new Client(this.msg, "inject"); const client = new Client(this.msg, "inject");
client.do("pageLoad", { scripts }); client.do("pageLoad", { scripts });
} }

View File

@ -72,6 +72,10 @@ export default class ExecScript {
this.sandboxContent?.valueUpdate(data); this.sandboxContent?.valueUpdate(data);
} }
menuClick(id: number) {
this.sandboxContent?.menuClick(id);
}
exec() { exec() {
this.logger.debug("script start"); this.logger.debug("script start");
return this.scriptFunc.apply(this.proxyContent, [this.proxyContent, this.GM_info]); return this.scriptFunc.apply(this.proxyContent, [this.proxyContent, this.GM_info]);

View File

@ -7,6 +7,7 @@ import { Message, MessageConnect } from "@Packages/message/server";
import { CustomEventMessage } from "@Packages/message/custom_event_message"; import { CustomEventMessage } from "@Packages/message/custom_event_message";
import LoggerCore from "@App/app/logger/core"; import LoggerCore from "@App/app/logger/core";
import { connect, sendMessage } from "@Packages/message/client"; import { connect, sendMessage } from "@Packages/message/client";
import EventEmitter from "eventemitter3";
interface ApiParam { interface ApiParam {
depend?: string[]; depend?: string[];
@ -106,6 +107,10 @@ export default class GMApi {
} }
} }
menuClick(id: number) {
this.EE.emit("menuClick" + id);
}
// 获取脚本信息和管理器信息 // 获取脚本信息和管理器信息
static GM_info(script: ScriptRunResouce) { static GM_info(script: ScriptRunResouce) {
const metadataStr = getMetadataStr(script.code); const metadataStr = getMetadataStr(script.code);
@ -202,18 +207,21 @@ export default class GMApi {
menuMap: Map<number, string> | undefined; menuMap: Map<number, string> | undefined;
EE: EventEmitter = new EventEmitter();
@GMContext.API() @GMContext.API()
GM_registerMenuCommand(name: string, listener: () => void, accessKey?: string): number { GM_registerMenuCommand(name: string, listener: () => void, accessKey?: string): number {
if (!this.menuMap) { if (!this.menuMap) {
this.menuMap = new Map(); this.menuMap = new Map();
} }
let flag = 0; let flag = 0;
this.menuMap.forEach((val, key) => { this.menuMap.forEach((val, menuId) => {
if (val === name) { if (val === name) {
flag = key; flag = menuId;
} }
}); });
if (flag) { if (flag) {
this.EE.addListener("menuClick" + flag, listener);
return flag; return flag;
} }
if (!this.menuId) { if (!this.menuId) {
@ -222,18 +230,9 @@ export default class GMApi {
this.menuId += 1; this.menuId += 1;
} }
const id = this.menuId; 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.menuMap.set(id, name);
this.EE.addListener("menuClick" + id, listener);
this.sendMessage("GM_registerMenuCommand", [id, name, accessKey]);
return id; return id;
} }
@ -243,6 +242,7 @@ export default class GMApi {
this.menuMap = new Map(); this.menuMap = new Map();
} }
this.menuMap.delete(id); this.menuMap.delete(id);
this.EE.removeAllListeners("menuClick" + id);
this.sendMessage("GM_unregisterMenuCommand", [id]); this.sendMessage("GM_unregisterMenuCommand", [id]);
} }

View File

@ -1,5 +1,5 @@
import { ScriptRunResouce } from "@App/app/repo/scripts"; 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 ExecScript from "./exec_script";
import { addStyle, ScriptFunc } from "./utils"; import { addStyle, ScriptFunc } from "./utils";
@ -7,6 +7,7 @@ export class InjectRuntime {
execList: ExecScript[] = []; execList: ExecScript[] = [];
constructor( constructor(
private server: Server,
private msg: Message, private msg: Message,
private scripts: ScriptRunResouce[] 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) { execScript(script: ScriptRunResouce, scriptFunc: ScriptFunc) {

View File

@ -3,6 +3,7 @@ import { v4 as uuidv4 } from "uuid";
import GMApi, { ApiValue, GMContext } from "./gm_api"; import GMApi, { ApiValue, GMContext } from "./gm_api";
import { has } from "@App/pkg/utils/lodash"; import { has } from "@App/pkg/utils/lodash";
import { Message } from "@Packages/message/server"; import { Message } from "@Packages/message/server";
import EventEmitter from "eventemitter3";
// 构建脚本运行代码 // 构建脚本运行代码
export function compileScriptCode(scriptRes: ScriptRunResouce): string { export function compileScriptCode(scriptRes: ScriptRunResouce): string {
@ -64,6 +65,8 @@ export function createContext(scriptRes: ScriptRunResouce, GMInfo: any, envPrefi
connect: GMApi.prototype.connect, connect: GMApi.prototype.connect,
runFlag: uuidv4(), runFlag: uuidv4(),
valueUpdate: GMApi.prototype.valueUpdate, valueUpdate: GMApi.prototype.valueUpdate,
menuClick: GMApi.prototype.menuClick,
EE: new EventEmitter(),
GM: { Info: GMInfo }, GM: { Info: GMInfo },
GM_info: GMInfo, GM_info: GMInfo,
}; };