优化
Some checks failed
test / Run tests (push) Failing after 15s
build / Build (push) Failing after 23s
Some checks failed
test / Run tests (push) Failing after 15s
build / Build (push) Failing after 23s
This commit is contained in:
7
src/app/service/offscreen/gm_api.ts
Normal file
7
src/app/service/offscreen/gm_api.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Group } from "@Packages/message/server";
|
||||
|
||||
export class GMApi {
|
||||
constructor(private group: Group) {}
|
||||
|
||||
init() {}
|
||||
}
|
@ -1,15 +1,18 @@
|
||||
import { forwardMessage, Server } from "@Packages/message/server";
|
||||
import { forwardMessage, Message, Server } from "@Packages/message/server";
|
||||
import { ScriptService } from "./script";
|
||||
import { Broker } from "@Packages/message/message_queue";
|
||||
import { Logger, LoggerDAO } from "@App/app/repo/logger";
|
||||
import { WindowMessage } from "@Packages/message/window_message";
|
||||
import { ExtensionMessageSend } from "@Packages/message/extension_message";
|
||||
import { ExtensionMessage } from "@Packages/message/extension_message";
|
||||
import { ServiceWorkerClient } from "../service_worker/client";
|
||||
import { sendMessage } from "@Packages/message/client";
|
||||
import { GMApi } from "./gm_api";
|
||||
|
||||
// offscreen环境的管理器
|
||||
export class OffscreenManager {
|
||||
private extensionMessage = new ExtensionMessageSend();
|
||||
private extensionMessage: Message = new ExtensionMessage("service_worker");
|
||||
|
||||
private api: Server = new Server("offscreen", this.extensionMessage);
|
||||
|
||||
private windowMessage = new WindowMessage(window, sandbox);
|
||||
|
||||
@ -42,6 +45,9 @@ export class OffscreenManager {
|
||||
script.init();
|
||||
// 转发gm api请求
|
||||
forwardMessage("serviceWorker/runtime/gmApi", this.windowApi, this.extensionMessage);
|
||||
// 处理gm请求
|
||||
const gmApi = new GMApi(this.api.group("gmApi"));
|
||||
gmApi.init();
|
||||
|
||||
// // 处理gm xhr请求
|
||||
// this.api.on("gmXhr", (data) => {
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
} from "../service_worker/client";
|
||||
import { SCRIPT_STATUS_ENABLE, SCRIPT_TYPE_NORMAL } from "@App/app/repo/scripts";
|
||||
import { disableScript, enableScript } from "../sandbox/client";
|
||||
import { ExtensionMessageSend } from "@Packages/message/extension_message";
|
||||
import { MessageSend } from "@Packages/message/server";
|
||||
|
||||
export class ScriptService {
|
||||
logger: Logger;
|
||||
@ -21,7 +21,7 @@ export class ScriptService {
|
||||
valueClient: ValueClient = new ValueClient(this.extensionMessage);
|
||||
|
||||
constructor(
|
||||
private extensionMessage: ExtensionMessageSend,
|
||||
private extensionMessage: MessageSend,
|
||||
private windowMessage: WindowMessage,
|
||||
private broker: Broker
|
||||
) {
|
||||
|
@ -3,10 +3,10 @@ import { Client } from "@Packages/message/client";
|
||||
import { InstallSource } from ".";
|
||||
import { Broker } from "@Packages/message/message_queue";
|
||||
import { Resource } from "@App/app/repo/resource";
|
||||
import { ExtensionMessageSend } from "@Packages/message/extension_message";
|
||||
import { MessageSend } from "@Packages/message/server";
|
||||
|
||||
export class ServiceWorkerClient extends Client {
|
||||
constructor(msg: ExtensionMessageSend) {
|
||||
constructor(msg: MessageSend) {
|
||||
super(msg, "serviceWorker");
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ export class ServiceWorkerClient extends Client {
|
||||
}
|
||||
|
||||
export class ScriptClient extends Client {
|
||||
constructor(msg: ExtensionMessageSend) {
|
||||
constructor(msg: MessageSend) {
|
||||
super(msg, "serviceWorker/script");
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ export class ScriptClient extends Client {
|
||||
}
|
||||
|
||||
export class ResourceClient extends Client {
|
||||
constructor(msg: ExtensionMessageSend) {
|
||||
constructor(msg: MessageSend) {
|
||||
super(msg, "serviceWorker/resource");
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ export class ResourceClient extends Client {
|
||||
}
|
||||
|
||||
export class ValueClient extends Client {
|
||||
constructor(msg: ExtensionMessageSend) {
|
||||
constructor(msg: MessageSend) {
|
||||
super(msg, "serviceWorker/value");
|
||||
}
|
||||
|
||||
|
87
src/app/service/service_worker/gm_api.ts
Normal file
87
src/app/service/service_worker/gm_api.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import LoggerCore from "@App/app/logger/core";
|
||||
import Logger from "@App/app/logger/logger";
|
||||
import { Script, ScriptDAO } from "@App/app/repo/scripts";
|
||||
import { Group, MessageConnect, MessageSender } from "@Packages/message/server";
|
||||
import { ValueService } from "@App/app/service/service_worker/value";
|
||||
import PermissionVerify from "./permission_verify";
|
||||
|
||||
// GMApi,处理脚本的GM API调用请求
|
||||
|
||||
export type MessageRequest = {
|
||||
uuid: string; // 脚本id
|
||||
api: string;
|
||||
runFlag: string;
|
||||
params: any[];
|
||||
};
|
||||
|
||||
export type Request = MessageRequest & {
|
||||
script: Script;
|
||||
sender: MessageSender;
|
||||
};
|
||||
|
||||
export type Api = (request: Request, con: MessageConnect | null) => Promise<any>;
|
||||
|
||||
export default class GMApi {
|
||||
logger: Logger;
|
||||
|
||||
scriptDAO: ScriptDAO = new ScriptDAO();
|
||||
|
||||
permissionVerify: PermissionVerify = new PermissionVerify();
|
||||
|
||||
constructor(
|
||||
private group: Group,
|
||||
private value: ValueService
|
||||
) {
|
||||
this.logger = LoggerCore.logger().with({ service: "runtime/gm_api" });
|
||||
}
|
||||
|
||||
async handlerRequest(data: MessageRequest, con: MessageConnect | null) {
|
||||
this.logger.trace("GM API request", { api: data.api, uuid: data.uuid, param: data.params });
|
||||
const api = PermissionVerify.apis.get(data.api);
|
||||
if (!api) {
|
||||
return Promise.reject(new Error("api is not found"));
|
||||
}
|
||||
const req = await this.parseRequest(data, { tabId: 0 });
|
||||
try {
|
||||
await this.permissionVerify.verify(req, api);
|
||||
} catch (e) {
|
||||
this.logger.error("verify error", { api: data.api }, Logger.E(e));
|
||||
return Promise.reject(e);
|
||||
}
|
||||
return api.api.call(this, req, con);
|
||||
}
|
||||
|
||||
// 解析请求
|
||||
async parseRequest(data: MessageRequest, sender: MessageSender): Promise<Request> {
|
||||
const script = await this.scriptDAO.get(data.uuid);
|
||||
if (!script) {
|
||||
return Promise.reject(new Error("script is not found"));
|
||||
}
|
||||
const req: Request = <Request>data;
|
||||
req.script = script;
|
||||
req.sender = sender;
|
||||
return Promise.resolve(req);
|
||||
}
|
||||
|
||||
@PermissionVerify.API()
|
||||
GM_setValue(request: Request): Promise<any> {
|
||||
if (!request.params || request.params.length !== 2) {
|
||||
return Promise.reject(new Error("param is failed"));
|
||||
}
|
||||
const [key, value] = request.params;
|
||||
const sender = <MessageSender & { runFlag: string }>request.sender;
|
||||
sender.runFlag = request.runFlag;
|
||||
return this.value.setValue(request.script.uuid, key, value);
|
||||
}
|
||||
|
||||
@PermissionVerify.API()
|
||||
GM_xmlhttpRequest(request: Request, con: MessageConnect) {
|
||||
console.log("xml", request, con);
|
||||
// 先处理unsafe hearder
|
||||
// 再发送到offscreen, 处理请求
|
||||
}
|
||||
|
||||
start() {
|
||||
this.group.on("gmApi", this.handlerRequest.bind(this));
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ export type InstallSource = "user" | "system" | "sync" | "subscribe" | "vscode";
|
||||
export default class ServiceWorkerManager {
|
||||
constructor() {}
|
||||
|
||||
private api: Server = new Server("service_worker", new ExtensionMessage());
|
||||
private api: Server = new Server("service_worker", new ExtensionMessage("service_worker"));
|
||||
|
||||
private mq: MessageQueue = new MessageQueue(this.api);
|
||||
|
||||
@ -82,7 +82,7 @@ export default class ServiceWorkerManager {
|
||||
// },
|
||||
// condition: {
|
||||
// resourceTypes: [chrome.declarativeNetRequest.ResourceType.XMLHTTPREQUEST],
|
||||
// urlFilter: "https://scriptcat.org/zh-CN",
|
||||
// urlFilter: "^https://scriptcat.org/zh-CN$",
|
||||
// excludedTabIds: excludedTabIds,
|
||||
// },
|
||||
// },
|
||||
|
262
src/app/service/service_worker/permission_verify.ts
Normal file
262
src/app/service/service_worker/permission_verify.ts
Normal file
@ -0,0 +1,262 @@
|
||||
// gm api 权限验证
|
||||
import Cache from "@App/app/cache";
|
||||
import { Script } from "@App/app/repo/scripts";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { Api, Request } from "./gm_api";
|
||||
import Queue from "@App/pkg/utils/queue";
|
||||
import CacheKey from "@App/app/cache_key";
|
||||
import { Permission, PermissionDAO } from "@App/app/repo/permission";
|
||||
|
||||
export interface ConfirmParam {
|
||||
// 权限名
|
||||
permission: string;
|
||||
// 权限值
|
||||
permissionValue?: string;
|
||||
// 确认权限标题
|
||||
title?: string;
|
||||
// 权限详情内容
|
||||
metadata?: { [key: string]: string };
|
||||
// 权限描述
|
||||
describe?: string;
|
||||
// 是否通配
|
||||
wildcard?: boolean;
|
||||
// 权限内容
|
||||
permissionContent?: string;
|
||||
}
|
||||
|
||||
export interface UserConfirm {
|
||||
allow: boolean;
|
||||
type: number; // 1: 允许一次 2: 临时允许全部 3: 临时允许此 4: 永久允许全部 5: 永久允许此
|
||||
}
|
||||
|
||||
export interface ApiParam {
|
||||
// 默认提供的函数
|
||||
default?: boolean;
|
||||
// 是否只有后台环境中才能执行
|
||||
background?: boolean;
|
||||
// 是否需要弹出页面让用户进行确认
|
||||
confirm?: (request: Request) => Promise<boolean | ConfirmParam>;
|
||||
// 监听方法
|
||||
listener?: () => void;
|
||||
// 别名
|
||||
alias?: string[];
|
||||
// 关联
|
||||
link?: string;
|
||||
}
|
||||
|
||||
export interface ApiValue {
|
||||
api: Api;
|
||||
param: ApiParam;
|
||||
}
|
||||
|
||||
export interface IPermissionVerify {
|
||||
verify(request: Request, api: ApiValue): Promise<boolean>;
|
||||
}
|
||||
|
||||
export default class PermissionVerify {
|
||||
static apis: Map<string, ApiValue> = new Map();
|
||||
|
||||
public static API(param: ApiParam = {}) {
|
||||
return (target: any, propertyName: string, descriptor: PropertyDescriptor) => {
|
||||
const key = propertyName;
|
||||
if (param.listener) {
|
||||
param.listener();
|
||||
}
|
||||
PermissionVerify.apis.set(key, {
|
||||
api: descriptor.value,
|
||||
param,
|
||||
});
|
||||
// 兼容GM.*
|
||||
const dot = key.replace("_", ".");
|
||||
if (dot !== key) {
|
||||
PermissionVerify.apis.set(dot, {
|
||||
api: descriptor.value,
|
||||
param,
|
||||
});
|
||||
if (param.alias) {
|
||||
param.alias.push(dot);
|
||||
} else {
|
||||
param.alias = [dot];
|
||||
}
|
||||
}
|
||||
|
||||
// 处理别名
|
||||
if (param.alias) {
|
||||
param.alias.forEach((alias) => {
|
||||
PermissionVerify.apis.set(alias, {
|
||||
api: descriptor.value,
|
||||
param,
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 确认队列
|
||||
confirmQueue: Queue<{
|
||||
request: Request;
|
||||
confirm: ConfirmParam | boolean;
|
||||
resolve: (value: boolean) => void;
|
||||
reject: (reason: any) => void;
|
||||
}> = new Queue();
|
||||
|
||||
async removePermissionCache(scriptId: number) {
|
||||
// 先删除缓存
|
||||
(await Cache.getInstance().list()).forEach((key) => {
|
||||
if (key.startsWith(`permission:${scriptId.toString()}:`)) {
|
||||
Cache.getInstance().del(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private permissionDAO: PermissionDAO;
|
||||
|
||||
constructor() {
|
||||
this.permissionDAO = new PermissionDAO();
|
||||
this.dealConfirmQueue();
|
||||
}
|
||||
|
||||
// 验证是否有权限
|
||||
verify(request: Request, api: ApiValue): Promise<boolean> {
|
||||
if (api.param.default) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
// 没有其它条件,从metadata.grant中判断
|
||||
const { grant } = request.script.metadata;
|
||||
if (!grant) {
|
||||
return Promise.reject(new Error("grant is undefined"));
|
||||
}
|
||||
for (let i = 0; i < grant.length; i += 1) {
|
||||
if (
|
||||
// 名称相等
|
||||
grant[i] === request.api ||
|
||||
// 别名相等
|
||||
(api.param.alias && api.param.alias.includes(grant[i])) ||
|
||||
// 有关联的
|
||||
grant[i] === api.param.link
|
||||
) {
|
||||
// 需要用户确认
|
||||
if (api.param.confirm) {
|
||||
return this.pushConfirmQueue(request, api);
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
return Promise.reject(new Error("permission not requested"));
|
||||
}
|
||||
|
||||
async dealConfirmQueue() {
|
||||
// 处理确认队列
|
||||
const data = await this.confirmQueue.pop();
|
||||
if (!data) {
|
||||
this.dealConfirmQueue();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const ret = await this.confirm(data.request, data.confirm);
|
||||
data.resolve(ret);
|
||||
} catch (e) {
|
||||
data.reject(e);
|
||||
}
|
||||
this.dealConfirmQueue();
|
||||
}
|
||||
|
||||
// 确认队列,为了防止一次性打开过多的窗口
|
||||
async pushConfirmQueue(request: Request, api: ApiValue): Promise<boolean> {
|
||||
const confirm = await api.param.confirm!(request);
|
||||
if (confirm === true) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
this.confirmQueue.push({ request, confirm, resolve, reject });
|
||||
});
|
||||
}
|
||||
|
||||
async confirm(request: Request, confirm: boolean | ConfirmParam): Promise<boolean> {
|
||||
if (typeof confirm === "boolean") {
|
||||
return confirm;
|
||||
}
|
||||
const cacheKey = CacheKey.permissionConfirm(request.script.uuid, confirm);
|
||||
// 从数据库中查询是否有此权限
|
||||
const ret = await Cache.getInstance().getOrSet(cacheKey, async () => {
|
||||
let model = await this.permissionDAO.findByKey(request.uuid, confirm.permission, confirm.permissionValue || "");
|
||||
if (!model) {
|
||||
// 允许通配
|
||||
if (confirm.wildcard) {
|
||||
model = await this.permissionDAO.findByKey(request.uuid, confirm.permission, confirm.permissionValue || "");
|
||||
}
|
||||
}
|
||||
return Promise.resolve(model);
|
||||
});
|
||||
// 有查询到结果,进入判断,不再需要用户确认
|
||||
if (ret) {
|
||||
if (ret.allow) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
// 权限拒绝
|
||||
return Promise.reject(new Error("permission denied"));
|
||||
}
|
||||
// 没有权限,则弹出页面让用户进行确认
|
||||
const userConfirm = await this.confirmWindow(request.script, confirm);
|
||||
// 成功存入数据库
|
||||
const model: Permission = {
|
||||
uuid: request.uuid,
|
||||
permission: confirm.permission,
|
||||
permissionValue: "",
|
||||
allow: userConfirm.allow,
|
||||
createtime: new Date().getTime(),
|
||||
updatetime: 0,
|
||||
};
|
||||
switch (userConfirm.type) {
|
||||
case 4:
|
||||
case 2: {
|
||||
// 通配
|
||||
model.permissionValue = "*";
|
||||
break;
|
||||
}
|
||||
case 5:
|
||||
case 3: {
|
||||
model.permissionValue = confirm.permissionValue || "";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// 临时 放入缓存
|
||||
if (userConfirm.type >= 2) {
|
||||
Cache.getInstance().set(cacheKey, model);
|
||||
}
|
||||
// 总是 放入数据库
|
||||
if (userConfirm.type >= 4) {
|
||||
const oldConfirm = await this.permissionDAO.findByKey(request.uuid, model.permission, model.permissionValue);
|
||||
if (!oldConfirm) {
|
||||
await this.permissionDAO.save(model);
|
||||
} else {
|
||||
await this.permissionDAO.update(this.permissionDAO.key(model), model);
|
||||
}
|
||||
}
|
||||
if (userConfirm.allow) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
return Promise.reject(new Error("permission not allowed"));
|
||||
}
|
||||
|
||||
// 确认map
|
||||
confirmMap: Map<
|
||||
string,
|
||||
{
|
||||
confirm: ConfirmParam;
|
||||
script: Script;
|
||||
resolve: (value: UserConfirm) => void;
|
||||
reject: (reason: any) => void;
|
||||
}
|
||||
> = new Map();
|
||||
|
||||
// 弹出窗口让用户进行确认
|
||||
async confirmWindow(script: Script, confirm: ConfirmParam): Promise<UserConfirm> {
|
||||
return Promise.resolve({
|
||||
allow: true,
|
||||
type: 1,
|
||||
});
|
||||
}
|
||||
}
|
@ -52,11 +52,9 @@ export class RuntimeService {
|
||||
});
|
||||
});
|
||||
|
||||
// 初始化gm api
|
||||
const gmApi = new GMApi(this.value);
|
||||
// 启动gm api
|
||||
const gmApi = new GMApi(this.group, this.value);
|
||||
gmApi.start();
|
||||
// 处理请求
|
||||
this.group.on("gmApi", gmApi.handlerRequest);
|
||||
}
|
||||
|
||||
registryPageScript(script: ScriptAndCode) {
|
||||
|
@ -157,7 +157,7 @@ export class ScriptService {
|
||||
const logger = this.logger.with({
|
||||
name: script.name,
|
||||
uuid: script.uuid,
|
||||
version: script.metadata.version[0],
|
||||
version: script.metadata.version![0],
|
||||
upsertBy,
|
||||
});
|
||||
let update = false;
|
||||
|
Reference in New Issue
Block a user