一个轻量、无依赖的用户脚本消息库,通过 GM.setValue / GM_setValue + GM_addValueChangeListener 实现 跨标签页 / 跨上下文的事件广播:
postMessage / onmessage,但基于 GM 存储事件。安装 / @require 这是一个库,而不是独立脚本。请在你的用户脚本头部加入:
// @require https://scriptcat.org/lib/4710/0.1.0/GM_postMessage.js?sha384-U9kZ0JxuJEQFA7LMejrhTcel/fMoH/g434fOTpP0a/fBXxMnutQ7/L+WOiA1p9UT
var { GM_postMessage, GM_onMessage } = (() => { /* ... */ })();
GM_postMessage(tag, message)string,消息频道名。相同 tag 的订阅者都会收到消息。说明: 该函数不返回 Promise,调用后立即返回,消息通过 GM 存储事件异步分发。
GM_onMessage(tag, callback)type MessagePayload = {
tag: string;
timeStamp: number; // 发送时的时间戳(ms)
remote: boolean; // 是否来自其他上下文(由 GM_addValueChangeListener 提供)
tabId: any; // 发送者标签页标识(仅在管理器支持时可用)
message: any; // 发送时传入的原始 message
};
GM_onMessage(tag: string, callback: (payload: MessagePayload) => void): void;
string,频道名。与 GM_postMessage 用的 tag 对应。多次对同一
tag调用GM_onMessage会将多个回调都加入此频道,按消息时间顺序触发。
// A 脚本 / A 标签页:订阅 "chat" 频道
GM_onMessage('chat', ({ tag, timeStamp, remote, tabId, message }) => {
console.log('[chat] 收到消息:', {
tag,
time: new Date(timeStamp).toISOString(),
fromRemote: remote,
tabId,
message,
});
});
// B 脚本 / B 标签页:向 "chat" 频道广播消息
GM_postMessage('chat', {
from: 'tab-B',
text: 'Hello from another tab!',
});
// Server 端:监听 "data:request" 并在 "data:response" 回发
GM_onMessage('data:request', async ({ message }) => {
const { requestId } = message;
const data = { time: Date.now(), foo: 'bar' };
GM_postMessage('data:response', { requestId, data });
});
// Client 端:发送请求 + 本地匹配响应
const requestId = `${Date.now()}-${Math.random()}`;
GM_onMessage('data:response', ({ message }) => {
if (message.requestId === requestId) {
console.log('收到响应:', message.data);
}
});
GM_postMessage('data:request', { requestId });
建议按功能 / 方向设计 tag,例如:
'chat'、'notify''data:request' / 'data:response''sync:user-settings' 等。需要一个支持以下 API 的用户脚本管理器:
GM.setValue 或 GM_setValue(二选一)GM_addValueChangeListener代码中自动兼容两种 setValue 写法:
const setValue = GM.setValue
? (key, value) => GM.setValue(key, value)
: (key, value) => GM_setValue(key, value);
已知在以下环境中通常可工作(取决于版本与设置):
所有消息以 postMessage::<tag> 为 key 写入 GM 存储,例如:
postMessage::chatpostMessage::data:requestGM_postMessage(tag, message) 会写入:
{ message, mId: `${Date.now()}.${Math.random()}` }
其中:
message 为原始消息数据;mId 用于生成时间戳 timeStamp 和打散顺序。GM_addValueChangeListener 监听对应 key,一旦有变化:
解析 newValue,提取:
messagetimeStamp = mId 中小数点前的时间戳部分remote / tabId 来自 GM 的回调参数将这些信息推入内部 messages 队列。
使用 setTimeout(collector, 1) 做一个极短的缓冲窗口:
timeStamp 排序(保证整体时间顺序);tag 分发给对应的回调列表。回调调用采用 Promise.resolve(...).then(execute):
当前实现为零配置:
若你需要更复杂的行为(例如:自动取消订阅、带超时的请求-响应等),建议:
GM_onMessage 再封装一层管理;消息必须可被 JSON 序列化:内部使用 JSON.stringify / JSON.parse,不能直接传函数、undefined、Symbol 等。
这是一个 广播机制:
tag 上的所有监听器都会收到消息;message 中加上 from / to / requestId 字段并在回调中自行过滤。回调执行是异步的(微任务 + setTimeout 批处理),请不要依赖同步立即可见的副作用。
由于基于 GM 存储事件:
remote / tabId 的支持程度可能不同;remote 再加上自定义 clientId。公共领域 — The Unlicense。
本库在源码头部附带完整 Unlicense 声明,简要概括:
详情可参考:https://unlicense.org