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
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
本项目遵循 [Semantic Versioning](https://semver.org/);变更记录格式参考 [Keep a Changelog](https://keepachangelog.com/)。
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-08-24
|
|
8
|
+
|
|
9
|
+
首次公开发布。
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- 框架无关的跨标签页发布/订阅数据总线,通过 BroadcastChannel 在同源标签页间分发消息
|
|
14
|
+
- Dedicated Worker / Shared Worker / 本地线程三档 transport 后端,`auto` 模式自动降级
|
|
15
|
+
- 基于 localStorage + BroadcastChannel 的 Worker 集群协调:粘性 Topic owner 路由、新 Topic 负载均衡、owner 崩溃 failover、`pagehide` 优雅交接
|
|
16
|
+
- 内置 Centrifuge WebSocket transport,连接、订阅、token 刷新、二进制数据均在 Worker 内处理,不阻塞主线程
|
|
17
|
+
- ArrayBuffer Transferable 支持,零拷贝传输二进制 publication
|
|
18
|
+
- 本地 handler 引用计数与订阅排队:连接期间订阅不丢失,去重 transport subscribe 调用
|
|
19
|
+
- 可选结构化 trace:生命周期、连接状态、协调快照、订阅事件,以及分桶延迟直方图(P50/P95/Max)与吞吐聚合指标
|
|
20
|
+
- localStorage 写入合并与指数退避重试,协调元数据写入移出热路径
|
|
21
|
+
- 连接 URL 与 Topic 通过 128 位非加密哈希转为不透明 key,明文不落入 localStorage 键名
|
|
22
|
+
- SharedWorker 端口回收器:主线程 PING 心跳 + 静默超时回收,崩溃 Tab 未发 STOP 也不泄漏 session
|
|
23
|
+
- 优雅降级:localStorage/BroadcastChannel 不可用时退化为本地单 Tab 模式
|
|
24
|
+
|
|
25
|
+
### Documentation
|
|
26
|
+
|
|
27
|
+
- 英文与中文文档:架构、API 参考、配置、快速上手、能力矩阵
|
|
28
|
+
- 多标签页浏览器演示页,可视化数据流、延迟指标与集群路由状态
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sun1090
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# cross-tab-worker-databus
|
|
2
|
+
|
|
3
|
+
> [中文](./README.zh.md) | English
|
|
4
|
+
|
|
5
|
+
Framework-agnostic browser cross-tab data bus.
|
|
6
|
+
|
|
7
|
+
By default each tab holds its own Dedicated Worker; when configured with `workerMode: 'shared'` or `'auto'`, same-origin tabs can reuse a single SharedWorker. In `auto` mode it degrades automatically through SharedWorker → Dedicated Worker → main-thread WebSocket. Same-origin tabs form a logical Worker cluster over BroadcastChannel; the SDK automatically coordinates sticky Topic owners, subscription reuse, new-Topic load distribution, failover, and page lifecycle, so the application only needs to subscribe to Topics and process data.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Subscribable immediately after creation; subscriptions auto-queue while the connection is pending
|
|
12
|
+
- Subscriptions within the same tab are deduplicated by handler reference counting
|
|
13
|
+
- Topic owners are reused across same-origin tabs, reducing duplicate real-time subscriptions
|
|
14
|
+
- In SharedWorker mode, same-origin tabs reuse a single SharedWorker; each tab's port maintains its own independent connection, so refreshing or stopping one tab does not affect others
|
|
15
|
+
- `workerMode` supports `dedicated` / `shared` / `auto`; `auto` degrades via SharedWorker → Dedicated Worker → main-thread WebSocket, while explicit `dedicated` degrades via Dedicated Worker → SharedWorker → main-thread WebSocket
|
|
16
|
+
- With `transferable: true`, ArrayBuffer messages are transmitted via Transferable while the object-message API stays unchanged
|
|
17
|
+
- localStorage coordination writes are merged and flushed in batches; heartbeat and route confirmation use exponential backoff
|
|
18
|
+
- Existing Topic owners remain stable while alive; visibility changes do not move established subscriptions
|
|
19
|
+
- New Topics are assigned to the least-loaded eligible Worker
|
|
20
|
+
- `pagehide` releases resources automatically; `pageshow` rebuilds the Worker and connection automatically
|
|
21
|
+
- Transport reconnect automatically restores the current owner's Topics
|
|
22
|
+
- After a tab exits abnormally, automatic migration happens via heartbeat TTL
|
|
23
|
+
- Automatically degrades to local mode when BroadcastChannel or localStorage is unavailable
|
|
24
|
+
- The persistence layer does not store connection addresses, raw Topic text, or message content
|
|
25
|
+
|
|
26
|
+
See the [Capabilities Matrix](./docs/capabilities.md) for the full list of implemented, unimplemented, and planned capabilities.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm add cross-tab-worker-databus
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Getting Started
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { createCentrifugeDataBus } from 'cross-tab-worker-databus/centrifuge';
|
|
38
|
+
|
|
39
|
+
interface ResourceEvent {
|
|
40
|
+
id: string;
|
|
41
|
+
version: number;
|
|
42
|
+
content: unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const bus = createCentrifugeDataBus<ResourceEvent>({
|
|
46
|
+
connection: {
|
|
47
|
+
url: getConnectionUrl(),
|
|
48
|
+
options: getConnectionOptions()
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const unsubscribe = bus.subscribe('resource.changed', ({ data }) => {
|
|
53
|
+
applyResourceEvent(data);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await bus.ready();
|
|
57
|
+
|
|
58
|
+
unsubscribe();
|
|
59
|
+
await bus.stop();
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The application does not need to handle Tab owner, Worker migration, page recovery, or re-subscription after reconnection.
|
|
63
|
+
|
|
64
|
+
## Browser Demo
|
|
65
|
+
|
|
66
|
+
The repository includes runnable multi-tab demo pages that showcase real-time data flow between publishing, receiving, cluster routing, Worker sessions, and the server:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pnpm install
|
|
70
|
+
pnpm build
|
|
71
|
+
pnpm examples
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Then open `http://localhost:4173/examples/demo/` in multiple tabs at the same time to see cross-tab data flowing. The demo page uses the public Centrifugo demo endpoint `wss://faye.centrifugal.dev/connection/websocket` by default; the address, Worker mode, and Topics can all be modified in-page. You can also switch to "local broadcast" mode, which does not depend on an external server and demonstrates multi-tab coordination purely through BroadcastChannel.
|
|
75
|
+
|
|
76
|
+
The demo page includes data-flow animations, an event stream, receive/dispatch latency metrics, and cluster Worker routing status.
|
|
77
|
+
|
|
78
|
+
## Docs
|
|
79
|
+
|
|
80
|
+
- [Docs Index](./docs/README.md)
|
|
81
|
+
- [Getting Started](./docs/getting-started.md)
|
|
82
|
+
- [Configuration](./docs/configuration.md)
|
|
83
|
+
- [API Reference](./docs/api.md)
|
|
84
|
+
- [Architecture](./docs/architecture.md)
|
|
85
|
+
- [Capabilities Matrix](./docs/capabilities.md)
|
|
86
|
+
- [Changelog](./CHANGELOG.md)
|
|
87
|
+
|
|
88
|
+
## API Examples
|
|
89
|
+
|
|
90
|
+
### Subscribe with lifecycle handling
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
const bus = createCentrifugeDataBus<ResourceEvent>({
|
|
94
|
+
connection: { url: 'wss://example.com/ws', options: {} }
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Subscribe before connection — queued until ready
|
|
98
|
+
const unsub = bus.subscribe('events.created', ({ data }) => {
|
|
99
|
+
console.log('event received:', data);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Monitor connection status
|
|
103
|
+
const unsubStatus = bus.onStatus(status => {
|
|
104
|
+
console.log('transport status:', status);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Handle errors
|
|
108
|
+
const unsubError = bus.onError(error => {
|
|
109
|
+
console.error('transport error:', error);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await bus.ready();
|
|
113
|
+
|
|
114
|
+
// Later: clean up
|
|
115
|
+
unsub();
|
|
116
|
+
unsubStatus();
|
|
117
|
+
unsubError();
|
|
118
|
+
await bus.stop();
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Multi-tab cluster with SharedWorker
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { createCentrifugeDataBus } from 'cross-tab-worker-databus/centrifuge';
|
|
125
|
+
|
|
126
|
+
const bus = createCentrifugeDataBus<ResourceEvent>({
|
|
127
|
+
connection: {
|
|
128
|
+
url: 'wss://example.com/connection/websocket',
|
|
129
|
+
options: {}
|
|
130
|
+
},
|
|
131
|
+
workerMode: 'auto' // SharedWorker → Dedicated Worker → local fallback
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
bus.subscribe('resource.changed', ({ data }) => {
|
|
135
|
+
// Data arrives from any tab's owner Worker
|
|
136
|
+
apply(data);
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Publish and receive
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const publisher = createCentrifugeDataBus<ResourceEvent>({
|
|
144
|
+
connection: { url: getUrl(), options: {} }
|
|
145
|
+
});
|
|
146
|
+
const receiver = createCentrifugeDataBus<ResourceEvent>({
|
|
147
|
+
connection: { url: getUrl(), options: {} }
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
receiver.subscribe('resource.updated', ({ data }) => {
|
|
151
|
+
console.log('got update:', data);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
await publisher.ready();
|
|
155
|
+
publisher.publish('resource.updated', { id: 'abc', version: 2, content: { title: 'hello' } });
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Diagnostics
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
const bus = createCentrifugeDataBus<ResourceEvent>({
|
|
162
|
+
connection: { url: getUrl(), options: {} },
|
|
163
|
+
trace: {
|
|
164
|
+
enabled: true,
|
|
165
|
+
sink: event => console.log('trace:', event.type, event)
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Snapshot
|
|
170
|
+
const snapshot = bus.getClusterSnapshot();
|
|
171
|
+
console.log(snapshot.workers, snapshot.routes, snapshot.assignedTopics);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Development
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
pnpm install
|
|
178
|
+
pnpm check # typecheck + unit tests + build
|
|
179
|
+
pnpm test:e2e # Playwright multi-tab browser tests (requires Google Chrome)
|
|
180
|
+
pnpm pack --pack-destination /tmp
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
[MIT](./LICENSE)
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# cross-tab-worker-databus
|
|
2
|
+
|
|
3
|
+
> 中文 | [English](./README.md)
|
|
4
|
+
|
|
5
|
+
框架无关的浏览器跨 Tab 数据总线。
|
|
6
|
+
|
|
7
|
+
默认每个 Tab 使用独立的 Dedicated Worker;配置 `workerMode: 'shared'` 或 `'auto'` 后,同源 Tab 可复用同一个 SharedWorker。`auto` 模式自动从 SharedWorker → Dedicated Worker → 主线程 WebSocket 降级。同源 Tab 通过 BroadcastChannel 形成逻辑 Worker 集群;SDK 自动协调粘性 Topic owner、订阅复用、新 Topic 负载分配、故障转移和页面生命周期,业务只需订阅 Topic 和处理数据。
|
|
8
|
+
|
|
9
|
+
## 特性
|
|
10
|
+
|
|
11
|
+
- 创建后可立即订阅;连接未就绪时订阅自动排队
|
|
12
|
+
- 同一 Tab 内同 Topic 多 handler 通过引用计数去重
|
|
13
|
+
- 同源 Tab 复用 Topic owner,减少重复实时订阅
|
|
14
|
+
- SharedWorker 模式同源 Tab 复用同一个 SharedWorker;每个 Tab 的 port 各自维护独立的连接,单个 Tab 刷新或停止不影响其他 Tab
|
|
15
|
+
- `workerMode` 支持 `dedicated` / `shared` / `auto`;`auto` 按 SharedWorker → Dedicated Worker → 主线程 WebSocket 降级,`dedicated` 按 Dedicated Worker → SharedWorker → 主线程 WebSocket 降级
|
|
16
|
+
- 开启 `transferable: true` 后,ArrayBuffer 消息通过 Transferable 传输,对象消息 API 不变
|
|
17
|
+
- localStorage 协调写入合并批量 flush;心跳和路由确认使用指数退避
|
|
18
|
+
- 已有 Topic 的 owner 存活时保持稳定,前后台切换不迁移已有订阅
|
|
19
|
+
- 新 Topic 分配给负载最低的候选 Worker
|
|
20
|
+
- `pagehide` 自动释放资源;`pageshow` 自动重建 Worker 和连接
|
|
21
|
+
- Transport 重连自动恢复当前 owner 的 Topic
|
|
22
|
+
- Tab 异常退出后通过心跳 TTL 自动迁移
|
|
23
|
+
- BroadcastChannel 或 localStorage 不可用时自动降级为本地模式
|
|
24
|
+
- 持久层不存储连接地址、原始 Topic 文本和消息内容
|
|
25
|
+
|
|
26
|
+
完整能力清单见 [能力矩阵](./docs/zh/capabilities.md)。
|
|
27
|
+
|
|
28
|
+
## 安装
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm add cross-tab-worker-databus
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 快速接入
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { createCentrifugeDataBus } from 'cross-tab-worker-databus/centrifuge';
|
|
38
|
+
|
|
39
|
+
interface ResourceEvent {
|
|
40
|
+
id: string;
|
|
41
|
+
version: number;
|
|
42
|
+
content: unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const bus = createCentrifugeDataBus<ResourceEvent>({
|
|
46
|
+
connection: {
|
|
47
|
+
url: getConnectionUrl(),
|
|
48
|
+
options: getConnectionOptions()
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const unsubscribe = bus.subscribe('resource.changed', ({ data }) => {
|
|
53
|
+
applyResourceEvent(data);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await bus.ready();
|
|
57
|
+
|
|
58
|
+
unsubscribe();
|
|
59
|
+
await bus.stop();
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
业务无需处理 Tab owner、Worker 迁移、页面恢复或重连后的重新订阅。
|
|
63
|
+
|
|
64
|
+
## 浏览器演示
|
|
65
|
+
|
|
66
|
+
仓库包含可运行的多标签演示页面,展示发布、接收、集群路由、Worker 会话和服务器之间的实时数据流:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pnpm install
|
|
70
|
+
pnpm build
|
|
71
|
+
pnpm examples
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
然后在多个浏览器标签页中同时打开 `http://localhost:4173/examples/demo/` 即可观察跨 Tab 数据流转。演示页默认使用公共 Centrifugo 演示地址 `wss://faye.centrifugal.dev/connection/websocket`;地址、Worker 模式和 Topic 都可在页面内修改。也可以切换到"本地广播"模式,不依赖外部服务器,仅通过 BroadcastChannel 演示多标签协同。
|
|
75
|
+
|
|
76
|
+
演示页包含数据流动画、事件流、接收/分发延迟指标和集群 Worker 路由状态。
|
|
77
|
+
|
|
78
|
+
## 文档
|
|
79
|
+
|
|
80
|
+
- [文档索引](./docs/zh/README.md)
|
|
81
|
+
- [快速接入](./docs/zh/getting-started.md)
|
|
82
|
+
- [配置说明](./docs/zh/configuration.md)
|
|
83
|
+
- [API 参考](./docs/zh/api.md)
|
|
84
|
+
- [架构说明](./docs/zh/architecture.md)
|
|
85
|
+
- [能力矩阵](./docs/zh/capabilities.md)
|
|
86
|
+
- [变更日志](./CHANGELOG.md)
|
|
87
|
+
|
|
88
|
+
## 开发
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pnpm install
|
|
92
|
+
pnpm check
|
|
93
|
+
pnpm pack --pack-destination /tmp
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 许可证
|
|
97
|
+
|
|
98
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker-thread protocol for Centrifuge WebSocket transport.
|
|
3
|
+
*
|
|
4
|
+
* Defines the message types exchanged between the main thread and a Web Worker
|
|
5
|
+
* (dedicated or shared) that runs a centrifuge client. The worker is isolated
|
|
6
|
+
* from the main thread so that WebSocket lifecycle, token refresh, and binary
|
|
7
|
+
* data handling never block the UI.
|
|
8
|
+
*/
|
|
9
|
+
import type { Options } from 'centrifuge';
|
|
10
|
+
import type { WorkerStatus } from './core/types';
|
|
11
|
+
/**
|
|
12
|
+
* Default interval between main-thread PING heartbeats to a SharedWorker. The
|
|
13
|
+
* SharedWorker reaps a silent port after `SESSION_TIMEOUT_MULTIPLIER` intervals.
|
|
14
|
+
* Shared here so the main thread (heartbeat sender) and the SharedWorker (reaper)
|
|
15
|
+
* always agree on the cadence.
|
|
16
|
+
*/
|
|
17
|
+
export declare const DEFAULT_HEARTBEAT_INTERVAL_MS = 10000;
|
|
18
|
+
/** SharedWorker per-port session timeout, expressed as a multiple of that port's
|
|
19
|
+
* heartbeat interval. A live port PINGs every heartbeat interval, so a timeout of
|
|
20
|
+
* several intervals tolerates throttled tabs without reaping healthy sessions. */
|
|
21
|
+
export declare const DEFAULT_SESSION_TIMEOUT_MULTIPLIER = 3;
|
|
22
|
+
type WorkerUnsafeOption = 'eventsource' | 'fetch' | 'getData' | 'getToken' | 'networkEventTarget' | 'readableStream' | 'sockjs' | 'websocket';
|
|
23
|
+
/** Centrifuge options safe to pass into a Worker; unsafe options are explicitly excluded. */
|
|
24
|
+
export type CentrifugeWorkerConfig = Omit<Partial<Options>, WorkerUnsafeOption> & {
|
|
25
|
+
[Key in WorkerUnsafeOption]?: never;
|
|
26
|
+
};
|
|
27
|
+
/** Messages sent from the main thread to the Worker. */
|
|
28
|
+
export type CentrifugeWorkerInput = {
|
|
29
|
+
type: 'INIT';
|
|
30
|
+
url: string;
|
|
31
|
+
config: CentrifugeWorkerConfig;
|
|
32
|
+
transferable?: boolean;
|
|
33
|
+
heartbeatIntervalMs?: number;
|
|
34
|
+
} | {
|
|
35
|
+
type: 'SUBSCRIBE';
|
|
36
|
+
topic: string;
|
|
37
|
+
} | {
|
|
38
|
+
type: 'UNSUBSCRIBE';
|
|
39
|
+
topic: string;
|
|
40
|
+
} | {
|
|
41
|
+
type: 'PUBLISH';
|
|
42
|
+
topic: string;
|
|
43
|
+
data: unknown;
|
|
44
|
+
} | {
|
|
45
|
+
type: 'PUBLISH_BIN';
|
|
46
|
+
topic: string;
|
|
47
|
+
data: ArrayBuffer;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'PING';
|
|
50
|
+
} | {
|
|
51
|
+
type: 'STOP';
|
|
52
|
+
};
|
|
53
|
+
/** Messages sent from the Worker back to the main thread. */
|
|
54
|
+
export type CentrifugeWorkerOutput<TData = unknown> = {
|
|
55
|
+
type: 'STATUS';
|
|
56
|
+
status: WorkerStatus;
|
|
57
|
+
} | {
|
|
58
|
+
type: 'MESSAGE';
|
|
59
|
+
topic: string;
|
|
60
|
+
data: TData;
|
|
61
|
+
} | {
|
|
62
|
+
type: 'MESSAGE_BIN';
|
|
63
|
+
topic: string;
|
|
64
|
+
data: ArrayBuffer;
|
|
65
|
+
} | {
|
|
66
|
+
type: 'ERROR';
|
|
67
|
+
error: SerializedWorkerError;
|
|
68
|
+
};
|
|
69
|
+
/** Error object serialized for cross-thread transfer (Error instances cannot be cloned via postMessage). */
|
|
70
|
+
export interface SerializedWorkerError {
|
|
71
|
+
name: string;
|
|
72
|
+
message: string;
|
|
73
|
+
stack?: string;
|
|
74
|
+
context?: unknown;
|
|
75
|
+
}
|
|
76
|
+
export {};
|
|
77
|
+
//# sourceMappingURL=centrifuge-protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"centrifuge-protocol.d.ts","sourceRoot":"","sources":["../src/centrifuge-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,QAAS,CAAC;AACpD;;kFAEkF;AAClF,eAAO,MAAM,kCAAkC,IAAI,CAAC;AAIpD,KAAK,kBAAkB,GACnB,aAAa,GACb,OAAO,GACP,SAAS,GACT,UAAU,GACV,oBAAoB,GACpB,gBAAgB,GAChB,QAAQ,GACR,WAAW,CAAC;AAEhB,6FAA6F;AAC7F,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAC,GAAG;KAC/E,GAAG,IAAI,kBAAkB,CAAC,CAAC,EAAE,KAAK;CACpC,CAAC;AAEF,wDAAwD;AACxD,MAAM,MAAM,qBAAqB,GAC7B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,sBAAsB,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAAE,GACnH;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB,6DAA6D;AAC7D,MAAM,MAAM,sBAAsB,CAAC,KAAK,GAAG,OAAO,IAC9C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,qBAAqB,CAAA;CAAE,CAAC;AAEpD,4GAA4G;AAC5G,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { CentrifugeWorkerInput, CentrifugeWorkerOutput } from './centrifuge-protocol';
|
|
2
|
+
/** Callback interface for posting messages back to the transport layer. */
|
|
3
|
+
export interface CentrifugeSessionSink<TData = unknown> {
|
|
4
|
+
post(message: CentrifugeWorkerOutput<TData>, transfer?: ArrayBuffer[]): void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Stateful Centrifuge client wrapper shared by Dedicated Worker, SharedWorker
|
|
8
|
+
* ports and the main-thread local fallback. Each session owns one connection.
|
|
9
|
+
*/
|
|
10
|
+
export declare class CentrifugeSession<TData = unknown> {
|
|
11
|
+
private readonly sink;
|
|
12
|
+
private client;
|
|
13
|
+
private readonly subscriptions;
|
|
14
|
+
private transferable;
|
|
15
|
+
constructor(sink: CentrifugeSessionSink<TData>);
|
|
16
|
+
/** Dispatch an incoming Worker message to the matching operation. */
|
|
17
|
+
handle(message: CentrifugeWorkerInput): void;
|
|
18
|
+
/** Create the Centrifuge client, wire up lifecycle listeners, and connect. */
|
|
19
|
+
private initialize;
|
|
20
|
+
/** Subscribe to a Centrifuge channel. Reuses an existing subscription if one exists. */
|
|
21
|
+
private subscribe;
|
|
22
|
+
/** Unsubscribe from a Centrifuge channel and clean up the local reference. */
|
|
23
|
+
private unsubscribe;
|
|
24
|
+
/** Publish a message to the Centrifuge channel. */
|
|
25
|
+
private publish;
|
|
26
|
+
/** Forward a publication to the transport, using Transferable for binary data when enabled. */
|
|
27
|
+
private postPublication;
|
|
28
|
+
/** Disconnect the client and clear all subscriptions. */
|
|
29
|
+
private stop;
|
|
30
|
+
/** Forward a message to the sink (the transport layer). */
|
|
31
|
+
private post;
|
|
32
|
+
/** Serialise and report an error. The Centrifuge client handles reconnection
|
|
33
|
+
* internally, so a transient error should not trigger a `STATUS: error` that
|
|
34
|
+
* would cause `selectActiveWorkers()` to exclude this worker from routing.
|
|
35
|
+
* Fatal errors are distinguished by the client eventually emitting
|
|
36
|
+
* `disconnected` without a subsequent `connected`. */
|
|
37
|
+
private postError;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=centrifuge-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"centrifuge-session.d.ts","sourceRoot":"","sources":["../src/centrifuge-session.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAEV,qBAAqB,EACrB,sBAAsB,EAEvB,MAAM,uBAAuB,CAAC;AAE/B,2EAA2E;AAC3E,MAAM,WAAW,qBAAqB,CAAC,KAAK,GAAG,OAAO;IACpD,IAAI,CAAC,OAAO,EAAE,sBAAsB,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;CAC9E;AAED;;;GAGG;AACH,qBAAa,iBAAiB,CAAC,KAAK,GAAG,OAAO;IAKhC,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJjC,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmC;IACjE,OAAO,CAAC,YAAY,CAAS;gBAEA,IAAI,EAAE,qBAAqB,CAAC,KAAK,CAAC;IAE/D,qEAAqE;IACrE,MAAM,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI;IAS5C,8EAA8E;IAC9E,OAAO,CAAC,UAAU;IAelB,wFAAwF;IACxF,OAAO,CAAC,SAAS;IAmBjB,8EAA8E;IAC9E,OAAO,CAAC,WAAW;IAOnB,mDAAmD;IACnD,OAAO,CAAC,OAAO;IAKf,+FAA+F;IAC/F,OAAO,CAAC,eAAe;IASvB,yDAAyD;IACzD,OAAO,CAAC,IAAI;IAOZ,2DAA2D;IAC3D,OAAO,CAAC,IAAI;IAIZ;;;;0DAIsD;IACtD,OAAO,CAAC,SAAS;CAGlB"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centrifuge WebSocket transport that runs inside a Web Worker.
|
|
3
|
+
*
|
|
4
|
+
* Supports three backends, selected in order of preference:
|
|
5
|
+
* 1. SharedWorker — one WebSocket per tab session, hosted in a shared process
|
|
6
|
+
* 2. Dedicated Worker — one WebSocket per tab
|
|
7
|
+
* 3. In-process (local) — Centrifuge runs on the main thread (fallback when
|
|
8
|
+
* neither Worker type is available, e.g. in non-browser environments)
|
|
9
|
+
*
|
|
10
|
+
* Binary data (ArrayBuffer) can be transferred via Transferable when the
|
|
11
|
+
* `transferable` option is enabled, avoiding structured-clone overhead.
|
|
12
|
+
*/
|
|
13
|
+
import { CrossTabDataBus } from './core/data-bus';
|
|
14
|
+
import type { CrossTabDataBusOptions } from './core/data-bus';
|
|
15
|
+
import type { DataBusTransport, DataBusTransportHandlers } from './core/types';
|
|
16
|
+
import type { WorkerMode } from './worker-mode';
|
|
17
|
+
import type { CentrifugeWorkerConfig } from './centrifuge-protocol';
|
|
18
|
+
export type { CentrifugeWorkerConfig, SerializedWorkerError } from './centrifuge-protocol';
|
|
19
|
+
export type { WorkerBackend, WorkerMode } from './worker-mode';
|
|
20
|
+
/** WebSocket connection parameters passed to the Centrifuge Worker. */
|
|
21
|
+
export interface CentrifugeDataBusConfig {
|
|
22
|
+
/** Centrifuge server WebSocket URL. */
|
|
23
|
+
url: string;
|
|
24
|
+
/** Centrifuge client options (token, channel params, etc.). */
|
|
25
|
+
options?: CentrifugeWorkerConfig;
|
|
26
|
+
}
|
|
27
|
+
/** Options for configuring the Worker backend (dedicated, shared, or local). */
|
|
28
|
+
export interface CentrifugeWorkerTransportOptions {
|
|
29
|
+
/** Custom dedicated Worker factory. Used for testing or bundler integration. */
|
|
30
|
+
workerFactory?: () => Worker;
|
|
31
|
+
/** Custom SharedWorker factory. */
|
|
32
|
+
sharedWorkerFactory?: () => SharedWorker;
|
|
33
|
+
/** Preferred Worker mode: 'dedicated', 'shared', or 'auto'. */
|
|
34
|
+
workerMode?: WorkerMode;
|
|
35
|
+
/** Enable transferable (ArrayBuffer) support for binary data. */
|
|
36
|
+
transferable?: boolean;
|
|
37
|
+
/** Interval (ms) between PING heartbeats sent to the SharedWorker. The
|
|
38
|
+
* SharedWorker reaps a silent port after `DEFAULT_SESSION_TIMEOUT_MULTIPLIER`
|
|
39
|
+
* × this interval. Pass `Infinity` to disable heartbeats entirely. Defaults
|
|
40
|
+
* to `DEFAULT_HEARTBEAT_INTERVAL_MS`. */
|
|
41
|
+
heartbeatIntervalMs?: number;
|
|
42
|
+
}
|
|
43
|
+
/** Options for creating a fully-configured CrossTabDataBus with a Centrifuge transport. */
|
|
44
|
+
export interface CreateCentrifugeDataBusOptions<TData = unknown> extends Omit<CrossTabDataBusOptions<CentrifugeDataBusConfig, TData>, 'autoStart' | 'clusterKey' | 'initialConfig' | 'transport'>, CentrifugeWorkerTransportOptions {
|
|
45
|
+
/** Centrifuge connection configuration. */
|
|
46
|
+
connection: CentrifugeDataBusConfig;
|
|
47
|
+
/** Cluster key for cross-tab coordination. Defaults to the connection URL. */
|
|
48
|
+
clusterKey?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Transport layer that runs a Centrifuge WebSocket client inside a Web Worker.
|
|
52
|
+
*
|
|
53
|
+
* Delegates the actual WebSocket connection to a Worker (dedicated or shared)
|
|
54
|
+
* or falls back to an in-process CentrifugeSession. The Worker is isolated from
|
|
55
|
+
* the main thread so that WebSocket lifecycle, token refresh, and binary data
|
|
56
|
+
* handling never block the UI.
|
|
57
|
+
*/
|
|
58
|
+
export declare class CentrifugeWorkerTransport<TData = unknown> implements DataBusTransport<CentrifugeDataBusConfig, TData> {
|
|
59
|
+
private readonly workerMode;
|
|
60
|
+
private readonly transferable;
|
|
61
|
+
private readonly heartbeatIntervalMs;
|
|
62
|
+
private readonly workerFactory;
|
|
63
|
+
private readonly sharedWorkerFactory;
|
|
64
|
+
private backend;
|
|
65
|
+
private worker;
|
|
66
|
+
private sharedWorker;
|
|
67
|
+
private port;
|
|
68
|
+
private heartbeatHandle;
|
|
69
|
+
private localSession;
|
|
70
|
+
private handlers;
|
|
71
|
+
private generation;
|
|
72
|
+
private backendGeneration;
|
|
73
|
+
constructor(options?: CentrifugeWorkerTransportOptions);
|
|
74
|
+
/**
|
|
75
|
+
* Start the transport: select a backend, initialise the Worker (or local
|
|
76
|
+
* session), and send the INIT message with connection parameters.
|
|
77
|
+
*/
|
|
78
|
+
start(config: CentrifugeDataBusConfig, handlers: DataBusTransportHandlers<TData>): void;
|
|
79
|
+
subscribe(topic: string): void;
|
|
80
|
+
unsubscribe(topic: string): void;
|
|
81
|
+
/**
|
|
82
|
+
* Publish data to `topic`. Binary data (ArrayBuffer) is sent via Transferable
|
|
83
|
+
* when `transferable` is enabled, avoiding a structured-clone cycle.
|
|
84
|
+
*/
|
|
85
|
+
publish(topic: string, data: unknown): void;
|
|
86
|
+
/**
|
|
87
|
+
* Gracefully stop the transport: send STOP, clean up event listeners, and
|
|
88
|
+
* terminate the Worker (or close the SharedWorker port).
|
|
89
|
+
*/
|
|
90
|
+
stop(): void;
|
|
91
|
+
/** Create and initialise a dedicated Worker, then send the INIT message. */
|
|
92
|
+
private startDedicatedWorker;
|
|
93
|
+
/** Create and initialise a SharedWorker, open the MessagePort, and send the INIT message. */
|
|
94
|
+
private startSharedWorker;
|
|
95
|
+
/** Handle a message event from the Worker (dedicated or shared). */
|
|
96
|
+
private readonly handleMessage;
|
|
97
|
+
/** Handle a message from the in-process CentrifugeSession (local fallback). */
|
|
98
|
+
private readonly handleSessionOutput;
|
|
99
|
+
/** Route a Worker output message to the appropriate handler callback. */
|
|
100
|
+
private handleOutput;
|
|
101
|
+
/** Handle a Worker-level failure (crash, message decode error). Discards the
|
|
102
|
+
* dead backend so a later start()/reopen can rebuild from scratch, and
|
|
103
|
+
* signals an error status so the DataBus can trigger recovery. */
|
|
104
|
+
private onWorkerFailed;
|
|
105
|
+
/** Remove every listener attached to the current SharedWorker and its port. */
|
|
106
|
+
private detachSharedWorkerListeners;
|
|
107
|
+
/** Periodically ping the SharedWorker so its session reaper can detect a dead tab. */
|
|
108
|
+
private startHeartbeat;
|
|
109
|
+
private clearHeartbeat;
|
|
110
|
+
private readonly handleWorkerError;
|
|
111
|
+
private readonly handlePortError;
|
|
112
|
+
private readonly handleSharedWorkerError;
|
|
113
|
+
/** Clear the Worker/port/backend references after a failure or stop. */
|
|
114
|
+
private resetBackend;
|
|
115
|
+
/**
|
|
116
|
+
* Post a message to the active backend. Accepts optional Transferable buffers
|
|
117
|
+
* for zero-copy ArrayBuffer transfer.
|
|
118
|
+
*/
|
|
119
|
+
private post;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Create a fully-configured CrossTabDataBus with a Centrifuge WebSocket transport.
|
|
123
|
+
*
|
|
124
|
+
* This is the primary entry point for consumers. It wires up the transport,
|
|
125
|
+
* cluster coordination, and lifecycle management:
|
|
126
|
+
*
|
|
127
|
+
* ```ts
|
|
128
|
+
* const bus = createCentrifugeDataBus({
|
|
129
|
+
* connection: { url: 'wss://example.com/connection/websocket', options: { token: '…' } },
|
|
130
|
+
* trace: { enabled: true, sink: event => console.log(event) },
|
|
131
|
+
* });
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export declare function createCentrifugeDataBus<TData = unknown>(options: CreateCentrifugeDataBusOptions<TData>): CrossTabDataBus<CentrifugeDataBusConfig, TData>;
|
|
135
|
+
//# sourceMappingURL=centrifuge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"centrifuge.d.ts","sourceRoot":"","sources":["../src/centrifuge.ts"],"names":[],"mappings":"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"}
|