This commit is contained in:
2025-04-16 14:01:26 +08:00
parent 071e728f06
commit 1a531dfad5
11 changed files with 259 additions and 35 deletions

View File

@@ -0,0 +1,17 @@
import { Repo } from "./repo";
export interface LocalStorageItem {
key: string;
value: any;
}
// 由于service worker不能使用localStorage这里新建一个类来实现localStorage的功能
export class LocalStorageDAO extends Repo<LocalStorageItem> {
constructor() {
super("localStorage");
}
save(value: LocalStorageItem) {
return super._save(value.key, value);
}
}

View File

@@ -318,6 +318,52 @@ export default class GMApi {
this.sendMessage("GM_unregisterMenuCommand", [id]);
}
@GMContext.API()
CAT_userConfig() {
return this.sendMessage("CAT_userConfig", []);
}
@GMContext.API({
depend: ["CAT_fetchBlob", "CAT_createBlobUrl"],
})
async CAT_fileStorage(action: "list" | "download" | "upload" | "delete" | "config", details: any) {
if (action === "config") {
this.sendMessage("CAT_fileStorage", ["config"]);
return;
}
const sendDetails: { [key: string]: string } = {
baseDir: details.baseDir || "",
path: details.path || "",
filename: details.filename,
file: details.file,
};
if (action === "upload") {
const url = await this.CAT_createBlobUrl(details.data);
sendDetails.data = url;
}
this.sendMessage("CAT_fileStorage", [action, sendDetails]).then(async (resp: { action: string; data: any }) => {
switch (resp.action) {
case "onload": {
if (action === "download") {
// 读取blob
const blob = await this.CAT_fetchBlob(resp.data);
details.onload && details.onload(blob);
} else {
details.onload && details.onload(resp.data);
}
break;
}
case "error": {
if (typeof resp.data.code === "undefined") {
details.onerror && details.onerror({ code: -1, message: resp.data.message });
return;
}
details.onerror && details.onerror(resp.data);
}
}
});
}
// 用于脚本跨域请求,需要@connect domain指定允许的域名
@GMContext.API({
depend: ["CAT_fetchBlob", "CAT_createBlobUrl", "CAT_fetchDocument"],

View File

@@ -10,10 +10,13 @@ import EventEmitter from "eventemitter3";
import { MessageQueue } from "@Packages/message/message_queue";
import { RuntimeService } from "./runtime";
import { getIcon, isFirefox } from "@App/pkg/utils/utils";
import { PopupService } from "./popup";
import { act } from "react";
import { MockMessageConnect } from "@Packages/message/mock_message";
import i18next, { i18nName } from "@App/locales/locales";
import { SystemConfig } from "@App/pkg/config/config";
import FileSystemFactory from "@Packages/filesystem/factory";
import FileSystem from "@Packages/filesystem/filesystem";
import { isWarpTokenError } from "@Packages/filesystem/error";
import { joinPath } from "@Packages/filesystem/utils";
// GMApi,处理脚本的GM API调用请求
@@ -71,6 +74,7 @@ export default class GMApi {
scriptDAO: ScriptDAO = new ScriptDAO();
constructor(
private systemConfig: SystemConfig,
private permissionVerify: PermissionVerify,
private group: Group,
private send: MessageSend,
@@ -248,6 +252,119 @@ export default class GMApi {
});
}
@PermissionVerify.API()
CAT_userConfig(request: Request) {
chrome.tabs.create({
url: `/src/options.html#/?userConfig=${request.uuid}`,
active: true,
});
}
@PermissionVerify.API({
confirm: (request: Request) => {
const [action, details] = request.params;
if (action === "config") {
return Promise.resolve(true);
}
const dir = details.baseDir ? details.baseDir : request.script.uuid;
const metadata: { [key: string]: string } = {};
metadata[i18next.t("script_name")] = i18nName(request.script);
return Promise.resolve({
permission: "file_storage",
permissionValue: dir,
title: i18next.t("script_operation_title"),
metadata,
describe: i18next.t("script_operation_description", { dir }),
wildcard: false,
permissionContent: i18next.t("script_permission_content"),
} as ConfirmParam);
},
})
async CAT_fileStorage(request: Request, sender: GetSender): Promise<{ action: string; data: any } | boolean> {
const [action, details] = request.params;
if (action === "config") {
chrome.tabs.create({
url: `/src/options.html#/setting`,
active: true,
});
return true;
}
const fsConfig = await this.systemConfig.getCatFileStorage();
if (fsConfig.status === "unset") {
return { action: "error", data: { code: 1, error: "file storage is unset" } };
}
if (fsConfig.status === "error") {
return { action: "error", data: { code: 2, error: "file storage is error" } };
}
let fs: FileSystem;
const baseDir = `ScriptCat/app/${details.baseDir ? details.baseDir : request.script.uuid}`;
try {
fs = await FileSystemFactory.create(fsConfig.filesystem, fsConfig.params[fsConfig.filesystem]);
await FileSystemFactory.mkdirAll(fs, baseDir);
fs = await fs.openDir(baseDir);
} catch (e: any) {
if (isWarpTokenError(e)) {
fsConfig.status = "error";
this.systemConfig.setCatFileStorage(fsConfig);
return { action: "error", data: { code: 2, error: e.error.message } };
}
return { action: "error", data: { code: 8, error: e.message } };
}
switch (action) {
case "list":
try {
const list = await fs.list();
list.forEach((file) => {
(<any>file).absPath = file.path;
file.path = joinPath(file.path.substring(file.path.indexOf(baseDir) + baseDir.length));
});
return { action: "onload", data: list };
} catch (e: any) {
return { action: "error", data: { code: 3, error: e.message } };
}
case "upload":
try {
const w = await fs.create(details.path);
await w.write(await (await fetch(<string>details.data)).blob());
return { action: "onload", data: true };
} catch (e: any) {
return { action: "error", data: { code: 4, error: e.message } };
}
case "download":
try {
const info = <CATType.FileStorageFileInfo>details.file;
fs = await fs.openDir(`${info.path}`);
const r = await fs.open({
fsid: (<any>info).fsid,
name: info.name,
path: info.absPath,
size: info.size,
digest: info.digest,
createtime: info.createtime,
updatetime: info.updatetime,
});
const blob = await r.read("blob");
const url = URL.createObjectURL(blob);
setTimeout(() => {
URL.revokeObjectURL(url);
}, 6000);
return { action: "onload", data: url };
} catch (e: any) {
return { action: "error", data: { code: 5, error: e.message } };
}
break;
case "delete":
try {
await fs.delete(`${details.path}`);
return { action: "onload", data: true };
} catch (e: any) {
return { action: "error", data: { code: 6, error: e.message } };
}
default:
throw new Error("action is not supported");
}
}
// 根据header生成dnr规则
async buildDNRRule(reqeustId: number, params: GMSend.XHRDetails): Promise<{ [key: string]: string }> {
// 检查是否有unsafe header,有则生成dnr规则
@@ -401,7 +518,6 @@ export default class GMApi {
@PermissionVerify.API({
confirm: async (request: Request) => {
console.log("confirm", request);
const config = <GMSend.XHRDetails>request.params[0];
const url = new URL(config.url);
if (request.script.metadata.connect) {

View File

@@ -6,6 +6,7 @@ import { ValueService } from "./value";
import { RuntimeService } from "./runtime";
import { ServiceWorkerMessageSend } from "@Packages/message/window_message";
import { PopupService } from "./popup";
import { SystemConfig } from "@App/pkg/config/config";
export type InstallSource = "user" | "system" | "sync" | "subscribe" | "vscode";
@@ -24,12 +25,14 @@ export default class ServiceWorkerManager {
this.mq.emit("preparationOffscreen", {});
});
const systemConfig = new SystemConfig(this.mq);
const resource = new ResourceService(this.api.group("resource"), this.mq);
resource.init();
const value = new ValueService(this.api.group("value"), this.sender);
const script = new ScriptService(this.api.group("script"), this.mq, value, resource);
script.init();
const runtime = new RuntimeService(this.api.group("runtime"), this.sender, this.mq, value, script);
const runtime = new RuntimeService(systemConfig, this.api.group("runtime"), this.sender, this.mq, value, script);
runtime.init();
const popup = new PopupService(this.api.group("popup"), this.mq, runtime);
popup.init();

View File

@@ -181,7 +181,6 @@ export default class PermissionVerify {
}
return Promise.resolve(model);
});
console.log("confirm", request, confirm);
// 有查询到结果,进入判断,不再需要用户确认
if (ret) {
if (ret.allow) {

View File

@@ -25,6 +25,7 @@ import { PopupService } from "./popup";
import Logger from "@App/app/logger/logger";
import LoggerCore from "@App/app/logger/core";
import PermissionVerify from "./permission_verify";
import { SystemConfig } from "@App/pkg/config/config";
// 为了优化性能存储到缓存时删除了code与value
export interface ScriptMatchInfo extends ScriptRunResouce {
@@ -48,6 +49,7 @@ export class RuntimeService {
scriptMatchCache: Map<string, ScriptMatchInfo> | null | undefined;
constructor(
private systemConfig: SystemConfig,
private group: Group,
private sender: MessageSend,
private mq: MessageQueue,
@@ -58,7 +60,7 @@ export class RuntimeService {
async init() {
// 启动gm api
const permission = new PermissionVerify(this.group.group("permission"));
const gmApi = new GMApi(permission, this.group, this.sender, this.mq, this.value, this);
const gmApi = new GMApi(this.systemConfig, permission, this.group, this.sender, this.mq, this.value, this);
permission.init();
gmApi.start();

View File

@@ -1,5 +1,6 @@
import { Metadata, Script } from "@App/app/repo/scripts";
import { CronTime } from "cron";
import crypto from "crypto-js";
import dayjs from "dayjs";
import semver from "semver";
@@ -234,3 +235,18 @@ export function getIcon(script: Script): string | undefined {
(script.metadata.icon64url && script.metadata.icon64url[0])
);
}
export function calculateMd5(blob: Blob) {
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.readAsArrayBuffer(blob);
reader.onloadend = () => {
if (!reader.result) {
reject(new Error("result is null"));
} else {
const wordArray = crypto.lib.WordArray.create(<ArrayBuffer>reader.result);
resolve(crypto.MD5(wordArray).toString());
}
};
});
}