inject的GM API调用
Some checks failed
build / Build (push) Failing after 7s
test / Run tests (push) Failing after 8s

This commit is contained in:
2025-04-06 00:32:20 +08:00
parent 651384f12c
commit a8054451ac
13 changed files with 50 additions and 32 deletions

View File

@@ -1,4 +1,4 @@
import { Group, MessageConnect } from "@Packages/message/server"; import { GetSender, Group, MessageConnect } from "@Packages/message/server";
export default class GMApi { export default class GMApi {
constructor(private group: Group) {} constructor(private group: Group) {}
@@ -24,8 +24,9 @@ export default class GMApi {
return response; return response;
} }
xmlHttpRequest(details: GMSend.XHRDetails, con: MessageConnect | null) { xmlHttpRequest(details: GMSend.XHRDetails, sender: GetSender) {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
const con = sender.getConnect();
xhr.open(details.method || "GET", details.url); xhr.open(details.method || "GET", details.url);
// 添加header // 添加header
if (details.headers) { if (details.headers) {
@@ -34,7 +35,7 @@ export default class GMApi {
} }
} }
xhr.onload = () => { xhr.onload = () => {
this.dealXhrResponse(con!, details, "onload", xhr); this.dealXhrResponse(con, details, "onload", xhr);
}; };
xhr.onloadstart = () => { xhr.onloadstart = () => {
this.dealXhrResponse(con!, details, "onloadstart", xhr); this.dealXhrResponse(con!, details, "onloadstart", xhr);

View File

@@ -1,7 +1,7 @@
import LoggerCore from "@App/app/logger/core"; import LoggerCore from "@App/app/logger/core";
import Logger from "@App/app/logger/logger"; import Logger from "@App/app/logger/logger";
import { Script, ScriptDAO } from "@App/app/repo/scripts"; import { Script, ScriptDAO } from "@App/app/repo/scripts";
import { GetSender, Group, MessageConnect, MessageSend, MessageSender } from "@Packages/message/server"; import { GetSender, Group, MessageSend, 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 { connect } from "@Packages/message/client"; import { connect } from "@Packages/message/client";
@@ -23,7 +23,7 @@ export type Request = MessageRequest & {
sender: MessageSender; sender: MessageSender;
}; };
export type Api = (request: Request, con: MessageConnect | null) => Promise<any>; export type Api = (request: Request, con: GetSender) => Promise<any>;
export default class GMApi { export default class GMApi {
logger: Logger; logger: Logger;
@@ -34,7 +34,7 @@ export default class GMApi {
constructor( constructor(
private group: Group, private group: Group,
private sender: MessageSend, private send: MessageSend,
private value: ValueService private value: ValueService
) { ) {
this.logger = LoggerCore.logger().with({ service: "runtime/gm_api" }); this.logger = LoggerCore.logger().with({ service: "runtime/gm_api" });
@@ -53,7 +53,7 @@ export default class GMApi {
this.logger.error("verify error", { api: data.api }, Logger.E(e)); this.logger.error("verify error", { api: data.api }, Logger.E(e));
return Promise.reject(e); return Promise.reject(e);
} }
return api.api.call(this, req, con.getConnect()); return api.api.call(this, req, con);
} }
// 解析请求 // 解析请求
@@ -80,11 +80,12 @@ export default class GMApi {
} }
// 根据header生成dnr规则 // 根据header生成dnr规则
async buildDNRRule(reqeustId: number, params: GMSend.XHRDetails) { async buildDNRRule(reqeustId: number, params: GMSend.XHRDetails): Promise<{ [key: string]: string }> {
// 检查是否有unsafe header,有则生成dnr规则 // 检查是否有unsafe header,有则生成dnr规则
const headers = params.headers; const headers = params.headers;
console.log(headers, !headers);
if (!headers) { if (!headers) {
return; return Promise.resolve({});
} }
const requestHeaders = [ const requestHeaders = [
{ {
@@ -100,6 +101,7 @@ export default class GMApi {
operation: chrome.declarativeNetRequest.HeaderOperation.SET, operation: chrome.declarativeNetRequest.HeaderOperation.SET,
value: headers[key], value: headers[key],
}); });
delete headers[key];
} }
}); });
const ruleId = reqeustId; const ruleId = reqeustId;
@@ -119,20 +121,23 @@ export default class GMApi {
}); });
rule.condition = { rule.condition = {
resourceTypes: [chrome.declarativeNetRequest.ResourceType.XMLHTTPREQUEST], resourceTypes: [chrome.declarativeNetRequest.ResourceType.XMLHTTPREQUEST],
urlFilter: "^" + params.url + "$", urlFilter: params.url,
requestMethods: [(params.method || "GET").toLowerCase() as chrome.declarativeNetRequest.RequestMethod],
excludedTabIds: excludedTabIds, excludedTabIds: excludedTabIds,
}; };
console.log(rule);
await chrome.declarativeNetRequest.updateSessionRules({ await chrome.declarativeNetRequest.updateSessionRules({
removeRuleIds: [ruleId], removeRuleIds: [ruleId],
addRules: [rule], addRules: [rule],
}); });
return ruleId; return Promise.resolve(headers);
} }
gmXhrHeadersReceived: EventEmitter = new EventEmitter(); gmXhrHeadersReceived: EventEmitter = new EventEmitter();
@PermissionVerify.API() @PermissionVerify.API()
async GM_xmlhttpRequest(request: Request, con: MessageConnect) { async GM_xmlhttpRequest(request: Request, con: GetSender) {
console.log("GM XHR", request);
if (request.params.length === 0) { if (request.params.length === 0) {
return Promise.reject(new Error("param is failed")); return Promise.reject(new Error("param is failed"));
} }
@@ -145,7 +150,8 @@ export default class GMApi {
params.headers = {}; params.headers = {};
} }
params.headers["X-Scriptcat-GM-XHR-Request-Id"] = requestId.toString(); params.headers["X-Scriptcat-GM-XHR-Request-Id"] = requestId.toString();
await this.buildDNRRule(requestId, request.params[0]); params.headers = await this.buildDNRRule(requestId, request.params[0]);
console.log(" params.headers", params.headers);
let responseHeader = ""; let responseHeader = "";
// 等待response // 等待response
this.gmXhrHeadersReceived.addListener( this.gmXhrHeadersReceived.addListener(
@@ -157,12 +163,12 @@ export default class GMApi {
} }
); );
// 再发送到offscreen, 处理请求 // 再发送到offscreen, 处理请求
const offscreenCon = await connect(this.sender, "gmApi/xmlHttpRequest", request.params[0]); const offscreenCon = await connect(this.send, "offscreen/gmApi/xmlHttpRequest", request.params[0]);
offscreenCon.onMessage((msg: { action: string; data: any }) => { offscreenCon.onMessage((msg: { action: string; data: any }) => {
// 发送到content // 发送到content
// 替换msg.data.responseHeaders // 替换msg.data.responseHeaders
msg.data.responseHeaders = responseHeader; msg.data.responseHeaders = responseHeader;
con.sendMessage(msg); con.getConnect().sendMessage(msg);
}); });
} }
@@ -171,6 +177,7 @@ export default class GMApi {
chrome.webRequest.onBeforeSendHeaders.addListener( chrome.webRequest.onBeforeSendHeaders.addListener(
(details) => { (details) => {
if (details.tabId === -1) { if (details.tabId === -1) {
console.log(details);
// 判断是否存在X-Scriptcat-GM-XHR-Request-Id // 判断是否存在X-Scriptcat-GM-XHR-Request-Id
// 讲请求id与chrome.webRequest的请求id关联 // 讲请求id与chrome.webRequest的请求id关联
if (details.requestHeaders) { if (details.requestHeaders) {

View File

@@ -8,7 +8,7 @@ import { ScriptService } from "./script";
import { runScript, stopScript } from "../offscreen/client"; import { runScript, stopScript } from "../offscreen/client";
import { getRunAt } from "./utils"; import { getRunAt } from "./utils";
import { randomString } from "@App/pkg/utils/utils"; import { randomString } from "@App/pkg/utils/utils";
import { compileInjectScript, compileScriptCode } from "@App/runtime/content/utils"; import { compileInjectScript } from "@App/runtime/content/utils";
import Cache from "@App/app/cache"; import Cache from "@App/app/cache";
import { dealPatternMatches, UrlMatch } from "@App/pkg/utils/match"; import { dealPatternMatches, UrlMatch } from "@App/pkg/utils/match";
@@ -113,7 +113,6 @@ export class RuntimeService {
// 匹配当前页面的脚本 // 匹配当前页面的脚本
const matchScriptUuid = match.match(chromeSender.url!); const matchScriptUuid = match.match(chromeSender.url!);
console.log("pageLoad", match.match(chromeSender.url!));
const scripts = await Promise.all( const scripts = await Promise.all(
matchScriptUuid.map( matchScriptUuid.map(
(uuid) => (uuid) =>
@@ -249,7 +248,6 @@ export class RuntimeService {
} }
const scriptRes = await this.script.buildScriptRunResource(script); const scriptRes = await this.script.buildScriptRunResource(script);
scriptRes.code = compileScriptCode(scriptRes);
scriptRes.code = compileInjectScript(scriptRes); scriptRes.code = compileInjectScript(scriptRes);
matches.push(...(script.metadata["include"] || [])); matches.push(...(script.metadata["include"] || []));

View File

@@ -4,6 +4,7 @@ import { ExtensionMessageSend } from "@Packages/message/extension_message";
import { CustomEventMessage } from "@Packages/message/custom_event_message"; import { CustomEventMessage } from "@Packages/message/custom_event_message";
import { RuntimeClient } from "./app/service/service_worker/client"; import { RuntimeClient } from "./app/service/service_worker/client";
import ContentRuntime from "./runtime/content/content"; import ContentRuntime from "./runtime/content/content";
import { Server } from "@Packages/message/server";
// 建立与service_worker页面的连接 // 建立与service_worker页面的连接
const send = new ExtensionMessageSend(); const send = new ExtensionMessageSend();
@@ -17,9 +18,9 @@ const loggerCore = new LoggerCore({
const client = new RuntimeClient(send); const client = new RuntimeClient(send);
client.pageLoad().then((data) => { client.pageLoad().then((data) => {
loggerCore.logger().debug("content start"); loggerCore.logger().debug("content start");
console.log("content", data);
const msg = new CustomEventMessage(data.flag, true); const msg = new CustomEventMessage(data.flag, true);
const server = new Server("content", msg);
// 初始化运行环境 // 初始化运行环境
const runtime = new ContentRuntime(send, msg); const runtime = new ContentRuntime(server, send, msg);
runtime.start(data.scripts); runtime.start(data.scripts);
}); });

View File

@@ -17,7 +17,6 @@ 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");
console.log("inject", data);
const runtime = new InjectRuntime(msg, data.scripts); const runtime = new InjectRuntime(msg, data.scripts);
runtime.start(); runtime.start();
}); });

View File

@@ -149,6 +149,9 @@ export default class Match<T> {
} }
protected static getId(val: any): string { protected static getId(val: any): string {
if (typeof val === "string") {
return val;
}
return (<{ uuid: string }>(<unknown>val)).uuid; return (<{ uuid: string }>(<unknown>val)).uuid;
} }

View File

@@ -1,19 +1,23 @@
import { ScriptRunResouce } from "@App/app/repo/scripts"; import { ScriptRunResouce } from "@App/app/repo/scripts";
import { Client } from "@Packages/message/client"; import { Client } from "@Packages/message/client";
import { Message, MessageSend } from "@Packages/message/server"; import { forwardMessage, Message, MessageSend, Server } from "@Packages/message/server";
// content页的处理 // content页的处理
export default class ContentRuntime { export default class ContentRuntime {
constructor( constructor(
private server: Server,
private send: MessageSend, private send: MessageSend,
private msg: Message private msg: Message
) {} ) {}
start(scripts: ScriptRunResouce[]) { start(scripts: ScriptRunResouce[]) {
console.log("onMessage");
this.msg.onMessage((msg, sendResponse) => { this.msg.onMessage((msg, sendResponse) => {
console.log("content onMessage", msg); console.log("content onMessage", msg);
}); });
this.msg.onConnect((msg, connect) => {
console.log(msg, connect);
});
forwardMessage("serviceWorker", "runtime/gmApi", this.server, this.send);
// 由content到background // 由content到background
// 转发gmApi消息 // 转发gmApi消息
// this.contentMessage.setHandler("gmApi", (action, data) => { // this.contentMessage.setHandler("gmApi", (action, data) => {

View File

@@ -2,7 +2,8 @@ import { ScriptRunResouce } from "@App/app/repo/scripts";
import ExecScript from "./exec_script"; import ExecScript from "./exec_script";
import { compileScript, compileScriptCode } from "./utils"; import { compileScript, compileScriptCode } from "./utils";
import { ExtVersion } from "@App/app/const"; import { ExtVersion } from "@App/app/const";
import initTestEnv from "@Tests/utils"; import { initTestEnv } from "@Tests/utils";
import { describe, expect, it } from "vitest";
initTestEnv(); initTestEnv();

View File

@@ -35,6 +35,7 @@ export default class ExecScript {
constructor( constructor(
scriptRes: ScriptRunResouce, scriptRes: ScriptRunResouce,
envPrefix: "content" | "offscreen",
message: Message, message: Message,
code: string | ScriptFunc, code: string | ScriptFunc,
thisContext?: { [key: string]: any } thisContext?: { [key: string]: any }
@@ -61,7 +62,7 @@ export default class ExecScript {
this.proxyContent = global; this.proxyContent = global;
} else { } else {
// 构建脚本GM上下文 // 构建脚本GM上下文
this.sandboxContent = createContext(scriptRes, this.GM_info, message); this.sandboxContent = createContext(scriptRes, this.GM_info, envPrefix, message);
this.proxyContent = proxyContext(global, this.sandboxContent, thisContext); this.proxyContent = proxyContext(global, this.sandboxContent, thisContext);
} }
} }
@@ -76,7 +77,6 @@ export default class ExecScript {
return this.scriptFunc.apply(this.proxyContent, [this.proxyContent, this.GM_info]); return this.scriptFunc.apply(this.proxyContent, [this.proxyContent, this.GM_info]);
} }
// TODO: 实现脚本的停止,资源释放
stop() { stop() {
this.logger.debug("script stop"); this.logger.debug("script stop");
return true; return true;

View File

@@ -63,7 +63,7 @@ export class BgExecScriptWarp extends ExecScript {
}; };
// @ts-ignore // @ts-ignore
thisContext.CATRetryError = CATRetryError; thisContext.CATRetryError = CATRetryError;
super(scriptRes, message, thisContext); super(scriptRes, "offscreen", message, scriptRes.code, thisContext);
this.setTimeout = setTimeout; this.setTimeout = setTimeout;
this.setInterval = setInterval; this.setInterval = setInterval;
} }

View File

@@ -58,12 +58,15 @@ export default class GMApi {
valueChangeListener = new Map<number, { name: string; listener: GMTypes.ValueChangeListener }>(); valueChangeListener = new Map<number, { name: string; listener: GMTypes.ValueChangeListener }>();
constructor(private message: Message) {} constructor(
private prefix: string,
private message: Message
) {}
// 单次回调使用 // 单次回调使用
public sendMessage(api: string, params: any[]) { public sendMessage(api: string, params: any[]) {
return this.message.sendMessage({ return this.message.sendMessage({
action: "runtime/gmApi", action: this.prefix + "/runtime/gmApi",
data: { data: {
uuid: this.scriptRes.uuid, uuid: this.scriptRes.uuid,
api, api,
@@ -75,7 +78,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: "runtime/gmApi", action: this.prefix + "/runtime/gmApi",
data: { data: {
uuid: this.scriptRes.uuid, uuid: this.scriptRes.uuid,
api, api,

View File

@@ -32,7 +32,7 @@ export class InjectRuntime {
execScript(script: ScriptRunResouce, scriptFunc: ScriptFunc) { execScript(script: ScriptRunResouce, scriptFunc: ScriptFunc) {
// @ts-ignore // @ts-ignore
delete window[script.flag]; delete window[script.flag];
const exec = new ExecScript(script, this.msg, scriptFunc); const exec = new ExecScript(script, "content", this.msg, scriptFunc);
this.execList.push(exec); this.execList.push(exec);
// 注入css // 注入css
if (script.metadata["require-css"]) { if (script.metadata["require-css"]) {

View File

@@ -53,9 +53,10 @@ function setDepend(context: { [key: string]: any }, apiVal: ApiValue) {
} }
// 构建沙盒上下文 // 构建沙盒上下文
export function createContext(scriptRes: ScriptRunResouce, GMInfo: any, message: Message): GMApi { export function createContext(scriptRes: ScriptRunResouce, GMInfo: any, envPrefix: string, message: Message): GMApi {
// 按照GMApi构建 // 按照GMApi构建
const context: { [key: string]: any } = { const context: { [key: string]: any } = {
prefix: envPrefix,
message: message, message: message,
scriptRes, scriptRes,
valueChangeListener: new Map<number, { name: string; listener: GMTypes.ValueChangeListener }>(), valueChangeListener: new Map<number, { name: string; listener: GMTypes.ValueChangeListener }>(),