优化消息
This commit is contained in:
parent
84261e22bd
commit
177594b638
10
packages/message/README.md
Normal file
10
packages/message/README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# 消息
|
||||||
|
|
||||||
|
对扩展内消息交互的抽象
|
||||||
|
|
||||||
|
主要会有以下几种类型的消息:
|
||||||
|
|
||||||
|
- 从脚本发起的GM请求,需要层层传递到service_worker/offscreen进行处理,有的GM只需要进行一次调用获取一次结果,有的需要进行
|
||||||
|
多次调用获取多次结果,使用connect的方式实现
|
||||||
|
- 从service_woker/offscreen发起的请求,类似消息队列,其它页面进行监听,触发后广播给所有页面,使用connect方式实现
|
||||||
|
- 从扩展页面发起的请求,需要传递到service_worker/offscreen进行处理,如果只是单次调用,获取一次结果,使用message方式实现
|
@ -1,54 +0,0 @@
|
|||||||
import EventEmitter from "eventemitter3";
|
|
||||||
import { IConnect, IServer } from ".";
|
|
||||||
|
|
||||||
export class ExtServer implements IServer {
|
|
||||||
private EE: EventEmitter;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.EE = new EventEmitter();
|
|
||||||
chrome.runtime.onConnect.addListener((port) => {
|
|
||||||
this.EE.emit("connect", new ExtConnect(port));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onConnect(callback: (con: IConnect) => void) {
|
|
||||||
this.EE.on("connect", callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function extConnect() {
|
|
||||||
return new ExtConnect(chrome.runtime.connect());
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ExtConnect implements IConnect {
|
|
||||||
private EE: EventEmitter;
|
|
||||||
private port: chrome.runtime.Port;
|
|
||||||
|
|
||||||
constructor(port: chrome.runtime.Port) {
|
|
||||||
this.EE = new EventEmitter();
|
|
||||||
this.port = port;
|
|
||||||
port.onMessage.addListener((message) => {
|
|
||||||
this.EE.emit("message", message);
|
|
||||||
});
|
|
||||||
port.onDisconnect.addListener(() => {
|
|
||||||
this.EE.emit("disconnect");
|
|
||||||
this.EE.removeAllListeners();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
postMessage(message: unknown) {
|
|
||||||
this.port.postMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMessage(callback: (message: unknown) => void) {
|
|
||||||
this.EE.on("message", callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDisconnect(callback: () => void) {
|
|
||||||
this.EE.on("disconnect", callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect() {
|
|
||||||
this.port.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
import EventEmitter from "eventemitter3";
|
|
||||||
import { v4 as uuidv4 } from "uuid";
|
|
||||||
|
|
||||||
export interface IServer {
|
|
||||||
onConnect: (callback: (con: IConnect) => void) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IConnect {
|
|
||||||
postMessage: (message: unknown) => void;
|
|
||||||
onMessage: (callback: (message: unknown) => void) => void;
|
|
||||||
onDisconnect: (callback: () => void) => void;
|
|
||||||
disconnect: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 消息通道, 通过连接封装消息通道
|
|
||||||
export class Server {
|
|
||||||
private EE: EventEmitter;
|
|
||||||
|
|
||||||
constructor(private connect: IServer) {
|
|
||||||
this.EE = new EventEmitter();
|
|
||||||
this.connect.onConnect((con) => {
|
|
||||||
this.EE.emit("connection", con);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
on(eventName: "connection", callback: (con: IConnect) => void): void;
|
|
||||||
on(eventName: string, callback: (con: IConnect) => void) {
|
|
||||||
this.EE.on(eventName, callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Connect {
|
|
||||||
private EE: EventEmitter;
|
|
||||||
|
|
||||||
constructor(private con: IConnect) {
|
|
||||||
this.EE = new EventEmitter();
|
|
||||||
this.con.onMessage((message) => {
|
|
||||||
this.messageHandler(message);
|
|
||||||
});
|
|
||||||
this.con.onDisconnect(() => {
|
|
||||||
this.EE.emit("disconnect");
|
|
||||||
this.EE.removeAllListeners();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private callbackFunc(msgId: string): (...data: unknown[]) => void {
|
|
||||||
return (...data: unknown[]) => {
|
|
||||||
this.con.postMessage({ eventName: "callback", data, messageId: msgId });
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private messageHandler(data: unknown) {
|
|
||||||
const subData = data as { eventName: string; data: unknown[]; messageId: string; conType: string; id: string };
|
|
||||||
if (subData.eventName === "callback") {
|
|
||||||
this.EE.emit(subData.eventName + subData.messageId, ...subData.data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
subData.data.push(this.callbackFunc(subData.messageId));
|
|
||||||
this.EE.emit(subData.eventName, ...subData.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
on(eventName: string, callback: (...args: any[]) => void) {
|
|
||||||
this.EE.on(eventName, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
send(eventName: string, ...data: unknown[]) {
|
|
||||||
this.con.postMessage({ eventName, data });
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(eventName: string, ...data: any[]) {
|
|
||||||
// 判断最后一个参数是否为函数
|
|
||||||
const callback = data.pop();
|
|
||||||
const messageId = uuidv4();
|
|
||||||
if (typeof callback !== "function") {
|
|
||||||
data.push(callback);
|
|
||||||
} else {
|
|
||||||
this.EE.on("callback" + messageId, (...args) => {
|
|
||||||
callback(...args);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const sendData = { eventName, data, messageId };
|
|
||||||
this.con.postMessage(sendData);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
// @vitest-environment jsdom
|
|
||||||
import { describe, expect, it, vi } from "vitest";
|
|
||||||
import { Server, Connect } from ".";
|
|
||||||
import { windowConnect, WindowServer } from "./window";
|
|
||||||
|
|
||||||
describe("server", () => {
|
|
||||||
it("hello", async () => {
|
|
||||||
const myFunc = vi.fn();
|
|
||||||
const server = new Server(new WindowServer(global.window));
|
|
||||||
server.on("connection", (con) => {
|
|
||||||
myFunc();
|
|
||||||
con.onMessage((message) => {
|
|
||||||
myFunc(message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const client = windowConnect(window, window);
|
|
||||||
client.postMessage("hello");
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
||||||
expect(myFunc).toHaveBeenCalledTimes(2);
|
|
||||||
expect(myFunc).toHaveBeenCalledWith("hello");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("connect", async () => {
|
|
||||||
it("hello", async () => {
|
|
||||||
const server = new Server(new WindowServer(global.window));
|
|
||||||
const myFunc = vi.fn();
|
|
||||||
server.on("connection", (con) => {
|
|
||||||
myFunc();
|
|
||||||
const wrapCon = new Connect(con);
|
|
||||||
wrapCon.on("hello", (message) => {
|
|
||||||
myFunc(message);
|
|
||||||
wrapCon.emit("world", "world");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const client = new Connect(windowConnect(window, window));
|
|
||||||
client.on("world", (message) => {
|
|
||||||
myFunc(message);
|
|
||||||
});
|
|
||||||
client.emit("hello", "hello");
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
||||||
expect(myFunc).toHaveBeenCalledTimes(3);
|
|
||||||
expect(myFunc).toHaveBeenCalledWith("hello");
|
|
||||||
expect(myFunc).toHaveBeenCalledWith("world");
|
|
||||||
});
|
|
||||||
it("response", async () => {
|
|
||||||
const server = new Server(new WindowServer(global.window));
|
|
||||||
const myFunc = vi.fn();
|
|
||||||
server.on("connection", (con) => {
|
|
||||||
const wrapCon = new Connect(con);
|
|
||||||
wrapCon.on("ping", (message, response) => {
|
|
||||||
myFunc(message);
|
|
||||||
response("pong");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const client = new Connect(windowConnect(window, window));
|
|
||||||
client.emit("ping", "ping", (message: string) => {
|
|
||||||
myFunc(message);
|
|
||||||
});
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
||||||
expect(myFunc).toHaveBeenCalledTimes(2);
|
|
||||||
expect(myFunc).toHaveBeenCalledWith("ping");
|
|
||||||
expect(myFunc).toHaveBeenCalledWith("pong");
|
|
||||||
});
|
|
||||||
});
|
|
66
packages/message/message_queue.ts
Normal file
66
packages/message/message_queue.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { ApiFunction } from "./server";
|
||||||
|
|
||||||
|
export class Broker {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
// 订阅
|
||||||
|
subscribe(topic: string, handler: (message: any) => void) {
|
||||||
|
const con = chrome.runtime.connect({ name: topic });
|
||||||
|
con.postMessage({ action: "subscribe", topic });
|
||||||
|
con.onMessage.addListener((msg: { action: string; topic: string; message: any }) => {
|
||||||
|
if (msg.action === "message") {
|
||||||
|
handler(msg.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发布
|
||||||
|
publish(topic: string, message: any) {
|
||||||
|
chrome.runtime.sendMessage({ action: "publish", topic, message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息队列
|
||||||
|
export class MessageQueue {
|
||||||
|
topicConMap: Map<string, { name: string; con: chrome.runtime.Port }[]> = new Map();
|
||||||
|
|
||||||
|
handler(): ApiFunction {
|
||||||
|
return ({ action, topic, message }: { action: string; topic: string; message: any }, con) => {
|
||||||
|
if (!con) {
|
||||||
|
throw new Error("con is required");
|
||||||
|
}
|
||||||
|
if (!topic) {
|
||||||
|
throw new Error("topic is required");
|
||||||
|
}
|
||||||
|
switch (action) {
|
||||||
|
case "subscribe":
|
||||||
|
this.subscribe(topic, con as chrome.runtime.Port);
|
||||||
|
break;
|
||||||
|
case "publish":
|
||||||
|
this.publish(topic, message);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error("action not found");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private subscribe(topic: string, con: chrome.runtime.Port) {
|
||||||
|
let list = this.topicConMap.get(topic);
|
||||||
|
if (!list) {
|
||||||
|
list = [];
|
||||||
|
this.topicConMap.set(topic, list);
|
||||||
|
}
|
||||||
|
list.push({ name: topic, con });
|
||||||
|
con.onDisconnect.addListener(() => {
|
||||||
|
list = list!.filter((item) => item.con !== con);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
publish(topic: string, message: any) {
|
||||||
|
const list = this.topicConMap.get(topic);
|
||||||
|
list?.forEach((item) => {
|
||||||
|
item.con.postMessage({ action: "message", topic, message });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
45
packages/message/server.ts
Normal file
45
packages/message/server.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
export type ApiFunction = (params: any, con: chrome.runtime.Port | chrome.runtime.MessageSender) => any;
|
||||||
|
|
||||||
|
export class Server {
|
||||||
|
apiFunctionMap: Map<string, ApiFunction> = new Map();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
chrome.runtime.onConnect.addListener((port) => {
|
||||||
|
port.onMessage.addListener((msg: { action: string }) => {
|
||||||
|
this.connectHandle(msg.action, msg, port);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||||
|
this.messageHandle(msg.action, msg, sender, sendResponse);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
on(name: string, func: ApiFunction) {
|
||||||
|
this.apiFunctionMap.set(name, func);
|
||||||
|
}
|
||||||
|
|
||||||
|
private connectHandle(msg: string, params: any, con: chrome.runtime.Port) {
|
||||||
|
const func = this.apiFunctionMap.get(msg);
|
||||||
|
if (func) {
|
||||||
|
func(params, con);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private messageHandle(
|
||||||
|
msg: string,
|
||||||
|
params: any,
|
||||||
|
sender: chrome.runtime.MessageSender,
|
||||||
|
sendResponse: (response: any) => void
|
||||||
|
) {
|
||||||
|
const func = this.apiFunctionMap.get(msg);
|
||||||
|
if (func) {
|
||||||
|
try {
|
||||||
|
const ret = func(params, sender);
|
||||||
|
sendResponse({ code: 0, data: ret });
|
||||||
|
} catch (e: any) {
|
||||||
|
sendResponse({ code: -1, message: e.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,61 +0,0 @@
|
|||||||
import EventEmitter from "eventemitter3";
|
|
||||||
import { IConnect, IServer } from ".";
|
|
||||||
import { v4 as uuidv4 } from "uuid";
|
|
||||||
|
|
||||||
export class WindowServer implements IServer {
|
|
||||||
private EE: EventEmitter;
|
|
||||||
|
|
||||||
constructor(win: Window) {
|
|
||||||
this.EE = new EventEmitter();
|
|
||||||
win.addEventListener("message", (event) => {
|
|
||||||
if (event.data.type === "connect") {
|
|
||||||
this.EE.emit("connection", new WindowConnect(event.data.connectId, win, event.source as Window));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onConnect(callback: (con: IConnect) => void) {
|
|
||||||
this.EE.on("connection", callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function windowConnect(source: Window, target: Window) {
|
|
||||||
const connectId = uuidv4();
|
|
||||||
target.postMessage({ type: "connect", connectId }, "*");
|
|
||||||
const con = new WindowConnect(connectId, source, target);
|
|
||||||
return con;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WindowConnect implements IConnect {
|
|
||||||
private EE: EventEmitter;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private id: string,
|
|
||||||
private source: Window,
|
|
||||||
private target: Window
|
|
||||||
) {
|
|
||||||
this.EE = new EventEmitter();
|
|
||||||
this.source.addEventListener("message", (event) => {
|
|
||||||
if (event.data.eventName === "message" && event.data.id === id) {
|
|
||||||
this.EE.emit("message", event.data.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
postMessage(data: unknown) {
|
|
||||||
this.target.postMessage({ eventName: "message", id: this.id, data }, "*");
|
|
||||||
}
|
|
||||||
|
|
||||||
onMessage(callback: (message: unknown) => void) {
|
|
||||||
this.EE.on("message", callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDisconnect(callback: () => void) {
|
|
||||||
this.EE.on("disconnect", callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect() {
|
|
||||||
this.EE.emit("disconnect");
|
|
||||||
this.EE.removeAllListeners();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,124 +1,11 @@
|
|||||||
import { fetchScriptInfo } from "@App/pkg/utils/script";
|
import { Server } from "@Packages/message/server";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
|
||||||
import { Connect } from "@Packages/message";
|
|
||||||
import Cache from "@App/app/cache";
|
|
||||||
import CacheKey from "@App/app/cache_key";
|
|
||||||
import { openInCurrentTab } from "@App/pkg/utils/utils";
|
|
||||||
import LoggerCore from "@App/app/logger/core";
|
|
||||||
|
|
||||||
export type InstallSource = "user" | "system" | "sync" | "subscribe" | "vscode";
|
// offscreen环境的管理器
|
||||||
|
export class Manager {
|
||||||
export default class Manager {
|
private api: Server = new Server();
|
||||||
constructor(private connect: Connect) {}
|
|
||||||
|
|
||||||
listenerScriptInstall() {
|
|
||||||
// 初始化脚本安装监听
|
|
||||||
chrome.webRequest.onCompleted.addListener(
|
|
||||||
(req: chrome.webRequest.WebResponseCacheDetails) => {
|
|
||||||
// 处理url, 实现安装脚本
|
|
||||||
if (req.method !== "GET") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const url = new URL(req.url);
|
|
||||||
// 判断是否有hash
|
|
||||||
if (!url.hash) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 判断是否有url参数
|
|
||||||
if (!url.hash.includes("url=")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 获取url参数
|
|
||||||
const targetUrl = url.hash.split("url=")[1];
|
|
||||||
// 读取脚本url内容, 进行安装
|
|
||||||
LoggerCore.getInstance().logger().debug("install script", { url: targetUrl });
|
|
||||||
this.openInstallPageByUrl(targetUrl).catch(() => {
|
|
||||||
// 如果打开失败, 则重定向到安装页
|
|
||||||
chrome.scripting.executeScript({
|
|
||||||
target: { tabId: req.tabId },
|
|
||||||
func: function () {
|
|
||||||
history.back();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// 并不再重定向当前url
|
|
||||||
chrome.declarativeNetRequest.updateDynamicRules(
|
|
||||||
{
|
|
||||||
removeRuleIds: [2],
|
|
||||||
addRules: [
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
priority: 1,
|
|
||||||
action: {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{
|
|
||||||
urls: [
|
|
||||||
"https://docs.scriptcat.org/docs/script_installation",
|
|
||||||
"https://www.tampermonkey.net/script_installation.php",
|
|
||||||
],
|
|
||||||
types: ["main_frame"],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// 重定向到脚本安装页
|
|
||||||
chrome.declarativeNetRequest.updateDynamicRules(
|
|
||||||
{
|
|
||||||
removeRuleIds: [1],
|
|
||||||
addRules: [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
priority: 1,
|
|
||||||
action: {
|
|
||||||
type: chrome.declarativeNetRequest.RuleActionType.REDIRECT,
|
|
||||||
redirect: {
|
|
||||||
regexSubstitution: "https://docs.scriptcat.org/docs/script_installation#url=\\0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
condition: {
|
|
||||||
regexFilter: "^([^#]+?)\\.user(\\.bg|\\.sub)?\\.js((\\?).*|$)",
|
|
||||||
resourceTypes: [chrome.declarativeNetRequest.ResourceType.MAIN_FRAME],
|
|
||||||
requestMethods: [chrome.declarativeNetRequest.RequestMethod.GET],
|
|
||||||
// 排除常见的复合上述条件的域名
|
|
||||||
excludedRequestDomains: ["github.com"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
if (chrome.runtime.lastError) {
|
|
||||||
console.error(chrome.runtime.lastError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public openInstallPageByUrl(url: string) {
|
|
||||||
return fetchScriptInfo(url, "user", false, uuidv4()).then((info) => {
|
|
||||||
Cache.getInstance().set(CacheKey.scriptInfo(info.uuid), info);
|
|
||||||
setTimeout(() => {
|
|
||||||
// 清理缓存
|
|
||||||
Cache.getInstance().del(CacheKey.scriptInfo(info.uuid));
|
|
||||||
}, 60 * 1000);
|
|
||||||
openInCurrentTab(`/src/install.html?uuid=${info.uuid}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
initManager() {
|
initManager() {
|
||||||
this.listenerScriptInstall();
|
// 监听消息
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
139
src/app/service/service_worker/index.ts
Normal file
139
src/app/service/service_worker/index.ts
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import { fetchScriptInfo } from "@App/pkg/utils/script";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import Cache from "@App/app/cache";
|
||||||
|
import CacheKey from "@App/app/cache_key";
|
||||||
|
import { openInCurrentTab } from "@App/pkg/utils/utils";
|
||||||
|
import LoggerCore from "@App/app/logger/core";
|
||||||
|
import { Server } from "@Packages/message/server";
|
||||||
|
import { MessageQueue } from "@Packages/message/message_queue";
|
||||||
|
|
||||||
|
export type InstallSource = "user" | "system" | "sync" | "subscribe" | "vscode";
|
||||||
|
|
||||||
|
// service worker的管理器
|
||||||
|
export default class ServiceWorkerManager {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
listenerScriptInstall() {
|
||||||
|
// 初始化脚本安装监听
|
||||||
|
chrome.webRequest.onCompleted.addListener(
|
||||||
|
(req: chrome.webRequest.WebResponseCacheDetails) => {
|
||||||
|
// 处理url, 实现安装脚本
|
||||||
|
if (req.method !== "GET") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = new URL(req.url);
|
||||||
|
// 判断是否有hash
|
||||||
|
if (!url.hash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 判断是否有url参数
|
||||||
|
if (!url.hash.includes("url=")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 获取url参数
|
||||||
|
const targetUrl = url.hash.split("url=")[1];
|
||||||
|
// 读取脚本url内容, 进行安装
|
||||||
|
LoggerCore.getInstance().logger().debug("install script", { url: targetUrl });
|
||||||
|
this.openInstallPageByUrl(targetUrl).catch(() => {
|
||||||
|
// 如果打开失败, 则重定向到安装页
|
||||||
|
chrome.scripting.executeScript({
|
||||||
|
target: { tabId: req.tabId },
|
||||||
|
func: function () {
|
||||||
|
history.back();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 并不再重定向当前url
|
||||||
|
chrome.declarativeNetRequest.updateDynamicRules(
|
||||||
|
{
|
||||||
|
removeRuleIds: [2],
|
||||||
|
addRules: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
priority: 1,
|
||||||
|
action: {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urls: [
|
||||||
|
"https://docs.scriptcat.org/docs/script_installation",
|
||||||
|
"https://www.tampermonkey.net/script_installation.php",
|
||||||
|
],
|
||||||
|
types: ["main_frame"],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// 重定向到脚本安装页
|
||||||
|
chrome.declarativeNetRequest.updateDynamicRules(
|
||||||
|
{
|
||||||
|
removeRuleIds: [1],
|
||||||
|
addRules: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
priority: 1,
|
||||||
|
action: {
|
||||||
|
type: chrome.declarativeNetRequest.RuleActionType.REDIRECT,
|
||||||
|
redirect: {
|
||||||
|
regexSubstitution: "https://docs.scriptcat.org/docs/script_installation#url=\\0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
condition: {
|
||||||
|
regexFilter: "^([^#]+?)\\.user(\\.bg|\\.sub)?\\.js((\\?).*|$)",
|
||||||
|
resourceTypes: [chrome.declarativeNetRequest.ResourceType.MAIN_FRAME],
|
||||||
|
requestMethods: [chrome.declarativeNetRequest.RequestMethod.GET],
|
||||||
|
// 排除常见的复合上述条件的域名
|
||||||
|
excludedRequestDomains: ["github.com"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
console.error(chrome.runtime.lastError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public openInstallPageByUrl(url: string) {
|
||||||
|
return fetchScriptInfo(url, "user", false, uuidv4()).then((info) => {
|
||||||
|
Cache.getInstance().set(CacheKey.scriptInfo(info.uuid), info);
|
||||||
|
setTimeout(() => {
|
||||||
|
// 清理缓存
|
||||||
|
Cache.getInstance().del(CacheKey.scriptInfo(info.uuid));
|
||||||
|
}, 60 * 1000);
|
||||||
|
openInCurrentTab(`/src/install.html?uuid=${info.uuid}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private api: Server = new Server();
|
||||||
|
|
||||||
|
private mq: MessageQueue = new MessageQueue();
|
||||||
|
|
||||||
|
// 获取安装信息
|
||||||
|
getInstallInfo(params: { uuid: string }) {
|
||||||
|
const info = Cache.getInstance().get(CacheKey.scriptInfo(params.uuid));
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
initManager() {
|
||||||
|
// 监听消息
|
||||||
|
this.api.on("getInstallInfo", this.getInstallInfo);
|
||||||
|
|
||||||
|
this.listenerScriptInstall();
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ import {
|
|||||||
ScriptDAO,
|
ScriptDAO,
|
||||||
UserConfig,
|
UserConfig,
|
||||||
} from "@App/app/repo/scripts";
|
} from "@App/app/repo/scripts";
|
||||||
import { InstallSource } from "@App/app/service/manager";
|
import { InstallSource } from "@App/app/service/service_worker";
|
||||||
import YAML from "yaml";
|
import YAML from "yaml";
|
||||||
import { Subscribe, SUBSCRIBE_STATUS_ENABLE, SubscribeDAO } from "@App/app/repo/subscribe";
|
import { Subscribe, SUBSCRIBE_STATUS_ENABLE, SubscribeDAO } from "@App/app/repo/subscribe";
|
||||||
import { nextTime } from "./utils";
|
import { nextTime } from "./utils";
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import { extConnect } from "@Packages/message/extension";
|
import ServiceWorkerManager from "./app/service/service_worker";
|
||||||
import Manager from "./app/service/manager";
|
|
||||||
import { Connect } from "@Packages/message";
|
|
||||||
import migrate from "./app/migrate";
|
import migrate from "./app/migrate";
|
||||||
import LoggerCore from "./app/logger/core";
|
import LoggerCore from "./app/logger/core";
|
||||||
import DBWriter from "./app/logger/db_writer";
|
import DBWriter from "./app/logger/db_writer";
|
||||||
@ -55,10 +53,8 @@ async function main() {
|
|||||||
loggerCore.logger().debug("background start");
|
loggerCore.logger().debug("background start");
|
||||||
// 初始化沙盒环境
|
// 初始化沙盒环境
|
||||||
await setupOffscreenDocument();
|
await setupOffscreenDocument();
|
||||||
// 初始化连接
|
|
||||||
const extClient = new Connect(extConnect());
|
|
||||||
// 初始化管理器
|
// 初始化管理器
|
||||||
const manager = new Manager(extClient);
|
const manager = new ServiceWorkerManager();
|
||||||
manager.initManager();
|
manager.initManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user