脚本设置

This commit is contained in:
王一之 2025-04-25 15:41:02 +08:00
parent d761c62500
commit 79e8b8869a
8 changed files with 192 additions and 121 deletions

View File

@ -10,6 +10,7 @@ import { v4 as uuidv4 } from "uuid";
import Cache from "@App/app/cache";
import CacheKey from "@App/app/cache_key";
import { Subscribe } from "@App/app/repo/subscribe";
import { Permission } from "@App/app/repo/permission";
export class ServiceWorkerClient extends Client {
constructor(msg: MessageSend) {
@ -59,6 +60,16 @@ export class ScriptClient extends Client {
return this.do("excludeUrl", { uuid, url, remove });
}
// 重置匹配项
resetMatch(uuid: string, match: string[] | undefined) {
return this.do("resetMatch", { uuid, match });
}
// 重置排除项
resetExclude(uuid: string, exclude: string[] | undefined) {
return this.do("resetExclude", { uuid, exclude });
}
requestCheckUpdate(uuid: string) {
return this.do("requestCheckUpdate", uuid);
}
@ -158,6 +169,22 @@ export class PermissionClient extends Client {
getPermissionInfo(uuid: string): ReturnType<PermissionVerify["getInfo"]> {
return this.do("getInfo", uuid);
}
deletePermission(uuid: string, permission: string, permissionValue: string) {
return this.do("deletePermission", { uuid, permission, permissionValue });
}
getScriptPermissions(uuid: string): ReturnType<PermissionVerify["getScriptPermissions"]> {
return this.do("getScriptPermissions", uuid);
}
addPermission(permission: Permission) {
return this.do("addPermission", permission);
}
resetPermission(uuid: string) {
return this.do("resetPermission", uuid);
}
}
export class SynchronizeClient extends Client {

View File

@ -96,15 +96,6 @@ export default class PermissionVerify {
reject: (reason: any) => void;
}> = new Queue();
async removePermissionCache(uuid: string) {
// 先删除缓存
(await Cache.getInstance().list()).forEach((key) => {
if (key.startsWith(`permission:${uuid}:`)) {
Cache.getInstance().del(key);
}
});
}
private permissionDAO: PermissionDAO = new PermissionDAO();
constructor(private group: Group) {}
@ -310,9 +301,45 @@ export default class PermissionVerify {
return Promise.resolve({ script, confirm, likeNum });
}
async deletePermission(data: { uuid: string; permission: string; permissionValue: string }) {
const oldConfirm = await this.permissionDAO.findByKey(data.uuid, data.permission, data.permissionValue);
if (!oldConfirm) {
throw new Error("permission not found");
} else {
await this.permissionDAO.delete(this.permissionDAO.key(oldConfirm));
// 删除缓存
Cache.getInstance().del(CacheKey.permissionConfirm(data.uuid, oldConfirm));
}
}
getScriptPermissions(uuid: string) {
// 获取脚本的所有权限
return this.permissionDAO.find((key, item) => item.uuid === uuid);
}
// 添加权限
async addPermission(permission: Permission) {
await this.permissionDAO.save(permission);
Cache.getInstance().del(CacheKey.permissionConfirm(permission.uuid, permission));
}
// 重置权限
async resetPermission(uuid: string) {
// 删除所有权限
const permissions = await this.permissionDAO.find((key, item) => item.uuid === uuid);
permissions.forEach((item) => {
this.permissionDAO.delete(this.permissionDAO.key(item));
Cache.getInstance().del(CacheKey.permissionConfirm(uuid, item));
});
}
init() {
this.dealConfirmQueue();
this.group.on("confirm", this.userConfirm.bind(this));
this.group.on("getInfo", this.getInfo.bind(this));
this.group.on("deletePermission", this.deletePermission.bind(this));
this.group.on("getScriptPermissions", this.getScriptPermissions.bind(this));
this.group.on("addPermission", this.getInfo.bind(this));
this.group.on("resetPermission", this.resetPermission.bind(this));
}
}

View File

@ -305,7 +305,15 @@ export class ResourceService {
return { url: urls[0], hash };
}
deleteResource(url: string) {
async deleteResource(url: string) {
// 删除缓存
const res = await this.resourceDAO.get(url);
if (!res) {
throw new Error("resource not found");
}
Object.keys(res.link).forEach((key) => {
this.cache.delete(key);
});
return this.resourceDAO.delete(url);
}

View File

@ -384,15 +384,15 @@ export class RuntimeService {
// 加载页面脚本, 会把脚本信息放入缓存中
// 如果脚本开启, 则注册脚本
async loadPageScript(script: Script) {
const matches = script.metadata["match"];
const scriptRes = await this.script.buildScriptRunResource(script);
const matches = scriptRes.metadata["match"];
if (!matches) {
return;
}
const scriptRes = await this.script.buildScriptRunResource(script);
scriptRes.code = compileInjectScript(scriptRes);
matches.push(...(script.metadata["include"] || []));
matches.push(...(scriptRes.metadata["include"] || []));
const patternMatches = dealPatternMatches(matches);
const scriptMatchInfo: ScriptMatchInfo = Object.assign(
{ matches: patternMatches.result, excludeMatches: [], customizeExcludeMatches: [] },
@ -435,11 +435,11 @@ export class RuntimeService {
// 如果脚本开启, 则注册脚本
if (script.status === SCRIPT_STATUS_ENABLE) {
if (!script.metadata["noframes"]) {
if (!scriptRes.metadata["noframes"]) {
registerScript.allFrames = true;
}
if (script.metadata["run-at"]) {
registerScript.runAt = getRunAt(script.metadata["run-at"]);
if (scriptRes.metadata["run-at"]) {
registerScript.runAt = getRunAt(scriptRes.metadata["run-at"]);
}
if (await Cache.getInstance().get("registryScript:" + script.uuid)) {
await chrome.userScripts.update([registerScript]);

View File

@ -320,6 +320,54 @@ export class ScriptService {
});
}
async resetExclude({ uuid, exclude }: { uuid: string; exclude: string[] | undefined }) {
const script = await this.scriptDAO.get(uuid);
if (!script) {
throw new Error("script not found");
}
script.selfMetadata = script.selfMetadata || {};
if (exclude) {
script.selfMetadata.exclude = exclude;
} else {
delete script.selfMetadata.exclude;
}
return this.scriptDAO
.update(uuid, script)
.then(() => {
// 广播一下
this.mq.publish("installScript", { script, update: true });
return true;
})
.catch((e) => {
this.logger.error("reset exclude error", Logger.E(e));
throw e;
});
}
async resetMatch({ uuid, match }: { uuid: string; match: string[] | undefined }) {
const script = await this.scriptDAO.get(uuid);
if (!script) {
throw new Error("script not found");
}
script.selfMetadata = script.selfMetadata || {};
if (match) {
script.selfMetadata.match = match;
} else {
delete script.selfMetadata.match;
}
return this.scriptDAO
.update(uuid, script)
.then(() => {
// 广播一下
this.mq.publish("installScript", { script, update: true });
return true;
})
.catch((e) => {
this.logger.error("reset match error", Logger.E(e));
throw e;
});
}
async checkUpdate(uuid: string, source: "user" | "system") {
// 检查更新
const script = await this.scriptDAO.get(uuid);
@ -443,6 +491,8 @@ export class ScriptService {
this.group.on("getCode", this.getCode.bind(this));
this.group.on("getScriptRunResource", this.buildScriptRunResource.bind(this));
this.group.on("excludeUrl", this.excludeUrl.bind(this));
this.group.on("resetMatch", this.resetMatch.bind(this));
this.group.on("resetExclude", this.resetExclude.bind(this));
this.group.on("requestCheckUpdate", this.requestCheckUpdate.bind(this));
// 定时检查更新, 每10分钟检查一次

View File

@ -42,7 +42,6 @@ export class ValueService {
const storageName = getStorageName(script);
let oldValue;
// 使用事务来保证数据一致性
console.log("setValue", key, value);
await Cache.getInstance().tx("setValue:" + storageName, async () => {
const valueModel = await this.valueDAO.get(storageName);
if (!valueModel) {

View File

@ -4,14 +4,15 @@ import { Script, ScriptDAO } from "@App/app/repo/scripts";
import { Space, Popconfirm, Button, Divider, Typography, Modal, Input } from "@arco-design/web-react";
import Table, { ColumnProps } from "@arco-design/web-react/es/Table";
import { IconDelete } from "@arco-design/web-react/icon";
import { scriptClient } from "@App/pages/store/features/script";
type MatchItem = {
// id是为了避免match重复
id: number;
match: string;
self: boolean;
hasMatch: boolean;
isExclude: boolean;
byUser: boolean;
hasMatch: boolean; // 是否已经匹配
isExclude: boolean; // 是否是排除项
};
const Match: React.FC<{
@ -40,23 +41,13 @@ const Match: React.FC<{
});
const v: MatchItem[] = [];
matchArr.forEach((value, index) => {
if (matchMap.has(value)) {
v.push({
id: index,
match: value,
self: false,
hasMatch: false,
isExclude: false,
});
} else {
v.push({
id: index,
match: value,
self: true,
hasMatch: false,
isExclude: false,
});
}
v.push({
id: index,
match: value,
byUser: !matchMap.has(value),
hasMatch: false,
isExclude: false,
});
});
setMatch(v);
@ -68,23 +59,13 @@ const Match: React.FC<{
const e: MatchItem[] = [];
excludeArr.forEach((value, index) => {
const hasMatch = matchMap.has(value);
if (excludeMap.has(value)) {
e.push({
id: index,
match: value,
self: false,
hasMatch,
isExclude: true,
});
} else {
e.push({
id: index,
match: value,
self: true,
hasMatch,
isExclude: true,
});
}
e.push({
id: index,
match: value,
byUser: !excludeMap.has(value),
hasMatch,
isExclude: true,
});
});
setExclude(e);
});
@ -99,8 +80,8 @@ const Match: React.FC<{
},
{
title: t("user_setting"),
dataIndex: "self",
key: "self",
dataIndex: "byUser",
key: "byUser",
width: 100,
render(col) {
if (col) {
@ -119,18 +100,21 @@ const Match: React.FC<{
title={`${t("confirm_delete_exclude")}${item.hasMatch ? ` ${t("after_deleting_match_item")}` : ""}`}
onOk={() => {
exclude.splice(exclude.indexOf(item), 1);
scriptCtrl
// 删除所有排除
scriptClient
.resetExclude(
script.id,
script.uuid,
exclude.map((m) => m.match)
)
.then(() => {
setExclude([...exclude]);
// 如果包含在里面再加回match
if (item.hasMatch) {
match.push(item);
scriptCtrl
// 重置匹配
scriptClient
.resetMatch(
script.id,
script.uuid,
match.map((m) => m.match)
)
.then(() => {
@ -148,22 +132,22 @@ const Match: React.FC<{
return (
<Space>
<Popconfirm
title={`${t("confirm_delete_match")}${item.self ? "" : ` ${t("after_deleting_exclude_item")}`}`}
title={`${t("confirm_delete_match")}${item.byUser ? "" : ` ${t("after_deleting_exclude_item")}`}`}
onOk={() => {
match.splice(match.indexOf(item), 1);
scriptCtrl
scriptClient
.resetMatch(
script.id,
script.uuid,
match.map((m) => m.match)
)
.then(() => {
setMatch([...match]);
// 添加到exclue
if (!item.self) {
if (!item.byUser) {
exclude.push(item);
scriptCtrl
scriptClient
.resetExclude(
script.id,
script.uuid,
exclude.map((m) => m.match)
)
.then(() => {
@ -192,13 +176,13 @@ const Match: React.FC<{
match.push({
id: Math.random(),
match: matchValue,
self: true,
byUser: true,
hasMatch: false,
isExclude: false,
});
scriptCtrl
scriptClient
.resetMatch(
script.id,
script.uuid,
match.map((m) => m.match)
)
.then(() => {
@ -224,13 +208,13 @@ const Match: React.FC<{
exclude.push({
id: Math.random(),
match: excludeValue,
self: true,
byUser: true,
hasMatch: false,
isExclude: true,
});
scriptCtrl
scriptClient
.resetExclude(
script.id,
script.uuid,
exclude.map((m) => m.match)
)
.then(() => {
@ -263,7 +247,7 @@ const Match: React.FC<{
<Popconfirm
title={t("confirm_reset")}
onOk={() => {
scriptCtrl.resetMatch(script.id, undefined).then(() => {
scriptClient.resetMatch(script.uuid, undefined).then(() => {
setMatch([]);
});
}}
@ -292,7 +276,7 @@ const Match: React.FC<{
<Popconfirm
title={t("confirm_reset")}
onOk={() => {
scriptCtrl.resetExclude(script.id, undefined).then(() => {
scriptClient.resetExclude(script.uuid, undefined).then(() => {
setExclude([]);
});
}}

View File

@ -2,19 +2,10 @@ import React, { useEffect, useState } from "react";
import { Permission } from "@App/app/repo/permission";
import { Script } from "@App/app/repo/scripts";
import { useTranslation } from "react-i18next";
import {
Space,
Popconfirm,
Message,
Button,
Checkbox,
Input,
Modal,
Select,
Typography,
} from "@arco-design/web-react";
import { Space, Popconfirm, Message, Button, Checkbox, Input, Modal, Select, Typography } from "@arco-design/web-react";
import Table, { ColumnProps } from "@arco-design/web-react/es/Table";
import { IconDelete } from "@arco-design/web-react/icon";
import { permissionClient } from "@App/pages/store/features/script";
const PermissionManager: React.FC<{
script: Script;
@ -56,14 +47,15 @@ const PermissionManager: React.FC<{
<Popconfirm
title={t("confirm_delete_permission")}
onOk={() => {
permissionCtrl
.deletePermission(script!.id, {
permission: item.permission,
permissionValue: item.permissionValue,
})
permissionClient
.deletePermission(script.uuid, item.permission, item.permissionValue)
.then(() => {
Message.success(t("delete_success")!);
setPermission(permission.filter((i) => i.id !== item.id));
setPermission(
permission.filter(
(i) => !(i.permission == item.permission && i.permissionValue == item.permissionValue)
)
);
})
.catch(() => {
Message.error(t("delete_failed")!);
@ -80,7 +72,7 @@ const PermissionManager: React.FC<{
useEffect(() => {
if (script) {
permissionCtrl.getPermissions(script.id).then((list) => {
permissionClient.getScriptPermissions(script.uuid).then((list) => {
setPermission(list);
});
}
@ -95,20 +87,17 @@ const PermissionManager: React.FC<{
onOk={() => {
if (permissionValue) {
permission.push({
id: 0,
uuid: script.id,
uuid: script.uuid,
permission: permissionValue.permission,
permissionValue: permissionValue.permissionValue,
allow: permissionValue.allow,
createtime: new Date().getTime(),
updatetime: 0,
});
permissionCtrl
.addPermission(script.id, permissionValue)
.then(() => {
setPermission([...permission]);
setPermissionVisible(false);
});
permissionClient.addPermission(permissionValue).then(() => {
setPermission([...permission]);
setPermissionVisible(false);
});
}
}}
>
@ -116,27 +105,22 @@ const PermissionManager: React.FC<{
<Select
value={permissionValue?.permission}
onChange={(e) => {
permissionValue &&
setPermissionValue({ ...permissionValue, permission: e });
permissionValue && setPermissionValue({ ...permissionValue, permission: e });
}}
>
<Select.Option value="cors">{t("permission_cors")}</Select.Option>
<Select.Option value="cookie">
{t("permission_cookie")}
</Select.Option>
<Select.Option value="cookie">{t("permission_cookie")}</Select.Option>
</Select>
<Input
value={permissionValue?.permissionValue}
onChange={(e) => {
permissionValue &&
setPermissionValue({ ...permissionValue, permissionValue: e });
permissionValue && setPermissionValue({ ...permissionValue, permissionValue: e });
}}
/>
<Checkbox
checked={permissionValue?.allow}
onChange={(e) => {
permissionValue &&
setPermissionValue({ ...permissionValue, allow: e });
permissionValue && setPermissionValue({ ...permissionValue, allow: e });
}}
>
{t("allow")}
@ -144,17 +128,14 @@ const PermissionManager: React.FC<{
</Space>
</Modal>
<div className="flex flex-row justify-between pb-2">
<Typography.Title heading={6}>
{t("permission_management")}
</Typography.Title>
<Typography.Title heading={6}>{t("permission_management")}</Typography.Title>
<Space>
<Button
type="primary"
size="small"
onClick={() => {
setPermissionValue({
id: 0,
uuid: script.id,
uuid: script.uuid,
permission: "cors",
permissionValue: "",
allow: true,
@ -169,7 +150,7 @@ const PermissionManager: React.FC<{
<Popconfirm
title={t("confirm_reset")}
onOk={() => {
permissionCtrl.resetPermission(script.id).then(() => {
permissionClient.resetPermission(script.uuid).then(() => {
setPermission([]);
});
}}
@ -180,12 +161,7 @@ const PermissionManager: React.FC<{
</Popconfirm>
</Space>
</div>
<Table
columns={columns}
data={permission}
rowKey="id"
pagination={false}
/>
<Table columns={columns} data={permission} rowKey="id" pagination={false} />
</>
);
};