CAT API
This commit is contained in:
17
src/app/repo/localStorage.ts
Normal file
17
src/app/repo/localStorage.ts
Normal 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);
|
||||
}
|
||||
}
|
@@ -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"],
|
||||
|
@@ -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) {
|
||||
|
@@ -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();
|
||||
|
@@ -181,7 +181,6 @@ export default class PermissionVerify {
|
||||
}
|
||||
return Promise.resolve(model);
|
||||
});
|
||||
console.log("confirm", request, confirm);
|
||||
// 有查询到结果,进入判断,不再需要用户确认
|
||||
if (ret) {
|
||||
if (ret.allow) {
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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());
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user