sharedworker 是唯一支持多窗口共用单 websocket/mqtt 连接的机制,需在安全上下文运行、连接逻辑全下沉至 worker、用 clientid 路由消息、由 worker 统一管控重连与心跳,并实现超时清理以防脏数据。

SharedWorker 是唯一能实现多窗口共用单条 WebSocket/MQTT 连接的机制,其他方案(BroadcastChannel + 页面代理、localStorage 轮询)本质仍是多连接,无法规避服务端频控、状态错乱和竞态问题。
SharedWorker 构造失败却无报错?检查协议与上下文
浏览器在非安全上下文中会静默拒绝 new SharedWorker(),连 onerror 都不会触发。常见误判是以为代码写错了,其实是运行环境不合规:
- 开发阶段必须用
http://localhost:port,禁用file://协议 - 上线后必须是
https://,哪怕同域名的http://也会直接失败 - Safari 仅支持 macOS 13.3+ / iOS 16.4+,旧版本需 fallback 到单页独连
WebSocket 实例不能传入 SharedWorker?所有连接逻辑必须下沉
试图在页面中 new WebSocket() 后通过 postMessage 传给 SharedWorker,必然抛出 DOMException: Failed to execute 'postMessage' on 'MessagePort'——因为 WebSocket 对象不可序列化。
- 所有连接动作(
new WebSocket(url)、onopen、onmessage、onclose)必须写在shared-worker.js内 - 页面只发指令,例如
{ type: "connect", url: "wss://api.example.com" } - Worker 收到后检查
ws?.readyState === WebSocket.OPEN,未连接才新建,避免重复实例
多个页面共用连接,但消息不能全量广播?必须带 clientId 路由
服务端下发的消息若无目标标识,Worker 无法判断该推给谁。A 页面订阅了 order/123,B 页面订阅了 chat/456,它们不该互相收到对方数据。
- 页面首次通信时必须携带唯一
clientId,推荐用crypto.randomUUID()生成 - Worker 用
Map维护clientId → port映射,并在校验port.onmessage时检查字段合法性 - 服务端消息需带
targetClientId或topic,Worker 查表后仅对匹配port.postMessage() - 页面关闭前主动发
{ type: "disconnect", clientId: "tab-7f3a" },Worker 清理映射与订阅关系
重连和心跳失效?必须由 SharedWorker 单点控制
若把重连或心跳放在页面主线程,切后台后定时器暂停,或多个标签页同时触发重连请求,会导致服务端雪崩。
- 断开后使用指数退避:第一次等 1s,第二次 2s,第三次 4s……上限 30s
- 心跳由 Worker 内
setInterval(() => ws.send("ping"), 30000)发起,不依赖页面活跃状态 - MQTT 场景下,必须用浏览器版
mqtt.js并通过importScripts加载,调用前校验client.connected
最容易被忽略的是:SharedWorker 的全局性是“脆弱”的——它依赖所有页面都正确发送 disconnect 指令;一旦某个页面异常崩溃或未清理,clientId → port 映射就成为脏数据,后续消息可能丢失或错发。务必在 Worker 中加入超时清理逻辑(如 5 分钟无响应自动移除 port),并让页面在 beforeunload 和 visibilitychange 时双重保障。











