Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/message/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
export const MouseEventClone = MouseEvent;
export const CustomEventClone = CustomEvent;

const performanceClone = (typeof process !== "undefined" && process.env.VI_TESTING === "true"
const performanceClone = (process.env.VI_TESTING === "true"
? new EventTarget()
: performance) as Performance;

Expand Down
1 change: 1 addition & 0 deletions src/app/message/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default class MessageContent

constructor(eventId: string, isContent: boolean) {
super();
if (!eventId || eventId[0] === "{") throw new Error("eventId is missing");
this.eventId = eventId;
this.isContent = isContent;
this.channelManager = new WarpChannelManager((data) => {
Expand Down
2 changes: 1 addition & 1 deletion src/app/message/message.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ global.sandbox = {};
const center = new MessageCenter();
center.start();

const content = new MessageInternal("background");
const content = new MessageInternal("testing");

describe("message center", () => {
it("set handler", async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/app/message/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export type HandlerWithChannel = (
) => void;

export type TargetTag =
| "background"
| "testing"
| "content"
| "sandbox"
| "popup"
Expand Down
11 changes: 7 additions & 4 deletions src/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,21 @@ const logger = new LoggerCore({

const scriptFlag = randomString(8);

// 通过flag与inject建立通讯
const contentMessage = new MessageContent(scriptFlag, true);

// 注入运行框架
const temp = document.createElementNS("http://www.w3.org/1999/xhtml", "script");
temp.setAttribute("type", "text/javascript");
temp.setAttribute("charset", "UTF-8");
temp.textContent = `(function (ScriptFlag) {\n${injectJs}\n})('${scriptFlag}')${sourceMapTo("injected.js")}`;
const injectJsConv = injectJs.replace("{{__ScriptFlag__}}", () =>`${scriptFlag}`);
temp.textContent = `(function () {\n${injectJsConv}\n})()${sourceMapTo("injected.js")}`;
temp.className = "injected-js";
document.documentElement.appendChild(temp);
temp.remove();

internalMessage.syncSend("pageLoad", null).then((resp) => {
logger.logger().debug("content start");
// 通过flag与inject建立通讯
const contentMessage = new MessageContent(scriptFlag, true);
new ContentRuntime(contentMessage, internalMessage).start(resp);
const contentRuntime = new ContentRuntime(contentMessage, internalMessage);
contentRuntime.start(resp);
});
12 changes: 6 additions & 6 deletions src/inject.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import LoggerCore from "./app/logger/core";
import MessageWriter from "./app/logger/message_writer";
import MessageContent from "./app/message/content";
import { type ScriptRunResource } from "./app/repo/scripts";
import InjectRuntime from "./runtime/content/inject";

// 通过flag与content建立通讯,这个ScriptFlag是后端注入时候生成的
// eslint-disable-next-line no-undef
const flag = ScriptFlag;
const flag = "{{__ScriptFlag__}}";

// 通过flag与content建立通讯
const message = new MessageContent(flag, false);

// 加载logger组件
Expand All @@ -16,9 +17,8 @@ const logger = new LoggerCore({
labels: { env: "inject", href: window.location.href },
});


message.setHandler("pageLoad", (_action, data) => {
message.setHandler("pageLoad", (_action, resp: { scripts: ScriptRunResource[], executionToken?: string }) => {
logger.logger().debug("inject start");
const runtime = new InjectRuntime(message, data.scripts, flag);
runtime.start();
const runtime = new InjectRuntime(message);
runtime.start(resp);
});
69 changes: 57 additions & 12 deletions src/runtime/background/gm_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type MessageRequest = {
scriptId: number; // 脚本id
api: string;
runFlag: string;
executionToken?: string;
params: any[];
};

Expand All @@ -49,7 +50,30 @@ export type Request = MessageRequest & {

export type Api = (request: Request, connect?: Channel) => Promise<any>;

const executionMap = new Map<
string,
{ scriptIds: Set<number>; tabId: number }
>();

export const registerScriptExecution = (scriptIds: number[], tabId: number): string => {
const token = uuidv4();
executionMap.set(token, {
scriptIds: new Set(scriptIds),
tabId,
});
return token;
};

export const removeTabExecutions = (tabId: number) => {
executionMap.forEach((execution, token) => {
if (execution.tabId === tabId) {
executionMap.delete(token);
}
});
};

export default class GMApi {

message: MessageHander;

script: ScriptDAO;
Expand Down Expand Up @@ -80,18 +104,18 @@ export default class GMApi {
this.message.setHandler(
"gmApi",
async (_action: string, data: MessageRequest, sender: MessageSender) => {
const api = PermissionVerify.apis.get(data.api);
if (!api) {
return Promise.reject(new Error("api is not found"));
}
const req = await this.parseRequest(data, sender);
try {
const api = PermissionVerify.apis.get(data.api);
if (!api) {
return Promise.reject(new Error("api is not found"));
}
const req = await this.parseRequest(data, sender);
await this.permissionVerify.verify(req, api);
return api.api.call(this, req);
} catch (e) {
this.logger.error("verify error", { api: data.api }, Logger.E(e));
return Promise.reject(e);
}
return api.api.call(this, req);
}
);
this.message.setHandlerWithChannel(
Expand All @@ -102,18 +126,18 @@ export default class GMApi {
data: MessageRequest,
sender: MessageSender
) => {
const api = PermissionVerify.apis.get(data.api);
if (!api) {
return connect.throw("api is not found");
}
const req = await this.parseRequest(data, sender);
try {
const api = PermissionVerify.apis.get(data.api);
if (!api) {
return connect.throw("api is not found");
}
const req = await this.parseRequest(data, sender);
await this.permissionVerify.verify(req, api);
return api.api.call(this, req, connect);
} catch (e: any) {
this.logger.error("verify error", { api: data.api }, Logger.E(e));
return connect.throw(e.message);
}
return api.api.call(this, req, connect);
}
);
// 只有background页才监听web请求
Expand Down Expand Up @@ -150,9 +174,30 @@ export default class GMApi {
const req: Request = <Request>data;
req.script = script;
req.sender = sender;
this.verifyExecution(req);
return Promise.resolve(req);
}

verifyExecution(request: Request) {
// 只适用于有 pageLoad 的 前台脚本(content), 不适用于没 pageLoad 的 后台脚本 (sandbox)
if (process.env.VI_TESTING === "true" && request.sender.targetTag === "testing") {
return;
}
if (request.sender.targetTag === "sandbox") {
return;
}
if (request.sender.targetTag !== "content") {
throw new Error("script execution must be from content or sandbox");
}
if (!request.executionToken) {
throw new Error("script execution is not trusted");
}
const execution = executionMap.get(request.executionToken);
if (!execution || !execution.scriptIds.has(request.scriptId)) {
throw new Error("script execution is not trusted");
}
}

@PermissionVerify.API()
GM_setValue(request: Request): Promise<any> {
if (!request.params || request.params.length !== 2) {
Expand Down
18 changes: 15 additions & 3 deletions src/runtime/background/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import Manager from "@App/app/service/manager";
import Hook from "@App/app/service/hook";
import { i18nName } from "@App/locales/locales";
import { compileInjectScript, compileScriptCode } from "../content/utils";
import GMApi, { type Request } from "./gm_api";
import GMApi, { registerScriptExecution, removeTabExecutions, type Request } from "./gm_api";
import { genScriptMenu } from "./utils";

export type RuntimeEvent = "start" | "stop" | "watchRunStatus";
Expand Down Expand Up @@ -285,6 +285,7 @@ export default class Runtime extends Manager {
};
chrome.tabs.onRemoved.addListener((tabId) => {
runScript.delete(tabId);
removeTabExecutions(tabId);
});
// 给popup页面获取运行脚本,与菜单
this.message.setHandler(
Expand Down Expand Up @@ -431,10 +432,21 @@ export default class Runtime extends Manager {
return;
}

resolve({ scripts: filter });
const executionToken = registerScriptExecution(
filter.map((script) => script.id),
sender.tabId!
);

const runResources = filter;
// const runResources = filter.map((script) => ({
// ...script,
// executionToken,
// }));

resolve({ scripts: runResources, executionToken });

// 注入脚本
filter.forEach((script) => {
runResources.forEach((script) => {
let runAt = "document_idle";
if (script.metadata["run-at"]) {
[runAt] = script.metadata["run-at"];
Expand Down
4 changes: 3 additions & 1 deletion src/runtime/content/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import MessageContent from "@App/app/message/content";
import MessageInternal from "@App/app/message/internal";
import { MessageHander, MessageManager } from "@App/app/message/message";
import { ScriptRunResource } from "@App/app/repo/scripts";
import { assignExecutionToken } from "./gm_api";

// content页的处理
export default class ContentRuntime {
Expand All @@ -18,7 +19,8 @@ export default class ContentRuntime {
this.internalMessage = internalMessage;
}

start(resp: { scripts: ScriptRunResource[] }) {
start(resp: { scripts: ScriptRunResource[], executionToken?: string }) {
assignExecutionToken(`${resp.executionToken || ""}`);
// 由content到background
// 转发gmApi消息
this.contentMessage.setHandler("gmApi", (action, data) => {
Expand Down
11 changes: 11 additions & 0 deletions src/runtime/content/gm_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ import { parseUserConfig } from "@App/pkg/utils/yaml";
import { v4 as uuidv4 } from "uuid";
import { type ValueUpdateData } from "./exec_script";

// content/page 环境的变数,不储存在任何object
let currentExecutionToken = "";
export const assignExecutionToken = (executionToken: string) => {
// content/page 环境 pageLoad 时储存由 background 生成的 executionToken
if (currentExecutionToken) throw new Error("currentExecutionToken cannot be re-assigned");
currentExecutionToken = executionToken;
};

interface ApiParam {
depend?: string[];
listener?: () => void;
Expand Down Expand Up @@ -77,6 +85,7 @@ export class GM_Base {
scriptId: this.scriptRes.id,
params,
runFlag: this.runFlag,
executionToken: currentExecutionToken,
});
}

Expand All @@ -90,6 +99,7 @@ export class GM_Base {
scriptId: this.scriptRes.id,
params,
runFlag: this.runFlag,
executionToken: currentExecutionToken,
});
return channel;
}
Expand Down Expand Up @@ -121,6 +131,7 @@ export class GM_Base {
});
}
}

}

export default class GMApi extends GM_Base {
Expand Down
15 changes: 5 additions & 10 deletions src/runtime/content/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,24 @@ import { type ScriptRunResource } from "@App/app/repo/scripts";
import ExecScript, { type ValueUpdateData } from "./exec_script";
import { addStyleSheet, type ScriptFunc } from "./utils";
import { onInjectPageLoaded } from "./external";
import { assignExecutionToken } from "./gm_api";

// 注入脚本的沙盒环境
export default class InjectRuntime {
scripts: ScriptRunResource[];

flag: string;

message: MessageContent;

execList: ExecScript[] = [];

constructor(
message: MessageContent,
scripts: ScriptRunResource[],
flag: string
message: MessageContent
) {
this.message = message;
this.scripts = scripts;
this.flag = flag;
}

start() {
this.scripts.forEach((script) => {
start(resp: { scripts: ScriptRunResource[], executionToken?: string }) {
assignExecutionToken(`${resp.executionToken || ""}`);
resp.scripts.forEach((script) => {
// @ts-ignore
const scriptFunc = window[script.flag];
if (scriptFunc) {
Expand Down
Loading
Loading