脚本匹配与注入
Some checks failed
build / Build (push) Failing after 5s
test / Run tests (push) Failing after 8s
Some checks failed
build / Build (push) Failing after 5s
test / Run tests (push) Failing after 8s
This commit is contained in:
parent
9ce1826a34
commit
651384f12c
@ -8,13 +8,22 @@ import { ScriptService } from "./script";
|
||||
import { runScript, stopScript } from "../offscreen/client";
|
||||
import { getRunAt } from "./utils";
|
||||
import { randomString } from "@App/pkg/utils/utils";
|
||||
import { compileInjectScript, compileInjectScriptInfo, compileScriptCode } from "@App/runtime/content/utils";
|
||||
import { compileInjectScript, compileScriptCode } from "@App/runtime/content/utils";
|
||||
import Cache from "@App/app/cache";
|
||||
import { dealPatternMatches } from "@App/pkg/utils/match";
|
||||
import { dealPatternMatches, UrlMatch } from "@App/pkg/utils/match";
|
||||
|
||||
// 为了优化性能,存储到缓存时删除了code与value
|
||||
export interface ScriptMatchInfo extends ScriptRunResouce {
|
||||
matches: string[];
|
||||
excludeMatches: string[];
|
||||
}
|
||||
|
||||
export class RuntimeService {
|
||||
scriptDAO: ScriptDAO = new ScriptDAO();
|
||||
|
||||
scriptMatch: UrlMatch<string> = new UrlMatch<string>();
|
||||
scriptMatchCache: Map<string, ScriptMatchInfo> | null | undefined;
|
||||
|
||||
constructor(
|
||||
private group: Group,
|
||||
private sender: MessageSend,
|
||||
@ -99,12 +108,23 @@ export class RuntimeService {
|
||||
}
|
||||
|
||||
async pageLoad(_, sender: GetSender) {
|
||||
const scriptFlag = await this.messageFlag();
|
||||
const [scriptFlag, match] = await Promise.all([this.messageFlag(), this.loadScriptMatchInfo()]);
|
||||
const chromeSender = sender.getSender() as chrome.runtime.MessageSender;
|
||||
// 匹配当前页面的脚本
|
||||
console.log("pageLoad");
|
||||
|
||||
return Promise.resolve({ flag: scriptFlag });
|
||||
// 匹配当前页面的脚本
|
||||
const matchScriptUuid = match.match(chromeSender.url!);
|
||||
console.log("pageLoad", match.match(chromeSender.url!));
|
||||
const scripts = await Promise.all(
|
||||
matchScriptUuid.map(
|
||||
(uuid) =>
|
||||
new Promise((resolve) => {
|
||||
// 获取value
|
||||
const scriptRes = Object.assign({}, this.scriptMatchCache?.get(uuid));
|
||||
resolve(scriptRes);
|
||||
})
|
||||
)
|
||||
);
|
||||
return Promise.resolve({ flag: scriptFlag, scripts: scripts });
|
||||
}
|
||||
|
||||
// 停止脚本
|
||||
@ -155,6 +175,70 @@ export class RuntimeService {
|
||||
});
|
||||
}
|
||||
|
||||
loadScripting: Promise<void> | null | undefined;
|
||||
|
||||
// 加载脚本匹配信息,由于service_worker的机制,如果由不活动状态恢复过来时,会优先触发事件
|
||||
// 可能当时会没有脚本匹配信息,所以使用脚本信息时,尽量使用此方法获取
|
||||
async loadScriptMatchInfo() {
|
||||
if (this.scriptMatchCache) {
|
||||
return this.scriptMatch;
|
||||
}
|
||||
if (this.loadScripting) {
|
||||
await this.loadScripting;
|
||||
} else {
|
||||
// 如果没有缓存, 则创建一个新的缓存
|
||||
this.loadScripting = Cache.getInstance()
|
||||
.get("scriptMatch")
|
||||
.then((data: { [key: string]: ScriptMatchInfo }) => {
|
||||
this.scriptMatchCache = new Map<string, ScriptMatchInfo>();
|
||||
if (data) {
|
||||
Object.keys(data).forEach((key) => {
|
||||
const item = data[key];
|
||||
this.scriptMatchCache!.set(item.uuid, item);
|
||||
item.matches.forEach((match) => {
|
||||
this.scriptMatch.add(match, item.uuid);
|
||||
});
|
||||
item.excludeMatches.forEach((match) => {
|
||||
this.scriptMatch.exclude(match, item.uuid);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
await this.loadScripting;
|
||||
this.loadScripting = null;
|
||||
}
|
||||
return this.scriptMatch;
|
||||
}
|
||||
|
||||
// 保存脚本匹配信息
|
||||
async saveScriptMatchInfo() {
|
||||
if (!this.scriptMatchCache) {
|
||||
return;
|
||||
}
|
||||
const scriptMatch = {} as { [key: string]: ScriptMatchInfo };
|
||||
this.scriptMatchCache.forEach((val, key) => {
|
||||
scriptMatch[key] = val;
|
||||
// 优化性能,将不需要的信息去掉
|
||||
scriptMatch[key].code = "";
|
||||
scriptMatch[key].value = {};
|
||||
});
|
||||
return await Cache.getInstance().set("scriptMatch", scriptMatch);
|
||||
}
|
||||
|
||||
async addScriptMatch(item: ScriptMatchInfo) {
|
||||
if (!this.scriptMatchCache) {
|
||||
await this.loadScriptMatchInfo();
|
||||
}
|
||||
this.scriptMatchCache!.set(item.uuid, item);
|
||||
item.matches.forEach((match) => {
|
||||
this.scriptMatch.add(match, item.uuid);
|
||||
});
|
||||
item.excludeMatches.forEach((match) => {
|
||||
this.scriptMatch.exclude(match, item.uuid);
|
||||
});
|
||||
this.saveScriptMatchInfo();
|
||||
}
|
||||
|
||||
async registryPageScript(script: Script) {
|
||||
if (await Cache.getInstance().has("registryScript:" + script.uuid)) {
|
||||
return;
|
||||
@ -170,6 +254,11 @@ export class RuntimeService {
|
||||
|
||||
matches.push(...(script.metadata["include"] || []));
|
||||
const patternMatches = dealPatternMatches(matches);
|
||||
const scriptMatchInfo: ScriptMatchInfo = Object.assign(
|
||||
{ matches: patternMatches.result, excludeMatches: [] },
|
||||
scriptRes
|
||||
);
|
||||
|
||||
const registerScript: chrome.userScripts.RegisteredUserScript = {
|
||||
id: scriptRes.uuid,
|
||||
js: [{ code: scriptRes.code }],
|
||||
@ -186,13 +275,16 @@ export class RuntimeService {
|
||||
const result = dealPatternMatches(excludeMatches);
|
||||
|
||||
registerScript.excludeMatches = result.patternResult;
|
||||
scriptMatchInfo.excludeMatches = result.result;
|
||||
}
|
||||
if (script.metadata["run-at"]) {
|
||||
registerScript.runAt = getRunAt(script.metadata["run-at"]);
|
||||
}
|
||||
chrome.userScripts.register([registerScript], () => {
|
||||
chrome.userScripts.register([registerScript], async () => {
|
||||
// 标记为已注册
|
||||
Cache.getInstance().set("registryScript:" + script.uuid, true);
|
||||
// 将脚本match信息放入缓存中
|
||||
this.addScriptMatch(scriptMatchInfo);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -15,9 +15,9 @@ const logger = new LoggerCore({
|
||||
|
||||
const server = new Server("inject", msg);
|
||||
|
||||
server.on("pageLoad", (data: ScriptRunResouce[]) => {
|
||||
server.on("pageLoad", (data: { scripts: ScriptRunResouce[] }) => {
|
||||
logger.logger().debug("inject start");
|
||||
console.log("inject", data);
|
||||
const runtime = new InjectRuntime(msg, data);
|
||||
const runtime = new InjectRuntime(msg, data.scripts);
|
||||
runtime.start();
|
||||
});
|
||||
|
@ -12,6 +12,14 @@ export default class Match<T> {
|
||||
|
||||
protected rule = new Map<string, T[]>();
|
||||
|
||||
protected kv = new Map<string, T>();
|
||||
|
||||
forEach(fn: (val: T, key: string) => void) {
|
||||
this.kv.forEach((val, key) => {
|
||||
fn(val, key);
|
||||
});
|
||||
}
|
||||
|
||||
protected parseURL(url: string): Url | undefined {
|
||||
if (url.indexOf("*http") === 0) {
|
||||
url = url.substring(1);
|
||||
@ -112,6 +120,7 @@ export default class Match<T> {
|
||||
this.rule.set(re, rule);
|
||||
}
|
||||
rule.push(val);
|
||||
this.kv.set(Match.getId(val), val);
|
||||
this.delCache();
|
||||
}
|
||||
|
||||
@ -129,7 +138,6 @@ export default class Match<T> {
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("bad match rule", Logger.E(e));
|
||||
// LoggerCore.getLogger({ component: "match" }).warn(
|
||||
// "bad match rule",
|
||||
@ -141,11 +149,8 @@ export default class Match<T> {
|
||||
}
|
||||
|
||||
protected static getId(val: any): string {
|
||||
if (typeof val === "object") {
|
||||
return (<{ uuid: string }>(<unknown>val)).uuid;
|
||||
}
|
||||
return <string>(<unknown>val);
|
||||
}
|
||||
|
||||
public del(val: T) {
|
||||
const id = Match.getId(val);
|
||||
|
@ -33,7 +33,12 @@ export default class ExecScript {
|
||||
|
||||
GM_info: any;
|
||||
|
||||
constructor(scriptRes: ScriptRunResouce, message: Message, thisContext?: { [key: string]: any }) {
|
||||
constructor(
|
||||
scriptRes: ScriptRunResouce,
|
||||
message: Message,
|
||||
code: string | ScriptFunc,
|
||||
thisContext?: { [key: string]: any }
|
||||
) {
|
||||
this.scriptRes = scriptRes;
|
||||
this.logger = LoggerCore.getInstance().logger({
|
||||
component: "exec",
|
||||
@ -42,7 +47,11 @@ export default class ExecScript {
|
||||
});
|
||||
this.GM_info = GMApi.GM_info(this.scriptRes);
|
||||
// 构建脚本资源
|
||||
this.scriptFunc = compileScript(this.scriptRes.code);
|
||||
if (typeof code === "string") {
|
||||
this.scriptFunc = compileScript(code);
|
||||
} else {
|
||||
this.scriptFunc = code;
|
||||
}
|
||||
const grantMap: { [key: string]: boolean } = {};
|
||||
scriptRes.metadata.grant?.forEach((key) => {
|
||||
grantMap[key] = true;
|
||||
|
@ -1,11 +1,48 @@
|
||||
import { ScriptRunResouce } from "@App/app/repo/scripts";
|
||||
import { Message } from "@Packages/message/server";
|
||||
import ExecScript from "./exec_script";
|
||||
import { addStyle, ScriptFunc } from "./utils";
|
||||
|
||||
export class InjectRuntime {
|
||||
execList: ExecScript[] = [];
|
||||
|
||||
constructor(
|
||||
private msg: Message,
|
||||
private scripts: ScriptRunResouce[]
|
||||
) {}
|
||||
|
||||
start(){}
|
||||
start() {
|
||||
this.scripts.forEach((script) => {
|
||||
// @ts-ignore
|
||||
const scriptFunc = window[script.flag];
|
||||
if (scriptFunc) {
|
||||
this.execScript(script, scriptFunc);
|
||||
} else {
|
||||
// 监听脚本加载,和屏蔽读取
|
||||
Object.defineProperty(window, script.flag, {
|
||||
configurable: true,
|
||||
set: (val: ScriptFunc) => {
|
||||
this.execScript(script, val);
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
execScript(script: ScriptRunResouce, scriptFunc: ScriptFunc) {
|
||||
// @ts-ignore
|
||||
delete window[script.flag];
|
||||
const exec = new ExecScript(script, this.msg, scriptFunc);
|
||||
this.execList.push(exec);
|
||||
// 注入css
|
||||
if (script.metadata["require-css"]) {
|
||||
script.metadata["require-css"].forEach((val) => {
|
||||
const res = script.resource[val];
|
||||
if (res) {
|
||||
addStyle(res.content);
|
||||
}
|
||||
});
|
||||
}
|
||||
exec.exec();
|
||||
}
|
||||
}
|
||||
|
@ -32,18 +32,6 @@ export function compileInjectScript(script: ScriptRunResouce): string {
|
||||
return `window['${script.flag}']=function(context,GM_info){\n${script.code}\n}`;
|
||||
}
|
||||
|
||||
// 编译注入脚本信息
|
||||
export function compileInjectScriptInfo(
|
||||
messageFlag: string,
|
||||
script: ScriptRunResouce,
|
||||
injectScriptInfoCode: string
|
||||
): string {
|
||||
return (
|
||||
`(function (MessageFlag, ScriptFlag, ScriptUuid) {\n${injectScriptInfoCode}\n})` +
|
||||
`('${messageFlag}', '${script.flag}', '${script.uuid}');`
|
||||
);
|
||||
}
|
||||
|
||||
// 设置api依赖
|
||||
function setDepend(context: { [key: string]: any }, apiVal: ApiValue) {
|
||||
if (apiVal.param.depend) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user