From 20124be0e45028cfba3c5e1b1095cfa18a55a7bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Tue, 1 Apr 2025 18:04:30 +0800 Subject: [PATCH] match --- packages/message/extension_message.ts | 28 ++++++++-- packages/message/message_queue.ts | 1 - packages/message/server.ts | 39 +++++++++----- src/app/service/service_worker/gm_api.ts | 6 +-- src/app/service/service_worker/runtime.ts | 63 +++++++++++++++++------ src/app/service/service_worker/utils.ts | 5 -- src/manifest.json | 12 ----- src/pkg/utils/match.test.ts | 11 ++++ src/pkg/utils/match.ts | 52 +++++++++++++++++++ src/pkg/utils/utils.ts | 10 ---- 10 files changed, 162 insertions(+), 65 deletions(-) create mode 100644 src/pkg/utils/match.test.ts create mode 100644 src/pkg/utils/match.ts diff --git a/packages/message/extension_message.ts b/packages/message/extension_message.ts index bea2a1a..9b33fa3 100644 --- a/packages/message/extension_message.ts +++ b/packages/message/extension_message.ts @@ -1,4 +1,4 @@ -import { Message, MessageConnect, MessageSend } from "./server"; +import { Message, MessageConnect, MessageSend, MessageSender } from "./server"; export class ExtensionMessageSend implements MessageSend { constructor() {} @@ -21,6 +21,28 @@ export class ExtensionMessageSend implements MessageSend { } } +// 由于service worker的限制,特殊处理chrome.runtime.onConnect/Message +export class ServiceWorkerMessage extends ExtensionMessageSend implements Message { + onConnect(callback: (data: any, con: MessageConnect) => void): void { + chrome.runtime.onConnect.addListener((port) => { + const handler = (msg: any) => { + port.onMessage.removeListener(handler); + callback(msg, new ExtensionMessageConnect(port)); + }; + port.onMessage.addListener(handler); + }); + } + + onMessage(callback: (data: any, sendResponse: (data: any) => void, sender: MessageSender) => void): void { + chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { + if (msg.action === "messageQueue") { + return false; + } + return callback(msg, sendResponse, sender); + }); + } +} + export class ExtensionMessage extends ExtensionMessageSend implements Message { onConnect(callback: (data: any, con: MessageConnect) => void) { chrome.runtime.onConnect.addListener((port) => { @@ -33,12 +55,12 @@ export class ExtensionMessage extends ExtensionMessageSend implements Message { } // 注意chrome.runtime.onMessage.addListener的回调函数需要返回true才能处理异步请求 - onMessage(callback: (data: any, sendResponse: (data: any) => void) => void) { + onMessage(callback: (data: any, sendResponse: (data: any) => void, sender: MessageSender) => void): void { chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { if (msg.action === "messageQueue") { return false; } - return callback(msg, sendResponse); + return callback(msg, sendResponse, sender); }); } } diff --git a/packages/message/message_queue.ts b/packages/message/message_queue.ts index f781db8..d3be52c 100644 --- a/packages/message/message_queue.ts +++ b/packages/message/message_queue.ts @@ -1,5 +1,4 @@ import EventEmitter from "eventemitter3"; -import Logger from "@App/app/logger/logger"; import LoggerCore from "@App/app/logger/core"; export type SubscribeCallback = (message: any) => void; diff --git a/packages/message/server.ts b/packages/message/server.ts index afee68d..ade01be 100644 --- a/packages/message/server.ts +++ b/packages/message/server.ts @@ -2,7 +2,7 @@ import LoggerCore from "@App/app/logger/core"; export interface Message extends MessageSend { onConnect(callback: (data: any, con: MessageConnect) => void): void; - onMessage(callback: (data: any, sendResponse: (data: any) => void) => void): void; + onMessage(callback: (data: any, sendResponse: (data: any) => void, sender?: MessageSender) => void): void; } export interface MessageSend { @@ -17,11 +17,21 @@ export interface MessageConnect { onDisconnect(callback: () => void): void; } -export type MessageSender = { - tabId: number; -}; +export type MessageSender = any; -export type ApiFunction = (params: any, con: MessageConnect | null) => Promise | void; +export class GetSender { + constructor(private sender: MessageConnect | MessageSender) {} + + getSender(): MessageSender { + return this.sender as MessageSender; + } + + getConnect(): MessageConnect { + return this.sender as MessageConnect; + } +} + +export type ApiFunction = (params: any, con: GetSender) => Promise | void; export class Server { private apiFunctionMap: Map = new Map(); @@ -37,10 +47,10 @@ export class Server { return false; }); - message.onMessage((msg: { action: string; data: any }, sendResponse) => { + message.onMessage((msg: { action: string; data: any }, sendResponse, sender) => { this.logger.trace("server onMessage", { msg: msg as any }); if (msg.action.startsWith(prefix)) { - return this.messageHandle(msg.action.slice(prefix.length + 1), msg.data, sendResponse); + return this.messageHandle(msg.action.slice(prefix.length + 1), msg.data, sendResponse, sender); } return false; }); @@ -57,15 +67,15 @@ export class Server { private connectHandle(msg: string, params: any, con: MessageConnect) { const func = this.apiFunctionMap.get(msg); if (func) { - func(params, con); + func(params, new GetSender(con)); } } - private messageHandle(msg: string, params: any, sendResponse: (response: any) => void) { + private messageHandle(msg: string, params: any, sendResponse: (response: any) => void, sender?: MessageSender) { const func = this.apiFunctionMap.get(msg); if (func) { try { - const ret = func(params, null); + const ret = func(params, new GetSender(sender!)); if (ret instanceof Promise) { ret.then((data) => { sendResponse({ code: 0, data }); @@ -108,18 +118,19 @@ export function forwardMessage(prefix: string, path: string, from: Server, to: M from.on(path, (params, fromCon) => { console.log("forwardMessage", path, prefix, params); if (fromCon) { + const fromConnect = fromCon.getConnect(); to.connect({ action: prefix + "/" + path, data: params }).then((toCon) => { - fromCon.onMessage((data) => { + fromConnect.onMessage((data) => { toCon.sendMessage(data); }); toCon.onMessage((data) => { - fromCon.sendMessage(data); + fromConnect.sendMessage(data); }); - fromCon.onDisconnect(() => { + fromConnect.onDisconnect(() => { toCon.disconnect(); }); toCon.onDisconnect(() => { - fromCon.disconnect(); + fromConnect.disconnect(); }); }); } else { diff --git a/src/app/service/service_worker/gm_api.ts b/src/app/service/service_worker/gm_api.ts index c826ddf..7617428 100644 --- a/src/app/service/service_worker/gm_api.ts +++ b/src/app/service/service_worker/gm_api.ts @@ -1,7 +1,7 @@ import LoggerCore from "@App/app/logger/core"; import Logger from "@App/app/logger/logger"; import { Script, ScriptDAO } from "@App/app/repo/scripts"; -import { Group, MessageConnect, MessageSend, MessageSender } from "@Packages/message/server"; +import { GetSender, Group, MessageConnect, MessageSend, MessageSender } from "@Packages/message/server"; import { ValueService } from "@App/app/service/service_worker/value"; import PermissionVerify from "./permission_verify"; import { connect } from "@Packages/message/client"; @@ -40,7 +40,7 @@ export default class GMApi { this.logger = LoggerCore.logger().with({ service: "runtime/gm_api" }); } - async handlerRequest(data: MessageRequest, con: MessageConnect | null) { + async handlerRequest(data: MessageRequest, con: GetSender) { this.logger.trace("GM API request", { api: data.api, uuid: data.uuid, param: data.params }); const api = PermissionVerify.apis.get(data.api); if (!api) { @@ -53,7 +53,7 @@ export default class GMApi { this.logger.error("verify error", { api: data.api }, Logger.E(e)); return Promise.reject(e); } - return api.api.call(this, req, con); + return api.api.call(this, req, con.getConnect()); } // 解析请求 diff --git a/src/app/service/service_worker/runtime.ts b/src/app/service/service_worker/runtime.ts index ccbd9bd..fd319d7 100644 --- a/src/app/service/service_worker/runtime.ts +++ b/src/app/service/service_worker/runtime.ts @@ -1,15 +1,16 @@ import { MessageQueue } from "@Packages/message/message_queue"; -import { Group, MessageSend } from "@Packages/message/server"; +import { GetSender, Group, MessageSend } from "@Packages/message/server"; import { Script, SCRIPT_STATUS_ENABLE, SCRIPT_TYPE_NORMAL, ScriptDAO, ScriptRunResouce } from "@App/app/repo/scripts"; import { ValueService } from "./value"; import GMApi from "./gm_api"; import { subscribeScriptDelete, subscribeScriptEnable, subscribeScriptInstall } from "../queue"; import { ScriptService } from "./script"; import { runScript, stopScript } from "../offscreen/client"; -import { dealMatches, getRunAt } from "./utils"; +import { getRunAt } from "./utils"; import { randomString } from "@App/pkg/utils/utils"; import { compileInjectScript, compileScriptCode } from "@App/runtime/content/utils"; import Cache from "@App/app/cache"; +import { dealMatches } from "@App/pkg/utils/match"; export class RuntimeService { scriptDAO: ScriptDAO = new ScriptDAO(); @@ -99,7 +100,10 @@ export class RuntimeService { this.group.on("pageLoad", this.pageLoad.bind(this)); } - pageLoad() { + pageLoad(_, sender: GetSender) { + const chromeSender = sender.getSender() as chrome.runtime.MessageSender; + // 匹配当前页面的脚本 + return Promise.resolve({ flag: this.scriptFlag }); } @@ -119,25 +123,41 @@ export class RuntimeService { } registerInjectScript() { - fetch("inject.js") - .then((res) => res.text()) - .then((injectJs) => { - // 替换ScriptFlag - const code = `(function (ScriptFlag) {\n${injectJs}\n})('${this.scriptFlag}')`; - chrome.userScripts.register([ + chrome.userScripts.getScripts({ ids: ["scriptcat-inject"] }).then((res) => { + if (res.length == 0) { + fetch("inject.js") + .then((res) => res.text()) + .then((injectJs) => { + // 替换ScriptFlag + const code = `(function (ScriptFlag) {\n${injectJs}\n})('${this.scriptFlag}')`; + chrome.userScripts.register([ + { + id: "scriptcat-inject", + js: [{ code }], + matches: [""], + allFrames: true, + world: "MAIN", + runAt: "document_start", + }, + ]); + }); + chrome.scripting.registerContentScripts([ { - id: "scriptcat-inject", - js: [{ code }], + id: "scriptcat-content", + js: ["src/content.js"], matches: [""], allFrames: true, - world: "MAIN", runAt: "document_start", }, ]); - }); + } + }); } async registryPageScript(script: Script) { + if (await Cache.getInstance().has("registryScript:" + script.uuid)) { + return; + } const matches = script.metadata["match"]; if (!matches) { return; @@ -167,12 +187,21 @@ export class RuntimeService { if (script.metadata["run-at"]) { registerScript.runAt = getRunAt(script.metadata["run-at"]); } - chrome.userScripts.register([registerScript]); + chrome.userScripts.register([registerScript], () => { + Cache.getInstance().set("registryScript:" + script.uuid, true); + }); + // 标记为已注册 } unregistryPageScript(script: Script) { - chrome.userScripts.unregister({ - ids: [script.uuid], - }); + chrome.userScripts.unregister( + { + ids: [script.uuid], + }, + () => { + // 删除缓存 + Cache.getInstance().del("registryScript:" + script.uuid); + } + ); } } diff --git a/src/app/service/service_worker/utils.ts b/src/app/service/service_worker/utils.ts index faba5d7..eb68ab4 100644 --- a/src/app/service/service_worker/utils.ts +++ b/src/app/service/service_worker/utils.ts @@ -5,11 +5,6 @@ export function isExtensionRequest(details: chrome.webRequest.ResourceRequest & ); } -// 处理油猴的match和include为chrome的matches -export function dealMatches(matches: string[]) { - return matches; -} - export function getRunAt(runAts: string[]): chrome.userScripts.RunAt { if (runAts.length === 0) { return "document_idle"; diff --git a/src/manifest.json b/src/manifest.json index 623c425..c86dc3e 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -17,18 +17,6 @@ "128": "assets/logo.png" } }, - "content_scripts": [ - { - "matches": [ - "" - ], - "js": [ - "src/content.js" - ], - "run_at": "document_start", - "all_frames": true - } - ], "icons": { "128": "assets/logo.png" }, diff --git a/src/pkg/utils/match.test.ts b/src/pkg/utils/match.test.ts new file mode 100644 index 0000000..cc96be2 --- /dev/null +++ b/src/pkg/utils/match.test.ts @@ -0,0 +1,11 @@ +import { describe, it } from "vitest"; +import { dealMatches, parseURL } from "./match"; + +// https://developer.chrome.com/docs/extensions/mv3/match_patterns/ +describe("dealMatches", () => { + it("*://link.17173.com*", () => { + const url = parseURL("*://link.17173.com*"); + const matches = dealMatches(["*://link.17173.com*"]); + console.log(url, matches); + }); +}); diff --git a/src/pkg/utils/match.ts b/src/pkg/utils/match.ts new file mode 100644 index 0000000..34dea1f --- /dev/null +++ b/src/pkg/utils/match.ts @@ -0,0 +1,52 @@ +export interface Url { + scheme: string; + host: string; + path: string; + search: string; +} + +// 根据https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns?hl=zh-cn进行匹配 +export class Match {} + +export function parseURL(url: string): Url | undefined { + const match = /^(.+?):\/\/(.*?)((\/.*?)(\?.*?|)|)$/.exec(url); + if (match) { + return { + scheme: match[1], + host: match[2], + path: match[4] || (url[url.length - 1] === "*" ? "*" : "/"), + search: match[5], + }; + } + // 处理一些特殊情况 + switch (url) { + case "*": + return { + scheme: "*", + host: "*", + path: "*", + search: "*", + }; + default: + } + return undefined; +} + +// 处理油猴的match和include为chrome的matches +export function dealMatches(matches: string[]) { + const result: string[] = []; + for (let i = 0; i < matches.length; i++) { + const url = parseURL(matches[i]); + if (url) { + // *开头但是不是*.的情况 + if (url.host.startsWith("*")) { + if (!url.host.startsWith("*.")) { + // 删除开头的*号 + url.host = url.host.slice(1); + } + } + result.push(`${url.scheme}://${url.host}${url.path}` + (url.search ? "?" + url.search : "")); + } + } + return result; +} diff --git a/src/pkg/utils/utils.ts b/src/pkg/utils/utils.ts index ef02639..eae72c7 100644 --- a/src/pkg/utils/utils.ts +++ b/src/pkg/utils/utils.ts @@ -217,13 +217,3 @@ export function sleep(time: number) { setTimeout(resolve, time); }); } - -// 使service_worker长时间存活 -export async function waitUntil(promise: Promise) { - const keepAlive = setInterval(chrome.runtime.getPlatformInfo, 25 * 1000); - try { - await promise; - } finally { - clearInterval(keepAlive); - } -}