cross-tab-worker-databus 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/README.zh.md +98 -0
- package/dist/centrifuge-protocol.d.ts +77 -0
- package/dist/centrifuge-protocol.d.ts.map +1 -0
- package/dist/centrifuge-session.d.ts +39 -0
- package/dist/centrifuge-session.d.ts.map +1 -0
- package/dist/centrifuge.d.ts +135 -0
- package/dist/centrifuge.d.ts.map +1 -0
- package/dist/centrifuge.js +407 -0
- package/dist/centrifuge.js.map +7 -0
- package/dist/centrifuge.shared.worker.js +5220 -0
- package/dist/centrifuge.shared.worker.js.map +7 -0
- package/dist/centrifuge.worker.js +5109 -0
- package/dist/centrifuge.worker.js.map +7 -0
- package/dist/chunk-GABYBK7I.js +1527 -0
- package/dist/chunk-GABYBK7I.js.map +7 -0
- package/dist/core/cluster.d.ts +219 -0
- package/dist/core/cluster.d.ts.map +1 -0
- package/dist/core/data-bus.d.ts +133 -0
- package/dist/core/data-bus.d.ts.map +1 -0
- package/dist/core/environment.d.ts +67 -0
- package/dist/core/environment.d.ts.map +1 -0
- package/dist/core/hash.d.ts +11 -0
- package/dist/core/hash.d.ts.map +1 -0
- package/dist/core/routing.d.ts +42 -0
- package/dist/core/routing.d.ts.map +1 -0
- package/dist/core/storage-batch.d.ts +35 -0
- package/dist/core/storage-batch.d.ts.map +1 -0
- package/dist/core/trace.d.ts +126 -0
- package/dist/core/trace.d.ts.map +1 -0
- package/dist/core/types.d.ts +112 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +7 -0
- package/dist/worker-mode.d.ts +25 -0
- package/dist/worker-mode.d.ts.map +1 -0
- package/dist/workers/centrifuge.shared.worker.d.ts +2 -0
- package/dist/workers/centrifuge.shared.worker.d.ts.map +1 -0
- package/dist/workers/centrifuge.worker.d.ts +2 -0
- package/dist/workers/centrifuge.worker.d.ts.map +1 -0
- package/dist/workers/port-reaper.d.ts +52 -0
- package/dist/workers/port-reaper.d.ts.map +1 -0
- package/docs/README.md +21 -0
- package/docs/api.md +261 -0
- package/docs/architecture.md +514 -0
- package/docs/capabilities.md +41 -0
- package/docs/configuration.md +211 -0
- package/docs/getting-started.md +161 -0
- package/docs/zh/README.md +23 -0
- package/docs/zh/api.md +261 -0
- package/docs/zh/architecture.md +515 -0
- package/docs/zh/capabilities.md +41 -0
- package/docs/zh/configuration.md +211 -0
- package/docs/zh/getting-started.md +161 -0
- package/package.json +71 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
> 中文 | [English](../configuration.md)
|
|
2
|
+
|
|
3
|
+
# 配置说明
|
|
4
|
+
|
|
5
|
+
## Core DataBus 配置
|
|
6
|
+
|
|
7
|
+
`CrossTabDataBus<TConfig, TData>` 接收 `CrossTabDataBusOptions<TConfig, TData>`。
|
|
8
|
+
|
|
9
|
+
| 配置 | 类型 | 默认值 | 说明 |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `clusterKey` | `string` | 必填 | 隔离不同连接上下文;不会以原文写入 storage |
|
|
12
|
+
| `transport` | `DataBusTransport<TConfig, TData>` | 必填 | 真实连接和订阅实现 |
|
|
13
|
+
| `initialConfig` | `TConfig` | 无 | 自动启动时传给 transport |
|
|
14
|
+
| `autoStart` | `boolean` | 传入 `initialConfig` 时为 `true` | 是否在创建实例后自动启动 |
|
|
15
|
+
| `storagePrefix` | `string` | `cross-tab-worker-databus` | storage key 和 BroadcastChannel 的命名空间 |
|
|
16
|
+
| `maxActiveWorkers` | `number` | `3` | 可作为 Topic owner 的最大 Worker 数 |
|
|
17
|
+
| `heartbeatIntervalMs` | `number` | `3000` | Worker 心跳间隔 |
|
|
18
|
+
| `workerTtlMs` | `number` | `10000` | Worker 失效判断时间 |
|
|
19
|
+
| `environment` | `ClusterEnvironment` | 浏览器原生环境 | 测试、嵌入式环境或能力替换使用 |
|
|
20
|
+
| `tabId` | `string` | 自动生成 | 高级调试和测试注入,不建议业务设置 |
|
|
21
|
+
| `workerId` | `string` | 自动生成 | 高级调试和测试注入,不建议业务设置 |
|
|
22
|
+
| `trace` | `DataBusTraceOptions` | 关闭 | 可选诊断事件、消息吞吐和分发延迟聚合,不影响数据传输 |
|
|
23
|
+
|
|
24
|
+
## 诊断与吞吐指标
|
|
25
|
+
|
|
26
|
+
trace 默认关闭。启用后,生命周期、连接状态、协调模式和订阅数量变化可以立即输出;高频消息只汇总计数,不逐条打印。
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const bus = new CrossTabDataBus({
|
|
30
|
+
clusterKey: 'realtime-feed',
|
|
31
|
+
initialConfig: {},
|
|
32
|
+
transport,
|
|
33
|
+
trace: {
|
|
34
|
+
enabled: true,
|
|
35
|
+
mode: 'all',
|
|
36
|
+
metricsIntervalMs: 5000,
|
|
37
|
+
sink: event => console.info('[DataBus]', event)
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
| 配置 | 类型 | 默认值 | 说明 |
|
|
43
|
+
|---|---|---|---|
|
|
44
|
+
| `enabled` | `boolean` | `false` | 总开关 |
|
|
45
|
+
| `mode` | `'events' \| 'metrics' \| 'all'` | `'all'` | 仅低频事件、仅聚合指标或两者都输出 |
|
|
46
|
+
| `metricsIntervalMs` | `number` | `5000` | 聚合窗口,必须是大于 0 的有限数值 |
|
|
47
|
+
| `sink` | `(event) => void` | 必填 | 由接入方决定 console、监控 SDK 或其他出口 |
|
|
48
|
+
|
|
49
|
+
`message_metrics` 包含窗口时长、接收数、分发数、活跃 Topic 数量,以及接收 → 分发延迟的样本数、平均值、P50、P95 和最大值。延迟按 50ms 桶聚合,不包含单条消息 payload。订阅事件会带 Topic,便于接入方关联 owner 变化;仅 owner transport 的订阅集合实际变化时才输出,幂等 `CONTROL` 重试不会产生重复订阅事件。将 trace 写入 console 或外部监控前,应按业务 Topic 约定进行脱敏。Trace 不包含 URL、凭证、payload 或错误正文。sink 抛错会被隔离,不会中断消息分发,但会向 `console.warn` 输出错误,方便接入方发现诊断配置问题。
|
|
50
|
+
|
|
51
|
+
`pagehide` 时聚合定时器会停止并丢弃未完成窗口,`pageshow` 后以新窗口恢复;永久 `stop()` 会清理定时器。这里节流的只是诊断输出,真实消息接收与分发不会被限速。
|
|
52
|
+
|
|
53
|
+
### 时间参数约束
|
|
54
|
+
|
|
55
|
+
- `workerTtlMs` 应至少大于两倍 `heartbeatIntervalMs`。
|
|
56
|
+
- TTL 过短会在后台调度抖动时产生误迁移。
|
|
57
|
+
- TTL 过长会延迟异常 Tab 的恢复。
|
|
58
|
+
- 默认 `3000/10000` 适合一般桌面浏览器实时场景。
|
|
59
|
+
|
|
60
|
+
### TTL 消息丢失窗口
|
|
61
|
+
|
|
62
|
+
当 Worker 异常退出(例如 Tab 崩溃)时:
|
|
63
|
+
|
|
64
|
+
- 其他 Worker 需要等到 `workerTtlMs`(默认 10 000 ms)之后才能检测到其死亡——过期记录会在下一次协调周期中被清理。
|
|
65
|
+
- 周期心跳写入 localStorage 时不会触发 BroadcastChannel 通知,因此可能需要一个完整的心跳间隔才能发现过期记录。
|
|
66
|
+
- **最坏情况窗口**:最多 `heartbeatIntervalMs + workerTtlMs`(默认约 13 秒)。在此窗口期间,死 Worker 拥有的 Topic 无人服务——发往这些 Topic 的 publication 将丢失。
|
|
67
|
+
- **缓解方案**:按比例减小 `heartbeatIntervalMs` 和 `workerTtlMs`(例如 1 s / 4 s)。这会增加存储写入频率,并提高调度抖动时误迁移的风险。
|
|
68
|
+
|
|
69
|
+
默认 3 s / 10 s 适合一般桌面浏览器实时场景。根据对丢失消息与误迁移率的容忍度进行调整。
|
|
70
|
+
|
|
71
|
+
### active Worker 数量
|
|
72
|
+
|
|
73
|
+
`maxActiveWorkers` 限制的是可成为 Topic owner 的 Worker,不限制 transport 建立的连接数。Dedicated Worker 模式下每个 Tab 仍可创建自己的 Worker;SharedWorker 模式下同源 Tab 复用同一个 Worker。
|
|
74
|
+
|
|
75
|
+
active 集合只在 Topic 需要选择新 owner 时使用。已有 owner Worker 只要仍存活,即使可见性变化后变为 standby 或不再属于当前候选集合,也会继续持有已经建立的 route。
|
|
76
|
+
|
|
77
|
+
- `1`:连接和订阅最少,但单 owner 负载集中。
|
|
78
|
+
- `2-3`:在资源复用和故障恢复之间取得平衡。
|
|
79
|
+
- 更大值:适合 Topic 很多且单连接存在服务端限制的场景。
|
|
80
|
+
|
|
81
|
+
## Centrifuge 配置
|
|
82
|
+
|
|
83
|
+
`createCentrifugeDataBus<TData>(options)` 的主要配置:
|
|
84
|
+
|
|
85
|
+
| 配置 | 类型 | 默认值 | 说明 |
|
|
86
|
+
|---|---|---|---|
|
|
87
|
+
| `connection.url` | `string` | 必填 | Centrifuge 连接地址 |
|
|
88
|
+
| `connection.options` | `CentrifugeWorkerConfig` | `{}` | 发送到 Worker 的客户端配置 |
|
|
89
|
+
| `clusterKey` | `string` | `connection.url` | 手动隔离逻辑集群 |
|
|
90
|
+
| `workerMode` | `'dedicated' \| 'shared' \| 'auto'` | `'dedicated'` | Worker transport 运行模式;`auto` 按 SharedWorker → Dedicated Worker → 本地模式降级,显式 `dedicated` 按 Dedicated Worker → SharedWorker → 本地模式降级 |
|
|
91
|
+
| `transferable` | `boolean` | `false` | 开启后 `publish(topic, ArrayBuffer)` 使用 Transferable 传输,接收侧 ArrayBuffer publication 也走转移路径 |
|
|
92
|
+
| `heartbeatIntervalMs` | `number` | `10000` | SharedWorker PING 心跳间隔(见下方 SharedWorker 会话回收);传 `Infinity` 完全禁用心跳。与 Core 集群心跳(默认 3000 ms,通过 localStorage 跟踪 worker 存活)相互独立 |
|
|
93
|
+
| `workerFactory` | `() => Worker` | 内置 Worker | 测试或自定义 Worker 加载方式 |
|
|
94
|
+
| `sharedWorkerFactory` | `() => SharedWorker` | 内置 SharedWorker | 测试或自定义 SharedWorker 加载方式 |
|
|
95
|
+
| 其他 Core 配置 | 对应类型 | Core 默认值 | `storagePrefix`、心跳、TTL 等 |
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const bus = createCentrifugeDataBus({
|
|
99
|
+
connection: {
|
|
100
|
+
url: getConnectionUrl(),
|
|
101
|
+
options: {
|
|
102
|
+
token: getConnectionCredential(),
|
|
103
|
+
timeout: 5000,
|
|
104
|
+
maxServerPingDelay: 10000
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
maxActiveWorkers: 3,
|
|
108
|
+
heartbeatIntervalMs: 3000,
|
|
109
|
+
workerTtlMs: 10000
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Worker 模式与降级
|
|
114
|
+
|
|
115
|
+
`workerMode` 控制 Centrifuge transport 的运行方式:
|
|
116
|
+
|
|
117
|
+
- `dedicated`(默认):每个 Tab 创建自己的 Dedicated Worker,兼容性最好。
|
|
118
|
+
- `shared`:同源 Tab 复用同一个 SharedWorker;Worker 内为每个连接 port 维护独立的 `CentrifugeSession`,一个 Tab 停止或刷新不会影响其他 Tab 的连接。
|
|
119
|
+
- `auto`:运行时选择 SharedWorker,不支持时降级到 Dedicated Worker,最后降级到主线程本地模式。
|
|
120
|
+
|
|
121
|
+
`auto` 的完整链路为 **SharedWorker → Dedicated Worker → 主线程本地模式**;显式 `shared` 的链路与 `auto` 相同(**SharedWorker → Dedicated Worker → 主线程本地模式**);显式 `dedicated` 的链路为 **Dedicated Worker → SharedWorker → 主线程本地模式**。
|
|
122
|
+
|
|
123
|
+
`shared` 模式不会拒绝降级:如果浏览器不支持 `SharedWorker`,它会走与 `auto` 相同的降级链路。`shared` 与 `auto` 的唯一区别是首选顺序——`shared` 始终优先使用 SharedWorker,而 `auto` 执行相同的选择逻辑,但适用于调用方没有强烈偏好时的场景。
|
|
124
|
+
|
|
125
|
+
`sharedWorkerFactory` 和 `workerFactory` 都未提供时,transport 在启动时检测全局 `SharedWorker` / `Worker` 能力。提供自定义 factory 时,对应后端视为可用,避免在 Node、SSR 或嵌入环境中被全局能力检测误判。各模式都会执行相同的结构化克隆校验,配置和 `publish` 数据必须可结构化克隆。
|
|
126
|
+
|
|
127
|
+
## SharedWorker 会话回收
|
|
128
|
+
|
|
129
|
+
`MessagePort` 没有 `close` 事件,因此 SharedWorker 无法在 Tab 崩溃或关闭时获知(除非收到 `STOP` 消息)。为避免泄漏已死 Tab 的 `CentrifugeSession`(及其 WebSocket),transport 定期向 SharedWorker 发送 **PING 心跳**,SharedWorker 运行一个**回收器**来关闭超过静默超时的端口会话。
|
|
130
|
+
|
|
131
|
+
- **心跳间隔**:`heartbeatIntervalMs`(默认 `10000` ms)。主线程按此间隔发送 `PING`。传 `Infinity` 完全禁用心跳——仅在确保 SharedWorker 会随 Tab 一起销毁时使用。
|
|
132
|
+
- **会话超时**:`3 × heartbeatIntervalMs`(默认 `30000` ms)。超过超时未收到消息的端口会被回收:其会话停止,WebSocket 关闭。这与 Core 集群心跳(默认 `3000` ms,通过 localStorage 跟踪 worker 存活)相互独立——见下方说明。
|
|
133
|
+
- **自适应频率**:回收器以所有活动端口中最小的心跳间隔运行,使短心跳端口的会话能被及时回收。当最后一个端口断开时,回收器定时器清除,避免长时间存在的 SharedWorker 在连接爆发间隙运行永久的空循环。
|
|
134
|
+
- **先关闭端口再停止会话**:回收端口时,先关闭端口,再停止会话。关闭端口会丢弃会话的 `disconnected` 状态通知(使其不会到达可能仍在运行但缓慢的主线程),并保证已关闭的端口永远无法传递后续消息,从而在回收器追踪之外复活僵尸会话。
|
|
135
|
+
|
|
136
|
+
这是从崩溃(未发送 `STOP`)的 Tab 中恢复会话的机制。降低 `heartbeatIntervalMs` 可更快回收死会话,代价是端口上更频繁的 PING 消息。
|
|
137
|
+
|
|
138
|
+
`heartbeatIntervalMs` 必须为正数或 `Infinity`;`0`、负数或 `NaN` 会导致 transport 构造函数立即抛出 `TypeError`(否则 `setInterval` 会降级为 0ms 忙循环)。
|
|
139
|
+
|
|
140
|
+
> **两个心跳,不要混淆。** Core 的 `heartbeatIntervalMs`(默认 `3000` ms)是集群心跳——WorkerClusterRuntime 按此间隔向 localStorage 写入存活记录。Centrifuge 的 `heartbeatIntervalMs`(默认 `10000` ms)是文档中所述的 SharedWorker PING 心跳。两者相互独立,都出现在配置项中;Centrifuge 的心跳仅在 `shared` 模式下有意义。
|
|
141
|
+
|
|
142
|
+
## 二进制消息传输
|
|
143
|
+
|
|
144
|
+
`transferable: true` 开启 ArrayBuffer 优化路径,不改变对外 API:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
const bus = createCentrifugeDataBus({
|
|
148
|
+
connection: { url: 'wss://example.test/connection/websocket' },
|
|
149
|
+
transferable: true
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
bus.publish('resource.command', binaryBuffer);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
开启后,`publish(topic, ArrayBuffer)` 在 Worker 传输时使用 `PUBLISH_BIN` 并把 buffer 加入 transfer list,避免 structured clone 复制;Worker 返回的 ArrayBuffer publication 以 `MESSAGE_BIN` 转移回主线程。业务层仍然只看到 `DataBusMessage<TData>`,对象、字符串和数字 payload 继续走原有对象消息路径。未开启时,ArrayBuffer 与普通对象一样按 structured clone 复制。
|
|
156
|
+
|
|
157
|
+
## Worker 结构化克隆约束
|
|
158
|
+
|
|
159
|
+
Worker 模式下配置通过 `Worker` / `SharedWorker` 的 `postMessage` 发送,本地模式也执行相同校验,必须可结构化克隆。以下配置不允许直接传入:
|
|
160
|
+
|
|
161
|
+
- `getToken`
|
|
162
|
+
- `getData`
|
|
163
|
+
- 自定义 `websocket`
|
|
164
|
+
- 自定义 `fetch`
|
|
165
|
+
- `eventsource`
|
|
166
|
+
- `sockjs`
|
|
167
|
+
- `networkEventTarget`
|
|
168
|
+
- `ReadableStream` 等运行时对象
|
|
169
|
+
|
|
170
|
+
传入不可克隆数据时,`CentrifugeWorkerTransport` 会抛出明确的 `TypeError`。
|
|
171
|
+
|
|
172
|
+
## storage 数据边界
|
|
173
|
+
|
|
174
|
+
storage 只保存:
|
|
175
|
+
|
|
176
|
+
- Worker ID、Tab ID、状态、可见性、负载和心跳
|
|
177
|
+
- Topic 的不透明 key、owner Worker 和更新时间
|
|
178
|
+
- Topic subscriber 的 Tab ID
|
|
179
|
+
|
|
180
|
+
storage 不保存:
|
|
181
|
+
|
|
182
|
+
- 连接地址原文
|
|
183
|
+
- Topic 原文
|
|
184
|
+
- 连接凭证
|
|
185
|
+
- publication 数据
|
|
186
|
+
- `publish` 数据
|
|
187
|
+
|
|
188
|
+
注意:BroadcastChannel 协调消息以明文传输 Topic 名称、事件类型和 publication payload(仅存在于内存中)。只有 localStorage 元数据通过 `createOpaqueKey()` 哈希处理。
|
|
189
|
+
|
|
190
|
+
## 安全与信任模型
|
|
191
|
+
|
|
192
|
+
协调平面**没有鉴权**。请仅在页面内所有同源脚本均可信时使用本库:
|
|
193
|
+
|
|
194
|
+
- BroadcastChannel 消息会投递给**每个同源 Tab**,明文传输,该源内任何脚本都能收发;`localStorage` 协调记录同样可被任意同源脚本读写。
|
|
195
|
+
- 恶意或异常的同源脚本可以伪造 Worker 记录、劫持 Topic owner、从 BroadcastChannel 读取 Topic 名称与 publication payload、注入发布消息或冒充订阅者。Topic 与 payload 在 BroadcastChannel 上以**明文**传输(仅存在于内存中)——不要在 Topic 名称或协调消息 payload 中放置凭证、token 或 PII(超过服务端本就要下发的范围)。
|
|
196
|
+
- `clusterKey` 只提供**隔离,不提供安全**:它只能防止逻辑集群之间的*意外*串扰,无法阻止能读取 `localStorage` 或监听 BroadcastChannel 的脚本——不透明 key 与频道名都由同源派生,可被重新计算,且脚本还可直接读取页面运行时状态。
|
|
197
|
+
- `clusterKey` 通过 `createOpaqueKey`(非密码学 128-bit 哈希)派生 storage 前缀与 BroadcastChannel 名称。实践中 `clusterKey` 总是连接 URL 或开发者控制的命名空间,两个不同 `clusterKey` 间的哈希碰撞(~2⁻⁶⁴ 生日界限)不是实际问题。
|
|
198
|
+
- 缓解措施:页面内不要加载不受信任的第三方脚本;在独立源上承载协调逻辑;将源的 `localStorage` 与 BroadcastChannel 命名空间视为公开区域。CSP 无法限制同源脚本对 BroadcastChannel 或 localStorage 的访问。
|
|
199
|
+
|
|
200
|
+
transport 平面(如 Centrifuge WebSocket)有自己的安全模型——token、TLS 和服务端权限——不受上述影响。集群只决定哪个 Tab 持有 transport 订阅,从不把 payload 写入 `localStorage`(payload 经 BroadcastChannel 内存传输或由服务端直发)。
|
|
201
|
+
|
|
202
|
+
## 集群隔离建议
|
|
203
|
+
|
|
204
|
+
以下上下文必须使用不同 `clusterKey`:
|
|
205
|
+
|
|
206
|
+
- 不同服务端连接
|
|
207
|
+
- 不同认证身份
|
|
208
|
+
- 不同数据权限范围
|
|
209
|
+
- 不同协议版本
|
|
210
|
+
|
|
211
|
+
使用 Centrifuge 工厂时,默认连接地址通常已经可以完成隔离。身份或权限会在同一地址下变化时,应显式提供包含上下文版本但不包含凭证原文的 `clusterKey`。
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
> 中文 | [English](../getting-started.md)
|
|
2
|
+
|
|
3
|
+
# 快速接入
|
|
4
|
+
|
|
5
|
+
## 1. 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add cross-tab-worker-databus
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
包提供以下入口:
|
|
12
|
+
|
|
13
|
+
- `cross-tab-worker-databus`:核心 DataBus 和 transport 接口
|
|
14
|
+
- `cross-tab-worker-databus/centrifuge`:内置 Centrifuge Worker transport
|
|
15
|
+
- `cross-tab-worker-databus/centrifuge.shared.worker`:SharedWorker 构建产物,默认由内置 factory 加载,通常无需直接引用
|
|
16
|
+
|
|
17
|
+
## 2. 创建实例
|
|
18
|
+
|
|
19
|
+
建议在应用基础设施层创建一个实例,其他模块直接导入。这样同一 Tab 内的业务模块会共享 Worker、连接和 Topic 引用。
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { createCentrifugeDataBus } from 'cross-tab-worker-databus/centrifuge';
|
|
23
|
+
|
|
24
|
+
export interface ResourceEvent {
|
|
25
|
+
id: string;
|
|
26
|
+
version: number;
|
|
27
|
+
content: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const dataBus = createCentrifugeDataBus<ResourceEvent>({
|
|
31
|
+
connection: {
|
|
32
|
+
url: getConnectionUrl(),
|
|
33
|
+
options: getConnectionOptions()
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`clusterKey` 默认由连接地址派生。它只用于集群隔离,进入 localStorage 和 BroadcastChannel 通道名称前会转换为不透明 key。请注意,通过 BroadcastChannel 协调通道发送的 Topic 名称和事件类型以明文传输;仅 localStorage 元数据通过哈希进行混淆。
|
|
39
|
+
|
|
40
|
+
默认使用 Dedicated Worker,每个 Tab 一个 Worker。希望同源 Tab 复用同一个连接时,设置 `workerMode: 'shared'` 或 `'auto'`:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
export const dataBus = createCentrifugeDataBus<ResourceEvent>({
|
|
44
|
+
connection: {
|
|
45
|
+
url: getConnectionUrl(),
|
|
46
|
+
options: getConnectionOptions()
|
|
47
|
+
},
|
|
48
|
+
workerMode: 'auto'
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`auto` 在 SharedWorker 可用时优先使用,否则降级到 Dedicated Worker,最后使用主线程本地模式。
|
|
53
|
+
|
|
54
|
+
## 3. 订阅
|
|
55
|
+
|
|
56
|
+
实例创建后可以立即订阅,不必等待连接完成。
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const unsubscribe = dataBus.subscribe('resource.changed', message => {
|
|
60
|
+
applyResourceEvent(message.data);
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
连接尚未准备好时,SDK 会保存订阅意图并在 transport ready 后执行。相同 Topic 的多个 handler 使用本地引用计数,只产生一次集群订阅。
|
|
65
|
+
|
|
66
|
+
## 4. 等待连接
|
|
67
|
+
|
|
68
|
+
多数业务不需要调用 `ready()`。只有后续流程必须确认 Worker 已创建、transport 的 `start` 已完成时才等待:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
await dataBus.ready();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`ready()` 不表示服务端一定已完成认证;连接状态以 `onStatus` 回调为准。
|
|
75
|
+
|
|
76
|
+
如果实例创建时没有传入 `initialConfig`,且在显式 `start(config)` 之前调用 `ready()`,返回的 Promise 会 reject 而不是同步抛出;请通过 `.catch` 处理,并在合适的时机用 `start(config)` 重试。
|
|
77
|
+
|
|
78
|
+
## 5. 发布
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
dataBus.publish('resource.command', {
|
|
82
|
+
action: 'refresh',
|
|
83
|
+
targetId: 'resource-id'
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
只有服务端协议允许客户端发布时才使用 `publish`。SDK 不会自动重放因页面暂停而未执行的发布操作,避免过期命令产生副作用。
|
|
88
|
+
|
|
89
|
+
## 6. 状态和错误
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const removeStatusListener = dataBus.onStatus(status => {
|
|
93
|
+
updateConnectionIndicator(status);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const removeErrorListener = dataBus.onError(error => {
|
|
97
|
+
reportDataBusError(error);
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
状态值:
|
|
102
|
+
|
|
103
|
+
- `connecting`
|
|
104
|
+
- `connected`
|
|
105
|
+
- `disconnected`
|
|
106
|
+
- `error`
|
|
107
|
+
|
|
108
|
+
## 7. 清理
|
|
109
|
+
|
|
110
|
+
释放单个模块订阅:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
unsubscribe();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
彻底销毁实例:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
removeStatusListener();
|
|
120
|
+
removeErrorListener();
|
|
121
|
+
await dataBus.stop();
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
正常的 Tab 隐藏、进入 BFCache 和恢复不需要业务调用 `stop()` 或重新订阅,SDK 会自动处理。
|
|
125
|
+
|
|
126
|
+
## 8. 运行多标签演示
|
|
127
|
+
|
|
128
|
+
仓库的 `examples/demo` 提供了一个浏览器演示页,可以看到消息在标签页、集群、Worker 会话和服务器之间的完整流向:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pnpm install
|
|
132
|
+
pnpm build
|
|
133
|
+
pnpm examples
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
打开 `http://localhost:4173/examples/demo/`,在多个浏览器标签页中同时打开即可观察跨 Tab 数据流转。页面支持:
|
|
137
|
+
|
|
138
|
+
- 默认连接公共 Centrifugo 演示地址 `wss://faye.centrifugal.dev/connection/websocket`
|
|
139
|
+
- 在页面内修改 WSS 地址、`workerMode`、Topic 和 `transferable` 配置
|
|
140
|
+
- 切换到"本地广播"模式,不依赖外部服务器,仅用 BroadcastChannel 演示多标签协同
|
|
141
|
+
- 数据流动画、事件流、分发延迟指标和集群 Worker 路由状态
|
|
142
|
+
- SDK 能力、transport 配置、活跃/等待 Worker 与可见/隐藏 Tab 状态
|
|
143
|
+
|
|
144
|
+
通过 Git 依赖直接接入仓库时,应固定到具体 commit。仓库随代码提供 `dist`,消费方安装时无需构建 SDK。
|
|
145
|
+
|
|
146
|
+
## 9. 显式启动
|
|
147
|
+
|
|
148
|
+
自定义 transport 的配置需要异步获取时,可以不传 `initialConfig`,准备完成后显式启动:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { CrossTabDataBus } from 'cross-tab-worker-databus';
|
|
152
|
+
|
|
153
|
+
const bus = new CrossTabDataBus({
|
|
154
|
+
clusterKey: 'shared-resource-stream',
|
|
155
|
+
transport
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const config = await loadTransportConfig();
|
|
159
|
+
await bus.start(config);
|
|
160
|
+
bus.subscribe('resource.changed', handleResourceEvent);
|
|
161
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cross-tab-worker-databus",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Framework-agnostic cross-tab data bus with Dedicated/Shared Worker clustering and Centrifuge support.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Sun1090",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/Sun1090/cross-tab-worker-databus.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/Sun1090/cross-tab-worker-databus#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Sun1090/cross-tab-worker-databus/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"broadcast-channel",
|
|
18
|
+
"cross-tab",
|
|
19
|
+
"data-bus",
|
|
20
|
+
"dedicated-worker",
|
|
21
|
+
"shared-worker",
|
|
22
|
+
"centrifuge",
|
|
23
|
+
"websocket"
|
|
24
|
+
],
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"docs",
|
|
28
|
+
"CHANGELOG.md",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"main": "./dist/index.js",
|
|
33
|
+
"module": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"import": "./dist/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./centrifuge": {
|
|
41
|
+
"types": "./dist/centrifuge.d.ts",
|
|
42
|
+
"import": "./dist/centrifuge.js"
|
|
43
|
+
},
|
|
44
|
+
"./centrifuge.worker": "./dist/centrifuge.worker.js",
|
|
45
|
+
"./centrifuge.shared.worker": "./dist/centrifuge.shared.worker.js",
|
|
46
|
+
"./package.json": "./package.json"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "node scripts/build.mjs",
|
|
50
|
+
"check": "pnpm typecheck && pnpm test && pnpm build",
|
|
51
|
+
"examples": "node scripts/serve-examples.mjs",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"test:watch": "vitest",
|
|
54
|
+
"test:e2e": "pnpm build && playwright test",
|
|
55
|
+
"typecheck": "tsc --noEmit"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"centrifuge": "^5.5.3"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@playwright/test": "^1.62.1",
|
|
62
|
+
"@types/node": "^24.0.0",
|
|
63
|
+
"esbuild": "^0.25.0",
|
|
64
|
+
"typescript": "^5.9.0",
|
|
65
|
+
"vitest": "^3.2.0"
|
|
66
|
+
},
|
|
67
|
+
"engines": {
|
|
68
|
+
"node": ">=18.0.0"
|
|
69
|
+
},
|
|
70
|
+
"packageManager": "pnpm@10.14.0"
|
|
71
|
+
}
|