CAT API
This commit is contained in:
parent
071e728f06
commit
1a531dfad5
@ -1,5 +1,6 @@
|
||||
import { ExtServer, ExtServerApi } from "@App/app/const";
|
||||
import { WarpTokenError } from "./error";
|
||||
import { LocalStorageDAO } from "@App/app/repo/localStorage";
|
||||
|
||||
type NetDiskType = "baidu" | "onedrive";
|
||||
|
||||
@ -8,11 +9,7 @@ export function GetNetDiskToken(netDiskType: NetDiskType): Promise<{
|
||||
msg: string;
|
||||
data: { token: { access_token: string; refresh_token: string } };
|
||||
}> {
|
||||
return fetch(ExtServerApi + `auth/net-disk/token?netDiskType=${netDiskType}`)
|
||||
.then((resp) => resp.json())
|
||||
.then((resp) => {
|
||||
return resp.data;
|
||||
});
|
||||
return fetch(ExtServerApi + `auth/net-disk/token?netDiskType=${netDiskType}`).then((resp) => resp.json());
|
||||
}
|
||||
|
||||
export function RefreshToken(
|
||||
@ -32,15 +29,12 @@ export function RefreshToken(
|
||||
netDiskType,
|
||||
refreshToken,
|
||||
}),
|
||||
})
|
||||
.then((resp) => resp.json())
|
||||
.then((resp) => {
|
||||
return resp.data;
|
||||
});
|
||||
}).then((resp) => resp.json());
|
||||
}
|
||||
|
||||
export function NetDisk(netDiskType: NetDiskType) {
|
||||
return new Promise<void>((resolve) => {
|
||||
if (globalThis.window) {
|
||||
const loginWindow = window.open(`${ExtServer}api/v1/auth/net-disk?netDiskType=${netDiskType}`);
|
||||
const t = setInterval(() => {
|
||||
try {
|
||||
@ -53,6 +47,27 @@ export function NetDisk(netDiskType: NetDiskType) {
|
||||
resolve();
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
chrome.tabs
|
||||
.create({
|
||||
url: `${ExtServer}api/v1/auth/net-disk?netDiskType=${netDiskType}`,
|
||||
})
|
||||
.then(({ id: tabId }) => {
|
||||
const t = setInterval(async () => {
|
||||
try {
|
||||
const tab = await chrome.tabs.get(tabId!);
|
||||
console.log("query tab", tab);
|
||||
if (!tab) {
|
||||
clearInterval(t);
|
||||
resolve();
|
||||
}
|
||||
} catch (e) {
|
||||
clearInterval(t);
|
||||
resolve();
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -64,8 +79,15 @@ export type Token = {
|
||||
|
||||
export async function AuthVerify(netDiskType: NetDiskType, invalid?: boolean) {
|
||||
let token: Token | undefined;
|
||||
const localStorageDao = new LocalStorageDAO();
|
||||
const key = `netdisk:token:${netDiskType}`;
|
||||
try {
|
||||
token = JSON.parse(localStorage[`netdisk:token:${netDiskType}`]);
|
||||
token = await localStorageDao.get(key).then((resp) => {
|
||||
if (resp) {
|
||||
return resp.value;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
@ -83,7 +105,10 @@ export async function AuthVerify(netDiskType: NetDiskType, invalid?: boolean) {
|
||||
createtime: Date.now(),
|
||||
};
|
||||
invalid = false;
|
||||
localStorage[`netdisk:token:${netDiskType}`] = JSON.stringify(token);
|
||||
await localStorageDao.save({
|
||||
key,
|
||||
value: token,
|
||||
});
|
||||
}
|
||||
// token过期或者失效
|
||||
if (Date.now() >= token.createtime + 3600000 || invalid) {
|
||||
@ -91,7 +116,7 @@ export async function AuthVerify(netDiskType: NetDiskType, invalid?: boolean) {
|
||||
try {
|
||||
const resp = await RefreshToken(netDiskType, token.refreshToken);
|
||||
if (resp.code !== 0) {
|
||||
localStorage.removeItem(`netdisk:token:${netDiskType}`);
|
||||
await localStorageDao.delete(key);
|
||||
// 刷新失败,并且标记失效,尝试重新获取token
|
||||
if (invalid) {
|
||||
return AuthVerify(netDiskType);
|
||||
@ -103,7 +128,11 @@ export async function AuthVerify(netDiskType: NetDiskType, invalid?: boolean) {
|
||||
refreshToken: resp.data.token.refresh_token,
|
||||
createtime: Date.now(),
|
||||
};
|
||||
localStorage[`netdisk:token:${netDiskType}`] = JSON.stringify(token);
|
||||
// 更新token
|
||||
await localStorageDao.save({
|
||||
key,
|
||||
value: token,
|
||||
});
|
||||
} catch (e) {
|
||||
// 报错返回原token
|
||||
return Promise.resolve(token.accessToken);
|
||||
|
@ -55,12 +55,11 @@ export default class BaiduFileSystem implements FileSystem {
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
request(url: string, config?: RequestInit) {
|
||||
async request(url: string, config?: RequestInit) {
|
||||
config = config || {};
|
||||
const headers = <Headers>config.headers || new Headers();
|
||||
// 处理请求匿名不发送cookie
|
||||
chrome.declarativeNetRequest.updateDynamicRules({
|
||||
await chrome.declarativeNetRequest.updateDynamicRules({
|
||||
removeRuleIds: [100],
|
||||
addRules: [
|
||||
{
|
||||
|
@ -1,5 +1,3 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
import { calculateMd5 } from "@App/pkg/utils/utils";
|
||||
import { MD5 } from "crypto-js";
|
||||
import { File, FileReader, FileWriter } from "../filesystem";
|
||||
|
@ -1,4 +1,3 @@
|
||||
// eslint-disable-next-line import/prefer-default-export, max-classes-per-file
|
||||
export class WarpTokenError {
|
||||
error: Error;
|
||||
|
||||
|
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());
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user