Some checks failed
build / Build (push) Failing after 10s
test / Run tests (push) Failing after 10s
294 lines
9.0 KiB
TypeScript
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));
|
|
}
|
|
}
|