diff --git a/src/app/repo/repo.ts b/src/app/repo/repo.ts index 88ce0c0eb..7c373503e 100644 --- a/src/app/repo/repo.ts +++ b/src/app/repo/repo.ts @@ -1,42 +1,149 @@ // 加载全局缓存 -let loadCachePromise: Promise | undefined = undefined; -let cache: { [key: string]: any } | undefined = undefined; +let loadCachePromise: Promise>> | undefined = undefined; +let cache: Partial> | undefined = undefined; // 加载数据到缓存 -function loadCache(): Promise { +function loadCache(): Promise>> { if (cache) { return Promise.resolve(cache); } - if (loadCachePromise) { - return loadCachePromise; + if (!loadCachePromise) { + loadCachePromise = new Promise>>((resolve) => { + chrome.storage.local.get((result: Partial> | undefined) => { + const lastError = chrome.runtime.lastError; + if (lastError) { + console.error("chrome.runtime.lastError in chrome.storage.local.get:", lastError); + // 无视storage API错误,继续执行 + } + cache = result || {}; + loadCachePromise = undefined; + resolve(cache); + }); + }); } - loadCachePromise = new Promise((resolve) => { - chrome.storage.local.get((result: { [key: string]: any } | undefined) => { + return loadCachePromise; +} + +function saveCacheAndStorage(key: string, value: T): Promise { + return Promise.all([ + loadCache().then((cache) => { + cache[key] = value; + }), + new Promise((resolve) => { + chrome.storage.local.set({ [key]: value }, () => { + const lastError = chrome.runtime.lastError; + if (lastError) { + console.error("chrome.runtime.lastError in chrome.storage.local.set:", lastError); + // 无视storage API错误,继续执行 + } + resolve(); + }); + }), + ]).then(() => value); +} + +function saveStorage(key: string, value: T): Promise { + return new Promise((resolve) => { + chrome.storage.local.set( + { + [key]: value, + }, + () => { + const lastError = chrome.runtime.lastError; + if (lastError) { + console.error("chrome.runtime.lastError in chrome.storage.local.set:", lastError); + // 无视storage API错误,继续执行 + } + resolve(value); + } + ); + }); +} + +function saveStorageRecord(record: Partial>): Promise { + return new Promise((resolve) => { + chrome.storage.local.set(record, () => { + const lastError = chrome.runtime.lastError; + if (lastError) { + console.error("chrome.runtime.lastError in chrome.storage.local.set:", lastError); + // 无视storage API错误,继续执行 + } + resolve(); + }); + }); +} + +function getCache(key: string): Promise { + return loadCache().then((cache) => { + if (cache[key]) { + return Object.assign({}, cache[key]); + } + return cache[key]; + }); +} + +function getStorage(key: string): Promise { + return new Promise((resolve) => { + chrome.storage.local.get(key, (result) => { const lastError = chrome.runtime.lastError; if (lastError) { console.error("chrome.runtime.lastError in chrome.storage.local.get:", lastError); // 无视storage API错误,继续执行 } - cache = result; - resolve(cache); + resolve(result[key]); }); }); - return loadCachePromise; } -function saveCache(key: string, value: any) { - loadCache().then(() => { - cache![key] = value; +function getStorageRecord(keys: string[]): Promise>> { + return new Promise((resolve) => { + chrome.storage.local.get(keys, (result) => { + const lastError = chrome.runtime.lastError; + if (lastError) { + console.error("chrome.runtime.lastError in chrome.storage.local.get:", lastError); + // 无视storage API错误,继续执行 + } + resolve(result); + }); }); - return chrome.storage.local.set({ [key]: value }); } function deleteCache(key: string) { - loadCache().then(() => { - delete cache![key]; + return loadCache().then((cache) => { + delete cache[key]; + }); +} + +function deleteStorage(key: string) { + return new Promise((resolve) => { + chrome.storage.local.remove(key, () => { + const lastError = chrome.runtime.lastError; + if (lastError) { + console.error("chrome.runtime.lastError in chrome.storage.local.remove:", lastError); + // 无视storage API错误,继续执行 + } + resolve(); + }); + }); +} +function deletesStorage(keys: string[]) { + return new Promise((resolve, reject) => { + chrome.storage.local.remove(keys, () => { + const lastError = chrome.runtime.lastError; + if (lastError) { + console.error("chrome.runtime.lastError in chrome.storage.local.remove:", lastError); + // 无视storage API错误,继续执行 + reject(); + } + resolve(); + }); + }).catch(async () => { + // fallback + for (const key of keys) { + await deleteStorage(key); + } }); - return chrome.storage.local.remove(key); } export abstract class Repo { @@ -58,44 +165,41 @@ export abstract class Repo { } protected async _save(key: string, val: T): Promise { - return new Promise((resolve) => { - const data = { - [this.joinKey(key)]: val, - }; - if (this.useCache) { - return saveCache(this.joinKey(key), val).then(() => { - return resolve(val); - }); - } - chrome.storage.local.set(data, () => { - const lastError = chrome.runtime.lastError; - if (lastError) { - console.error("chrome.runtime.lastError in chrome.storage.local.set:", lastError); - // 无视storage API错误,继续执行 - } - resolve(val); - }); - }); + key = this.joinKey(key); + if (this.useCache) { + return saveCacheAndStorage(key, val); + } + return saveStorage(key, val); } public get(key: string): Promise { + key = this.joinKey(key); + if (this.useCache) { + return getCache(key); + } + return getStorage(key); + } + + public gets(keys: string[]): Promise<(T | undefined)[]> { + keys = keys.map((key) => this.joinKey(key)); if (this.useCache) { return loadCache().then((cache) => { - if (cache[this.joinKey(key)]) { - return Object.assign({}, cache[this.joinKey(key)]); - } - return cache[this.joinKey(key)]; + return keys.map((key) => { + if (cache[key]) { + return Object.assign({}, cache[key]); + } + return cache[key]; + }); }); } return new Promise((resolve) => { - key = this.joinKey(key); - chrome.storage.local.get(key, (result) => { + chrome.storage.local.get(keys, (result) => { const lastError = chrome.runtime.lastError; if (lastError) { console.error("chrome.runtime.lastError in chrome.storage.local.get:", lastError); // 无视storage API错误,继续执行 } - resolve(result[key]); + resolve(keys.map((key) => result[key])); }); }); } @@ -123,69 +227,98 @@ export abstract class Repo { }); }); } - const loadData = () => { - return new Promise((resolve) => { - chrome.storage.local.get((result: { [key: string]: T }) => { - const lastError = chrome.runtime.lastError; - if (lastError) { - console.error("chrome.runtime.lastError in chrome.storage.local.get:", lastError); - // 无视storage API错误,继续执行 - } - resolve(this.filter(result, filters)); - }); + return new Promise((resolve) => { + chrome.storage.local.get((result: { [key: string]: T }) => { + const lastError = chrome.runtime.lastError; + if (lastError) { + console.error("chrome.runtime.lastError in chrome.storage.local.get:", lastError); + // 无视storage API错误,继续执行 + } + resolve(this.filter(result, filters)); }); - }; - return loadData(); + }); } async findOne(filters?: (key: string, value: T) => boolean): Promise { - const list = await this.find(filters); - if (list.length > 0) { - return list[0]; - } - return undefined; + return this.find(filters).then((list) => { + if (list.length > 0) { + return list[0]; + } + return undefined; + }); } - public delete(key: string) { + public delete(key: string): Promise { + key = this.joinKey(key); if (this.useCache) { - return deleteCache(this.joinKey(key)); + return Promise.all([deleteCache(key), deleteStorage(key)]).then(() => undefined); } - return new Promise((resolve) => { - chrome.storage.local.remove(this.joinKey(key), () => { - const lastError = chrome.runtime.lastError; - if (lastError) { - console.error("chrome.runtime.lastError in chrome.storage.local.remove:", lastError); - // 无视storage API错误,继续执行 + return deleteStorage(key); + } + + public deletes(keys: string[]): Promise { + keys = keys.map((key) => this.joinKey(key)); + if (this.useCache) { + return loadCache().then((cache) => { + for (const key of keys) { + delete cache[key]; } - resolve(); + return deletesStorage(keys); }); - }); + } + return deletesStorage(keys); } + // 資料不存在時無法更新, 回傳 false + // 資料存在時進行Object.assign更新,回傳更新後的資料項目 update(key: string, val: Partial): Promise { + key = this.joinKey(key); if (this.useCache) { return loadCache().then((cache) => { - const data = cache[this.joinKey(key)]; + const data = cache[key] as T; if (data) { Object.assign(data, val); - return saveCache(this.joinKey(key), data).then(() => { - return data; - }); + return saveCacheAndStorage(key, data) as Promise; } return false; }); } - return new Promise((resolve) => { - this.get(key).then((result) => { - if (result) { - Object.assign(result, val); - this._save(key, result).then(() => { - resolve(result); - }); - } else { - resolve(false); + return getStorage(key).then((result) => { + if (result) { + Object.assign(result, val); + return saveStorage(key, result) as T; + } else { + return false; + } + }); + } + + updates(keys: string[], val: Partial): Promise<(T | false)[]> { + keys = keys.map((key) => this.joinKey(key)); + if (this.useCache) { + return loadCache().then((cache) => + Promise.all( + keys.map((key) => { + const data = cache[key] as T; + if (data) { + Object.assign(data, val); + return saveCacheAndStorage(key, data) as Promise; + } + return false; + }) + ) + ); + } + return getStorageRecord(keys).then((record) => { + const result = keys.map((key) => { + const o = record[key]; + if (o) { + Object.assign(o, val); + return o as T; } + return false; }); + return saveStorageRecord(record).then(() => result); }); } diff --git a/src/app/service/offscreen/script.ts b/src/app/service/offscreen/script.ts index 75e1a1553..0b9425aea 100644 --- a/src/app/service/offscreen/script.ts +++ b/src/app/service/offscreen/script.ts @@ -35,17 +35,19 @@ export class ScriptService { } async init() { - this.messageQueue.subscribe("enableScript", async (data) => { - const script = await this.scriptClient.info(data.uuid); - if (script.type === SCRIPT_TYPE_NORMAL) { - return; - } - if (data.enable) { - // 构造脚本运行资源,发送给沙盒运行 - enableScript(this.windowMessage, await this.scriptClient.getScriptRunResource(script)); - } else { - // 发送给沙盒停止 - disableScript(this.windowMessage, script.uuid); + this.messageQueue.subscribe("enableScripts", async (data) => { + for (const { uuid, enable } of data) { + const script = await this.scriptClient.info(uuid); + if (script.type === SCRIPT_TYPE_NORMAL) { + continue; + } + if (enable) { + // 构造脚本运行资源,发送给沙盒运行 + enableScript(this.windowMessage, await this.scriptClient.getScriptRunResourceByUUID(uuid)); + } else { + // 发送给沙盒停止 + disableScript(this.windowMessage, script.uuid); + } } }); this.messageQueue.subscribe("installScript", async (data) => { @@ -56,14 +58,16 @@ export class ScriptService { // 判断是开启还是关闭 if (data.script.status === SCRIPT_STATUS_ENABLE) { // 构造脚本运行资源,发送给沙盒运行 - enableScript(this.windowMessage, await this.scriptClient.getScriptRunResource(data.script)); + enableScript(this.windowMessage, await this.scriptClient.getScriptRunResourceByUUID(data.script.uuid)); } else { // 发送给沙盒停止 disableScript(this.windowMessage, data.script.uuid); } }); - this.messageQueue.subscribe("deleteScript", async (data) => { - disableScript(this.windowMessage, data.uuid); + this.messageQueue.subscribe("deleteScripts", async (data) => { + for (const { uuid } of data) { + await disableScript(this.windowMessage, uuid); + } }); this.group.on("runScript", this.runScript.bind(this)); diff --git a/src/app/service/queue.ts b/src/app/service/queue.ts index a73cfc07c..9d591aaec 100644 --- a/src/app/service/queue.ts +++ b/src/app/service/queue.ts @@ -1,12 +1,23 @@ -import type { Script, SCRIPT_RUN_STATUS } from "../repo/scripts"; +import type { Script, SCRIPT_RUN_STATUS, SCRIPT_STATUS, SCRIPT_TYPE } from "../repo/scripts"; import type { InstallSource, ScriptMenuItem } from "./service_worker/types"; import type { Subscribe } from "../repo/subscribe"; -export type TInstallScript = { script: Script; update: boolean; upsertBy?: InstallSource }; +export type TInstallScriptParams = { + uuid: string; // 脚本uuid,通过脚本uuid识别唯一脚本 + type: SCRIPT_TYPE; // 脚本类型 1:普通脚本 2:定时脚本 3:后台脚本 + status: SCRIPT_STATUS; // 脚本状态 1:启用 2:禁用 3:错误 4:初始化 + name: string; // 脚本名称 + namespace: string; // 脚本命名空间 + origin?: string; // 脚本来源 + checkUpdateUrl?: string; // 检查更新URL + downloadUrl?: string; // 脚本下载URL +}; + +export type TInstallScript = { script: TInstallScriptParams; update: boolean; upsertBy?: InstallSource }; -export type TDeleteScript = { uuid: string; script: Script }; +export type TDeleteScript = { uuid: string; storageName: string }; -export type TSortScript = Script[]; +export type TSortedScript = { uuid: string; sort: number }; export type TInstallSubscribe = { subscribe: Subscribe }; diff --git a/src/app/service/service_worker/client.ts b/src/app/service/service_worker/client.ts index c170a5825..95d3e9212 100644 --- a/src/app/service/service_worker/client.ts +++ b/src/app/service/service_worker/client.ts @@ -46,14 +46,22 @@ export class ScriptClient extends Client { return this.doThrow("install", { script, code, upsertBy }); } - delete(uuid: string) { - return this.do("delete", uuid); + // delete(uuid: string) { + // return this.do("delete", uuid); + // } + + deletes(uuids: string[]) { + return this.do("deletes", uuids); } enable(uuid: string, enable: boolean) { return this.do("enable", { uuid, enable }); } + enables(uuids: string[], enable: boolean) { + return this.do("enables", { uuids, enable }); + } + info(uuid: string): Promise