aa
Some checks failed
test / Run tests (push) Failing after 14s
build / Build (push) Failing after 19s

This commit is contained in:
王一之 2025-02-13 18:04:36 +08:00
parent 3d869643b4
commit 7697c91d95
13 changed files with 1553 additions and 278 deletions

View File

@ -20,7 +20,7 @@
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@reduxjs/toolkit": "^2.3.0", "@reduxjs/toolkit": "^2.5.1",
"cron": "^3.2.1", "cron": "^3.2.1",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
@ -41,10 +41,21 @@
"yaml": "^2.6.1" "yaml": "^2.6.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^1.2.0", "@eslint/compat": "^1.2.6",
"@eslint/js": "^9.12.0", "@eslint/js": "^9.19.0",
"@rspack/cli": "^1.0.14", "@rspack/cli": "^1.2.3",
"@rspack/core": "^1.0.14", "@rspack/core": "^1.2.3",
"@rspack/plugin-react-refresh": "^1.0.1",
"cross-env": "^7.0.3",
"eslint": "^9.19.0",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.1.0",
"globals": "^15.14.0",
"prettier": "^3.4.2",
"react-refresh": "^0.16.0",
"ts-node": "^10.9.2",
"typescript": "^5.7.3",
"typescript-eslint": "^8.22.0",
"@types/chrome": "^0.0.279", "@types/chrome": "^0.0.279",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/node": "^22.10.2", "@types/node": "^22.10.2",
@ -56,18 +67,9 @@
"@unocss/postcss": "0.65.0-beta.2", "@unocss/postcss": "0.65.0-beta.2",
"@vitest/coverage-v8": "2.1.4", "@vitest/coverage-v8": "2.1.4",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"cross-env": "^7.0.3",
"eslint": "^9.12.0",
"eslint-plugin-react": "^7.37.1",
"eslint-plugin-react-hooks": "^5.0.0",
"globals": "^15.11.0",
"jsdom": "^25.0.1", "jsdom": "^25.0.1",
"postcss": "^8.4.49", "postcss": "^8.4.49",
"postcss-loader": "^8.1.1", "postcss-loader": "^8.1.1",
"prettier": "^3.3.3",
"ts-node": "^10.9.2",
"typescript": "^5.6.3",
"typescript-eslint": "^8.8.1",
"unocss": "0.65.0-beta.2", "unocss": "0.65.0-beta.2",
"vitest": "^2.1.4" "vitest": "^2.1.4"
} }

View File

@ -1,5 +1,5 @@
import LoggerCore from "@App/app/logger/core"; import LoggerCore from "@App/app/logger/core";
import { Message, MessageConnect, MessageSend } from "./server"; import { MessageConnect, MessageSend } from "./server";
export async function sendMessage(msg: MessageSend, action: string, data?: any): Promise<any> { export async function sendMessage(msg: MessageSend, action: string, data?: any): Promise<any> {
const res = await msg.sendMessage({ action, data }); const res = await msg.sendMessage({ action, data });
@ -12,17 +12,19 @@ export async function sendMessage(msg: MessageSend, action: string, data?: any):
} }
} }
export function connect(msg: Message, action: string, data?: any): Promise<MessageConnect> { export function connect(msg: MessageSend, action: string, data?: any): Promise<MessageConnect> {
return msg.connect({ action, data }); return msg.connect({ action, data });
} }
export class Client { export class Client {
constructor( constructor(
private msg: MessageSend, private msg: MessageSend,
private prefix: string private prefix?: string
) { ) {
if (!this.prefix.endsWith("/")) { if (this.prefix && !this.prefix.endsWith("/")) {
this.prefix += "/"; this.prefix += "/";
} else {
this.prefix = "";
} }
} }

View File

@ -1,16 +1,12 @@
import { Message, MessageConnect, MessageSend } from "./server"; import { Message, MessageConnect, MessageSend } from "./server";
export class ExtensionMessageSend implements MessageSend { export class ExtensionMessageSend implements MessageSend {
constructor(protected serverEnv: string) {} constructor() {}
connect(data: any): Promise<MessageConnect> { connect(data: any): Promise<MessageConnect> {
return new Promise((resolve) => { return new Promise((resolve) => {
const con = chrome.runtime.connect(); const con = chrome.runtime.connect();
con.postMessage( con.postMessage(data);
Object.assign(data, {
serverEnv: this.serverEnv,
})
);
resolve(new ExtensionMessageConnect(con)); resolve(new ExtensionMessageConnect(con));
}); });
} }
@ -18,14 +14,9 @@ export class ExtensionMessageSend implements MessageSend {
// 发送消息 注意不进行回调的内存泄漏 // 发送消息 注意不进行回调的内存泄漏
sendMessage(data: any): Promise<any> { sendMessage(data: any): Promise<any> {
return new Promise((resolve) => { return new Promise((resolve) => {
chrome.runtime.sendMessage( chrome.runtime.sendMessage(data, (resp) => {
Object.assign(data, { resolve(resp);
serverEnv: this.serverEnv, });
}),
(resp) => {
resolve(resp);
}
);
}); });
} }
} }
@ -35,10 +26,6 @@ export class ExtensionMessage extends ExtensionMessageSend implements Message {
chrome.runtime.onConnect.addListener((port) => { chrome.runtime.onConnect.addListener((port) => {
const handler = (msg: any) => { const handler = (msg: any) => {
port.onMessage.removeListener(handler); port.onMessage.removeListener(handler);
if (msg.serverEnv !== this.serverEnv) {
port.disconnect();
return false;
}
callback(msg, new ExtensionMessageConnect(port)); callback(msg, new ExtensionMessageConnect(port));
}; };
port.onMessage.addListener(handler); port.onMessage.addListener(handler);
@ -48,9 +35,6 @@ export class ExtensionMessage extends ExtensionMessageSend implements Message {
// 注意chrome.runtime.onMessage.addListener的回调函数需要返回true才能处理异步请求 // 注意chrome.runtime.onMessage.addListener的回调函数需要返回true才能处理异步请求
onMessage(callback: (data: any, sendResponse: (data: any) => void) => void) { onMessage(callback: (data: any, sendResponse: (data: any) => void) => void) {
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.serverEnv !== this.serverEnv) {
return false;
}
return callback(msg, sendResponse); return callback(msg, sendResponse);
}); });
} }

1607
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ export class ExtCache implements CacheStorage {
get(key: string): Promise<any> { get(key: string): Promise<any> {
return new Promise((resolve) => { return new Promise((resolve) => {
chrome.storage.local.get(key, (value) => { chrome.storage.local.get(key, (value) => {
resolve(value); resolve(value[key]);
}); });
}); });
} }
@ -89,6 +89,14 @@ export class MapCache {
} }
} }
export async function incr(cache: Cache, key: string, increase: number): Promise<number> {
const value = await cache.get(key);
let num = value || 0;
num += increase;
await cache.set(key, num);
return num;
}
export default class Cache { export default class Cache {
static instance: Cache = new Cache(new ExtCache()); static instance: Cache = new Cache(new ExtCache());

View File

@ -20,5 +20,5 @@ export function proxyUpdateRunStatus(
msg: WindowMessage, msg: WindowMessage,
data: { uuid: string; runStatus: SCRIPT_RUN_STATUS; error?: any; nextruntime?: number } data: { uuid: string; runStatus: SCRIPT_RUN_STATUS; error?: any; nextruntime?: number }
) { ) {
return sendMessageToServiceWorker(msg, "serviceWorker/script/updateRunStatus", data); return sendMessageToServiceWorker(msg, "script/updateRunStatus", data);
} }

View File

@ -3,8 +3,12 @@ import { Group } from "@Packages/message/server";
export class GMApi { export class GMApi {
constructor(private group: Group) {} constructor(private group: Group) {}
xmlHttpRequest(){
}
init() { init() {
this.group.on("requestXhr", async (data) => { this.group.on("xmlHttpRequest", async (data) => {
console.log(data); console.log(data);
}); });
} }

View File

@ -10,7 +10,7 @@ import { GMApi } from "./gm_api";
// offscreen环境的管理器 // offscreen环境的管理器
export class OffscreenManager { export class OffscreenManager {
private extensionMessage: MessageSend = new ExtensionMessageSend("service_worker"); private extensionMessage: MessageSend = new ExtensionMessageSend();
private windowMessage = new WindowMessage(window, sandbox, true); private windowMessage = new WindowMessage(window, sandbox, true);
@ -35,11 +35,6 @@ export class OffscreenManager {
} }
async initManager() { async initManager() {
navigator.serviceWorker.ready.then((registration) => {
// 通知service worker已经准备好了
registration.active?.postMessage("okkkk");
});
// 监听消息 // 监听消息
this.windowApi.on("logger", this.logger.bind(this)); this.windowApi.on("logger", this.logger.bind(this));
this.windowApi.on("preparationSandbox", this.preparationSandbox.bind(this)); this.windowApi.on("preparationSandbox", this.preparationSandbox.bind(this));
@ -47,7 +42,7 @@ export class OffscreenManager {
const script = new ScriptService(this.extensionMessage, this.windowMessage, this.broker); const script = new ScriptService(this.extensionMessage, this.windowMessage, this.broker);
script.init(); script.init();
// 转发gm api请求 // 转发gm api请求
forwardMessage("serviceWorker/runtime/gmApi", this.windowApi, this.extensionMessage); forwardMessage("runtime/gmApi", this.windowApi, this.extensionMessage);
const gmApi = new GMApi(this.windowApi.group("gmApi")); const gmApi = new GMApi(this.windowApi.group("gmApi"));
gmApi.init(); gmApi.init();

View File

@ -7,7 +7,7 @@ import { MessageSend } from "@Packages/message/server";
export class ServiceWorkerClient extends Client { export class ServiceWorkerClient extends Client {
constructor(msg: MessageSend) { constructor(msg: MessageSend) {
super(msg, "serviceWorker"); super(msg);
} }
preparationOffscreen() { preparationOffscreen() {
@ -17,7 +17,7 @@ export class ServiceWorkerClient extends Client {
export class ScriptClient extends Client { export class ScriptClient extends Client {
constructor(msg: MessageSend) { constructor(msg: MessageSend) {
super(msg, "serviceWorker/script"); super(msg, "script");
} }
// 获取安装信息 // 获取安装信息
@ -52,7 +52,7 @@ export class ScriptClient extends Client {
export class ResourceClient extends Client { export class ResourceClient extends Client {
constructor(msg: MessageSend) { constructor(msg: MessageSend) {
super(msg, "serviceWorker/resource"); super(msg, "resource");
} }
getScriptResources(script: Script): Promise<{ [key: string]: Resource }> { getScriptResources(script: Script): Promise<{ [key: string]: Resource }> {
@ -62,7 +62,7 @@ export class ResourceClient extends Client {
export class ValueClient extends Client { export class ValueClient extends Client {
constructor(msg: MessageSend) { constructor(msg: MessageSend) {
super(msg, "serviceWorker/value"); super(msg, "value");
} }
getScriptValue(script: Script) { getScriptValue(script: Script) {

View File

@ -5,6 +5,9 @@ import { Group, MessageConnect, MessageSender } from "@Packages/message/server";
import { ValueService } from "@App/app/service/service_worker/value"; import { ValueService } from "@App/app/service/service_worker/value";
import PermissionVerify from "./permission_verify"; import PermissionVerify from "./permission_verify";
import { ServiceWorkerMessageSend } from "@Packages/message/window_message"; import { ServiceWorkerMessageSend } from "@Packages/message/window_message";
import { connect, sendMessage } from "@Packages/message/client";
import Cache, { incr } from "@App/app/cache";
import { unsafeHeaders } from "@App/runtime/utils";
// GMApi,处理脚本的GM API调用请求 // GMApi,处理脚本的GM API调用请求
@ -76,25 +79,75 @@ export default class GMApi {
return this.value.setValue(request.script.uuid, key, value); return this.value.setValue(request.script.uuid, key, value);
} }
// 根据header生成dnr规则
async buildDNRRule(params: GMSend.XHRDetails) {
// 检查是否有unsafe header,有则生成dnr规则
const headers = params.headers;
if (!headers) {
return;
}
const requestHeaders = [] as chrome.declarativeNetRequest.ModifyHeaderInfo[];
Object.keys(headers).forEach((key) => {
const lowKey = key.toLowerCase();
if (unsafeHeaders[lowKey] || lowKey.startsWith("sec-") || lowKey.startsWith("proxy-")) {
requestHeaders.push({
header: key,
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
value: headers[key],
});
}
});
if (requestHeaders.length === 0) {
return;
}
const ruleId = 1000 + (await incr(Cache.getInstance(), "dnrRuleId", 1));
const rule = {} as chrome.declarativeNetRequest.Rule;
rule.id = ruleId;
rule.action = {
type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS,
requestHeaders: requestHeaders,
};
rule.priority = 1;
const tabs = await chrome.tabs.query({});
const excludedTabIds: number[] = [];
tabs.forEach((tab) => {
if (tab.id) {
excludedTabIds.push(tab.id);
}
});
rule.condition = {
resourceTypes: [chrome.declarativeNetRequest.ResourceType.XMLHTTPREQUEST],
urlFilter: "^" + params.url + "$",
excludedTabIds: excludedTabIds,
};
return chrome.declarativeNetRequest.updateSessionRules({
removeRuleIds: [ruleId],
addRules: [rule],
});
}
@PermissionVerify.API() @PermissionVerify.API()
GM_xmlhttpRequest(request: Request, con: MessageConnect) { async GM_xmlhttpRequest(request: Request, con: MessageConnect) {
console.log("xml request", request, con); console.log("xml request", request, con);
// 先处理unsafe hearder // 先处理unsafe hearder
await this.buildDNRRule(request.params[0]);
// 再发送到offscreen, 处理请求 // 再发送到offscreen, 处理请求
sendMessageToOffscreen(this.sender, "offscreen/gmApi/requestXhr", request.params); connect(this.sender, "gmApi/xmlHttpRequest", request.params);
} }
start() { start() {
this.group.on("gmApi", this.handlerRequest.bind(this)); this.group.on("gmApi", this.handlerRequest.bind(this));
// 处理收到的header
chrome.webRequest.onHeadersReceived.addListener(
(details) => {
console.log(details);
},
{
urls: ["<all_urls>"],
types: ["xmlhttprequest"],
},
["responseHeaders", "extraHeaders"]
);
} }
} }
export async function sendMessageToOffscreen(sender: ServiceWorkerMessageSend, action: string, data?: any) {
// service_worker和offscreen同时监听消息,会导致消息被两边同时接收,但是返回结果时会产生问题,导致报错
// 不进行监听的话又无法从service_worker主动发送消息
// 所以这里通过clients.matchAll()获取到所有的client,然后通过postMessage发送消息
sender.sendMessage({
type: "sendMessage",
data: { action, data },
});
}

View File

@ -11,27 +11,26 @@ export type InstallSource = "user" | "system" | "sync" | "subscribe" | "vscode";
// service worker的管理器 // service worker的管理器
export default class ServiceWorkerManager { export default class ServiceWorkerManager {
private api: Server = new Server(new ExtensionMessage("service_worker")); private api: Server = new Server(new ExtensionMessage());
private mq: MessageQueue = new MessageQueue(this.api); private mq: MessageQueue = new MessageQueue(this.api);
private sender: ServiceWorkerMessageSend = new ServiceWorkerMessageSend(); private sender: ServiceWorkerMessageSend = new ServiceWorkerMessageSend();
async initManager() { async initManager() {
const group = this.api.group("serviceWorker"); this.api.on("preparationOffscreen", async () => {
group.on("preparationOffscreen", async () => {
// 准备好环境 // 准备好环境
await this.sender.init(); await this.sender.init();
this.mq.emit("preparationOffscreen", {}); this.mq.emit("preparationOffscreen", {});
}); });
const resource = new ResourceService(group.group("resource"), this.mq); const resource = new ResourceService(this.api.group("resource"), this.mq);
resource.init(); resource.init();
const value = new ValueService(group.group("value"), this.mq); const value = new ValueService(this.api.group("value"), this.mq);
value.init(); value.init();
const script = new ScriptService(group.group("script"), this.mq, value, resource); const script = new ScriptService(this.api.group("script"), this.mq, value, resource);
script.init(); script.init();
const runtime = new RuntimeService(group.group("runtime"), this.sender, this.mq, value); const runtime = new RuntimeService(this.api.group("runtime"), this.sender, this.mq, value);
runtime.init(); runtime.init();
// 测试xhr // 测试xhr

View File

@ -63,7 +63,7 @@ export default class GMApi {
// 单次回调使用 // 单次回调使用
public sendMessage(api: string, params: any[]) { public sendMessage(api: string, params: any[]) {
return this.message.sendMessage({ return this.message.sendMessage({
action: "serviceWorker/runtime/gmApi", action: "runtime/gmApi",
data: { data: {
uuid: this.scriptRes.uuid, uuid: this.scriptRes.uuid,
api, api,
@ -75,7 +75,7 @@ export default class GMApi {
// 长连接使用,connect只用于接受消息,不发送消息 // 长连接使用,connect只用于接受消息,不发送消息
public connect(api: string, params: any[]) { public connect(api: string, params: any[]) {
return this.message.connect({ return this.message.connect({
action: "serviceWorker/runtime/gmApi", action: "runtime/gmApi",
data: { data: {
uuid: this.scriptRes.uuid, uuid: this.scriptRes.uuid,
api, api,

View File

@ -1,5 +1,34 @@
import { Script } from "@App/app/repo/scripts"; import { Script } from "@App/app/repo/scripts";
export const unsafeHeaders: { [key: string]: boolean } = {
// 部分浏览器中并未允许
"user-agent": true,
// 这两个是前缀
"proxy-": true,
"sec-": true,
// cookie已经特殊处理
cookie: true,
"accept-charset": true,
"accept-encoding": true,
"access-control-request-headers": true,
"access-control-request-method": true,
connection: true,
"content-length": true,
date: true,
dnt: true,
expect: true,
"feature-policy": true,
host: true,
"keep-alive": true,
origin: true,
referer: true,
te: true,
trailer: true,
"transfer-encoding": true,
upgrade: true,
via: true,
};
export function storageKey(script: Script): string { export function storageKey(script: Script): string {
if (script.metadata && script.metadata.storagename) { if (script.metadata && script.metadata.storagename) {
return script.metadata.storagename[0]; return script.metadata.storagename[0];