GM_postMessage

创建于 1 天前
更新于 1 天前
一个基于 GM 存储的跨脚本/跨标签消息通道
#GM
总安装量
31
今日新增
+0
用户评分
- / 5.0 (0)
当前版本
0.1.0
// @require https://scriptcat.org/lib/4710/0.1.0/GM_postMessage.js?sha384-U9kZ0JxuJEQFA7LMejrhTcel/fMoH/g434fOTpP0a/fBXxMnutQ7/L+WOiA1p9UT
库详情
这是一个用户脚本使用的库,你可以在你的脚本中直接引用它。

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)

  • tagstring,消息频道名。相同 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;
  • tagstring,频道名。与 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.setValueGM_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::chat
    • postMessage::data:request
  • GM_postMessage(tag, message) 会写入:

    { message, mId: `${Date.now()}.${Math.random()}` }
    

    其中:

    • message 为原始消息数据;
    • mId 用于生成时间戳 timeStamp 和打散顺序。
  • GM_addValueChangeListener 监听对应 key,一旦有变化:

    • 解析 newValue,提取:

      • message
      • timeStamp = 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,不能直接传函数、undefinedSymbol 等。

  • 这是一个 广播机制

    • 同一 tag 上的所有监听器都会收到消息;
    • 如需点对点/一对一通信,建议在 message 中加上 from / to / requestId 字段并在回调中自行过滤。
  • 回调执行是异步的(微任务 + setTimeout 批处理),请不要依赖同步立即可见的副作用。

  • 由于基于 GM 存储事件:

    • 不同浏览器 / 管理器对 remote / tabId 的支持程度可能不同;
    • 如要区分“自己发的”和“其他标签发的”,可以结合 remote 再加上自定义 clientId

许可协议

公共领域 — The Unlicense

本库在源码头部附带完整 Unlicense 声明,简要概括:

  • 你可以自由复制、修改、发布、使用、编译、销售、再分发本软件,
    用于任何目的(商业或非商业),以任何方式。
  • 作者放弃在版权法下的全部权利,不对软件做任何形式的担保。
  • 使用造成的任何损失,作者概不负责。

详情可参考:https://unlicense.org