Compare commits
15 Commits
d9fdded7fb
...
main
Author | SHA1 | Date | |
---|---|---|---|
ec28795dbb | |||
44041f4735 | |||
ffabe268b1 | |||
ddd3219bae | |||
14baa176d9 | |||
3c1e30182f | |||
1aaf1bbd4a | |||
8a216933ca | |||
51fe2a89e1 | |||
a26f1c5014 | |||
e1a890a400 | |||
79e8b8869a | |||
d761c62500 | |||
d200809fee | |||
67ba515b2c |
34
example/gm_value/gm_value.js
Normal file
34
example/gm_value/gm_value.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name gm value
|
||||||
|
// @namespace https://bbs.tampermonkey.net.cn/
|
||||||
|
// @version 0.1.0
|
||||||
|
// @description 可以持久化存储数据, 并且可以监听数据变化
|
||||||
|
// @author You
|
||||||
|
// @match https://bbs.tampermonkey.net.cn/
|
||||||
|
// @run-at document-start
|
||||||
|
// @grant GM_setValue
|
||||||
|
// @grant GM_getValue
|
||||||
|
// @grant GM_addValueChangeListener
|
||||||
|
// @grant GM_listValues
|
||||||
|
// @grant GM_deleteValue
|
||||||
|
// @grant GM_cookie
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
GM_addValueChangeListener("test_set", function (name, oldval, newval, remote) {
|
||||||
|
console.log("test_set change", name, oldval, newval, remote);
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
console.log(GM_getValue("test_set"));
|
||||||
|
console.log(GM_listValues());
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
GM_deleteValue("test_set");
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
GM_setValue("test_set", new Date().getTime());
|
||||||
|
|
||||||
|
console.log(GM_getValue("test_set2"));
|
||||||
|
|
||||||
|
GM_setValue("test_set2", new Date().getTime());
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "scriptcat",
|
"name": "scriptcat",
|
||||||
"version": "0.17.0-alpha.2",
|
"version": "0.17.0-alpha.4",
|
||||||
"description": "脚本猫,一个可以执行用户脚本的浏览器扩展,万物皆可脚本化,让你的浏览器可以做更多的事情!",
|
"description": "脚本猫,一个可以执行用户脚本的浏览器扩展,万物皆可脚本化,让你的浏览器可以做更多的事情!",
|
||||||
"author": "CodFrm",
|
"author": "CodFrm",
|
||||||
"license": "GPLv3",
|
"license": "GPLv3",
|
||||||
|
@ -208,6 +208,16 @@ export default defineConfig({
|
|||||||
minimizerOptions: { targets },
|
minimizerOptions: { targets },
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
splitChunks: {
|
||||||
|
chunks: (chunk) => {
|
||||||
|
// 排除这些文件,不进行分离
|
||||||
|
return !["editor.worker", "ts.worker", "linter.worker", "service_worker", "content", "inject"].includes(
|
||||||
|
chunk.name || ""
|
||||||
|
);
|
||||||
|
},
|
||||||
|
minSize: 307200,
|
||||||
|
maxSize: 4194304,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
experiments: {
|
experiments: {
|
||||||
css: true,
|
css: true,
|
||||||
|
@ -5,13 +5,16 @@ import { MessageSend } from "@Packages/message/server";
|
|||||||
export default class MessageWriter implements Writer {
|
export default class MessageWriter implements Writer {
|
||||||
send: MessageSend;
|
send: MessageSend;
|
||||||
|
|
||||||
constructor(connect: MessageSend) {
|
constructor(
|
||||||
|
connect: MessageSend,
|
||||||
|
private action: string = "logger"
|
||||||
|
) {
|
||||||
this.send = connect;
|
this.send = connect;
|
||||||
}
|
}
|
||||||
|
|
||||||
write(level: LogLevel, message: string, label: LogLabel): void {
|
write(level: LogLevel, message: string, label: LogLabel): void {
|
||||||
this.send.sendMessage({
|
this.send.sendMessage({
|
||||||
action: "logger",
|
action: this.action,
|
||||||
data: {
|
data: {
|
||||||
id: 0,
|
id: 0,
|
||||||
level,
|
level,
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
|
import { getStorageName } from "@App/pkg/utils/utils";
|
||||||
import { db } from "./repo/dao";
|
import { db } from "./repo/dao";
|
||||||
import { Script, ScriptAndCode, ScriptCodeDAO, ScriptDAO } from "./repo/scripts";
|
import { Script, ScriptAndCode, ScriptCodeDAO, ScriptDAO } from "./repo/scripts";
|
||||||
import { Subscribe, SubscribeDAO } from "./repo/subscribe";
|
import { Subscribe, SubscribeDAO } from "./repo/subscribe";
|
||||||
|
import { Value, ValueDAO } from "./repo/value";
|
||||||
|
import { Permission, PermissionDAO } from "./repo/permission";
|
||||||
|
|
||||||
// 0.10.0重构,重命名字段,统一使用小峰驼
|
// 0.10.0重构,重命名字段,统一使用小峰驼
|
||||||
function renameField() {
|
function renameField() {
|
||||||
@ -34,98 +37,194 @@ function renameField() {
|
|||||||
export: "++id,&scriptId",
|
export: "++id,&scriptId",
|
||||||
});
|
});
|
||||||
// 将脚本数据迁移到chrome.storage
|
// 将脚本数据迁移到chrome.storage
|
||||||
db.version(18).upgrade(async (tx) => {
|
db.version(18).upgrade(() => {
|
||||||
// 迁移脚本
|
// 默认使用的事务,这里加个延时,用db.open()打开数据库后,再执行
|
||||||
const scripts = await tx.table("scripts").toArray();
|
setTimeout(async () => {
|
||||||
const scriptDAO = new ScriptDAO();
|
try {
|
||||||
const scriptCodeDAO = new ScriptCodeDAO();
|
// 迁移脚本
|
||||||
await Promise.all(
|
const scripts = await db.table("scripts").toArray();
|
||||||
scripts.map((script: ScriptAndCode) => {
|
const scriptDAO = new ScriptDAO();
|
||||||
const {
|
const scriptCodeDAO = new ScriptCodeDAO();
|
||||||
uuid,
|
console.log("开始迁移脚本数据", scripts.length);
|
||||||
name,
|
await Promise.all(
|
||||||
namespace,
|
scripts.map(async (script: ScriptAndCode) => {
|
||||||
author,
|
const {
|
||||||
originDomain,
|
uuid,
|
||||||
subscribeUrl,
|
name,
|
||||||
type,
|
namespace,
|
||||||
sort,
|
author,
|
||||||
status,
|
originDomain,
|
||||||
runStatus,
|
subscribeUrl,
|
||||||
metadata,
|
type,
|
||||||
createtime,
|
sort,
|
||||||
checktime,
|
status,
|
||||||
code,
|
runStatus,
|
||||||
} = script;
|
metadata,
|
||||||
return scriptDAO
|
createtime,
|
||||||
.save({
|
checktime,
|
||||||
uuid,
|
|
||||||
name,
|
|
||||||
namespace,
|
|
||||||
author,
|
|
||||||
originDomain,
|
|
||||||
subscribeUrl,
|
|
||||||
type,
|
|
||||||
sort,
|
|
||||||
status,
|
|
||||||
runStatus,
|
|
||||||
metadata,
|
|
||||||
createtime,
|
|
||||||
checktime,
|
|
||||||
})
|
|
||||||
.then((s) =>
|
|
||||||
scriptCodeDAO.save({
|
|
||||||
uuid: s.uuid,
|
|
||||||
code,
|
code,
|
||||||
|
checkUpdateUrl,
|
||||||
|
downloadUrl,
|
||||||
|
selfMetadata,
|
||||||
|
config,
|
||||||
|
error,
|
||||||
|
updatetime,
|
||||||
|
lastruntime,
|
||||||
|
nextruntime,
|
||||||
|
} = script;
|
||||||
|
const s = await scriptDAO.save({
|
||||||
|
uuid,
|
||||||
|
name,
|
||||||
|
namespace,
|
||||||
|
author,
|
||||||
|
originDomain,
|
||||||
|
origin,
|
||||||
|
checkUpdateUrl,
|
||||||
|
downloadUrl,
|
||||||
|
metadata,
|
||||||
|
selfMetadata,
|
||||||
|
subscribeUrl,
|
||||||
|
config,
|
||||||
|
type,
|
||||||
|
status,
|
||||||
|
sort,
|
||||||
|
runStatus,
|
||||||
|
error,
|
||||||
|
createtime,
|
||||||
|
updatetime,
|
||||||
|
checktime,
|
||||||
|
lastruntime,
|
||||||
|
nextruntime,
|
||||||
|
});
|
||||||
|
return scriptCodeDAO
|
||||||
|
.save({
|
||||||
|
uuid: s.uuid,
|
||||||
|
code,
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log("脚本代码迁移失败", e);
|
||||||
|
return Promise.reject(e);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// 迁移订阅
|
||||||
|
const subscribe = await db.table("subscribe").toArray();
|
||||||
|
const subscribeDAO = new SubscribeDAO();
|
||||||
|
if (subscribe.length) {
|
||||||
|
await Promise.all(
|
||||||
|
subscribe.map((s: Subscribe) => {
|
||||||
|
const { url, name, code, author, scripts, metadata, status, createtime, updatetime, checktime } = s;
|
||||||
|
return subscribeDAO.save({
|
||||||
|
url,
|
||||||
|
name,
|
||||||
|
code,
|
||||||
|
author,
|
||||||
|
scripts,
|
||||||
|
metadata,
|
||||||
|
status,
|
||||||
|
createtime,
|
||||||
|
updatetime,
|
||||||
|
checktime,
|
||||||
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
})
|
}
|
||||||
);
|
console.log("订阅数据迁移完成", subscribe.length);
|
||||||
// 迁移订阅
|
// 迁移value
|
||||||
const subscribe = await tx.table("subscribe").toArray();
|
interface MV2Value {
|
||||||
const subscribeDAO = new SubscribeDAO();
|
id: number;
|
||||||
await Promise.all(
|
scriptId: number;
|
||||||
subscribe.map((s: Subscribe) => {
|
storageName?: string;
|
||||||
const { url, name, code, author, scripts, metadata, status, createtime, updatetime, checktime } = s;
|
key: string;
|
||||||
return subscribeDAO.save({
|
value: any;
|
||||||
url,
|
createtime: number;
|
||||||
name,
|
updatetime: number;
|
||||||
code,
|
}
|
||||||
author,
|
const values = await db.table("value").toArray();
|
||||||
scripts,
|
const valueDAO = new ValueDAO();
|
||||||
metadata,
|
const valueMap = new Map<string, Value>();
|
||||||
status,
|
await Promise.all(
|
||||||
createtime,
|
values.map((v: MV2Value) => {
|
||||||
updatetime,
|
const { scriptId, storageName, key, value, createtime } = v;
|
||||||
checktime,
|
return db
|
||||||
});
|
.table("scripts")
|
||||||
})
|
.where("id")
|
||||||
);
|
.equals(scriptId)
|
||||||
// 迁移value
|
.first((script: Script) => {
|
||||||
interface MV2Value {
|
if (script) {
|
||||||
id: number;
|
let data: { [key: string]: any } = {};
|
||||||
scriptId: number;
|
if (!valueMap.has(script.uuid)) {
|
||||||
storageName?: string;
|
valueMap.set(script.uuid, {
|
||||||
key: string;
|
uuid: script.uuid,
|
||||||
value: any;
|
storageName: getStorageName(script),
|
||||||
createtime: number;
|
data: data,
|
||||||
updatetime: number;
|
createtime,
|
||||||
}
|
updatetime: 0,
|
||||||
const values = await tx.table("value").toArray();
|
});
|
||||||
const valueDAO = new ScriptCodeDAO();
|
} else {
|
||||||
await Promise.all(
|
data = valueMap.get(script.uuid)!.data;
|
||||||
values.map((v) => {
|
}
|
||||||
const { scriptId, storageName, key, value, createtime } = v;
|
data[key] = value;
|
||||||
return valueDAO.save({
|
}
|
||||||
scriptId,
|
});
|
||||||
storageName,
|
})
|
||||||
key,
|
);
|
||||||
value,
|
// 保存到数据库
|
||||||
createtime,
|
await Promise.all(
|
||||||
});
|
Array.from(valueMap.keys()).map((uuid) => {
|
||||||
})
|
const { storageName, data, createtime } = valueMap.get(uuid)!;
|
||||||
);
|
return valueDAO.save(storageName!, {
|
||||||
// 迁移permission
|
uuid,
|
||||||
|
storageName,
|
||||||
|
data,
|
||||||
|
createtime,
|
||||||
|
updatetime: 0,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
console.log("脚本value数据迁移完成", values.length);
|
||||||
|
// 迁移permission
|
||||||
|
const permissions = await db.table("permission").toArray();
|
||||||
|
const permissionDAO = new PermissionDAO();
|
||||||
|
await Promise.all(
|
||||||
|
permissions.map((p: Permission & { scriptId: number }) => {
|
||||||
|
const { scriptId, permission, permissionValue, createtime, updatetime, allow } = p;
|
||||||
|
return db
|
||||||
|
.table("scripts")
|
||||||
|
.where("id")
|
||||||
|
.equals(scriptId)
|
||||||
|
.first((script: Script) => {
|
||||||
|
if (script) {
|
||||||
|
return permissionDAO.save({
|
||||||
|
uuid: script.uuid,
|
||||||
|
permission,
|
||||||
|
permissionValue,
|
||||||
|
createtime,
|
||||||
|
updatetime,
|
||||||
|
allow,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
console.log("脚本permission数据迁移完成", permissions.length);
|
||||||
|
// 打开页面,告知数据储存+升级至了mv3,重启一次扩展
|
||||||
|
setTimeout(async () => {
|
||||||
|
const scripts = await scriptDAO.all();
|
||||||
|
console.log("脚本数据迁移完成", scripts.length);
|
||||||
|
if (scripts.length > 0) {
|
||||||
|
chrome.tabs.create({
|
||||||
|
url: "https://docs.scriptcat.org/docs/change/v0.17/",
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
chrome.runtime.reload();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("脚本数据迁移失败", e);
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
});
|
});
|
||||||
return db.open();
|
return db.open();
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import LoggerCore from "@App/app/logger/core";
|
||||||
|
import Logger from "@App/app/logger/logger";
|
||||||
import { ScriptRunResouce } from "@App/app/repo/scripts";
|
import { ScriptRunResouce } from "@App/app/repo/scripts";
|
||||||
import { Client, sendMessage } from "@Packages/message/client";
|
import { Client, sendMessage } from "@Packages/message/client";
|
||||||
import { CustomEventMessage } from "@Packages/message/custom_event_message";
|
import { CustomEventMessage } from "@Packages/message/custom_event_message";
|
||||||
@ -21,6 +23,7 @@ export default class ContentRuntime {
|
|||||||
// 转发给inject
|
// 转发给inject
|
||||||
return sendMessage(this.msg, "inject/runtime/valueUpdate", data);
|
return sendMessage(this.msg, "inject/runtime/valueUpdate", data);
|
||||||
});
|
});
|
||||||
|
forwardMessage("serviceWorker", "script/isInstalled", this.server, this.extSend);
|
||||||
forwardMessage(
|
forwardMessage(
|
||||||
"serviceWorker",
|
"serviceWorker",
|
||||||
"runtime/gmApi",
|
"runtime/gmApi",
|
||||||
@ -83,7 +86,17 @@ export default class ContentRuntime {
|
|||||||
case "GM_log":
|
case "GM_log":
|
||||||
// 拦截GM_log,打印到控制台
|
// 拦截GM_log,打印到控制台
|
||||||
// 由于某些页面会处理掉console.log,所以丢到这里来打印
|
// 由于某些页面会处理掉console.log,所以丢到这里来打印
|
||||||
console.log(...data.params);
|
switch (data.params.length) {
|
||||||
|
case 1:
|
||||||
|
console.log(data.params[0]);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
console.log("[" + data.params[1] + "]", data.params[0]);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
console.log("[" + data.params[1] + "]", data.params[0], data.params[2]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -159,10 +159,11 @@ export default class GMApi {
|
|||||||
}
|
}
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
delete this.scriptRes.value[key];
|
delete this.scriptRes.value[key];
|
||||||
|
return this.sendMessage("GM_setValue", [key]);
|
||||||
} else {
|
} else {
|
||||||
this.scriptRes.value[key] = value;
|
this.scriptRes.value[key] = value;
|
||||||
|
return this.sendMessage("GM_setValue", [key, value]);
|
||||||
}
|
}
|
||||||
return this.sendMessage("GM_setValue", [key, value]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GMContext.API({ depend: ["GM_setValue"] })
|
@GMContext.API({ depend: ["GM_setValue"] })
|
||||||
@ -391,7 +392,7 @@ export default class GMApi {
|
|||||||
anonymous: details.anonymous,
|
anonymous: details.anonymous,
|
||||||
user: details.user,
|
user: details.user,
|
||||||
password: details.password,
|
password: details.password,
|
||||||
maxRedirects: details.maxRedirects,
|
redirect: details.redirect,
|
||||||
};
|
};
|
||||||
if (!param.headers) {
|
if (!param.headers) {
|
||||||
param.headers = {};
|
param.headers = {};
|
||||||
|
@ -4,6 +4,8 @@ import ExecScript, { ValueUpdateData } from "./exec_script";
|
|||||||
import { addStyle, ScriptFunc } from "./utils";
|
import { addStyle, ScriptFunc } from "./utils";
|
||||||
import { getStorageName } from "@App/pkg/utils/utils";
|
import { getStorageName } from "@App/pkg/utils/utils";
|
||||||
import { EmitEventRequest } from "../service_worker/runtime";
|
import { EmitEventRequest } from "../service_worker/runtime";
|
||||||
|
import { ExternalWhitelist } from "@App/app/const";
|
||||||
|
import { sendMessage } from "@Packages/message/client";
|
||||||
|
|
||||||
export class InjectRuntime {
|
export class InjectRuntime {
|
||||||
execList: ExecScript[] = [];
|
execList: ExecScript[] = [];
|
||||||
@ -44,6 +46,40 @@ export class InjectRuntime {
|
|||||||
val.valueUpdate(data);
|
val.valueUpdate(data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// 注入允许外部调用
|
||||||
|
this.externalMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
externalMessage() {
|
||||||
|
// 对外接口白名单
|
||||||
|
let msg = this.msg;
|
||||||
|
for (let i = 0; i < ExternalWhitelist.length; i += 1) {
|
||||||
|
if (window.location.host.endsWith(ExternalWhitelist[i])) {
|
||||||
|
// 注入
|
||||||
|
(<{ external: any }>(<unknown>window)).external = window.external || {};
|
||||||
|
(<
|
||||||
|
{
|
||||||
|
external: {
|
||||||
|
Scriptcat: {
|
||||||
|
isInstalled: (name: string, namespace: string, callback: any) => void;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>(<unknown>window)).external.Scriptcat = {
|
||||||
|
async isInstalled(name: string, namespace: string, callback: any) {
|
||||||
|
const resp = await sendMessage(msg, "content/script/isInstalled", {
|
||||||
|
name,
|
||||||
|
namespace,
|
||||||
|
});
|
||||||
|
callback(resp);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
(<{ external: { Tampermonkey: any } }>(<unknown>window)).external.Tampermonkey = (<
|
||||||
|
{ external: { Scriptcat: any } }
|
||||||
|
>(<unknown>window)).external.Scriptcat;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
execScript(script: ScriptRunResouce, scriptFunc: ScriptFunc) {
|
execScript(script: ScriptRunResouce, scriptFunc: ScriptFunc) {
|
||||||
|
@ -20,6 +20,10 @@ export class OffscreenManager {
|
|||||||
|
|
||||||
private serviceWorker = new ServiceWorkerClient(this.extensionMessage);
|
private serviceWorker = new ServiceWorkerClient(this.extensionMessage);
|
||||||
|
|
||||||
|
constructor(private extensionMessage:MessageSend) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
logger(data: Logger) {
|
logger(data: Logger) {
|
||||||
const dao = new LoggerDAO();
|
const dao = new LoggerDAO();
|
||||||
dao.save(data);
|
dao.save(data);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
@ -72,6 +83,10 @@ export class ResourceClient extends Client {
|
|||||||
getScriptResources(script: Script): Promise<{ [key: string]: Resource }> {
|
getScriptResources(script: Script): Promise<{ [key: string]: Resource }> {
|
||||||
return this.do("getScriptResources", script);
|
return this.do("getScriptResources", script);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteResource(url: string) {
|
||||||
|
return this.do("deleteResource", url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ValueClient extends Client {
|
export class ValueClient extends Client {
|
||||||
@ -79,7 +94,7 @@ export class ValueClient extends Client {
|
|||||||
super(msg, "serviceWorker/value");
|
super(msg, "serviceWorker/value");
|
||||||
}
|
}
|
||||||
|
|
||||||
getScriptValue(script: Script) {
|
getScriptValue(script: Script): Promise<{ [key: string]: any }> {
|
||||||
return this.do("getScriptValue", script);
|
return this.do("getScriptValue", script);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,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 {
|
||||||
|
@ -242,7 +242,7 @@ export default class GMApi {
|
|||||||
|
|
||||||
@PermissionVerify.API()
|
@PermissionVerify.API()
|
||||||
async GM_setValue(request: Request, sender: GetSender) {
|
async GM_setValue(request: Request, sender: GetSender) {
|
||||||
if (!request.params || request.params.length !== 2) {
|
if (!request.params || request.params.length < 1) {
|
||||||
throw new Error("param is failed");
|
throw new Error("param is failed");
|
||||||
}
|
}
|
||||||
const [key, value] = request.params;
|
const [key, value] = request.params;
|
||||||
|
@ -9,6 +9,8 @@ import { PopupService } from "./popup";
|
|||||||
import { SystemConfig } from "@App/pkg/config/config";
|
import { SystemConfig } from "@App/pkg/config/config";
|
||||||
import { SynchronizeService } from "./synchronize";
|
import { SynchronizeService } from "./synchronize";
|
||||||
import { SubscribeService } from "./subscribe";
|
import { SubscribeService } from "./subscribe";
|
||||||
|
import { ExtServer, ExtVersion } from "@App/app/const";
|
||||||
|
import { systemConfig } from "@App/pages/store/global";
|
||||||
|
|
||||||
export type InstallSource = "user" | "system" | "sync" | "subscribe" | "vscode";
|
export type InstallSource = "user" | "system" | "sync" | "subscribe" | "vscode";
|
||||||
|
|
||||||
@ -78,8 +80,17 @@ export default class ServiceWorkerManager {
|
|||||||
case "checkSubscribeUpdate":
|
case "checkSubscribeUpdate":
|
||||||
subscribe.checkSubscribeUpdate();
|
subscribe.checkSubscribeUpdate();
|
||||||
break;
|
break;
|
||||||
|
case "checkUpdate":
|
||||||
|
// 检查扩展更新
|
||||||
|
this.checkUpdate();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// 8小时检查一次扩展更新
|
||||||
|
chrome.alarms.create("checkUpdate", {
|
||||||
|
delayInMinutes: 0,
|
||||||
|
periodInMinutes: 8 * 60,
|
||||||
|
});
|
||||||
|
|
||||||
// 监听配置变化
|
// 监听配置变化
|
||||||
this.mq.subscribe("systemConfigChange", (msg) => {
|
this.mq.subscribe("systemConfigChange", (msg) => {
|
||||||
@ -95,5 +106,31 @@ export default class ServiceWorkerManager {
|
|||||||
systemConfig.getCloudSync().then((config) => {
|
systemConfig.getCloudSync().then((config) => {
|
||||||
synchronize.cloudSyncConfigChange(config);
|
synchronize.cloudSyncConfigChange(config);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === "production") {
|
||||||
|
chrome.runtime.onInstalled.addListener((details) => {
|
||||||
|
if (details.reason === "install") {
|
||||||
|
chrome.tabs.create({ url: "https://docs.scriptcat.org/" });
|
||||||
|
} else if (details.reason === "update") {
|
||||||
|
chrome.tabs.create({
|
||||||
|
url: `https://docs.scriptcat.org/docs/change/#${ExtVersion}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkUpdate() {
|
||||||
|
fetch(`${ExtServer}api/v1/system/version?version=${ExtVersion}`)
|
||||||
|
.then((resp) => resp.json())
|
||||||
|
.then((resp: { data: { notice: string; version: string } }) => {
|
||||||
|
systemConfig.getCheckUpdate().then((items) => {
|
||||||
|
if (items.notice !== resp.data.notice) {
|
||||||
|
systemConfig.setCheckUpdate(Object.assign(resp.data, { isRead: false }));
|
||||||
|
} else {
|
||||||
|
systemConfig.setCheckUpdate(Object.assign(resp.data, { isRead: items.isRead }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,7 +257,7 @@ export class ResourceService {
|
|||||||
return fetch(u.url)
|
return fetch(u.url)
|
||||||
.then(async (resp) => {
|
.then(async (resp) => {
|
||||||
if (resp.status !== 200) {
|
if (resp.status !== 200) {
|
||||||
throw new Error(`resource response status not 200:${resp.status}`);
|
throw new Error(`resource response status not 200: ${resp.status}`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
data: await resp.blob(),
|
data: await resp.blob(),
|
||||||
@ -305,7 +305,20 @@ export class ResourceService {
|
|||||||
return { url: urls[0], hash };
|
return { url: urls[0], hash };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.group.on("getScriptResources", this.getScriptResources.bind(this));
|
this.group.on("getScriptResources", this.getScriptResources.bind(this));
|
||||||
|
this.group.on("deleteResource", this.deleteResource.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { MessageQueue } from "@Packages/message/message_queue";
|
import { MessageQueue, Unsubscribe } from "@Packages/message/message_queue";
|
||||||
import { ExtMessageSender, GetSender, Group, MessageSend } from "@Packages/message/server";
|
import { ExtMessageSender, GetSender, Group, MessageSend } from "@Packages/message/server";
|
||||||
import {
|
import {
|
||||||
Script,
|
Script,
|
||||||
@ -15,18 +15,17 @@ import { subscribeScriptDelete, subscribeScriptEnable, subscribeScriptInstall }
|
|||||||
import { ScriptService } from "./script";
|
import { ScriptService } from "./script";
|
||||||
import { runScript, stopScript } from "../offscreen/client";
|
import { runScript, stopScript } from "../offscreen/client";
|
||||||
import { getRunAt } from "./utils";
|
import { getRunAt } from "./utils";
|
||||||
import { randomString } from "@App/pkg/utils/utils";
|
import { isUserScriptsAvailable, randomString } from "@App/pkg/utils/utils";
|
||||||
import Cache from "@App/app/cache";
|
import Cache from "@App/app/cache";
|
||||||
import { dealPatternMatches, UrlMatch } from "@App/pkg/utils/match";
|
import { dealPatternMatches, UrlMatch } from "@App/pkg/utils/match";
|
||||||
import { ExtensionContentMessageSend } from "@Packages/message/extension_message";
|
import { ExtensionContentMessageSend } from "@Packages/message/extension_message";
|
||||||
import { sendMessage } from "@Packages/message/client";
|
import { sendMessage } from "@Packages/message/client";
|
||||||
import { compileInjectScript } from "../content/utils";
|
import { compileInjectScript } from "../content/utils";
|
||||||
import { PopupService } from "./popup";
|
|
||||||
import Logger from "@App/app/logger/logger";
|
|
||||||
import LoggerCore from "@App/app/logger/core";
|
import LoggerCore from "@App/app/logger/core";
|
||||||
import PermissionVerify from "./permission_verify";
|
import PermissionVerify from "./permission_verify";
|
||||||
import { SystemConfig } from "@App/pkg/config/config";
|
import { SystemConfig } from "@App/pkg/config/config";
|
||||||
import { ResourceService } from "./resource";
|
import { ResourceService } from "./resource";
|
||||||
|
import { LocalStorageDAO } from "@App/app/repo/localStorage";
|
||||||
|
|
||||||
// 为了优化性能,存储到缓存时删除了code、value与resource
|
// 为了优化性能,存储到缓存时删除了code、value与resource
|
||||||
export interface ScriptMatchInfo extends ScriptRunResouce {
|
export interface ScriptMatchInfo extends ScriptRunResouce {
|
||||||
@ -49,6 +48,9 @@ export class RuntimeService {
|
|||||||
scriptCustomizeMatch: UrlMatch<string> = new UrlMatch<string>();
|
scriptCustomizeMatch: UrlMatch<string> = new UrlMatch<string>();
|
||||||
scriptMatchCache: Map<string, ScriptMatchInfo> | null | undefined;
|
scriptMatchCache: Map<string, ScriptMatchInfo> | null | undefined;
|
||||||
|
|
||||||
|
isEnableDeveloperMode = false;
|
||||||
|
isEnableUserscribe = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private systemConfig: SystemConfig,
|
private systemConfig: SystemConfig,
|
||||||
private group: Group,
|
private group: Group,
|
||||||
@ -70,8 +72,35 @@ export class RuntimeService {
|
|||||||
this.group.on("runScript", this.runScript.bind(this));
|
this.group.on("runScript", this.runScript.bind(this));
|
||||||
this.group.on("pageLoad", this.pageLoad.bind(this));
|
this.group.on("pageLoad", this.pageLoad.bind(this));
|
||||||
|
|
||||||
// 读取inject.js注入页面
|
// 检查是否开启了开发者模式
|
||||||
this.registerInjectScript();
|
this.isEnableDeveloperMode = isUserScriptsAvailable();
|
||||||
|
if (!this.isEnableDeveloperMode) {
|
||||||
|
// 未开启加上警告引导
|
||||||
|
// 判断是否首次
|
||||||
|
const localStorage = new LocalStorageDAO();
|
||||||
|
localStorage.get("firstShowDeveloperMode").then((res) => {
|
||||||
|
if (!res) {
|
||||||
|
localStorage.save({
|
||||||
|
key: "firstShowDeveloperMode",
|
||||||
|
value: true,
|
||||||
|
});
|
||||||
|
// 打开页面
|
||||||
|
chrome.tabs.create({
|
||||||
|
url: `https://docs.scriptcat.org/docs/use/open-dev/`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
chrome.action.setBadgeBackgroundColor({
|
||||||
|
color: "#ff8c00",
|
||||||
|
});
|
||||||
|
chrome.action.setBadgeTextColor({
|
||||||
|
color: "#ffffff",
|
||||||
|
});
|
||||||
|
chrome.action.setBadgeText({
|
||||||
|
text: "!",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 监听脚本开启
|
// 监听脚本开启
|
||||||
subscribeScriptEnable(this.mq, async (data) => {
|
subscribeScriptEnable(this.mq, async (data) => {
|
||||||
const script = await this.scriptDAO.getAndCode(data.uuid);
|
const script = await this.scriptDAO.getAndCode(data.uuid);
|
||||||
@ -82,6 +111,7 @@ export class RuntimeService {
|
|||||||
// 如果是后台脚本, 在offscreen中进行处理
|
// 如果是后台脚本, 在offscreen中进行处理
|
||||||
if (script.type === SCRIPT_TYPE_NORMAL) {
|
if (script.type === SCRIPT_TYPE_NORMAL) {
|
||||||
// 加载页面脚本
|
// 加载页面脚本
|
||||||
|
// 不管开没开启都要加载一次脚本信息
|
||||||
await this.loadPageScript(script);
|
await this.loadPageScript(script);
|
||||||
if (!data.enable) {
|
if (!data.enable) {
|
||||||
await this.unregistryPageScript(script.uuid);
|
await this.unregistryPageScript(script.uuid);
|
||||||
@ -104,6 +134,32 @@ export class RuntimeService {
|
|||||||
this.deleteScriptMatch(uuid);
|
this.deleteScriptMatch(uuid);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.systemConfig.addListener("enable_script", (enable) => {
|
||||||
|
this.isEnableUserscribe = enable;
|
||||||
|
if (enable) {
|
||||||
|
this.registerUserscripts();
|
||||||
|
} else {
|
||||||
|
this.unregisterUserscripts();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 检查是否开启
|
||||||
|
this.isEnableUserscribe = await this.systemConfig.getEnableScript();
|
||||||
|
if (this.isEnableUserscribe) {
|
||||||
|
this.registerUserscripts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe: Unsubscribe[] = [];
|
||||||
|
|
||||||
|
// 取消脚本注册
|
||||||
|
unregisterUserscripts() {
|
||||||
|
chrome.userScripts.unregister();
|
||||||
|
this.deleteMessageFlag();
|
||||||
|
}
|
||||||
|
|
||||||
|
async registerUserscripts() {
|
||||||
|
// 读取inject.js注入页面
|
||||||
|
this.registerInjectScript();
|
||||||
// 将开启的脚本发送一次enable消息
|
// 将开启的脚本发送一次enable消息
|
||||||
const scriptDao = new ScriptDAO();
|
const scriptDao = new ScriptDAO();
|
||||||
const list = await scriptDao.all();
|
const list = await scriptDao.all();
|
||||||
@ -132,6 +188,14 @@ export class RuntimeService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteMessageFlag() {
|
||||||
|
return Cache.getInstance().del("scriptInjectMessageFlag");
|
||||||
|
}
|
||||||
|
|
||||||
|
getMessageFlag() {
|
||||||
|
return Cache.getInstance().get("scriptInjectMessageFlag");
|
||||||
|
}
|
||||||
|
|
||||||
// 给指定tab发送消息
|
// 给指定tab发送消息
|
||||||
sendMessageToTab(to: ExtMessageSender, action: string, data: any) {
|
sendMessageToTab(to: ExtMessageSender, action: string, data: any) {
|
||||||
if (to.tabId === -1) {
|
if (to.tabId === -1) {
|
||||||
@ -205,7 +269,7 @@ export class RuntimeService {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
// 如果是iframe,判断是否允许在iframe里运行
|
// 如果是iframe,判断是否允许在iframe里运行
|
||||||
if (chromeSender.frameId !== undefined) {
|
if (chromeSender.frameId) {
|
||||||
if (scriptRes.metadata.noframes) {
|
if (scriptRes.metadata.noframes) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -254,40 +318,57 @@ export class RuntimeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 注册inject.js
|
// 注册inject.js
|
||||||
registerInjectScript() {
|
async registerInjectScript() {
|
||||||
chrome.userScripts.getScripts({ ids: ["scriptcat-inject"] }).then((res) => {
|
// 如果没设置过, 则更新messageFlag
|
||||||
if (res.length == 0) {
|
let messageFlag = await this.getMessageFlag();
|
||||||
chrome.userScripts.configureWorld({
|
if (!messageFlag) {
|
||||||
csp: "script-src 'self' 'unsafe-inline' 'unsafe-eval' *",
|
messageFlag = await this.messageFlag();
|
||||||
messaging: true,
|
const injectJs = await fetch("inject.js").then((res) => res.text());
|
||||||
|
// 替换ScriptFlag
|
||||||
|
const code = `(function (MessageFlag) {\n${injectJs}\n})('${messageFlag}')`;
|
||||||
|
chrome.userScripts.configureWorld({
|
||||||
|
csp: "script-src 'self' 'unsafe-inline' 'unsafe-eval' *",
|
||||||
|
messaging: true,
|
||||||
|
});
|
||||||
|
const scripts: chrome.userScripts.RegisteredUserScript[] = [
|
||||||
|
{
|
||||||
|
id: "scriptcat-inject",
|
||||||
|
js: [{ code }],
|
||||||
|
matches: ["<all_urls>"],
|
||||||
|
allFrames: true,
|
||||||
|
world: "MAIN",
|
||||||
|
runAt: "document_start",
|
||||||
|
},
|
||||||
|
// 注册content
|
||||||
|
{
|
||||||
|
id: "scriptcat-content",
|
||||||
|
js: [{ file: "src/content.js" }],
|
||||||
|
matches: ["<all_urls>"],
|
||||||
|
allFrames: true,
|
||||||
|
runAt: "document_start",
|
||||||
|
world: "USER_SCRIPT",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
try {
|
||||||
|
// 如果使用getScripts来判断, 会出现找不到的问题
|
||||||
|
// 另外如果使用
|
||||||
|
await chrome.userScripts.register(scripts);
|
||||||
|
} catch (e: any) {
|
||||||
|
LoggerCore.logger().error("register inject.js error", {
|
||||||
|
error: e,
|
||||||
});
|
});
|
||||||
fetch("inject.js")
|
if (e.message?.indexOf("Duplicate script ID") !== -1) {
|
||||||
.then((res) => res.text())
|
// 如果是重复注册, 则更新
|
||||||
.then(async (injectJs) => {
|
chrome.userScripts.update(scripts, () => {
|
||||||
// 替换ScriptFlag
|
if (chrome.runtime.lastError) {
|
||||||
const code = `(function (MessageFlag) {\n${injectJs}\n})('${await this.messageFlag()}')`;
|
LoggerCore.logger().error("update inject.js error", {
|
||||||
chrome.userScripts.register([
|
error: chrome.runtime.lastError,
|
||||||
{
|
});
|
||||||
id: "scriptcat-inject",
|
}
|
||||||
js: [{ code }],
|
|
||||||
matches: ["<all_urls>"],
|
|
||||||
allFrames: true,
|
|
||||||
world: "MAIN",
|
|
||||||
runAt: "document_start",
|
|
||||||
},
|
|
||||||
// 注册content
|
|
||||||
{
|
|
||||||
id: "scriptcat-content",
|
|
||||||
js: [{ file: "src/content.js" }],
|
|
||||||
matches: ["<all_urls>"],
|
|
||||||
allFrames: true,
|
|
||||||
runAt: "document_start",
|
|
||||||
world: "USER_SCRIPT",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingScript: Promise<void> | null | undefined;
|
loadingScript: Promise<void> | null | undefined;
|
||||||
@ -367,8 +448,11 @@ export class RuntimeService {
|
|||||||
if (!this.scriptMatchCache) {
|
if (!this.scriptMatchCache) {
|
||||||
await this.loadScriptMatchInfo();
|
await this.loadScriptMatchInfo();
|
||||||
}
|
}
|
||||||
this.scriptMatchCache!.get(uuid)!.status = status;
|
const script = await this.scriptMatchCache!.get(uuid);
|
||||||
this.saveScriptMatchInfo();
|
if (script) {
|
||||||
|
script.status = status;
|
||||||
|
this.saveScriptMatchInfo();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteScriptMatch(uuid: string) {
|
async deleteScriptMatch(uuid: string) {
|
||||||
@ -384,15 +468,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: [] },
|
||||||
@ -434,25 +518,36 @@ export class RuntimeService {
|
|||||||
this.addScriptMatch(scriptMatchInfo);
|
this.addScriptMatch(scriptMatchInfo);
|
||||||
|
|
||||||
// 如果脚本开启, 则注册脚本
|
// 如果脚本开启, 则注册脚本
|
||||||
if (script.status === SCRIPT_STATUS_ENABLE) {
|
if (this.isEnableDeveloperMode && this.isEnableUserscribe && script.status === SCRIPT_STATUS_ENABLE) {
|
||||||
if (!script.metadata["noframes"]) {
|
if (scriptRes.metadata["noframes"]) {
|
||||||
|
registerScript.allFrames = false;
|
||||||
|
} else {
|
||||||
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)) {
|
const res = await chrome.userScripts.getScripts({ ids: [script.uuid] });
|
||||||
await chrome.userScripts.update([registerScript]);
|
const logger = LoggerCore.logger({
|
||||||
|
name: script.name,
|
||||||
|
registerMatch: {
|
||||||
|
matches: registerScript.matches,
|
||||||
|
excludeMatches: registerScript.excludeMatches,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (res.length > 0) {
|
||||||
|
await chrome.userScripts.update([registerScript], () => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
logger.error("update registerScript error", {
|
||||||
|
error: chrome.runtime.lastError,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
await chrome.userScripts.register([registerScript], () => {
|
await chrome.userScripts.register([registerScript], () => {
|
||||||
if (chrome.runtime.lastError) {
|
if (chrome.runtime.lastError) {
|
||||||
LoggerCore.logger().error("registerScript error", {
|
logger.error("registerScript error", {
|
||||||
error: chrome.runtime.lastError,
|
error: chrome.runtime.lastError,
|
||||||
name: script.name,
|
|
||||||
registerMatch: {
|
|
||||||
matches: registerScript.matches,
|
|
||||||
excludeMatches: registerScript.excludeMatches,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -462,19 +557,17 @@ export class RuntimeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async unregistryPageScript(uuid: string) {
|
async unregistryPageScript(uuid: string) {
|
||||||
if (!(await Cache.getInstance().get("registryScript:" + uuid))) {
|
if (
|
||||||
|
!this.isEnableDeveloperMode ||
|
||||||
|
!this.isEnableUserscribe ||
|
||||||
|
!(await Cache.getInstance().get("registryScript:" + uuid))
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
chrome.userScripts.unregister(
|
// 删除缓存
|
||||||
{
|
Cache.getInstance().del("registryScript:" + uuid);
|
||||||
ids: [uuid],
|
// 修改脚本状态为disable
|
||||||
},
|
this.updateScriptStatus(uuid, SCRIPT_STATUS_DISABLE);
|
||||||
() => {
|
chrome.userScripts.unregister({ ids: [uuid] });
|
||||||
// 删除缓存
|
|
||||||
Cache.getInstance().del("registryScript:" + uuid);
|
|
||||||
// 修改脚本状态为disable
|
|
||||||
this.updateScriptStatus(uuid, SCRIPT_STATUS_DISABLE);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import { ResourceService } from "./resource";
|
|||||||
import { ValueService } from "./value";
|
import { ValueService } from "./value";
|
||||||
import { compileScriptCode } from "../content/utils";
|
import { compileScriptCode } from "../content/utils";
|
||||||
import { SystemConfig } from "@App/pkg/config/config";
|
import { SystemConfig } from "@App/pkg/config/config";
|
||||||
|
import i18n, { localePath } from "@App/locales/locales";
|
||||||
|
|
||||||
export class ScriptService {
|
export class ScriptService {
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
@ -59,50 +60,55 @@ export class ScriptService {
|
|||||||
// 读取脚本url内容, 进行安装
|
// 读取脚本url内容, 进行安装
|
||||||
const logger = this.logger.with({ url: targetUrl });
|
const logger = this.logger.with({ url: targetUrl });
|
||||||
logger.debug("install script");
|
logger.debug("install script");
|
||||||
this.openInstallPageByUrl(targetUrl, "user").catch((e) => {
|
this.openInstallPageByUrl(targetUrl, "user")
|
||||||
logger.error("install script error", Logger.E(e));
|
.catch((e) => {
|
||||||
// 如果打开失败, 则重定向到安装页
|
logger.error("install script error", Logger.E(e));
|
||||||
chrome.scripting.executeScript({
|
// 不再重定向当前url
|
||||||
target: { tabId: req.tabId },
|
chrome.declarativeNetRequest.updateDynamicRules(
|
||||||
func: function () {
|
{
|
||||||
history.back();
|
removeRuleIds: [2],
|
||||||
},
|
addRules: [
|
||||||
});
|
{
|
||||||
// 并不再重定向当前url
|
id: 2,
|
||||||
chrome.declarativeNetRequest.updateDynamicRules(
|
priority: 1,
|
||||||
{
|
action: {
|
||||||
removeRuleIds: [2],
|
type: chrome.declarativeNetRequest.RuleActionType.ALLOW,
|
||||||
addRules: [
|
},
|
||||||
{
|
condition: {
|
||||||
id: 2,
|
regexFilter: targetUrl,
|
||||||
priority: 1,
|
resourceTypes: [chrome.declarativeNetRequest.ResourceType.MAIN_FRAME],
|
||||||
action: {
|
requestMethods: [chrome.declarativeNetRequest.RequestMethod.GET],
|
||||||
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);
|
||||||
},
|
}
|
||||||
],
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
if (chrome.runtime.lastError) {
|
|
||||||
console.error(chrome.runtime.lastError);
|
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
);
|
})
|
||||||
});
|
.finally(() => {
|
||||||
|
// 回退到到安装页
|
||||||
|
chrome.scripting.executeScript({
|
||||||
|
target: { tabId: req.tabId },
|
||||||
|
func: function () {
|
||||||
|
history.back();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
urls: [
|
urls: [
|
||||||
"https://docs.scriptcat.org/docs/script_installation",
|
"https://docs.scriptcat.org/docs/script_installation/",
|
||||||
|
"https://docs.scriptcat.org/en/docs/script_installation/",
|
||||||
"https://www.tampermonkey.net/script_installation.php",
|
"https://www.tampermonkey.net/script_installation.php",
|
||||||
],
|
],
|
||||||
types: ["main_frame"],
|
types: ["main_frame"],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
// 获取i18n
|
||||||
// 重定向到脚本安装页
|
// 重定向到脚本安装页
|
||||||
chrome.declarativeNetRequest.updateDynamicRules(
|
chrome.declarativeNetRequest.updateDynamicRules(
|
||||||
{
|
{
|
||||||
@ -114,7 +120,7 @@ export class ScriptService {
|
|||||||
action: {
|
action: {
|
||||||
type: chrome.declarativeNetRequest.RuleActionType.REDIRECT,
|
type: chrome.declarativeNetRequest.RuleActionType.REDIRECT,
|
||||||
redirect: {
|
redirect: {
|
||||||
regexSubstitution: "https://docs.scriptcat.org/docs/script_installation#url=\\0",
|
regexSubstitution: `https://docs.scriptcat.org${localePath}/docs/script_installation/#url=\\0`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
condition: {
|
condition: {
|
||||||
@ -320,6 +326,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);
|
||||||
@ -431,6 +485,15 @@ export class ScriptService {
|
|||||||
return this.checkUpdate(uuid, "user");
|
return this.checkUpdate(uuid, "user");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isInstalled({ name, namespace }: { name: string; namespace: string }) {
|
||||||
|
return this.scriptDAO.findByNameAndNamespace(name, namespace).then((script) => {
|
||||||
|
if (script) {
|
||||||
|
return { installed: true, version: script.metadata.version && script.metadata.version[0] };
|
||||||
|
}
|
||||||
|
return { installed: false };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.listenerScriptInstall();
|
this.listenerScriptInstall();
|
||||||
|
|
||||||
@ -443,7 +506,10 @@ 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));
|
||||||
|
this.group.on("isInstalled", this.isInstalled.bind(this));
|
||||||
|
|
||||||
// 定时检查更新, 每10分钟检查一次
|
// 定时检查更新, 每10分钟检查一次
|
||||||
chrome.alarms.create("checkScriptUpdate", {
|
chrome.alarms.create("checkScriptUpdate", {
|
||||||
|
@ -54,7 +54,11 @@ export class ValueService {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
oldValue = valueModel.data[key];
|
oldValue = valueModel.data[key];
|
||||||
valueModel.data[key] = value;
|
if (value === undefined) {
|
||||||
|
delete valueModel.data[key];
|
||||||
|
} else {
|
||||||
|
valueModel.data[key] = value;
|
||||||
|
}
|
||||||
await this.valueDAO.save(storageName, valueModel);
|
await this.valueDAO.save(storageName, valueModel);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -27,10 +27,15 @@ i18n.use(initReactI18next).init({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export let localePath = "";
|
||||||
|
|
||||||
chrome.i18n.getAcceptLanguages((lngs) => {
|
chrome.i18n.getAcceptLanguages((lngs) => {
|
||||||
systemConfig.getLanguage().then((lng) => {
|
systemConfig.getLanguage(lngs).then((lng) => {
|
||||||
i18n.changeLanguage(lng);
|
i18n.changeLanguage(lng);
|
||||||
dayjs.locale(lng.toLocaleLowerCase());
|
dayjs.locale(lng.toLocaleLowerCase());
|
||||||
|
if (lng !== "zh-CN") {
|
||||||
|
localePath = "en";
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -68,6 +68,7 @@
|
|||||||
"confirm_delete_backup_file": "确认删除备份文件",
|
"confirm_delete_backup_file": "确认删除备份文件",
|
||||||
"confirm_update": "确认更新",
|
"confirm_update": "确认更新",
|
||||||
"delete_success": "删除成功",
|
"delete_success": "删除成功",
|
||||||
|
"deleting": "删除中",
|
||||||
"backup_strategy": "备份策略",
|
"backup_strategy": "备份策略",
|
||||||
"under_construction": "建设中",
|
"under_construction": "建设中",
|
||||||
"development_debugging": "开发调试",
|
"development_debugging": "开发调试",
|
||||||
@ -367,5 +368,8 @@
|
|||||||
"eslint_config_format_error": "eslint配置格式错误",
|
"eslint_config_format_error": "eslint配置格式错误",
|
||||||
"export_success": "导出成功",
|
"export_success": "导出成功",
|
||||||
"get_backup_dir_url_failed": "获取备份目录地址失败",
|
"get_backup_dir_url_failed": "获取备份目录地址失败",
|
||||||
"get_backup_files_failed": "获取备份文件失败"
|
"get_backup_files_failed": "获取备份文件失败",
|
||||||
|
"develop_mode_guide": "检测到当前未开启开发者模式,您的脚本无法正常使用,<a href=\"https://docs.scriptcat.org/docs/use/open-dev/\" target=\"black\" style=\"color: var(--color-text-1)\">👉点我了解如何开启</a>",
|
||||||
|
"enable_script_failed": "脚本开启失败",
|
||||||
|
"disable_script_failed": "脚本关闭失败"
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "__MSG_scriptcat__",
|
"name": "__MSG_scriptcat__",
|
||||||
"version": "0.17.0.1003",
|
"version": "0.17.0.1005",
|
||||||
"author": "CodFrm",
|
"author": "CodFrm",
|
||||||
"description": "__MSG_scriptcat_description__",
|
"description": "__MSG_scriptcat_description__",
|
||||||
"options_ui": {
|
"options_ui": {
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
import migrate from "./app/migrate";
|
import { MessageSend } from "@Packages/message/server";
|
||||||
import LoggerCore from "./app/logger/core";
|
import LoggerCore from "./app/logger/core";
|
||||||
import DBWriter from "./app/logger/db_writer";
|
import MessageWriter from "./app/logger/message_writer";
|
||||||
import { LoggerDAO } from "./app/repo/logger";
|
|
||||||
import { OffscreenManager } from "./app/service/offscreen";
|
import { OffscreenManager } from "./app/service/offscreen";
|
||||||
|
import { ExtensionMessageSend } from "@Packages/message/extension_message";
|
||||||
migrate();
|
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
// 初始化日志组件
|
// 初始化日志组件
|
||||||
|
const extensionMessage: MessageSend = new ExtensionMessageSend();
|
||||||
const loggerCore = new LoggerCore({
|
const loggerCore = new LoggerCore({
|
||||||
writer: new DBWriter(new LoggerDAO()),
|
writer: new MessageWriter(extensionMessage),
|
||||||
labels: { env: "offscreen" },
|
labels: { env: "offscreen" },
|
||||||
});
|
});
|
||||||
loggerCore.logger().debug("offscreen start");
|
loggerCore.logger().debug("offscreen start");
|
||||||
// 初始化管理器
|
// 初始化管理器
|
||||||
const manager = new OffscreenManager();
|
const manager = new OffscreenManager(extensionMessage);
|
||||||
manager.initManager();
|
manager.initManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ const CodeEditor: React.ForwardRefRenderFunction<{ editor: editor.IStandaloneCod
|
|||||||
{ id, className, code, diffCode, editable },
|
{ id, className, code, diffCode, editable },
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const settings = useAppSelector((state) => state.setting);
|
|
||||||
const [monacoEditor, setEditor] = useState<editor.IStandaloneCodeEditor>();
|
const [monacoEditor, setEditor] = useState<editor.IStandaloneCodeEditor>();
|
||||||
const [enableEslint, setEnableEslint] = useState(false);
|
const [enableEslint, setEnableEslint] = useState(false);
|
||||||
const [eslintConfig, setEslintConfig] = useState("");
|
const [eslintConfig, setEslintConfig] = useState("");
|
||||||
|
@ -1,22 +1,12 @@
|
|||||||
import { Resource } from "@App/app/repo/resource";
|
import { Resource } from "@App/app/repo/resource";
|
||||||
import { Script } from "@App/app/repo/scripts";
|
import { Script } from "@App/app/repo/scripts";
|
||||||
|
import { ResourceClient } from "@App/app/service/service_worker/client";
|
||||||
|
import { message } from "@App/pages/store/global";
|
||||||
import { base64ToBlob } from "@App/pkg/utils/script";
|
import { base64ToBlob } from "@App/pkg/utils/script";
|
||||||
import {
|
import { Button, Drawer, Input, Message, Popconfirm, Space, Table } from "@arco-design/web-react";
|
||||||
Button,
|
|
||||||
Drawer,
|
|
||||||
Input,
|
|
||||||
Message,
|
|
||||||
Popconfirm,
|
|
||||||
Space,
|
|
||||||
Table,
|
|
||||||
} from "@arco-design/web-react";
|
|
||||||
import { RefInputType } from "@arco-design/web-react/es/Input/interface";
|
import { RefInputType } from "@arco-design/web-react/es/Input/interface";
|
||||||
import { ColumnProps } from "@arco-design/web-react/es/Table";
|
import { ColumnProps } from "@arco-design/web-react/es/Table";
|
||||||
import {
|
import { IconDelete, IconDownload, IconSearch } from "@arco-design/web-react/icon";
|
||||||
IconDelete,
|
|
||||||
IconDownload,
|
|
||||||
IconSearch,
|
|
||||||
} from "@arco-design/web-react/icon";
|
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@ -33,14 +23,14 @@ const ScriptResource: React.FC<{
|
|||||||
}> = ({ script, visible, onCancel, onOk }) => {
|
}> = ({ script, visible, onCancel, onOk }) => {
|
||||||
const [data, setData] = useState<ResourceListItem[]>([]);
|
const [data, setData] = useState<ResourceListItem[]>([]);
|
||||||
const inputRef = useRef<RefInputType>(null);
|
const inputRef = useRef<RefInputType>(null);
|
||||||
// const resourceCtrl = IoC.instance(ResourceController) as ResourceController;
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const resourceClient = new ResourceClient(message);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!script) {
|
if (!script) {
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
resourceCtrl.getResource(script).then((res) => {
|
resourceClient.getScriptResources(script).then((res) => {
|
||||||
const arr: ResourceListItem[] = [];
|
const arr: ResourceListItem[] = [];
|
||||||
Object.keys(res).forEach((key) => {
|
Object.keys(res).forEach((key) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -120,10 +110,21 @@ const ScriptResource: React.FC<{
|
|||||||
title={t("confirm_delete_resource")}
|
title={t("confirm_delete_resource")}
|
||||||
onOk={() => {
|
onOk={() => {
|
||||||
Message.info({
|
Message.info({
|
||||||
content: t("delete_success"),
|
content: t("deleting"),
|
||||||
});
|
});
|
||||||
resourceCtrl.deleteResource(value.id);
|
resourceClient
|
||||||
setData(data.filter((_, i) => i !== index));
|
.deleteResource(value.url)
|
||||||
|
.then(() => {
|
||||||
|
Message.info({
|
||||||
|
content: t("delete_success"),
|
||||||
|
});
|
||||||
|
setData(data.filter((_, i) => i !== index));
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
Message.error({
|
||||||
|
content: t("delete_failed") + ": " + e.message,
|
||||||
|
});
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button type="text" iconOnly icon={<IconDelete />} />
|
<Button type="text" iconOnly icon={<IconDelete />} />
|
||||||
@ -154,7 +155,7 @@ const ScriptResource: React.FC<{
|
|||||||
onOk={() => {
|
onOk={() => {
|
||||||
setData((prev) => {
|
setData((prev) => {
|
||||||
prev.forEach((v) => {
|
prev.forEach((v) => {
|
||||||
resourceCtrl.deleteResource(v.id);
|
resourceClient.deleteResource(v.url);
|
||||||
});
|
});
|
||||||
Message.info({
|
Message.info({
|
||||||
content: t("clear_success"),
|
content: t("clear_success"),
|
||||||
|
@ -1,31 +1,24 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Script } from "@App/app/repo/scripts";
|
import { Script, ScriptDAO } from "@App/app/repo/scripts";
|
||||||
import {
|
import { Space, Popconfirm, Button, Divider, Typography, Modal, Input } from "@arco-design/web-react";
|
||||||
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<{
|
||||||
script: Script;
|
script: Script;
|
||||||
}> = ({ script }) => {
|
}> = ({ script }) => {
|
||||||
// const scriptCtrl = IoC.instance(ScriptController) as ScriptController;
|
const scriptDAO = new ScriptDAO();
|
||||||
const [match, setMatch] = useState<MatchItem[]>([]);
|
const [match, setMatch] = useState<MatchItem[]>([]);
|
||||||
const [exclude, setExclude] = useState<MatchItem[]>([]);
|
const [exclude, setExclude] = useState<MatchItem[]>([]);
|
||||||
const [matchValue, setMatchValue] = useState<string>("");
|
const [matchValue, setMatchValue] = useState<string>("");
|
||||||
@ -37,7 +30,7 @@ const Match: React.FC<{
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (script) {
|
if (script) {
|
||||||
// 从数据库中获取是简单处理数据一致性的问题
|
// 从数据库中获取是简单处理数据一致性的问题
|
||||||
scriptCtrl.scriptDAO.findById(script.id).then((res) => {
|
scriptDAO.get(script.uuid).then((res) => {
|
||||||
if (!res) {
|
if (!res) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -48,28 +41,17 @@ 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,
|
byUser: !matchMap.has(value),
|
||||||
self: false,
|
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);
|
||||||
|
|
||||||
const excludeArr =
|
const excludeArr = res.selfMetadata?.exclude || res.metadata.exclude || [];
|
||||||
res.selfMetadata?.exclude || res.metadata.exclude || [];
|
|
||||||
const excludeMap = new Map<string, boolean>();
|
const excludeMap = new Map<string, boolean>();
|
||||||
res.metadata.exclude?.forEach((m) => {
|
res.metadata.exclude?.forEach((m) => {
|
||||||
excludeMap.set(m, true);
|
excludeMap.set(m, true);
|
||||||
@ -77,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,
|
byUser: !excludeMap.has(value),
|
||||||
self: false,
|
hasMatch,
|
||||||
hasMatch,
|
isExclude: true,
|
||||||
isExclude: true,
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
e.push({
|
|
||||||
id: index,
|
|
||||||
match: value,
|
|
||||||
self: true,
|
|
||||||
hasMatch,
|
|
||||||
isExclude: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
setExclude(e);
|
setExclude(e);
|
||||||
});
|
});
|
||||||
@ -108,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) {
|
||||||
@ -125,23 +97,24 @@ const Match: React.FC<{
|
|||||||
return (
|
return (
|
||||||
<Space>
|
<Space>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={`${t("confirm_delete_exclude")}${
|
title={`${t("confirm_delete_exclude")}${item.hasMatch ? ` ${t("after_deleting_match_item")}` : ""}`}
|
||||||
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(() => {
|
||||||
@ -159,24 +132,22 @@ const Match: React.FC<{
|
|||||||
return (
|
return (
|
||||||
<Space>
|
<Space>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={`${t("confirm_delete_match")}${
|
title={`${t("confirm_delete_match")}${item.byUser ? "" : ` ${t("after_deleting_exclude_item")}`}`}
|
||||||
item.self ? "" : ` ${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(() => {
|
||||||
@ -205,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(() => {
|
||||||
@ -237,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(() => {
|
||||||
@ -276,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([]);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -305,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([]);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
@ -2,26 +2,14 @@ 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;
|
||||||
}> = ({ script }) => {
|
}> = ({ script }) => {
|
||||||
// const permissionCtrl = IoC.instance(
|
|
||||||
// PermissionController
|
|
||||||
// ) as PermissionController;
|
|
||||||
const [permission, setPermission] = useState<Permission[]>([]);
|
const [permission, setPermission] = useState<Permission[]>([]);
|
||||||
const [permissionVisible, setPermissionVisible] = useState<boolean>(false);
|
const [permissionVisible, setPermissionVisible] = useState<boolean>(false);
|
||||||
const [permissionValue, setPermissionValue] = useState<Permission>();
|
const [permissionValue, setPermissionValue] = useState<Permission>();
|
||||||
@ -59,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")!);
|
||||||
@ -83,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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -98,20 +87,17 @@ 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)
|
setPermission([...permission]);
|
||||||
.then(() => {
|
setPermissionVisible(false);
|
||||||
setPermission([...permission]);
|
});
|
||||||
setPermissionVisible(false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -119,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")}
|
||||||
@ -147,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,
|
||||||
@ -172,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([]);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -183,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}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
import { Script } from "@App/app/repo/scripts";
|
import { Script, ScriptDAO } from "@App/app/repo/scripts";
|
||||||
import { formatUnixTime } from "@App/pkg/utils/utils";
|
import { formatUnixTime } from "@App/pkg/utils/utils";
|
||||||
import {
|
import { Descriptions, Divider, Drawer, Empty, Input, Message } from "@arco-design/web-react";
|
||||||
Descriptions,
|
|
||||||
Divider,
|
|
||||||
Drawer,
|
|
||||||
Empty,
|
|
||||||
Input,
|
|
||||||
Message,
|
|
||||||
} from "@arco-design/web-react";
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Match from "./Match";
|
import Match from "./Match";
|
||||||
@ -19,14 +12,14 @@ const ScriptSetting: React.FC<{
|
|||||||
onOk: () => void;
|
onOk: () => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}> = ({ script, visible, onCancel, onOk }) => {
|
}> = ({ script, visible, onCancel, onOk }) => {
|
||||||
// const scriptCtrl = IoC.instance(ScriptController) as ScriptController;
|
const scriptDAO = new ScriptDAO();
|
||||||
const [checkUpdateUrl, setCheckUpdateUrl] = useState<string>("");
|
const [checkUpdateUrl, setCheckUpdateUrl] = useState<string>("");
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (script) {
|
if (script) {
|
||||||
scriptCtrl.scriptDAO.findById(script.id).then((v) => {
|
scriptDAO.get(script.uuid).then((v) => {
|
||||||
setCheckUpdateUrl(v?.downloadUrl || "");
|
setCheckUpdateUrl(v?.downloadUrl || "");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -56,9 +49,7 @@ const ScriptSetting: React.FC<{
|
|||||||
data={[
|
data={[
|
||||||
{
|
{
|
||||||
label: t("last_updated"),
|
label: t("last_updated"),
|
||||||
value: formatUnixTime(
|
value: formatUnixTime((script?.updatetime || script?.createtime || 0) / 1000),
|
||||||
(script?.updatetime || script?.createtime || 0) / 1000
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "UUID",
|
label: "UUID",
|
||||||
@ -83,8 +74,8 @@ const ScriptSetting: React.FC<{
|
|||||||
setCheckUpdateUrl(e);
|
setCheckUpdateUrl(e);
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
scriptCtrl
|
scriptDAO
|
||||||
.updateCheckUpdateUrl(script!.id, checkUpdateUrl)
|
.update(script.uuid, { downloadUrl: checkUpdateUrl, checkUpdateUrl: checkUpdateUrl })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
Message.success(t("update_success")!);
|
Message.success(t("update_success")!);
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Script } from "@App/app/repo/scripts";
|
import { Script } from "@App/app/repo/scripts";
|
||||||
import { Value } from "@App/app/repo/value";
|
import { Value } from "@App/app/repo/value";
|
||||||
|
import { valueClient } from "@App/pages/store/features/script";
|
||||||
import { valueType } from "@App/pkg/utils/utils";
|
import { valueType } from "@App/pkg/utils/utils";
|
||||||
import { Button, Drawer, Form, Input, Message, Modal, Popconfirm, Select, Space, Table } from "@arco-design/web-react";
|
import { Button, Drawer, Form, Input, Message, Modal, Popconfirm, Select, Space, Table } from "@arco-design/web-react";
|
||||||
import { RefInputType } from "@arco-design/web-react/es/Input/interface";
|
import { RefInputType } from "@arco-design/web-react/es/Input/interface";
|
||||||
@ -10,16 +11,20 @@ import { useTranslation } from "react-i18next";
|
|||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
interface ValueModel {
|
||||||
|
key: string;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
const ScriptStorage: React.FC<{
|
const ScriptStorage: React.FC<{
|
||||||
// eslint-disable-next-line react/require-default-props
|
|
||||||
script?: Script;
|
script?: Script;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onOk: () => void;
|
onOk: () => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}> = ({ script, visible, onCancel, onOk }) => {
|
}> = ({ script, visible, onCancel, onOk }) => {
|
||||||
const [data, setData] = useState<Value[]>([]);
|
const [data, setData] = useState<ValueModel[]>([]);
|
||||||
const inputRef = useRef<RefInputType>(null);
|
const inputRef = useRef<RefInputType>(null);
|
||||||
const [currentValue, setCurrentValue] = useState<Value>();
|
const [currentValue, setCurrentValue] = useState<ValueModel>();
|
||||||
const [visibleEdit, setVisibleEdit] = useState(false);
|
const [visibleEdit, setVisibleEdit] = useState(false);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -28,31 +33,13 @@ const ScriptStorage: React.FC<{
|
|||||||
if (!script) {
|
if (!script) {
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
// valueCtrl.getValues(script).then((values) => {
|
valueClient.getScriptValue(script).then((value) => {
|
||||||
// setData(values);
|
setData(
|
||||||
// });
|
Object.keys(value).map((key) => {
|
||||||
// Monitor value changes
|
return { key: key, value: value[key] };
|
||||||
// const channel = valueCtrl.watchValue(script);
|
})
|
||||||
// channel.setHandler((value: Value) => {
|
);
|
||||||
// setData((prev) => {
|
});
|
||||||
// const index = prev.findIndex((item) => item.key === value.key);
|
|
||||||
// if (index === -1) {
|
|
||||||
// if (value.value === undefined) {
|
|
||||||
// return prev;
|
|
||||||
// }
|
|
||||||
// return [value, ...prev];
|
|
||||||
// }
|
|
||||||
// if (value.value === undefined) {
|
|
||||||
// prev.splice(index, 1);
|
|
||||||
// return [...prev];
|
|
||||||
// }
|
|
||||||
// prev[index] = value;
|
|
||||||
// return [...prev];
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
return () => {
|
|
||||||
// channel.disChannel();
|
|
||||||
};
|
|
||||||
}, [script]);
|
}, [script]);
|
||||||
const columns: ColumnProps[] = [
|
const columns: ColumnProps[] = [
|
||||||
{
|
{
|
||||||
@ -61,7 +48,6 @@ const ScriptStorage: React.FC<{
|
|||||||
key: "key",
|
key: "key",
|
||||||
filterIcon: <IconSearch />,
|
filterIcon: <IconSearch />,
|
||||||
width: 140,
|
width: 140,
|
||||||
// eslint-disable-next-line react/no-unstable-nested-components
|
|
||||||
filterDropdown: ({ filterKeys, setFilterKeys, confirm }: any) => {
|
filterDropdown: ({ filterKeys, setFilterKeys, confirm }: any) => {
|
||||||
return (
|
return (
|
||||||
<div className="arco-table-custom-filter">
|
<div className="arco-table-custom-filter">
|
||||||
@ -120,7 +106,7 @@ const ScriptStorage: React.FC<{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("action"),
|
title: t("action"),
|
||||||
render(_col, value: Value, index) {
|
render(_col, value: { key: string; value: string }, index) {
|
||||||
return (
|
return (
|
||||||
<Space>
|
<Space>
|
||||||
<Button
|
<Button
|
||||||
@ -136,7 +122,7 @@ const ScriptStorage: React.FC<{
|
|||||||
iconOnly
|
iconOnly
|
||||||
icon={<IconDelete />}
|
icon={<IconDelete />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
valueCtrl.setValue(script!.id, value.key, undefined);
|
valueClient.setScriptValue(script!.uuid, value.key, undefined);
|
||||||
Message.info({
|
Message.info({
|
||||||
content: t("delete_success"),
|
content: t("delete_success"),
|
||||||
});
|
});
|
||||||
@ -179,7 +165,7 @@ const ScriptStorage: React.FC<{
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
valueCtrl.setValue(script!.id, value.key, value.value);
|
valueClient.setScriptValue(script!.uuid, value.key, value.value);
|
||||||
if (currentValue) {
|
if (currentValue) {
|
||||||
Message.info({
|
Message.info({
|
||||||
content: t("update_success"),
|
content: t("update_success"),
|
||||||
@ -201,13 +187,8 @@ const ScriptStorage: React.FC<{
|
|||||||
});
|
});
|
||||||
setData([
|
setData([
|
||||||
{
|
{
|
||||||
id: 0,
|
|
||||||
scriptId: script!.id,
|
|
||||||
storageName: (script?.metadata.storagename && script?.metadata.storagename[0]) || "",
|
|
||||||
key: value.key,
|
key: value.key,
|
||||||
value: value.value,
|
value: value.value,
|
||||||
createtime: Date.now(),
|
|
||||||
updatetime: 0,
|
|
||||||
},
|
},
|
||||||
...data,
|
...data,
|
||||||
]);
|
]);
|
||||||
@ -254,7 +235,7 @@ const ScriptStorage: React.FC<{
|
|||||||
onOk={() => {
|
onOk={() => {
|
||||||
setData((prev) => {
|
setData((prev) => {
|
||||||
prev.forEach((v) => {
|
prev.forEach((v) => {
|
||||||
valueCtrl.setValue(script!.id, v.key, undefined);
|
valueClient.setScriptValue(script!.uuid, v.key, undefined);
|
||||||
});
|
});
|
||||||
Message.info({
|
Message.info({
|
||||||
content: t("clear_success"),
|
content: t("clear_success"),
|
||||||
|
@ -8,12 +8,12 @@ import "@App/index.css";
|
|||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { store } from "@App/pages/store/store.ts";
|
import { store } from "@App/pages/store/store.ts";
|
||||||
import LoggerCore from "@App/app/logger/core.ts";
|
import LoggerCore from "@App/app/logger/core.ts";
|
||||||
import { LoggerDAO } from "@App/app/repo/logger.ts";
|
import MessageWriter from "@App/app/logger/message_writer.ts";
|
||||||
import DBWriter from "@App/app/logger/db_writer.ts";
|
import { message } from "../store/global.ts";
|
||||||
|
|
||||||
// 初始化日志组件
|
// 初始化日志组件
|
||||||
const loggerCore = new LoggerCore({
|
const loggerCore = new LoggerCore({
|
||||||
writer: new DBWriter(new LoggerDAO()),
|
writer: new MessageWriter(message),
|
||||||
labels: { env: "confirm" },
|
labels: { env: "confirm" },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -8,12 +8,12 @@ import "@App/index.css";
|
|||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { store } from "@App/pages/store/store.ts";
|
import { store } from "@App/pages/store/store.ts";
|
||||||
import LoggerCore from "@App/app/logger/core.ts";
|
import LoggerCore from "@App/app/logger/core.ts";
|
||||||
import { LoggerDAO } from "@App/app/repo/logger.ts";
|
import MessageWriter from "@App/app/logger/message_writer.ts";
|
||||||
import DBWriter from "@App/app/logger/db_writer.ts";
|
import { message } from "../store/global.ts";
|
||||||
|
|
||||||
// 初始化日志组件
|
// 初始化日志组件
|
||||||
const loggerCore = new LoggerCore({
|
const loggerCore = new LoggerCore({
|
||||||
writer: new DBWriter(new LoggerDAO()),
|
writer: new MessageWriter(message),
|
||||||
labels: { env: "import" },
|
labels: { env: "import" },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -8,12 +8,12 @@ import "@App/index.css";
|
|||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { store } from "@App/pages/store/store.ts";
|
import { store } from "@App/pages/store/store.ts";
|
||||||
import LoggerCore from "@App/app/logger/core.ts";
|
import LoggerCore from "@App/app/logger/core.ts";
|
||||||
import { LoggerDAO } from "@App/app/repo/logger.ts";
|
import MessageWriter from "@App/app/logger/message_writer.ts";
|
||||||
import DBWriter from "@App/app/logger/db_writer.ts";
|
import { message } from "../store/global.ts";
|
||||||
|
|
||||||
// 初始化日志组件
|
// 初始化日志组件
|
||||||
const loggerCore = new LoggerCore({
|
const loggerCore = new LoggerCore({
|
||||||
writer: new DBWriter(new LoggerDAO()),
|
writer: new MessageWriter(message),
|
||||||
labels: { env: "install" },
|
labels: { env: "install" },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -13,8 +13,12 @@ import { LoggerDAO } from "@App/app/repo/logger.ts";
|
|||||||
import DBWriter from "@App/app/logger/db_writer.ts";
|
import DBWriter from "@App/app/logger/db_writer.ts";
|
||||||
import registerEditor from "@App/pkg/utils/monaco-editor.ts";
|
import registerEditor from "@App/pkg/utils/monaco-editor.ts";
|
||||||
import storeSubscribe from "../store/subscribe.ts";
|
import storeSubscribe from "../store/subscribe.ts";
|
||||||
|
import migrate from "@App/app/migrate.ts";
|
||||||
|
|
||||||
|
migrate();
|
||||||
|
|
||||||
registerEditor();
|
registerEditor();
|
||||||
|
|
||||||
// 初始化日志组件
|
// 初始化日志组件
|
||||||
const loggerCore = new LoggerCore({
|
const loggerCore = new LoggerCore({
|
||||||
writer: new DBWriter(new LoggerDAO()),
|
writer: new DBWriter(new LoggerDAO()),
|
||||||
|
@ -81,6 +81,8 @@ import {
|
|||||||
requestStopScript,
|
requestStopScript,
|
||||||
requestRunScript,
|
requestRunScript,
|
||||||
scriptClient,
|
scriptClient,
|
||||||
|
enableLoading,
|
||||||
|
updateEnableStatus,
|
||||||
} from "@App/pages/store/features/script";
|
} from "@App/pages/store/features/script";
|
||||||
import { message, systemConfig } from "@App/pages/store/global";
|
import { message, systemConfig } from "@App/pages/store/global";
|
||||||
import { SynchronizeClient, ValueClient } from "@App/app/service/service_worker/client";
|
import { SynchronizeClient, ValueClient } from "@App/app/service/service_worker/client";
|
||||||
@ -615,6 +617,7 @@ function ScriptList() {
|
|||||||
const dealColumns: ColumnProps[] = [];
|
const dealColumns: ColumnProps[] = [];
|
||||||
|
|
||||||
newColumns.forEach((item) => {
|
newColumns.forEach((item) => {
|
||||||
|
console.log(newColumns);
|
||||||
switch (item.width) {
|
switch (item.width) {
|
||||||
case -1:
|
case -1:
|
||||||
break;
|
break;
|
||||||
@ -625,37 +628,39 @@ function ScriptList() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const sortIndex = dealColumns.findIndex((item) => item.key === "sort");
|
const sortIndex = dealColumns.findIndex((item) => item.key === "sort");
|
||||||
|
let SortableItem;
|
||||||
|
if (sortIndex !== -1) {
|
||||||
|
SortableItem = (props: any) => {
|
||||||
|
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: props!.record.uuid });
|
||||||
|
|
||||||
const SortableItem = (props: any) => {
|
const style = {
|
||||||
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: props!.record.uuid });
|
transform: CSS.Transform.toString(transform),
|
||||||
|
transition,
|
||||||
|
};
|
||||||
|
|
||||||
const style = {
|
// 替换排序列,使其可以拖拽
|
||||||
transform: CSS.Transform.toString(transform),
|
props.children[sortIndex + 1] = (
|
||||||
transition,
|
<td
|
||||||
|
className="arco-table-td"
|
||||||
|
style={{
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
key="drag"
|
||||||
|
>
|
||||||
|
<div className="arco-table-cell">
|
||||||
|
<IconMenu
|
||||||
|
style={{
|
||||||
|
cursor: "move",
|
||||||
|
}}
|
||||||
|
{...listeners}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <tr ref={setNodeRef} style={style} {...attributes} {...props} />;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
// 替换排序列,使其可以拖拽
|
|
||||||
props.children[sortIndex + 1] = (
|
|
||||||
<td
|
|
||||||
className="arco-table-td"
|
|
||||||
style={{
|
|
||||||
textAlign: "center",
|
|
||||||
}}
|
|
||||||
key="drag"
|
|
||||||
>
|
|
||||||
<div className="arco-table-cell">
|
|
||||||
<IconMenu
|
|
||||||
style={{
|
|
||||||
cursor: "move",
|
|
||||||
}}
|
|
||||||
{...listeners}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
|
|
||||||
return <tr ref={setNodeRef} style={style} {...attributes} {...props} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
const components: ComponentsProps = {
|
const components: ComponentsProps = {
|
||||||
table: React.forwardRef(SortableWrapper),
|
table: React.forwardRef(SortableWrapper),
|
||||||
@ -703,19 +708,23 @@ function ScriptList() {
|
|||||||
type="primary"
|
type="primary"
|
||||||
size="mini"
|
size="mini"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const uuids: string[] = [];
|
const enableAction = (enable: boolean) => {
|
||||||
|
const uuids = select.map((item) => item.uuid);
|
||||||
|
dispatch(enableLoading({ uuids: uuids, loading: true }));
|
||||||
|
Promise.allSettled(uuids.map((uuid) => scriptClient.enable(uuid, enable))).finally(() => {
|
||||||
|
dispatch(updateEnableStatus({ uuids: uuids, enable: enable }));
|
||||||
|
dispatch(enableLoading({ uuids: uuids, loading: false }));
|
||||||
|
});
|
||||||
|
};
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "enable":
|
case "enable":
|
||||||
select.forEach((item) => {
|
enableAction(true);
|
||||||
dispatch(requestEnableScript({ uuid: item.uuid, enable: true }));
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case "disable":
|
case "disable":
|
||||||
select.forEach((item) => {
|
enableAction(false);
|
||||||
dispatch(requestEnableScript({ uuid: item.uuid, enable: false }));
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case "export":
|
case "export":
|
||||||
|
const uuids: string[] = [];
|
||||||
select.forEach((item) => {
|
select.forEach((item) => {
|
||||||
uuids.push(item.uuid);
|
uuids.push(item.uuid);
|
||||||
});
|
});
|
||||||
|
@ -30,7 +30,7 @@ function Tools() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 获取配置
|
// 获取配置
|
||||||
const loadConfig = async () => {
|
const loadConfig = async () => {
|
||||||
const [backup, vscodeUrl] = await Promise.all([
|
const [backup, vscodeUrl, vscodeReconnect] = await Promise.all([
|
||||||
systemConfig.getBackup(),
|
systemConfig.getBackup(),
|
||||||
systemConfig.getVscodeUrl(),
|
systemConfig.getVscodeUrl(),
|
||||||
systemConfig.getVscodeReconnect(),
|
systemConfig.getVscodeReconnect(),
|
||||||
@ -38,7 +38,7 @@ function Tools() {
|
|||||||
setFilesystemType(backup.filesystem);
|
setFilesystemType(backup.filesystem);
|
||||||
setFilesystemParam(backup.params[backup.filesystem] || {});
|
setFilesystemParam(backup.params[backup.filesystem] || {});
|
||||||
setVscodeUrl(vscodeUrl);
|
setVscodeUrl(vscodeUrl);
|
||||||
setVscodeReconnect(systemConfig.vscodeReconnect);
|
setVscodeReconnect(vscodeReconnect);
|
||||||
};
|
};
|
||||||
loadConfig();
|
loadConfig();
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Script, ScriptAndCode, ScriptCodeDAO, ScriptDAO } from "@App/app/repo/scripts";
|
import { Script, SCRIPT_TYPE_NORMAL, ScriptAndCode, ScriptCodeDAO, ScriptDAO } from "@App/app/repo/scripts";
|
||||||
import CodeEditor from "@App/pages/components/CodeEditor";
|
import CodeEditor from "@App/pages/components/CodeEditor";
|
||||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||||
@ -16,7 +16,7 @@ import { prepareScriptByCode } from "@App/pkg/utils/script";
|
|||||||
import ScriptStorage from "@App/pages/components/ScriptStorage";
|
import ScriptStorage from "@App/pages/components/ScriptStorage";
|
||||||
import ScriptResource from "@App/pages/components/ScriptResource";
|
import ScriptResource from "@App/pages/components/ScriptResource";
|
||||||
import ScriptSetting from "@App/pages/components/ScriptSetting";
|
import ScriptSetting from "@App/pages/components/ScriptSetting";
|
||||||
import { scriptClient } from "@App/pages/store/features/script";
|
import { runtimeClient, scriptClient } from "@App/pages/store/features/script";
|
||||||
import { i18nName } from "@App/locales/locales";
|
import { i18nName } from "@App/locales/locales";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@ -32,11 +32,12 @@ type HotKey = {
|
|||||||
|
|
||||||
const Editor: React.FC<{
|
const Editor: React.FC<{
|
||||||
id: string;
|
id: string;
|
||||||
script: ScriptAndCode;
|
script: Script;
|
||||||
|
code: string;
|
||||||
hotKeys: HotKey[];
|
hotKeys: HotKey[];
|
||||||
callbackEditor: (e: editor.IStandaloneCodeEditor) => void;
|
callbackEditor: (e: editor.IStandaloneCodeEditor) => void;
|
||||||
onChange: (code: string) => void;
|
onChange: (code: string) => void;
|
||||||
}> = ({ id, script, hotKeys, callbackEditor, onChange }) => {
|
}> = ({ id, script, code, hotKeys, callbackEditor, onChange }) => {
|
||||||
const [node, setNode] = useState<{ editor: editor.IStandaloneCodeEditor }>();
|
const [node, setNode] = useState<{ editor: editor.IStandaloneCodeEditor }>();
|
||||||
const ref = useCallback<(node: { editor: editor.IStandaloneCodeEditor }) => void>(
|
const ref = useCallback<(node: { editor: editor.IStandaloneCodeEditor }) => void>(
|
||||||
(inlineNode) => {
|
(inlineNode) => {
|
||||||
@ -77,7 +78,7 @@ const Editor: React.FC<{
|
|||||||
};
|
};
|
||||||
}, [node?.editor]);
|
}, [node?.editor]);
|
||||||
|
|
||||||
return <CodeEditor key={id} id={id} ref={ref} code={script.code} diffCode="" editable />;
|
return <CodeEditor key={id} id={id} ref={ref} code={code} diffCode="" editable />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const WarpEditor = React.memo(Editor, (prev, next) => {
|
const WarpEditor = React.memo(Editor, (prev, next) => {
|
||||||
@ -154,7 +155,8 @@ function ScriptEditor() {
|
|||||||
const [visible, setVisible] = useState<{ [key: string]: boolean }>({});
|
const [visible, setVisible] = useState<{ [key: string]: boolean }>({});
|
||||||
const [editors, setEditors] = useState<
|
const [editors, setEditors] = useState<
|
||||||
{
|
{
|
||||||
script: ScriptAndCode;
|
script: Script;
|
||||||
|
code: string;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
hotKeys: HotKey[];
|
hotKeys: HotKey[];
|
||||||
editor?: editor.IStandaloneCodeEditor;
|
editor?: editor.IStandaloneCodeEditor;
|
||||||
@ -186,57 +188,58 @@ function ScriptEditor() {
|
|||||||
|
|
||||||
const save = (script: Script, e: editor.IStandaloneCodeEditor): Promise<Script> => {
|
const save = (script: Script, e: editor.IStandaloneCodeEditor): Promise<Script> => {
|
||||||
// 解析code生成新的script并更新
|
// 解析code生成新的script并更新
|
||||||
return new Promise(() => {
|
return prepareScriptByCode(e.getValue(), script.origin || "", script.uuid)
|
||||||
prepareScriptByCode(e.getValue(), script.origin || "", script.uuid)
|
.then((prepareScript) => {
|
||||||
.then((prepareScript) => {
|
const newScript = prepareScript.script;
|
||||||
const newScript = prepareScript.script;
|
if (!newScript.name) {
|
||||||
if (!newScript.name) {
|
Message.warning(t("script_name_cannot_be_set_to_empty"));
|
||||||
Message.warning(t("script_name_cannot_be_set_to_empty"));
|
return Promise.reject(new Error("script name cannot be empty"));
|
||||||
return;
|
}
|
||||||
}
|
return scriptClient
|
||||||
scriptClient.install(newScript, e.getValue()).then(
|
.install(newScript, e.getValue())
|
||||||
(update) => {
|
.then((update): Script => {
|
||||||
if (!update) {
|
if (!update) {
|
||||||
Message.success("新建成功,请注意后台脚本不会默认开启");
|
Message.success("新建成功,请注意后台脚本不会默认开启");
|
||||||
// 保存的时候如何左侧没有脚本即新建
|
// 保存的时候如何左侧没有脚本即新建
|
||||||
setScriptList((prev) => {
|
setScriptList((prev) => {
|
||||||
setSelectSciptButtonAndTab(newScript.uuid);
|
setSelectSciptButtonAndTab(newScript.uuid);
|
||||||
return [newScript, ...prev];
|
return [newScript, ...prev];
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setScriptList((prev) => {
|
setScriptList((prev) => {
|
||||||
// eslint-disable-next-line no-shadow, array-callback-return
|
prev.map((script: Script) => {
|
||||||
prev.map((script: Script) => {
|
if (script.uuid === newScript.uuid) {
|
||||||
if (script.uuid === newScript.uuid) {
|
script.name = newScript.name;
|
||||||
script.name = newScript.name;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [...prev];
|
|
||||||
});
|
|
||||||
Message.success("保存成功");
|
|
||||||
}
|
|
||||||
setEditors((prev) => {
|
|
||||||
for (let i = 0; i < prev.length; i += 1) {
|
|
||||||
if (prev[i].script.uuid === newScript.uuid) {
|
|
||||||
prev[i].script.code = prepareScript.scriptCode;
|
|
||||||
prev[i].isChanged = false;
|
|
||||||
prev[i].script.name = newScript.name;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
return [...prev];
|
return [...prev];
|
||||||
});
|
});
|
||||||
},
|
Message.success("保存成功");
|
||||||
(err: any) => {
|
|
||||||
Message.error(`保存失败: ${err}`);
|
|
||||||
}
|
}
|
||||||
);
|
setEditors((prev) => {
|
||||||
})
|
for (let i = 0; i < prev.length; i += 1) {
|
||||||
.catch((err) => {
|
if (prev[i].script.uuid === newScript.uuid) {
|
||||||
Message.error(`错误的脚本代码: ${err}`);
|
prev[i].code = e.getValue();
|
||||||
});
|
prev[i].isChanged = false;
|
||||||
});
|
prev[i].script.name = newScript.name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...prev];
|
||||||
|
});
|
||||||
|
return newScript;
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
Message.error(`保存失败: ${err}`);
|
||||||
|
return Promise.reject(err);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
Message.error(`错误的脚本代码: ${err}`);
|
||||||
|
return Promise.reject(err);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveAs = (script: Script, e: editor.IStandaloneCodeEditor) => {
|
const saveAs = (script: Script, e: editor.IStandaloneCodeEditor) => {
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
chrome.downloads.download(
|
chrome.downloads.download(
|
||||||
@ -287,30 +290,35 @@ function ScriptEditor() {
|
|||||||
title: t("run"),
|
title: t("run"),
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "debug",
|
id: "run",
|
||||||
title: t("debug"),
|
title: t("run"),
|
||||||
hotKey: KeyMod.CtrlCmd | KeyCode.F5,
|
hotKey: KeyMod.CtrlCmd | KeyCode.F5,
|
||||||
hotKeyString: "Ctrl+F5",
|
hotKeyString: "Ctrl+F5",
|
||||||
tooltip: "只有后台脚本/定时脚本才能调试, 且调试模式下不对进行权限校验(例如@connect)",
|
tooltip: "只有后台脚本/定时脚本才能运行",
|
||||||
action: async (script, e) => {
|
action: async (script, e) => {
|
||||||
// 保存更新代码之后再调试
|
// 保存更新代码之后再调试
|
||||||
const newScript = await save(script, e);
|
const newScript = await save(script, e);
|
||||||
|
// 判断脚本类型
|
||||||
|
if (newScript.type === SCRIPT_TYPE_NORMAL) {
|
||||||
|
Message.error("只有后台脚本/定时脚本才能运行");
|
||||||
|
return;
|
||||||
|
}
|
||||||
Message.loading({
|
Message.loading({
|
||||||
id: "debug_script",
|
id: "debug_script",
|
||||||
content: "正在准备脚本资源...",
|
content: "正在准备脚本资源...",
|
||||||
duration: 3000,
|
duration: 3000,
|
||||||
});
|
});
|
||||||
runtimeCtrl
|
runtimeClient
|
||||||
.debugScript(newScript)
|
.runScript(newScript.uuid)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
Message.success({
|
Message.success({
|
||||||
id: "debug_script",
|
id: "debug_script",
|
||||||
content: "构建成功, 可以打开开发者工具在控制台中查看输出",
|
content: "构建成功, 可以在扩展页打开开发者工具在控制台中查看输出",
|
||||||
duration: 3000,
|
duration: 3000,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
LoggerCore.getLogger(Logger.E(err)).debug("debug script error");
|
LoggerCore.logger(Logger.E(err)).debug("run script error");
|
||||||
Message.error({
|
Message.error({
|
||||||
id: "debug_script",
|
id: "debug_script",
|
||||||
content: `构建失败: ${err}`,
|
content: `构建失败: ${err}`,
|
||||||
@ -398,7 +406,8 @@ function ScriptEditor() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
prev.push({
|
prev.push({
|
||||||
script: Object.assign(scripts[i], code),
|
script: scripts[i],
|
||||||
|
code: code?.code || "",
|
||||||
active: true,
|
active: true,
|
||||||
hotKeys,
|
hotKeys,
|
||||||
isChanged: false,
|
isChanged: false,
|
||||||
@ -725,7 +734,8 @@ function ScriptEditor() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
editors.push({
|
editors.push({
|
||||||
script: Object.assign(script, code),
|
script,
|
||||||
|
code: code.code,
|
||||||
active: true,
|
active: true,
|
||||||
hotKeys,
|
hotKeys,
|
||||||
isChanged: false,
|
isChanged: false,
|
||||||
@ -874,6 +884,7 @@ function ScriptEditor() {
|
|||||||
key={`e_${item.script.uuid}`}
|
key={`e_${item.script.uuid}`}
|
||||||
id={`e_${item.script.uuid}`}
|
id={`e_${item.script.uuid}`}
|
||||||
script={item.script}
|
script={item.script}
|
||||||
|
code={item.code}
|
||||||
hotKeys={item.hotKeys}
|
hotKeys={item.hotKeys}
|
||||||
callbackEditor={(e) => {
|
callbackEditor={(e) => {
|
||||||
setEditors((prev) => {
|
setEditors((prev) => {
|
||||||
@ -886,7 +897,7 @@ function ScriptEditor() {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onChange={(code) => {
|
onChange={(code) => {
|
||||||
const isChanged = !(item.script.code === code);
|
const isChanged = !(item.code === code);
|
||||||
if (isChanged !== item.isChanged) {
|
if (isChanged !== item.isChanged) {
|
||||||
setEditors((prev) => {
|
setEditors((prev) => {
|
||||||
prev.forEach((v) => {
|
prev.forEach((v) => {
|
||||||
|
@ -11,12 +11,14 @@ import {
|
|||||||
IconSearch,
|
IconSearch,
|
||||||
} from "@arco-design/web-react/icon";
|
} from "@arco-design/web-react/icon";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { RiMessage2Line } from "react-icons/ri";
|
import { RiMessage2Line, RiZzzFill } from "react-icons/ri";
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import ScriptMenuList from "../components/ScriptMenuList";
|
import ScriptMenuList from "../components/ScriptMenuList";
|
||||||
import { popupClient } from "../store/features/script";
|
import { popupClient } from "../store/features/script";
|
||||||
import { ScriptMenu } from "@App/app/service/service_worker/popup";
|
import { ScriptMenu } from "@App/app/service/service_worker/popup";
|
||||||
|
import { systemConfig } from "../store/global";
|
||||||
|
import { isUserScriptsAvailable } from "@App/pkg/utils/utils";
|
||||||
|
|
||||||
const CollapseItem = Collapse.Item;
|
const CollapseItem = Collapse.Item;
|
||||||
|
|
||||||
@ -30,11 +32,13 @@ function App() {
|
|||||||
const [scriptList, setScriptList] = useState<ScriptMenu[]>([]);
|
const [scriptList, setScriptList] = useState<ScriptMenu[]>([]);
|
||||||
const [backScriptList, setBackScriptList] = useState<ScriptMenu[]>([]);
|
const [backScriptList, setBackScriptList] = useState<ScriptMenu[]>([]);
|
||||||
const [showAlert, setShowAlert] = useState(false);
|
const [showAlert, setShowAlert] = useState(false);
|
||||||
const [notice, setNotice] = useState("");
|
const [checkUpdate, setCheckUpdate] = useState<Parameters<typeof systemConfig.setCheckUpdate>[0]>({
|
||||||
const [isRead, setIsRead] = useState(true);
|
version: ExtVersion,
|
||||||
const [version, setVersion] = useState(ExtVersion);
|
notice: "",
|
||||||
|
isRead: false,
|
||||||
|
});
|
||||||
const [currentUrl, setCurrentUrl] = useState("");
|
const [currentUrl, setCurrentUrl] = useState("");
|
||||||
const [isEnableScript, setIsEnableScript] = useState(localStorage.enable_script !== "false");
|
const [isEnableScript, setIsEnableScript] = useState(true);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
let url: URL | undefined;
|
let url: URL | undefined;
|
||||||
@ -45,22 +49,21 @@ function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// systemManage.getNotice().then((res) => {
|
const loadConfig = async () => {
|
||||||
// if (res) {
|
const [isEnableScript, checkUpdate] = await Promise.all([
|
||||||
// setNotice(res.notice);
|
systemConfig.getEnableScript(),
|
||||||
// setIsRead(res.isRead);
|
systemConfig.getCheckUpdate(),
|
||||||
// }
|
]);
|
||||||
// });
|
setIsEnableScript(isEnableScript);
|
||||||
// systemManage.getVersion().then((res) => {
|
setCheckUpdate(checkUpdate);
|
||||||
// res && setVersion(res);
|
};
|
||||||
// });
|
loadConfig();
|
||||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||||
if (!tabs.length) {
|
if (!tabs.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setCurrentUrl(tabs[0].url || "");
|
setCurrentUrl(tabs[0].url || "");
|
||||||
popupClient.getPopupData({ url: tabs[0].url!, tabId: tabs[0].id! }).then((resp) => {
|
popupClient.getPopupData({ url: tabs[0].url!, tabId: tabs[0].id! }).then((resp) => {
|
||||||
console.log(resp);
|
|
||||||
// 按照开启状态和更新时间排序
|
// 按照开启状态和更新时间排序
|
||||||
const list = resp.scriptList;
|
const list = resp.scriptList;
|
||||||
list.sort((a, b) => {
|
list.sort((a, b) => {
|
||||||
@ -82,141 +85,147 @@ function App() {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<Card
|
<>
|
||||||
size="small"
|
{!isUserScriptsAvailable() && (
|
||||||
title={
|
<Alert type="warning" content={<div dangerouslySetInnerHTML={{ __html: t("develop_mode_guide") }} />} />
|
||||||
<div className="flex justify-between">
|
)}
|
||||||
<span className="text-xl">ScriptCat</span>
|
<Card
|
||||||
<div className="flex flex-row items-center">
|
size="small"
|
||||||
<Switch
|
title={
|
||||||
size="small"
|
<div className="flex justify-between">
|
||||||
checked={isEnableScript}
|
<span className="text-xl">ScriptCat</span>
|
||||||
onChange={(val) => {
|
<div className="flex flex-row items-center">
|
||||||
setIsEnableScript(val);
|
<Switch
|
||||||
if (val) {
|
size="small"
|
||||||
localStorage.enable_script = "true";
|
checked={isEnableScript}
|
||||||
} else {
|
onChange={(val) => {
|
||||||
localStorage.enable_script = "false";
|
setIsEnableScript(val);
|
||||||
}
|
if (val) {
|
||||||
}}
|
systemConfig.setEnableScript(true);
|
||||||
/>
|
} else {
|
||||||
<Button
|
systemConfig.setEnableScript(false);
|
||||||
type="text"
|
}
|
||||||
icon={<IconHome />}
|
|
||||||
iconOnly
|
|
||||||
onClick={() => {
|
|
||||||
// 用a链接的方式,vivaldi竟然会直接崩溃
|
|
||||||
window.open("/src/options.html", "_blank");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Badge count={isRead ? 0 : 1} dot offset={[-8, 6]}>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
icon={<IconNotification />}
|
|
||||||
iconOnly
|
|
||||||
onClick={() => {
|
|
||||||
setShowAlert(!showAlert);
|
|
||||||
setIsRead(true);
|
|
||||||
systemManage.setRead(true);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Badge>
|
<Button
|
||||||
<Dropdown
|
type="text"
|
||||||
droplist={
|
icon={<IconHome />}
|
||||||
<Menu
|
iconOnly
|
||||||
style={{
|
onClick={() => {
|
||||||
maxHeight: "none",
|
// 用a链接的方式,vivaldi竟然会直接崩溃
|
||||||
|
window.open("/src/options.html", "_blank");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Badge count={checkUpdate.isRead ? 0 : 1} dot offset={[-8, 6]}>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<IconNotification />}
|
||||||
|
iconOnly
|
||||||
|
onClick={() => {
|
||||||
|
setShowAlert(!showAlert);
|
||||||
|
checkUpdate.isRead = true;
|
||||||
|
setCheckUpdate(checkUpdate);
|
||||||
|
systemConfig.setCheckUpdate(checkUpdate);
|
||||||
}}
|
}}
|
||||||
onClickMenuItem={async (key) => {
|
/>
|
||||||
switch (key) {
|
</Badge>
|
||||||
case "newScript":
|
<Dropdown
|
||||||
await chrome.storage.local.set({
|
droplist={
|
||||||
activeTabUrl: {
|
<Menu
|
||||||
url: currentUrl,
|
style={{
|
||||||
},
|
maxHeight: "none",
|
||||||
});
|
}}
|
||||||
window.open("/src/options.html#/script/editor?target=initial", "_blank");
|
onClickMenuItem={async (key) => {
|
||||||
break;
|
switch (key) {
|
||||||
default:
|
case "newScript":
|
||||||
window.open(key, "_blank");
|
await chrome.storage.local.set({
|
||||||
break;
|
activeTabUrl: {
|
||||||
}
|
url: currentUrl,
|
||||||
}}
|
},
|
||||||
>
|
});
|
||||||
<Menu.Item key="newScript">
|
window.open("/src/options.html#/script/editor?target=initial", "_blank");
|
||||||
<IconPlus style={iconStyle} />
|
break;
|
||||||
{t("create_script")}
|
default:
|
||||||
</Menu.Item>
|
window.open(key, "_blank");
|
||||||
<Menu.Item key={`https://scriptcat.org/search?domain=${url && url.host}`}>
|
break;
|
||||||
<IconSearch style={iconStyle} />
|
}
|
||||||
{t("get_script")}
|
}}
|
||||||
</Menu.Item>
|
>
|
||||||
<Menu.Item key="https://github.com/scriptscat/scriptcat/issues">
|
<Menu.Item key="newScript">
|
||||||
<IconBug style={iconStyle} />
|
<IconPlus style={iconStyle} />
|
||||||
{t("report_issue")}
|
{t("create_script")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="https://docs.scriptcat.org/">
|
<Menu.Item key={`https://scriptcat.org/search?domain=${url && url.host}`}>
|
||||||
<IconBook style={iconStyle} />
|
<IconSearch style={iconStyle} />
|
||||||
{t("project_docs")}
|
{t("get_script")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="https://bbs.tampermonkey.net.cn/">
|
<Menu.Item key="https://github.com/scriptscat/scriptcat/issues">
|
||||||
<RiMessage2Line style={iconStyle} />
|
<IconBug style={iconStyle} />
|
||||||
{t("community")}
|
{t("report_issue")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="https://github.com/scriptscat/scriptcat">
|
<Menu.Item key="https://docs.scriptcat.org/">
|
||||||
<IconGithub style={iconStyle} />
|
<IconBook style={iconStyle} />
|
||||||
GitHub
|
{t("project_docs")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu>
|
<Menu.Item key="https://bbs.tampermonkey.net.cn/">
|
||||||
}
|
<RiMessage2Line style={iconStyle} />
|
||||||
trigger="click"
|
{t("community")}
|
||||||
>
|
</Menu.Item>
|
||||||
<Button type="text" icon={<IconMoreVertical />} iconOnly />
|
<Menu.Item key="https://github.com/scriptscat/scriptcat">
|
||||||
</Dropdown>
|
<IconGithub style={iconStyle} />
|
||||||
|
GitHub
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
<Button type="text" icon={<IconMoreVertical />} iconOnly />
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
bodyStyle={{ padding: 0 }}
|
||||||
bodyStyle={{ padding: 0 }}
|
>
|
||||||
>
|
<Alert
|
||||||
<Alert
|
style={{ display: showAlert ? "flex" : "none" }}
|
||||||
style={{ marginBottom: 20, display: showAlert ? "flex" : "none" }}
|
type="info"
|
||||||
type="info"
|
content={<div dangerouslySetInnerHTML={{ __html: checkUpdate.notice || "" }} />}
|
||||||
content={<div dangerouslySetInnerHTML={{ __html: notice }} />}
|
/>
|
||||||
/>
|
<Collapse bordered={false} defaultActiveKey={["script", "background"]} style={{ maxWidth: 640 }}>
|
||||||
<Collapse bordered={false} defaultActiveKey={["script", "background"]} style={{ maxWidth: 640 }}>
|
<CollapseItem
|
||||||
<CollapseItem
|
header={t("current_page_scripts")}
|
||||||
header={t("current_page_scripts")}
|
name="script"
|
||||||
name="script"
|
style={{ padding: "0" }}
|
||||||
style={{ padding: "0" }}
|
contentStyle={{ padding: "0" }}
|
||||||
contentStyle={{ padding: "0" }}
|
|
||||||
>
|
|
||||||
<ScriptMenuList script={scriptList} isBackscript={false} currentUrl={currentUrl} />
|
|
||||||
</CollapseItem>
|
|
||||||
|
|
||||||
<CollapseItem
|
|
||||||
header={t("enabled_background_scripts")}
|
|
||||||
name="background"
|
|
||||||
style={{ padding: "0" }}
|
|
||||||
contentStyle={{ padding: "0" }}
|
|
||||||
>
|
|
||||||
<ScriptMenuList script={backScriptList} isBackscript currentUrl={currentUrl} />
|
|
||||||
</CollapseItem>
|
|
||||||
</Collapse>
|
|
||||||
<div className="flex flex-row arco-card-header !h-6">
|
|
||||||
<span className="text-[12px] font-500">{`v${ExtVersion}`}</span>
|
|
||||||
{semver.lt(ExtVersion, version) && (
|
|
||||||
<span
|
|
||||||
onClick={() => {
|
|
||||||
window.open(`https://github.com/scriptscat/scriptcat/releases/tag/v${version}`);
|
|
||||||
}}
|
|
||||||
className="text-1 font-500"
|
|
||||||
style={{ cursor: "pointer" }}
|
|
||||||
>
|
>
|
||||||
{t("popup.new_version_available")}
|
<ScriptMenuList script={scriptList} isBackscript={false} currentUrl={currentUrl} />
|
||||||
</span>
|
</CollapseItem>
|
||||||
)}
|
|
||||||
</div>
|
<CollapseItem
|
||||||
</Card>
|
header={t("enabled_background_scripts")}
|
||||||
|
name="background"
|
||||||
|
style={{ padding: "0" }}
|
||||||
|
contentStyle={{ padding: "0" }}
|
||||||
|
>
|
||||||
|
<ScriptMenuList script={backScriptList} isBackscript currentUrl={currentUrl} />
|
||||||
|
</CollapseItem>
|
||||||
|
</Collapse>
|
||||||
|
<div className="flex flex-row arco-card-header !h-6">
|
||||||
|
<span className="text-[12px] font-500">{`v${ExtVersion}`}</span>
|
||||||
|
{semver.lt(ExtVersion, checkUpdate.version) && (
|
||||||
|
<span
|
||||||
|
onClick={() => {
|
||||||
|
window.open(`https://github.com/scriptscat/scriptcat/releases/tag/v${checkUpdate.version}`);
|
||||||
|
}}
|
||||||
|
className="text-1 font-500"
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
>
|
||||||
|
{t("popup.new_version_available")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,18 +2,18 @@ import React from "react";
|
|||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import App from "./App.tsx";
|
import App from "./App.tsx";
|
||||||
import LoggerCore from "@App/app/logger/core.ts";
|
import LoggerCore from "@App/app/logger/core.ts";
|
||||||
import { LoggerDAO } from "@App/app/repo/logger.ts";
|
|
||||||
import DBWriter from "@App/app/logger/db_writer.ts";
|
|
||||||
import "@arco-design/web-react/dist/css/arco.css";
|
import "@arco-design/web-react/dist/css/arco.css";
|
||||||
import "@App/locales/locales";
|
import "@App/locales/locales";
|
||||||
import "@App/index.css";
|
import "@App/index.css";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { store } from "../store/store.ts";
|
import { store } from "../store/store.ts";
|
||||||
|
import MessageWriter from "@App/app/logger/message_writer.ts";
|
||||||
|
import { message } from "../store/global.ts";
|
||||||
|
|
||||||
// 初始化日志组件
|
// 初始化日志组件
|
||||||
const loggerCore = new LoggerCore({
|
const loggerCore = new LoggerCore({
|
||||||
writer: new DBWriter(new LoggerDAO()),
|
writer: new MessageWriter(message),
|
||||||
labels: { env: "install" },
|
labels: { env: "install" },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -102,6 +102,22 @@ export const scriptSlice = createAppSlice({
|
|||||||
script.runStatus = action.payload.runStatus;
|
script.runStatus = action.payload.runStatus;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updateEnableStatus: (state, action: PayloadAction<{ uuids: string[]; enable: boolean }>) => {
|
||||||
|
state.scripts = state.scripts.map((s) => {
|
||||||
|
if (action.payload.uuids.includes(s.uuid)) {
|
||||||
|
s.status = action.payload.enable ? SCRIPT_STATUS_ENABLE : SCRIPT_STATUS_DISABLE;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
enableLoading(state, action: PayloadAction<{ uuids: string[]; loading: boolean }>) {
|
||||||
|
state.scripts = state.scripts.map((s) => {
|
||||||
|
if (action.payload.uuids.includes(s.uuid)) {
|
||||||
|
s.enableLoading = action.payload.loading;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder
|
builder
|
||||||
@ -144,6 +160,6 @@ export const scriptSlice = createAppSlice({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { sortScript, upsertScript, deleteScript } = scriptSlice.actions;
|
export const { sortScript, upsertScript, deleteScript, enableLoading, updateEnableStatus } = scriptSlice.actions;
|
||||||
|
|
||||||
export const { selectScripts } = scriptSlice.selectors;
|
export const { selectScripts } = scriptSlice.selectors;
|
||||||
|
@ -5,6 +5,7 @@ import { FileSystemType } from "@Packages/filesystem/factory";
|
|||||||
import { MessageQueue } from "@Packages/message/message_queue";
|
import { MessageQueue } from "@Packages/message/message_queue";
|
||||||
import i18n from "@App/locales/locales";
|
import i18n from "@App/locales/locales";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { ExtVersion } from "@App/app/const";
|
||||||
|
|
||||||
export const SystamConfigChange = "systemConfigChange";
|
export const SystamConfigChange = "systemConfigChange";
|
||||||
|
|
||||||
@ -34,8 +35,11 @@ export class SystemConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addListener(key: string, callback: (value: any) => void) {
|
addListener(key: string, callback: (value: any) => void) {
|
||||||
this.mq.subscribe(key, (msg) => {
|
this.mq.subscribe(SystamConfigChange, (data: { key: string; value: string }) => {
|
||||||
const { value } = msg;
|
if (data.key !== key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { value } = data;
|
||||||
callback(value);
|
callback(value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -65,9 +69,7 @@ export class SystemConfig {
|
|||||||
|
|
||||||
public set(key: string, val: any) {
|
public set(key: string, val: any) {
|
||||||
this.cache.set(key, val);
|
this.cache.set(key, val);
|
||||||
this.storage.set(key, val).then(() => {
|
this.storage.set(key, val);
|
||||||
console.log(chrome.runtime.lastError, val);
|
|
||||||
});
|
|
||||||
// 发送消息通知更新
|
// 发送消息通知更新
|
||||||
this.mq.publish(SystamConfigChange, {
|
this.mq.publish(SystamConfigChange, {
|
||||||
key,
|
key,
|
||||||
@ -226,18 +228,19 @@ export class SystemConfig {
|
|||||||
this.set("menu_expand_num", val);
|
this.set("menu_expand_num", val);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLanguage() {
|
async getLanguage(acceptLanguages?: string[]): Promise<string> {
|
||||||
const defaultLanguage = await new Promise<string>((resolve) => {
|
const defaultLanguage = await new Promise<string>(async (resolve) => {
|
||||||
chrome.i18n.getAcceptLanguages((lngs) => {
|
if (!acceptLanguages) {
|
||||||
// 遍历数组寻找匹配语言
|
acceptLanguages = await chrome.i18n.getAcceptLanguages();
|
||||||
for (let i = 0; i < lngs.length; i += 1) {
|
}
|
||||||
const lng = lngs[i];
|
// 遍历数组寻找匹配语言
|
||||||
if (i18n.hasResourceBundle(lng, "translation")) {
|
for (let i = 0; i < acceptLanguages.length; i += 1) {
|
||||||
resolve(lng);
|
const lng = acceptLanguages[i];
|
||||||
break;
|
if (i18n.hasResourceBundle(lng, "translation")) {
|
||||||
}
|
resolve(lng);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
return this.get("language", defaultLanguage || chrome.i18n.getUILanguage());
|
return this.get("language", defaultLanguage || chrome.i18n.getUILanguage());
|
||||||
}
|
}
|
||||||
@ -247,4 +250,28 @@ export class SystemConfig {
|
|||||||
i18n.changeLanguage(value);
|
i18n.changeLanguage(value);
|
||||||
dayjs.locale(value.toLocaleLowerCase());
|
dayjs.locale(value.toLocaleLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCheckUpdate(data: { notice: string; version: string; isRead: boolean }) {
|
||||||
|
this.set("check_update", {
|
||||||
|
notice: data.notice,
|
||||||
|
version: data.version,
|
||||||
|
isRead: data.isRead,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getCheckUpdate(): Promise<Parameters<typeof this.setCheckUpdate>[0]> {
|
||||||
|
return this.get("check_update", {
|
||||||
|
notice: "",
|
||||||
|
isRead: false,
|
||||||
|
version: ExtVersion,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnableScript(enable: boolean) {
|
||||||
|
this.set("enable_script", enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
getEnableScript(): Promise<boolean> {
|
||||||
|
return this.get("enable_script", true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { dealPatternMatches, parsePatternMatchesURL, UrlMatch } from "./match";
|
import { dealPatternMatches, parsePatternMatchesURL, UrlMatch } from "./match";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
// https://developer.chrome.com/docs/extensions/mv3/match_patterns/
|
// https://developer.chrome.com/docs/extensions/mv3/match_patterns/
|
||||||
describe("UrlMatch-google", () => {
|
describe("UrlMatch-google", () => {
|
||||||
@ -39,16 +40,10 @@ describe("UrlMatch-google", () => {
|
|||||||
describe("UrlMatch-google-error", () => {
|
describe("UrlMatch-google-error", () => {
|
||||||
const url = new UrlMatch<string>();
|
const url = new UrlMatch<string>();
|
||||||
it("error-1", () => {
|
it("error-1", () => {
|
||||||
expect(() => {
|
|
||||||
url.add("https://*foo/bar", "ok1");
|
|
||||||
}).toThrow(Error);
|
|
||||||
});
|
|
||||||
// 从v0.17.0开始允许这种
|
|
||||||
it("error-2", () => {
|
|
||||||
url.add("https://foo.*.bar/baz", "ok1");
|
url.add("https://foo.*.bar/baz", "ok1");
|
||||||
expect(url.match("https://foo.api.bar/baz")).toEqual(["ok1"]);
|
expect(url.match("https://foo.api.bar/baz")).toEqual(["ok1"]);
|
||||||
});
|
});
|
||||||
it("error-3", () => {
|
it("error-2", () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
url.add("http:/bar", "ok1");
|
url.add("http:/bar", "ok1");
|
||||||
}).toThrow(Error);
|
}).toThrow(Error);
|
||||||
@ -77,6 +72,13 @@ describe("UrlMatch-search", () => {
|
|||||||
expect(url.match("http://api.bar.example.com/")).toEqual(["ok1"]);
|
expect(url.match("http://api.bar.example.com/")).toEqual(["ok1"]);
|
||||||
expect(url.match("http://api.example.com/")).toEqual([]);
|
expect(url.match("http://api.example.com/")).toEqual([]);
|
||||||
});
|
});
|
||||||
|
it("*://example*/*/example.path*", () => {
|
||||||
|
const url = new UrlMatch<string>();
|
||||||
|
url.add("*://example*/*/example.path*", "ok1");
|
||||||
|
expect(url.match("https://example.com/foo/example.path")).toEqual(["ok1"]);
|
||||||
|
expect(url.match("https://example.com/foo/bar/example.path")).toEqual(["ok1"]);
|
||||||
|
expect(url.match("https://example.com/foo/bar/example.path2")).toEqual(["ok1"]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("UrlMatch-port1", () => {
|
describe("UrlMatch-port1", () => {
|
||||||
@ -177,6 +179,12 @@ describe("parsePatternMatchesURL", () => {
|
|||||||
host: "127.0.0.1",
|
host: "127.0.0.1",
|
||||||
path: "",
|
path: "",
|
||||||
});
|
});
|
||||||
|
const matches4 = parsePatternMatchesURL("*://*/*");
|
||||||
|
expect(matches4).toEqual({
|
||||||
|
scheme: "*",
|
||||||
|
host: "*",
|
||||||
|
path: "*",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it("search", () => {
|
it("search", () => {
|
||||||
// 会忽略掉search部分
|
// 会忽略掉search部分
|
||||||
@ -191,7 +199,7 @@ describe("parsePatternMatchesURL", () => {
|
|||||||
const matches = parsePatternMatchesURL("*://www.example.com*");
|
const matches = parsePatternMatchesURL("*://www.example.com*");
|
||||||
expect(matches).toEqual({
|
expect(matches).toEqual({
|
||||||
scheme: "*",
|
scheme: "*",
|
||||||
host: "www.example.com",
|
host: "*",
|
||||||
path: "*",
|
path: "*",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -203,4 +211,24 @@ describe("parsePatternMatchesURL", () => {
|
|||||||
path: "*",
|
path: "*",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it("一些怪异的情况", () => {
|
||||||
|
let matches = parsePatternMatchesURL("*://*./*");
|
||||||
|
expect(matches).toEqual({
|
||||||
|
scheme: "*",
|
||||||
|
host: "*",
|
||||||
|
path: "*",
|
||||||
|
});
|
||||||
|
matches = parsePatternMatchesURL("*://example*/*");
|
||||||
|
expect(matches).toEqual({
|
||||||
|
scheme: "*",
|
||||||
|
host: "*",
|
||||||
|
path: "*",
|
||||||
|
});
|
||||||
|
matches = parsePatternMatchesURL("http*://*.example.com/*");
|
||||||
|
expect(matches).toEqual({
|
||||||
|
scheme: "*",
|
||||||
|
host: "*.example.com",
|
||||||
|
path: "*",
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -64,20 +64,11 @@ export default class Match<T> {
|
|||||||
let pos = u.host.indexOf("*");
|
let pos = u.host.indexOf("*");
|
||||||
if (u.host === "*" || u.host === "**") {
|
if (u.host === "*" || u.host === "**") {
|
||||||
pos = -1;
|
pos = -1;
|
||||||
} else if (u.host.endsWith("*")) {
|
|
||||||
// 处理*结尾
|
|
||||||
if (!u.host.endsWith(":*")) {
|
|
||||||
u.host = u.host.substring(0, u.host.length - 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
u.host = u.host.replace(/\*/g, "[^/]*?");
|
u.host = u.host.replace(/\*/g, "[^/]*?");
|
||||||
// 处理 *.开头
|
// 处理 *.开头
|
||||||
if (u.host.startsWith("[^/]*?.")) {
|
if (u.host.startsWith("[^/]*?.")) {
|
||||||
u.host = `([^/]*?\\.?)${u.host.substring(7)}`;
|
u.host = `([^/]*?\\.?)${u.host.substring(7)}`;
|
||||||
} else if (pos !== -1) {
|
|
||||||
if (u.host.indexOf(".") === -1) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 处理顶域
|
// 处理顶域
|
||||||
if (u.host.endsWith("tld")) {
|
if (u.host.endsWith("tld")) {
|
||||||
@ -223,6 +214,7 @@ export interface PatternMatchesUrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 解析URL, 根据https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns?hl=zh-cn进行处理
|
// 解析URL, 根据https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns?hl=zh-cn进行处理
|
||||||
|
// 将一些异常情况直接转为通配,用最大的范围去注册userScript,在执行的时候再用UrlMatch去匹配过滤
|
||||||
export function parsePatternMatchesURL(
|
export function parsePatternMatchesURL(
|
||||||
url: string,
|
url: string,
|
||||||
options?: {
|
options?: {
|
||||||
@ -251,6 +243,9 @@ export function parsePatternMatchesURL(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (result) {
|
if (result) {
|
||||||
|
if (result.scheme === "http*") {
|
||||||
|
result.scheme = "*";
|
||||||
|
}
|
||||||
if (result.host !== "*") {
|
if (result.host !== "*") {
|
||||||
// *开头但是不是*.的情况
|
// *开头但是不是*.的情况
|
||||||
if (result.host.startsWith("*")) {
|
if (result.host.startsWith("*")) {
|
||||||
@ -261,6 +256,10 @@ export function parsePatternMatchesURL(
|
|||||||
}
|
}
|
||||||
// 结尾是*的情况
|
// 结尾是*的情况
|
||||||
if (result.host.endsWith("*")) {
|
if (result.host.endsWith("*")) {
|
||||||
|
result.host = "*";
|
||||||
|
}
|
||||||
|
// 结尾是.的情况
|
||||||
|
if (result.host.endsWith(".")) {
|
||||||
result.host = result.host.slice(0, -1);
|
result.host = result.host.slice(0, -1);
|
||||||
}
|
}
|
||||||
// 处理 www.*.example.com 的情况为 *.example.com
|
// 处理 www.*.example.com 的情况为 *.example.com
|
||||||
|
@ -264,3 +264,14 @@ export function errorMsg(e: any): string {
|
|||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isUserScriptsAvailable() {
|
||||||
|
try {
|
||||||
|
// Property access which throws if developer mode is not enabled.
|
||||||
|
chrome.userScripts;
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
// Not available.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -48,14 +48,14 @@ async function setupOffscreenDocument() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
// 初始化管理器
|
||||||
|
const message = new ExtensionMessage(true);
|
||||||
// 初始化日志组件
|
// 初始化日志组件
|
||||||
const loggerCore = new LoggerCore({
|
const loggerCore = new LoggerCore({
|
||||||
writer: new DBWriter(new LoggerDAO()),
|
writer: new DBWriter(new LoggerDAO()),
|
||||||
labels: { env: "service_worker" },
|
labels: { env: "service_worker" },
|
||||||
});
|
});
|
||||||
loggerCore.logger().debug("service worker start");
|
loggerCore.logger().debug("service worker start");
|
||||||
// 初始化管理器
|
|
||||||
const message = new ExtensionMessage(true);
|
|
||||||
const server = new Server("serviceWorker", message);
|
const server = new Server("serviceWorker", message);
|
||||||
const messageQueue = new MessageQueue();
|
const messageQueue = new MessageQueue();
|
||||||
const manager = new ServiceWorkerManager(server, messageQueue, new ServiceWorkerMessageSend());
|
const manager = new ServiceWorkerManager(server, messageQueue, new ServiceWorkerMessageSend());
|
||||||
|
@ -139,21 +139,6 @@ declare function GM_cookie(
|
|||||||
ondone: (cookie: GMTypes.Cookie[], error: unknown | undefined) => void
|
ondone: (cookie: GMTypes.Cookie[], error: unknown | undefined) => void
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 可以通过GM_addValueChangeListener获取tabid
|
|
||||||
* 再通过tabid(前后端通信可能用到,ValueChangeListener会返回tabid),获取storeid,后台脚本用.
|
|
||||||
* 请注意这是一个实验性质的API,后续可能会改变
|
|
||||||
* @param tabid 页面的tabid
|
|
||||||
* @param ondone 完成事件
|
|
||||||
* @param callback.storeid 该页面的storeid,可以给GM_cookie使用
|
|
||||||
* @param callback.error 错误信息
|
|
||||||
* @deprecated 已废弃,请使用GM_cookie("store", tabid)替代
|
|
||||||
*/
|
|
||||||
declare function GM_getCookieStore(
|
|
||||||
tabid: number,
|
|
||||||
ondone: (storeId: number | undefined, error: unknown | undefined) => void
|
|
||||||
): void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置浏览器代理
|
* 设置浏览器代理
|
||||||
* @deprecated 正式版中已废弃,后续可能会在beta版本中添加
|
* @deprecated 正式版中已废弃,后续可能会在beta版本中添加
|
||||||
@ -289,10 +274,7 @@ declare namespace CATType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare namespace GMTypes {
|
declare namespace GMTypes {
|
||||||
/*
|
type CookieAction = "list" | "delete" | "set";
|
||||||
* store为获取隐身窗口之类的cookie,这是一个实验性质的API,后续可能会改变
|
|
||||||
*/
|
|
||||||
type CookieAction = "list" | "delete" | "set" | "store";
|
|
||||||
|
|
||||||
type LoggerLevel = "debug" | "info" | "warn" | "error";
|
type LoggerLevel = "debug" | "info" | "warn" | "error";
|
||||||
|
|
||||||
@ -308,17 +290,13 @@ declare namespace GMTypes {
|
|||||||
path?: string;
|
path?: string;
|
||||||
secure?: boolean;
|
secure?: boolean;
|
||||||
session?: boolean;
|
session?: boolean;
|
||||||
storeId?: string;
|
|
||||||
httpOnly?: boolean;
|
httpOnly?: boolean;
|
||||||
expirationDate?: number;
|
expirationDate?: number;
|
||||||
// store用
|
|
||||||
tabId?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Cookie {
|
interface Cookie {
|
||||||
domain: string;
|
domain: string;
|
||||||
name: string;
|
name: string;
|
||||||
storeId: string;
|
|
||||||
value: string;
|
value: string;
|
||||||
session: boolean;
|
session: boolean;
|
||||||
hostOnly: boolean;
|
hostOnly: boolean;
|
||||||
@ -341,7 +319,7 @@ declare namespace GMTypes {
|
|||||||
active?: boolean;
|
active?: boolean;
|
||||||
insert?: boolean;
|
insert?: boolean;
|
||||||
setParent?: boolean;
|
setParent?: boolean;
|
||||||
useOpen?: boolean; // 这是一个实验性/不兼容其他管理器/不兼容Firefox的功能
|
useOpen?: boolean; // 这是一个实验性/不兼容其他管理器/不兼容Firefox的功能 表示使用window.open打开新窗口 #178
|
||||||
}
|
}
|
||||||
|
|
||||||
interface XHRResponse {
|
interface XHRResponse {
|
||||||
@ -384,7 +362,7 @@ declare namespace GMTypes {
|
|||||||
user?: string;
|
user?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
nocache?: boolean;
|
nocache?: boolean;
|
||||||
maxRedirects?: number;
|
redirect?: "follow" | "error" | "manual";// 为了与tm保持一致, 在v0.17.0后废弃maxRedirects, 使用redirect替代, 会强制使用fetch模式
|
||||||
|
|
||||||
onload?: Listener<XHRResponse>;
|
onload?: Listener<XHRResponse>;
|
||||||
onloadstart?: Listener<XHRResponse>;
|
onloadstart?: Listener<XHRResponse>;
|
||||||
|
1
src/types/main.d.ts
vendored
1
src/types/main.d.ts
vendored
@ -31,7 +31,6 @@ declare namespace GMSend {
|
|||||||
nocache?: boolean;
|
nocache?: boolean;
|
||||||
dataType?: "FormData" | "Blob";
|
dataType?: "FormData" | "Blob";
|
||||||
redirect?: "follow" | "error" | "manual";
|
redirect?: "follow" | "error" | "manual";
|
||||||
maxRedirects?: number; // 为了与tm保持一致, 在v0.17.0后废弃, 使用redirect替代
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface XHRFormData {
|
interface XHRFormData {
|
||||||
|
2
src/types/scriptcat.d.ts
vendored
2
src/types/scriptcat.d.ts
vendored
@ -362,7 +362,7 @@ declare namespace GMTypes {
|
|||||||
user?: string;
|
user?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
nocache?: boolean;
|
nocache?: boolean;
|
||||||
maxRedirects?: number;
|
redirect?: "follow" | "error" | "manual";// 为了与tm保持一致, 在v0.17.0后废弃maxRedirects, 使用redirect替代, 会强制使用fetch模式
|
||||||
|
|
||||||
onload?: Listener<XHRResponse>;
|
onload?: Listener<XHRResponse>;
|
||||||
onloadstart?: Listener<XHRResponse>;
|
onloadstart?: Listener<XHRResponse>;
|
||||||
|
Reference in New Issue
Block a user