脚本设置

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 Cache from "@App/app/cache";
import CacheKey from "@App/app/cache_key"; import CacheKey from "@App/app/cache_key";
import { Subscribe } from "@App/app/repo/subscribe"; import { Subscribe } from "@App/app/repo/subscribe";
import { Permission } from "@App/app/repo/permission";
export class ServiceWorkerClient extends Client { export class ServiceWorkerClient extends Client {
constructor(msg: MessageSend) { constructor(msg: MessageSend) {
@ -59,6 +60,16 @@ export class ScriptClient extends Client {
return this.do("excludeUrl", { uuid, url, remove }); 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) { requestCheckUpdate(uuid: string) {
return this.do("requestCheckUpdate", uuid); return this.do("requestCheckUpdate", uuid);
} }
@ -158,6 +169,22 @@ export class PermissionClient extends Client {
getPermissionInfo(uuid: string): ReturnType<PermissionVerify["getInfo"]> { getPermissionInfo(uuid: string): ReturnType<PermissionVerify["getInfo"]> {
return this.do("getInfo", uuid); 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 { export class SynchronizeClient extends Client {

View File

@ -96,15 +96,6 @@ export default class PermissionVerify {
reject: (reason: any) => void; reject: (reason: any) => void;
}> = new Queue(); }> = 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(); private permissionDAO: PermissionDAO = new PermissionDAO();
constructor(private group: Group) {} constructor(private group: Group) {}
@ -310,9 +301,45 @@ export default class PermissionVerify {
return Promise.resolve({ script, confirm, likeNum }); 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() { init() {
this.dealConfirmQueue(); this.dealConfirmQueue();
this.group.on("confirm", this.userConfirm.bind(this)); this.group.on("confirm", this.userConfirm.bind(this));
this.group.on("getInfo", this.getInfo.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 }; 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); return this.resourceDAO.delete(url);
} }

View File

@ -384,15 +384,15 @@ export class RuntimeService {
// 加载页面脚本, 会把脚本信息放入缓存中 // 加载页面脚本, 会把脚本信息放入缓存中
// 如果脚本开启, 则注册脚本 // 如果脚本开启, 则注册脚本
async loadPageScript(script: Script) { async loadPageScript(script: Script) {
const matches = script.metadata["match"]; const scriptRes = await this.script.buildScriptRunResource(script);
const matches = scriptRes.metadata["match"];
if (!matches) { if (!matches) {
return; return;
} }
const scriptRes = await this.script.buildScriptRunResource(script);
scriptRes.code = compileInjectScript(scriptRes); scriptRes.code = compileInjectScript(scriptRes);
matches.push(...(script.metadata["include"] || [])); matches.push(...(scriptRes.metadata["include"] || []));
const patternMatches = dealPatternMatches(matches); const patternMatches = dealPatternMatches(matches);
const scriptMatchInfo: ScriptMatchInfo = Object.assign( const scriptMatchInfo: ScriptMatchInfo = Object.assign(
{ matches: patternMatches.result, excludeMatches: [], customizeExcludeMatches: [] }, { matches: patternMatches.result, excludeMatches: [], customizeExcludeMatches: [] },
@ -435,11 +435,11 @@ export class RuntimeService {
// 如果脚本开启, 则注册脚本 // 如果脚本开启, 则注册脚本
if (script.status === SCRIPT_STATUS_ENABLE) { if (script.status === SCRIPT_STATUS_ENABLE) {
if (!script.metadata["noframes"]) { if (!scriptRes.metadata["noframes"]) {
registerScript.allFrames = true; registerScript.allFrames = true;
} }
if (script.metadata["run-at"]) { if (scriptRes.metadata["run-at"]) {
registerScript.runAt = getRunAt(script.metadata["run-at"]); registerScript.runAt = getRunAt(scriptRes.metadata["run-at"]);
} }
if (await Cache.getInstance().get("registryScript:" + script.uuid)) { if (await Cache.getInstance().get("registryScript:" + script.uuid)) {
await chrome.userScripts.update([registerScript]); 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") { async checkUpdate(uuid: string, source: "user" | "system") {
// 检查更新 // 检查更新
const script = await this.scriptDAO.get(uuid); 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("getCode", this.getCode.bind(this));
this.group.on("getScriptRunResource", this.buildScriptRunResource.bind(this)); this.group.on("getScriptRunResource", this.buildScriptRunResource.bind(this));
this.group.on("excludeUrl", this.excludeUrl.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)); this.group.on("requestCheckUpdate", this.requestCheckUpdate.bind(this));
// 定时检查更新, 每10分钟检查一次 // 定时检查更新, 每10分钟检查一次

View File

@ -42,7 +42,6 @@ export class ValueService {
const storageName = getStorageName(script); const storageName = getStorageName(script);
let oldValue; let oldValue;
// 使用事务来保证数据一致性 // 使用事务来保证数据一致性
console.log("setValue", key, value);
await Cache.getInstance().tx("setValue:" + storageName, async () => { await Cache.getInstance().tx("setValue:" + storageName, async () => {
const valueModel = await this.valueDAO.get(storageName); const valueModel = await this.valueDAO.get(storageName);
if (!valueModel) { 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 { Space, Popconfirm, Button, Divider, Typography, Modal, Input } from "@arco-design/web-react";
import Table, { ColumnProps } from "@arco-design/web-react/es/Table"; import Table, { ColumnProps } from "@arco-design/web-react/es/Table";
import { IconDelete } from "@arco-design/web-react/icon"; import { IconDelete } from "@arco-design/web-react/icon";
import { scriptClient } from "@App/pages/store/features/script";
type MatchItem = { type MatchItem = {
// id是为了避免match重复 // id是为了避免match重复
id: number; id: number;
match: string; match: string;
self: boolean; byUser: boolean;
hasMatch: boolean; hasMatch: boolean; // 是否已经匹配
isExclude: boolean; isExclude: boolean; // 是否是排除项
}; };
const Match: React.FC<{ const Match: React.FC<{
@ -40,23 +41,13 @@ const Match: React.FC<{
}); });
const v: MatchItem[] = []; const v: MatchItem[] = [];
matchArr.forEach((value, index) => { matchArr.forEach((value, index) => {
if (matchMap.has(value)) {
v.push({ v.push({
id: index, id: index,
match: value, match: value,
self: false, byUser: !matchMap.has(value),
hasMatch: false, hasMatch: false,
isExclude: false, isExclude: false,
}); });
} else {
v.push({
id: index,
match: value,
self: true,
hasMatch: false,
isExclude: false,
});
}
}); });
setMatch(v); setMatch(v);
@ -68,23 +59,13 @@ const Match: React.FC<{
const e: MatchItem[] = []; const e: MatchItem[] = [];
excludeArr.forEach((value, index) => { excludeArr.forEach((value, index) => {
const hasMatch = matchMap.has(value); const hasMatch = matchMap.has(value);
if (excludeMap.has(value)) {
e.push({ e.push({
id: index, id: index,
match: value, match: value,
self: false, byUser: !excludeMap.has(value),
hasMatch, hasMatch,
isExclude: true, isExclude: true,
}); });
} else {
e.push({
id: index,
match: value,
self: true,
hasMatch,
isExclude: true,
});
}
}); });
setExclude(e); setExclude(e);
}); });
@ -99,8 +80,8 @@ const Match: React.FC<{
}, },
{ {
title: t("user_setting"), title: t("user_setting"),
dataIndex: "self", dataIndex: "byUser",
key: "self", key: "byUser",
width: 100, width: 100,
render(col) { render(col) {
if (col) { if (col) {
@ -119,18 +100,21 @@ const Match: React.FC<{
title={`${t("confirm_delete_exclude")}${item.hasMatch ? ` ${t("after_deleting_match_item")}` : ""}`} title={`${t("confirm_delete_exclude")}${item.hasMatch ? ` ${t("after_deleting_match_item")}` : ""}`}
onOk={() => { onOk={() => {
exclude.splice(exclude.indexOf(item), 1); exclude.splice(exclude.indexOf(item), 1);
scriptCtrl // 删除所有排除
scriptClient
.resetExclude( .resetExclude(
script.id, script.uuid,
exclude.map((m) => m.match) exclude.map((m) => m.match)
) )
.then(() => { .then(() => {
setExclude([...exclude]); setExclude([...exclude]);
// 如果包含在里面再加回match
if (item.hasMatch) { if (item.hasMatch) {
match.push(item); match.push(item);
scriptCtrl // 重置匹配
scriptClient
.resetMatch( .resetMatch(
script.id, script.uuid,
match.map((m) => m.match) match.map((m) => m.match)
) )
.then(() => { .then(() => {
@ -148,22 +132,22 @@ const Match: React.FC<{
return ( return (
<Space> <Space>
<Popconfirm <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={() => { onOk={() => {
match.splice(match.indexOf(item), 1); match.splice(match.indexOf(item), 1);
scriptCtrl scriptClient
.resetMatch( .resetMatch(
script.id, script.uuid,
match.map((m) => m.match) match.map((m) => m.match)
) )
.then(() => { .then(() => {
setMatch([...match]); setMatch([...match]);
// 添加到exclue // 添加到exclue
if (!item.self) { if (!item.byUser) {
exclude.push(item); exclude.push(item);
scriptCtrl scriptClient
.resetExclude( .resetExclude(
script.id, script.uuid,
exclude.map((m) => m.match) exclude.map((m) => m.match)
) )
.then(() => { .then(() => {
@ -192,13 +176,13 @@ const Match: React.FC<{
match.push({ match.push({
id: Math.random(), id: Math.random(),
match: matchValue, match: matchValue,
self: true, byUser: true,
hasMatch: false, hasMatch: false,
isExclude: false, isExclude: false,
}); });
scriptCtrl scriptClient
.resetMatch( .resetMatch(
script.id, script.uuid,
match.map((m) => m.match) match.map((m) => m.match)
) )
.then(() => { .then(() => {
@ -224,13 +208,13 @@ const Match: React.FC<{
exclude.push({ exclude.push({
id: Math.random(), id: Math.random(),
match: excludeValue, match: excludeValue,
self: true, byUser: true,
hasMatch: false, hasMatch: false,
isExclude: true, isExclude: true,
}); });
scriptCtrl scriptClient
.resetExclude( .resetExclude(
script.id, script.uuid,
exclude.map((m) => m.match) exclude.map((m) => m.match)
) )
.then(() => { .then(() => {
@ -263,7 +247,7 @@ const Match: React.FC<{
<Popconfirm <Popconfirm
title={t("confirm_reset")} title={t("confirm_reset")}
onOk={() => { onOk={() => {
scriptCtrl.resetMatch(script.id, undefined).then(() => { scriptClient.resetMatch(script.uuid, undefined).then(() => {
setMatch([]); setMatch([]);
}); });
}} }}
@ -292,7 +276,7 @@ const Match: React.FC<{
<Popconfirm <Popconfirm
title={t("confirm_reset")} title={t("confirm_reset")}
onOk={() => { onOk={() => {
scriptCtrl.resetExclude(script.id, undefined).then(() => { scriptClient.resetExclude(script.uuid, undefined).then(() => {
setExclude([]); setExclude([]);
}); });
}} }}

View File

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