王一之 65b231911e
Some checks failed
build / Build (push) Failing after 10s
test / Run tests (push) Failing after 10s
xhr测试
2025-02-05 18:01:59 +08:00

294 lines
9.0 KiB
TypeScript

import { fetchScriptInfo } from "@App/pkg/utils/script";
import { v4 as uuidv4 } from "uuid";
import { Group } from "@Packages/message/server";
import Logger from "@App/app/logger/logger";
import LoggerCore from "@App/app/logger/core";
import Cache from "@App/app/cache";
import CacheKey from "@App/app/cache_key";
import { openInCurrentTab, randomString } from "@App/pkg/utils/utils";
import {
Script,
SCRIPT_RUN_STATUS,
SCRIPT_STATUS_DISABLE,
SCRIPT_STATUS_ENABLE,
ScriptCodeDAO,
ScriptDAO,
ScriptRunResouce,
} from "@App/app/repo/scripts";
import { MessageQueue } from "@Packages/message/message_queue";
import { InstallSource } from ".";
import { ResourceService } from "./resource";
import { ValueService } from "./value";
import { compileScriptCode } from "@App/runtime/content/utils";
export class ScriptService {
logger: Logger;
scriptDAO: ScriptDAO = new ScriptDAO();
scriptCodeDAO: ScriptCodeDAO = new ScriptCodeDAO();
constructor(
private group: Group,
private mq: MessageQueue,
private valueService: ValueService,
private resourceService: ResourceService
) {
this.logger = LoggerCore.logger().with({ service: "script" });
}
listenerScriptInstall() {
// 初始化脚本安装监听
chrome.webRequest.onBeforeRequest.addListener(
(req: chrome.webRequest.WebRequestBodyDetails) => {
// 处理url, 实现安装脚本
if (req.method !== "GET") {
return;
}
const url = new URL(req.url);
// 判断是否有hash
if (!url.hash) {
return;
}
// 判断是否有url参数
if (!url.hash.includes("url=")) {
return;
}
// 获取url参数
const targetUrl = url.hash.split("url=")[1];
// 读取脚本url内容, 进行安装
const logger = this.logger.with({ url: targetUrl });
logger.debug("install script");
this.openInstallPageByUrl(targetUrl).catch((e) => {
logger.error("install script error", Logger.E(e));
// 如果打开失败, 则重定向到安装页
chrome.scripting.executeScript({
target: { tabId: req.tabId },
func: function () {
history.back();
},
});
// 并不再重定向当前url
chrome.declarativeNetRequest.updateDynamicRules(
{
removeRuleIds: [2],
addRules: [
{
id: 2,
priority: 1,
action: {
type: chrome.declarativeNetRequest.RuleActionType.ALLOW,
},
condition: {
regexFilter: targetUrl,
resourceTypes: [chrome.declarativeNetRequest.ResourceType.MAIN_FRAME],
requestMethods: [chrome.declarativeNetRequest.RequestMethod.GET],
},
},
],
},
() => {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
}
}
);
});
},
{
urls: [
"https://docs.scriptcat.org/docs/script_installation",
"https://www.tampermonkey.net/script_installation.php",
],
types: ["main_frame"],
}
);
// 重定向到脚本安装页
chrome.declarativeNetRequest.updateDynamicRules(
{
removeRuleIds: [1, 2],
addRules: [
{
id: 1,
priority: 1,
action: {
type: chrome.declarativeNetRequest.RuleActionType.REDIRECT,
redirect: {
regexSubstitution: "https://docs.scriptcat.org/docs/script_installation#url=\\0",
},
},
condition: {
regexFilter: "^([^#]+?)\\.user(\\.bg|\\.sub)?\\.js((\\?).*|$)",
resourceTypes: [chrome.declarativeNetRequest.ResourceType.MAIN_FRAME],
requestMethods: [chrome.declarativeNetRequest.RequestMethod.GET],
// 排除常见的符合上述条件的域名
excludedRequestDomains: ["github.com"],
},
},
],
},
() => {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
}
}
);
}
public openInstallPageByUrl(url: string) {
const uuid = uuidv4();
return fetchScriptInfo(url, "user", false, uuidv4()).then((info) => {
Cache.getInstance().set(CacheKey.scriptInstallInfo(uuid), info);
setTimeout(() => {
// 清理缓存
Cache.getInstance().del(CacheKey.scriptInstallInfo(uuid));
}, 60 * 1000);
openInCurrentTab(`/src/install.html?uuid=${uuid}`);
});
}
// 获取安装信息
getInstallInfo(uuid: string) {
return Cache.getInstance().get(CacheKey.scriptInstallInfo(uuid));
}
// 安装脚本
async installScript(param: { script: Script; code: string; upsertBy: InstallSource }) {
param.upsertBy = param.upsertBy || "user";
const { script, upsertBy } = param;
const logger = this.logger.with({
name: script.name,
uuid: script.uuid,
version: script.metadata.version[0],
upsertBy,
});
let update = false;
// 判断是否已经安装
const oldScript = await this.scriptDAO.get(script.uuid);
if (oldScript) {
// 执行更新逻辑
update = true;
script.selfMetadata = oldScript.selfMetadata;
}
return this.scriptDAO
.save(script)
.then(async () => {
await this.scriptCodeDAO.save({
uuid: script.uuid,
code: param.code,
});
logger.info("install success");
// 广播一下
this.mq.publish("installScript", { script, update });
return {};
})
.catch((e: any) => {
logger.error("install error", Logger.E(e));
throw e;
});
}
async deleteScript(uuid: string) {
const logger = this.logger.with({ uuid });
const script = await this.scriptDAO.get(uuid);
if (!script) {
logger.error("script not found");
throw new Error("script not found");
}
return this.scriptDAO
.delete(uuid)
.then(() => {
logger.info("delete success");
this.mq.publish("deleteScript", { uuid });
return {};
})
.catch((e) => {
logger.error("delete error", Logger.E(e));
throw e;
});
}
async enableScript(param: { uuid: string; enable: boolean }) {
const logger = this.logger.with({ uuid: param.uuid, enable: param.enable });
const script = await this.scriptDAO.get(param.uuid);
if (!script) {
logger.error("script not found");
throw new Error("script not found");
}
return this.scriptDAO
.update(param.uuid, { status: param.enable ? SCRIPT_STATUS_ENABLE : SCRIPT_STATUS_DISABLE })
.then(() => {
logger.info("enable success");
this.mq.publish("enableScript", { uuid: param.uuid, enable: param.enable });
return {};
})
.catch((e) => {
logger.error("enable error", Logger.E(e));
throw e;
});
}
async fetchInfo(uuid: string) {
const script = await this.scriptDAO.get(uuid);
if (!script) {
return null;
}
return script;
}
async updateRunStatus(params: { uuid: string; runStatus: SCRIPT_RUN_STATUS; error?: string; nextruntime?: number }) {
this.mq.publish("updateRunStatus", params);
if (
(await this.scriptDAO.update(params.uuid, {
runStatus: params.runStatus,
lastruntime: new Date().getTime(),
error: params.error,
nextruntime: params.nextruntime,
})) === false
) {
return Promise.reject("update error");
}
return Promise.resolve(true);
}
getCode(uuid: string) {
return this.scriptCodeDAO.get(uuid);
}
async buildScriptRunResource(script: Script): Promise<ScriptRunResouce> {
const ret: ScriptRunResouce = <ScriptRunResouce>Object.assign(script);
// 自定义配置
if (ret.selfMetadata) {
ret.metadata = { ...ret.metadata };
Object.keys(ret.selfMetadata).forEach((key) => {
ret.metadata[key] = ret.selfMetadata![key];
});
}
ret.value = await this.valueService.getScriptValue(ret);
ret.resource = await this.resourceService.getScriptResources(ret);
ret.flag = randomString(16);
const code = await this.getCode(ret.uuid);
if (!code) {
throw new Error("code is null");
}
ret.code = compileScriptCode(ret, code.code);
return Promise.resolve(ret);
}
async init() {
this.listenerScriptInstall();
this.group.on("getInstallInfo", this.getInstallInfo);
this.group.on("install", this.installScript.bind(this));
this.group.on("delete", this.deleteScript.bind(this));
this.group.on("enable", this.enableScript.bind(this));
this.group.on("fetchInfo", this.fetchInfo.bind(this));
this.group.on("updateRunStatus", this.updateRunStatus.bind(this));
this.group.on("getCode", this.getCode.bind(this));
this.group.on("getScriptRunResource", this.buildScriptRunResource.bind(this));
}
}