value问题处理
Some checks failed
test / Run tests (push) Failing after 3s
build / Build (push) Failing after 6s

This commit is contained in:
王一之 2025-04-09 18:05:59 +08:00
parent 9f70b7eb7a
commit 0d86dae710
18 changed files with 122 additions and 68 deletions

View File

@ -9,7 +9,7 @@
// @connect example.com // @connect example.com
// ==/UserScript== // ==/UserScript==
// GM_cookie("store") 方法请看gm_value.js的例子, 可用于隐身窗口的操作 // GM_cookie("store") 方法请看storage_name/gm_value.js的例子, 可用于隐身窗口的操作
GM_cookie("set", { GM_cookie("set", {
url: "http://example.com/cookie", url: "http://example.com/cookie",

View File

@ -11,13 +11,10 @@
// @grant GM_addValueChangeListener // @grant GM_addValueChangeListener
// @grant GM_listValues // @grant GM_listValues
// @grant GM_deleteValue // @grant GM_deleteValue
// @grant GM_cookie
// ==/UserScript== // ==/UserScript==
GM_addValueChangeListener("test_set", function (name, oldval, newval, remote, tabid) { GM_addValueChangeListener("test_set", function (name, oldval, newval, remote, tabid) {
GM_cookie("store", tabid, (storeId) => { console.log("test_set change", name, oldval, newval, remote, tabid);
console.log("store", storeId);
});
}); });
setInterval(() => { setInterval(() => {

View File

@ -0,0 +1,18 @@
// ==UserScript==
// @name gm value storage 设置方
// @namespace https://bbs.tampermonkey.net.cn/
// @version 0.1.0
// @description 多个脚本之间共享数据 设置方
// @author You
// @match https://bbs.tampermonkey.net.cn/
// @run-at document-start
// @grant GM_setValue
// @grant GM_deleteValue
// @storageName example
// ==/UserScript==
setTimeout(() => {
GM_deleteValue("test_set");
}, 3000);
GM_setValue("test_set", new Date().getTime());

View File

@ -0,0 +1,28 @@
// ==UserScript==
// @name gm value storage 读取与监听方
// @namespace https://bbs.tampermonkey.net.cn/
// @version 0.1.0
// @description 多个脚本之间共享数据 读取与监听方
// @author You
// @match https://bbs.tampermonkey.net.cn/
// @run-at document-start
// @grant GM_getValue
// @grant GM_addValueChangeListener
// @grant GM_listValues
// @grant GM_cookie
// @storageName example
// ==/UserScript==
GM_addValueChangeListener("test_set", function (name, oldval, newval, remote, tabid) {
console.log("test_set change", name, oldval, newval, remote, tabid);
// 可以通过tabid获取到触发变化的tab
// GM_cookie.store可以获取到对应的cookie storeId
GM_cookie("store", tabid, (storeId) => {
console.log("store", storeId);
});
});
setInterval(() => {
console.log(GM_getValue("test_set"));
console.log(GM_listValues());
}, 2000);

View File

@ -1,4 +1,5 @@
import LoggerCore from "@App/app/logger/core"; import LoggerCore from "@App/app/logger/core";
import { connect } from "./client";
export interface Message extends MessageSend { export interface Message extends MessageSend {
onConnect(callback: (data: any, con: MessageConnect) => void): void; onConnect(callback: (data: any, con: MessageConnect) => void): void;
@ -124,7 +125,7 @@ export function forwardMessage(prefix: string, path: string, from: Server, to: M
} }
const fromConnect = fromCon.getConnect(); const fromConnect = fromCon.getConnect();
if (fromConnect) { if (fromConnect) {
to.connect({ action: prefix + "/" + path, data: params }).then((toCon) => { connect(to, prefix + "/" + path, params).then((toCon) => {
fromConnect.onMessage((data) => { fromConnect.onMessage((data) => {
toCon.sendMessage(data); toCon.sendMessage(data);
}); });

View File

@ -90,11 +90,11 @@ export class MapCache {
} }
export async function incr(cache: Cache, key: string, increase: number): Promise<number> { export async function incr(cache: Cache, key: string, increase: number): Promise<number> {
const value = await cache.get(key); return cache.tx<number>(key, async (value) => {
let num = value || 0; let num = value || 0;
num += increase; num += increase;
await cache.set(key, num);
return num; return num;
});
} }
export default class Cache { export default class Cache {
@ -138,15 +138,17 @@ export default class Cache {
private txPromise: Map<string, Promise<any>> = new Map(); private txPromise: Map<string, Promise<any>> = new Map();
// 事务处理,如果有事务正在进行,则等待 // 事务处理,如果有事务正在进行,则等待
public async tx(key: string, set: (result: any) => Promise<any>): Promise<void> { public async tx<T>(key: string, set: (result: T) => Promise<T>): Promise<T> {
let promise = this.txPromise.get(key); let promise = this.txPromise.get(key);
if (promise) { if (promise) {
await promise; await promise;
} }
let newValue: T;
promise = this.get(key) promise = this.get(key)
.then((result) => set(result)) .then((result) => set(result))
.then((value) => { .then((value) => {
if (value) { if (value) {
newValue = value;
return this.set(key, value); return this.set(key, value);
} }
return Promise.resolve(); return Promise.resolve();
@ -154,5 +156,6 @@ export default class Cache {
this.txPromise.set(key, promise); this.txPromise.set(key, promise);
await promise; await promise;
this.txPromise.delete(key); this.txPromise.delete(key);
return newValue!;
} }
} }

View File

@ -3,7 +3,7 @@ import Logger from "./logger";
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error"; export type LogLevel = "trace" | "debug" | "info" | "warn" | "error";
export interface LogLabel { export interface LogLabel {
[key: string]: string | string[] | boolean | number | undefined; [key: string]: string | string[] | boolean | number | object | undefined;
component?: string; component?: string;
} }

View File

@ -3,6 +3,8 @@ import Logger from "@App/app/logger/logger";
import { GetSender, Group, MessageConnect } from "@Packages/message/server"; import { GetSender, Group, MessageConnect } from "@Packages/message/server";
export default class GMApi { export default class GMApi {
logger: Logger = LoggerCore.logger().with({ service: "gmApi" });
constructor(private group: Group) {} constructor(private group: Group) {}
async dealXhrResponse( async dealXhrResponse(

View File

@ -282,7 +282,6 @@ export class Runtime {
} }
async runScript(script: ScriptRunResouce) { async runScript(script: ScriptRunResouce) {
console.log("runScript", script);
const exec = this.execScripts.get(script.uuid); const exec = this.execScripts.get(script.uuid);
// 如果正在运行,先释放 // 如果正在运行,先释放
if (exec) { if (exec) {

View File

@ -71,13 +71,15 @@ export default class GMApi {
} }
@PermissionVerify.API() @PermissionVerify.API()
async GM_setValue(request: Request) { async GM_setValue(request: Request, sender: GetSender) {
console.log("setValue", request);
if (!request.params || request.params.length !== 2) { if (!request.params || request.params.length !== 2) {
return Promise.reject(new Error("param is failed")); return Promise.reject(new Error("param is failed"));
} }
const [key, value] = request.params; const [key, value] = request.params;
await this.value.setValue(request.script.uuid, key, value); await this.value.setValue(request.script.uuid, key, value, {
runFlag: request.runFlag,
tabId: sender.getSender().tab?.id,
});
} }
// 根据header生成dnr规则 // 根据header生成dnr规则
@ -183,7 +185,6 @@ export default class GMApi {
@PermissionVerify.API() @PermissionVerify.API()
GM_registerMenuCommand(request: Request, sender: GetSender) { GM_registerMenuCommand(request: Request, sender: GetSender) {
console.log("registerMenuCommand", request.params, sender.getSender(), sender.getSender().tab!.id!);
const [id, name, accessKey] = request.params; const [id, name, accessKey] = request.params;
// 触发菜单注册, 在popup中处理 // 触发菜单注册, 在popup中处理
this.mq.emit("registerMenuCommand", { this.mq.emit("registerMenuCommand", {

View File

@ -20,7 +20,7 @@ import {
subscribeScriptMenuRegister, subscribeScriptMenuRegister,
subscribeScriptRunStatus, subscribeScriptRunStatus,
} from "../queue"; } from "../queue";
import { storageKey } from "@App/runtime/utils"; import { getStorageName } from "@App/runtime/utils";
export type ScriptMenuItem = { export type ScriptMenuItem = {
id: number; id: number;
@ -87,7 +87,6 @@ export class PopupService {
// 移除之前所有的菜单 // 移除之前所有的菜单
chrome.contextMenus.removeAll(); chrome.contextMenus.removeAll();
const [menu, backgroundMenu] = await Promise.all([this.getScriptMenu(tabId), this.getScriptMenu(-1)]); const [menu, backgroundMenu] = await Promise.all([this.getScriptMenu(tabId), this.getScriptMenu(-1)]);
console.log(menu, backgroundMenu, tabId);
if (!menu.length && !backgroundMenu.length) { if (!menu.length && !backgroundMenu.length) {
return; return;
} }
@ -140,7 +139,6 @@ export class PopupService {
if (script) { if (script) {
script.menus = script.menus.filter((item) => item.id !== id); script.menus = script.menus.filter((item) => item.id !== id);
} }
console.log("unregister menu", data);
this.updateScriptMenu(); this.updateScriptMenu();
return data; return data;
}); });
@ -149,7 +147,6 @@ export class PopupService {
updateScriptMenu() { updateScriptMenu() {
// 获取当前页面并更新菜单 // 获取当前页面并更新菜单
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
console.log("query", tabs);
if (!tabs.length) { if (!tabs.length) {
return; return;
} }
@ -163,7 +160,7 @@ export class PopupService {
return { return {
uuid: script.uuid, uuid: script.uuid,
name: script.name, name: script.name,
storageName: storageKey(script), storageName: getStorageName(script),
enable: script.status === SCRIPT_STATUS_ENABLE, enable: script.status === SCRIPT_STATUS_ENABLE,
updatetime: script.updatetime || 0, updatetime: script.updatetime || 0,
hasUserConfig: !!script.config, hasUserConfig: !!script.config,

View File

@ -194,6 +194,8 @@ export class RuntimeService {
}) })
); );
console.log("pageLoad", enableScript);
this.mq.emit("pageLoad", { this.mq.emit("pageLoad", {
tabId: chromeSender.tab?.id, tabId: chromeSender.tab?.id,
frameId: chromeSender.frameId, frameId: chromeSender.frameId,

View File

@ -2,12 +2,13 @@ import LoggerCore from "@App/app/logger/core";
import Logger from "@App/app/logger/logger"; import Logger from "@App/app/logger/logger";
import { Script, SCRIPT_TYPE_NORMAL, ScriptDAO } from "@App/app/repo/scripts"; import { Script, SCRIPT_TYPE_NORMAL, ScriptDAO } from "@App/app/repo/scripts";
import { ValueDAO } from "@App/app/repo/value"; import { ValueDAO } from "@App/app/repo/value";
import { storageKey } from "@App/runtime/utils"; import { getStorageName } from "@App/runtime/utils";
import { Group, MessageSend } from "@Packages/message/server"; import { Group, MessageSend } from "@Packages/message/server";
import { RuntimeService } from "./runtime"; import { RuntimeService } from "./runtime";
import { PopupService } from "./popup"; import { PopupService } from "./popup";
import { ValueUpdateData } from "@App/runtime/content/exec_script"; import { ValueUpdateData, ValueUpdateSender } from "@App/runtime/content/exec_script";
import { sendMessage } from "@Packages/message/client"; import { sendMessage } from "@Packages/message/client";
import Cache from "@App/app/cache";
export class ValueService { export class ValueService {
logger: Logger; logger: Logger;
@ -24,25 +25,27 @@ export class ValueService {
} }
async getScriptValue(script: Script) { async getScriptValue(script: Script) {
const ret = await this.valueDAO.get(storageKey(script)); const ret = await this.valueDAO.get(getStorageName(script));
if (!ret) { if (!ret) {
return {}; return {};
} }
return ret.data; return ret.data;
} }
async setValue(uuid: string, key: string, value: any, sender?: any): Promise<boolean> { async setValue(uuid: string, key: string, value: any, sender: ValueUpdateSender): Promise<boolean> {
// 查询出脚本 // 查询出脚本
const script = await this.scriptDAO.get(uuid); const script = await this.scriptDAO.get(uuid);
if (!script) { if (!script) {
return Promise.reject(new Error("script not found")); return Promise.reject(new Error("script not found"));
} }
// 查询老的值 // 查询老的值
const storageName = storageKey(script); const storageName = getStorageName(script);
const valueModel = await this.valueDAO.get(storageName);
let oldValue; let oldValue;
// 使用事务来保证数据一致性
await Cache.getInstance().tx("setValue:" + storageName, async () => {
const valueModel = await this.valueDAO.get(storageName);
if (!valueModel) { if (!valueModel) {
this.valueDAO.save(storageName, { await this.valueDAO.save(storageName, {
uuid: script.uuid, uuid: script.uuid,
storageName: storageName, storageName: storageName,
data: { [key]: value }, data: { [key]: value },
@ -52,18 +55,20 @@ export class ValueService {
} else { } else {
oldValue = valueModel.data[key]; oldValue = valueModel.data[key];
valueModel.data[key] = value; valueModel.data[key] = value;
this.valueDAO.save(storageName, valueModel); await this.valueDAO.save(storageName, valueModel);
} }
console.log(valueModel);
return true;
});
const sendData: ValueUpdateData = { const sendData: ValueUpdateData = {
oldValue, oldValue,
sender, sender,
value, value,
key, key,
uuid, uuid,
storageKey: storageName, storageName: storageName,
}; };
// 判断是后台脚本还是前台脚本 // 判断是后台脚本还是前台脚本
console.log("value update", script, sendData);
if (script.type === SCRIPT_TYPE_NORMAL) { if (script.type === SCRIPT_TYPE_NORMAL) {
chrome.tabs.query({}, (tabs) => { chrome.tabs.query({}, (tabs) => {
// 推送到所有加载了本脚本的tab中 // 推送到所有加载了本脚本的tab中

View File

@ -5,7 +5,6 @@ import { deleteScript, scriptSlice, upsertScript } from "./features/script";
export default function storeSubscribe() { export default function storeSubscribe() {
subscribeScriptRunStatus(messageQueue, (data) => { subscribeScriptRunStatus(messageQueue, (data) => {
console.log("subscribeScriptRunStatus", data);
store.dispatch(scriptSlice.actions.updateRunStatus(data)); store.dispatch(scriptSlice.actions.updateRunStatus(data));
}); });
subscribeScriptInstall(messageQueue, (message) => { subscribeScriptInstall(messageQueue, (message) => {

View File

@ -5,16 +5,18 @@ import GMApi from "./gm_api";
import { compileScript, createContext, proxyContext, ScriptFunc } from "./utils"; import { compileScript, createContext, proxyContext, ScriptFunc } from "./utils";
import { Message } from "@Packages/message/server"; import { Message } from "@Packages/message/server";
export type ValueUpdateSender = {
runFlag: string;
tabId?: number;
};
export type ValueUpdateData = { export type ValueUpdateData = {
oldValue: any; oldValue: any;
value: any; value: any;
key: string; // 值key key: string; // 值key
uuid: string; uuid: string;
storageKey: string; // 储存key storageName: string; // 储存name
sender: { sender: ValueUpdateSender;
runFlag: string;
tabId?: number;
};
}; };
export class RuntimeMessage {} export class RuntimeMessage {}

View File

@ -2,7 +2,7 @@ import { ScriptRunResouce } from "@App/app/repo/scripts";
import { getMetadataStr, getUserConfigStr, parseUserConfig } from "@App/pkg/utils/script"; import { getMetadataStr, getUserConfigStr, parseUserConfig } from "@App/pkg/utils/script";
import { ValueUpdateData } from "./exec_script"; import { ValueUpdateData } from "./exec_script";
import { ExtVersion } from "@App/app/const"; import { ExtVersion } from "@App/app/const";
import { storageKey } from "../utils"; import { getStorageName } from "../utils";
import { Message, MessageConnect } from "@Packages/message/server"; import { Message, MessageConnect } from "@Packages/message/server";
import { CustomEventMessage } from "@Packages/message/custom_event_message"; import { CustomEventMessage } from "@Packages/message/custom_event_message";
import LoggerCore from "@App/app/logger/core"; import LoggerCore from "@App/app/logger/core";
@ -86,22 +86,18 @@ export default class GMApi {
} }
public valueUpdate(data: ValueUpdateData) { public valueUpdate(data: ValueUpdateData) {
if (data.uuid === this.scriptRes.uuid || data.storageKey === storageKey(this.scriptRes)) { if (data.uuid === this.scriptRes.uuid || data.storageName === getStorageName(this.scriptRes)) {
// 触发,并更新值 // 触发,并更新值
if (data.value === undefined) { if (data.value === undefined) {
delete this.scriptRes.value[data.value]; if (this.scriptRes.value[data.key] !== undefined) {
delete this.scriptRes.value[data.key];
}
} else { } else {
this.scriptRes.value[data.key] = data.value; this.scriptRes.value[data.key] = data.value;
} }
this.valueChangeListener.forEach((item) => { this.valueChangeListener.forEach((item) => {
if (item.name === data.value.key) { if (item.name === data.key) {
item.listener( item.listener(data.key, data.oldValue, data.value, data.sender.runFlag !== this.runFlag, data.sender.tabId);
data.value.key,
data.oldValue,
data.value,
data.sender.runFlag !== this.runFlag,
data.sender.tabId
);
} }
}); });
} }
@ -345,6 +341,8 @@ export default class GMApi {
// 处理blob // 处理blob
param.dataType = "Blob"; param.dataType = "Blob";
param.data = await this.CAT_createBlobUrl(details.data); param.data = await this.CAT_createBlobUrl(details.data);
} else {
param.data = details.data;
} }
// 处理返回数据 // 处理返回数据

View File

@ -1,7 +1,8 @@
import { ScriptRunResouce } from "@App/app/repo/scripts"; import { ScriptRunResouce } from "@App/app/repo/scripts";
import { Message, Server } from "@Packages/message/server"; import { Message, Server } from "@Packages/message/server";
import ExecScript from "./exec_script"; import ExecScript, { ValueUpdateData } from "./exec_script";
import { addStyle, ScriptFunc } from "./utils"; import { addStyle, ScriptFunc } from "./utils";
import { getStorageName } from "../utils";
export class InjectRuntime { export class InjectRuntime {
execList: ExecScript[] = []; execList: ExecScript[] = [];
@ -35,11 +36,12 @@ export class InjectRuntime {
exec.menuClick(data.id); exec.menuClick(data.id);
} }
}); });
this.server.on("runtime/valueUpdate", (data: { uuid: string; key: string; value: any }) => { this.server.on("runtime/valueUpdate", (data: ValueUpdateData) => {
const exec = this.execList.find((val) => val.scriptRes.uuid === data.uuid); this.execList
if (exec) { .filter((val) => val.scriptRes.uuid === data.uuid || getStorageName(val.scriptRes) === data.storageName)
// exec.valueUpdate(data.key,); .forEach((val) => {
} val.valueUpdate(data);
});
}); });
} }

View File

@ -29,7 +29,7 @@ export const unsafeHeaders: { [key: string]: boolean } = {
via: true, via: true,
}; };
export function storageKey(script: Script): string { export function getStorageName(script: Script): string {
if (script.metadata && script.metadata.storagename) { if (script.metadata && script.metadata.storagename) {
return script.metadata.storagename[0]; return script.metadata.storagename[0];
} }