GM_postMessage — 一个基于 GM 存储的跨脚本/跨标签消息通道
一个轻量、无依赖的用户脚本消息库,通过 GM.setValue / GM_setValue + GM_addValueChangeListener 实现 跨标签页 / 跨上下文的事件广播:
- 任意脚本、任意标签页,只要共享同一用户脚本存储,就可以通过 tag 频道 相互发送消息。
- API 形态类似
postMessage/onmessage,但基于 GM 存储事件。
安装 / @require
这是一个库,而不是独立脚本。请在你的用户脚本头部加入:// @require https://scriptcat.org/lib/4710/0.1.0/GM_postMessage.js?sha384-U9kZ0JxuJEQFA7LMejrhTcel/fMoH/g434fOTpP0a/fBXxMnutQ7/L+WOiA1p9UT
API
var { GM_postMessage, GM_onMessage } = (() => { /* ... */ })();
GM_postMessage(tag, message)
- tag:
string,消息频道名。相同 tag 的订阅者都会收到消息。 - message:任意可 JSON 序列化的数据(对象 / 数组 / 字符串 / 数字等)。
说明: 该函数不返回 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;
- tag:
string,频道名。与GM_postMessage用的 tag 对应。 - callback:回调函数,会在有消息到达时被调用。
- 返回值:无(不会返回取消监听的句柄,若需要解除监听,需自行包装)。
多次对同一
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!',
});
简单请求-响应模式(通过 tag 分频道)
// 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);
已知在以下环境中通常可工作(取决于版本与设置):
- Tampermonkey
- Violentmonkey
- ScriptCat
- 其他实现了上述 API 的管理器
工作原理(高层概述)
所有消息以
postMessage::<tag>为 key 写入 GM 存储,例如:postMessage::chatpostMessage::data:request
GM_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 的内部事件处理。
选项(本库无显式配置项)
当前实现为零配置:
- 不提供日志开关;
- 不清理历史值(由下一次写入覆盖);
- 不暴露取消监听 API(如需要,可自己再封一层管理 callback 列表)。
若你需要更复杂的行为(例如:自动取消订阅、带超时的请求-响应等),建议:
- 在你自己的代码中为
GM_onMessage再封装一层管理; - 或 fork 本库,在其基础上增加选项。
注意事项 & 建议
消息必须可被 JSON 序列化:内部使用
JSON.stringify/JSON.parse,不能直接传函数、undefined、Symbol等。这是一个 广播机制:
- 同一
tag上的所有监听器都会收到消息; - 如需点对点/一对一通信,建议在
message中加上from/to/requestId字段并在回调中自行过滤。
- 同一
回调执行是异步的(微任务 +
setTimeout批处理),请不要依赖同步立即可见的副作用。由于基于 GM 存储事件:
- 不同浏览器 / 管理器对
remote/tabId的支持程度可能不同; - 如要区分“自己发的”和“其他标签发的”,可以结合
remote再加上自定义clientId。
- 不同浏览器 / 管理器对
许可协议
公共领域 — The Unlicense。
本库在源码头部附带完整 Unlicense 声明,简要概括:
- 你可以自由复制、修改、发布、使用、编译、销售、再分发本软件,
用于任何目的(商业或非商业),以任何方式。 - 作者放弃在版权法下的全部权利,不对软件做任何形式的担保。
- 使用造成的任何损失,作者概不负责。
详情可参考:https://unlicense.org