cross-tab-worker-databus 0.1.2 → 0.2.1

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +32 -0
  3. package/README.zh.md +30 -0
  4. package/dist/centrifuge-protocol.d.ts +51 -15
  5. package/dist/centrifuge-protocol.d.ts.map +1 -1
  6. package/dist/centrifuge-session.d.ts +11 -3
  7. package/dist/centrifuge-session.d.ts.map +1 -1
  8. package/dist/centrifuge.d.ts +10 -2
  9. package/dist/centrifuge.d.ts.map +1 -1
  10. package/dist/centrifuge.js +69 -26
  11. package/dist/centrifuge.js.map +2 -2
  12. package/dist/centrifuge.shared.worker.js +73 -25
  13. package/dist/centrifuge.shared.worker.js.map +2 -2
  14. package/dist/centrifuge.worker.js +41 -12
  15. package/dist/centrifuge.worker.js.map +2 -2
  16. package/dist/{chunk-53INHVYO.js → chunk-LBXREMZA.js} +263 -158
  17. package/dist/chunk-LBXREMZA.js.map +7 -0
  18. package/dist/core/cluster.d.ts +49 -4
  19. package/dist/core/cluster.d.ts.map +1 -1
  20. package/dist/core/data-bus.d.ts +19 -2
  21. package/dist/core/data-bus.d.ts.map +1 -1
  22. package/dist/core/environment.d.ts +16 -2
  23. package/dist/core/environment.d.ts.map +1 -1
  24. package/dist/core/hash.d.ts.map +1 -1
  25. package/dist/core/routing.d.ts +5 -2
  26. package/dist/core/routing.d.ts.map +1 -1
  27. package/dist/core/storage-batch.d.ts +12 -0
  28. package/dist/core/storage-batch.d.ts.map +1 -1
  29. package/dist/core/trace.d.ts +16 -1
  30. package/dist/core/trace.d.ts.map +1 -1
  31. package/dist/core/types.d.ts +76 -21
  32. package/dist/core/types.d.ts.map +1 -1
  33. package/dist/index.js +1 -1
  34. package/dist/worker-mode.d.ts +12 -3
  35. package/dist/worker-mode.d.ts.map +1 -1
  36. package/dist/workers/port-reaper.d.ts +21 -6
  37. package/dist/workers/port-reaper.d.ts.map +1 -1
  38. package/docs/README.md +1 -0
  39. package/docs/architecture.md +18 -8
  40. package/docs/getting-started.md +3 -0
  41. package/docs/transports.md +145 -0
  42. package/docs/zh/README.md +1 -0
  43. package/docs/zh/architecture.md +18 -8
  44. package/docs/zh/getting-started.md +3 -0
  45. package/docs/zh/transports.md +133 -0
  46. package/package.json +29 -6
  47. package/dist/chunk-53INHVYO.js.map +0 -7
package/CHANGELOG.md CHANGED
@@ -4,6 +4,70 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ### Added
8
+
9
+ - 发布自动化:tag 触发的 GitHub Actions release workflow(typecheck + 单测 + build 门禁 → 从 CHANGELOG 抽取版本说明创建 GitHub Release → `npm publish --provenance`)。
10
+ - 测试基建:ESLint(typescript-eslint flat config,含 `lint` 脚本)、Vitest 覆盖率(`pnpm test:coverage`,阈值 statements 85 / branches 80 / functions 90 / lines 85)、`.editorconfig`。
11
+ - 51 个新单元测试(137 → 188):`CentrifugeSession` 协议分支(UNSUBSCRIBE、server-side publication、错误序列化)、浏览器环境适配层(`createBrowserEnvironment`/`getOrCreateTabId`/`canUseStorage`)、trace 上限截断与 sink 异常隔离、cluster 健壮性(损坏 JSON、存储写失败、TTL 清理、handoff UNSUBSCRIBE 短路、路由抢占 reconcile)、routing/storage-batch/port-reaper 边界分支、`CentrifugeWorkerTransport` 边界路径。
12
+ - 1 个新 E2E:BFCache 往返(pagehide 交接 + pageshow 恢复后双向收发)。
13
+ - React 18 使用示例(`examples/react`,StrictMode 安全的 bus 生命周期);示例服务器支持 `.jsx`。
14
+ - README(中英)FAQ 与 0.1 → 0.2 迁移说明。
15
+
16
+ ### Changed
17
+
18
+ - 包导出加固:`./centrifuge.worker` / `./centrifuge.shared.worker` 补 `types` 条件(指向 `dist/workers/*.d.ts`);新增 `sideEffects` 白名单保护 worker 产物不被 tree-shake;`prepublishOnly` 门禁(`pnpm check`)。
19
+ - tsconfig 追加严格开关:`noFallthroughCasesInSwitch`、`noImplicitOverride`、`allowUnreachableCode: false`(零代码改动通过)。
20
+ - 测试总覆盖率:语句 89.9% → 96.6%,分支 86.3% → 90.9%(environment 32.9% → 100%,centrifuge-session 77.8% → 100%)。
21
+
22
+ ### Fixed
23
+
24
+ - 清理 18 处 ESLint 违规:未使用变量/导入、注释中的 U+202F 不规则空白。
25
+ - `scripts/serve-examples.mjs` 之前不识别 `.jsx` MIME 导致模块脚本被拒(随新示例修复)。
26
+
27
+ ## [0.2.0] - 2026-08-27
28
+
29
+ ### Changed
30
+
31
+ - `centrifuge` 从 `dependencies` 改为 optional `peerDependencies`:仅在使用内置 Centrifuge 后端时需要安装,核心包零运行时依赖。
32
+ - `CentrifugeSession.handle()` 由连续 `if` 改为 `switch` + `default`,未知消息类型静默忽略,为未来协议扩展留出兼容余地。
33
+ - `routing.ts` 的 workerId 平局处理由 `localeCompare` 改为数值比较,消除宿主 locale 对路由确定性的影响。
34
+ - `port-reaper.ts` 提取 `computeMinHeartbeat()` 方法,reap 迭代改用 `Array.from` 快照,reaper 节奏计算与计时器逻辑解耦。
35
+ - `centrifuge-protocol.ts` 新增 `DEFAULT_SESSION_TIMEOUT_MS` 派生常量,消除 `port-reaper` 中重复的字面量计算。
36
+ - `CentrifugeWorkerTransport` 提取 `buildInitInput()` 与 `postToPortLike()` helper,消除 `start()`/`post()` 中的重复构造逻辑。
37
+ - `cluster.ts` 提取 `readAllByPrefix()` helper,统一 `readWorkers`/`cleanupOrphanedRoutes`/`cleanupOrphanedSubscribers`/`getSnapshot` 的 listKeys+readJson 循环;删除无调用点的 `listKeysSafe`。
38
+ - `cluster.ts` 提取 `buildLocalRoute()`,将 `readRoute` 的无 storage 降级分支独立命名并补文档;`readSubscriberTabIds` 同类分支拆为多行可读形式。
39
+ - `cluster.ts` 提取 `sendRouteReleased()` helper,消除三处 `ROUTE_RELEASED` 消息构造的重复;`handoffAssignedTopics` 的 generation 计算复用单次求值;`reconcileAssignedTopics` 去除同一 topicKey 的二次 `readRoute` 调用。
40
+ - `cluster.ts` `releaseSubscription` 返回 topicKey,`unsubscribe` 复用而非重新哈希;`handleMessage`/`handleControlMessage`/`sendControl` 连续 `if` 改 `switch`;`getSnapshot` 的序列化改用 `Array.from`。
41
+ - `data-bus.ts` `onControl` 连续 `if` 改 `switch`;提取 `invokeHandlers()` 统一 dispatch/status/error 三处 handler 遍历的 try-catch 隔离;提取 `formatWorkerTrace`/`formatRouteTrace` 格式化函数。
42
+ - `trace.ts` 提取 `metricsActive` getter,统一四个 record/flush 方法的守卫表达式。
43
+ - `cluster.ts` 提取 `routeOwnerIsLive()` 与 `isActiveAmong()`,消除 subscribe/publish 和 isActiveWorker/refreshRole 中的重复 route+workers.some / selectActiveWorkers+some 模式。
44
+ - `cluster.ts` 提取 `isStaleRouteRelease()`,命名 handleRouteReleasedMessage 的守卫条件。
45
+ - `cluster.ts` `activate()` 合并两个分支的 rememberTopic 调用为单循环;`handoffAssignedTopics` 去掉同一 topicKey 的二次 readRoute。
46
+ - `cluster.ts` `readSubscriberTabIds` 改用 `readAllByPrefix` + `Array.from`;`getSnapshot` 序列化改用 `Array.from` + mapping callback。
47
+ - `data-bus.ts` 提取 `formatWorkerTrace`/`formatRouteTrace` 格式化函数;`invokeHandlers` 的 label 参数改为联合类型字面量而非自由字符串。
48
+ - 多处 JSDoc/注释补全:writeRecord notify 参数、activate channel 判空、reconcileWorkers cleanup 顺序、rememberTopic eviction break、suspendTransport pendingStop 守卫、reopenTransport startPromise 检查、trace flushNow 条件、storage-batch flush break / scheduleFlush 降级 / scheduleRetry、port-reaper 各方法守卫、centrifuge assertHeartbeatInterval/assertStructuredCloneable/deserializeWorkerError、environment canUseStorage。
49
+
50
+ ### Documentation (JSDoc 全覆盖)
51
+
52
+ - `hash.ts` 提取 seed/prime/avalanche 常量与 `avalancheMix()` 函数,补非 ASCII 字符处理注释。
53
+ - `types.ts` 全部类型(WorkerRecord/WorkerRoute/WorkerClusterMessage/DataBusTransport/DataBusMessage/handler 别名)字段级 JSDoc 补全。
54
+ - `centrifuge-protocol.ts` 全部协议变体(WorkerUnsafeOption/CentrifugeWorkerConfig/CentrifugeWorkerInput 各变体/CentrifugeWorkerOutput 各变体/SerializedWorkerError)JSDoc 补全。
55
+ - `worker-mode.ts` WorkerMode/WorkerBackend/WorkerAvailability/selectWorkerBackend JSDoc 补全。
56
+ - `routing.ts` DEFAULT_MAX_ACTIVE_WORKERS/selectLeastLoadedWorker JSDoc 增强。
57
+ - `storage-batch.ts` INITIAL/MAX_RETRY 常量 + BatchingStorageWriter 类 + pendingSize getter JSDoc 补全。
58
+ - `trace.ts` normalizeInterval/roundMs JSDoc 补全。
59
+ - `environment.ts` ClusterEnvironment 各字段 + canUseStorage JSDoc 补全。
60
+ - `cluster.ts` readJson/writeJson/WorkerClusterOptions 各字段 JSDoc 补全。
61
+ - `data-bus.ts` PUBLICATION_EVENT/getStatus/getClusterSnapshot/CrossTabDataBusOptions JSDoc 补全。
62
+ - `centrifuge.ts` handleOutput/onWorkerFailed JSDoc 补全。
63
+
64
+ ### Documentation
65
+
66
+ - 架构运行时图(中英)补全 `BatchingStorageWriter`、`PortReaper`、`CentrifugeSession`、`CentrifugeWorkerTransport` 节点,与代码结构对齐。
67
+ - `AGENTS.md` 目录注释明确列出每个测试文件职责。
68
+ - `README`(中英)补充 `centrifuge` 为可选 peer 依赖的安装说明。
69
+ - `trace.ts`、`environment.ts` 补充 JSDoc:trace 模式与选项字段含义、SSR 场景的 storage/ BroadcastChannel 检测说明。
70
+
7
71
  ## [0.1.2] - 2026-08-24
8
72
 
9
73
  ### Fixed
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # cross-tab-worker-databus
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/cross-tab-worker-databus)](https://www.npmjs.com/package/cross-tab-worker-databus)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
5
+
3
6
  > [中文](./README.zh.md) | English
4
7
 
5
8
  Framework-agnostic browser cross-tab data bus.
@@ -31,6 +34,17 @@ See the [Capabilities Matrix](./docs/capabilities.md) for the full list of imple
31
34
  pnpm add cross-tab-worker-databus
32
35
  ```
33
36
 
37
+ The core package has zero runtime dependencies. The Centrifuge transport
38
+ (`cross-tab-worker-databus/centrifuge`) declares `centrifuge` as an optional
39
+ peer dependency — install it only when you use the built-in Centrifuge backend:
40
+
41
+ ```bash
42
+ pnpm add cross-tab-worker-databus centrifuge
43
+ ```
44
+
45
+ Tabs that only use the local BroadcastChannel data bus (no WebSocket server)
46
+ do not need to install `centrifuge` at all.
47
+
34
48
  ## Getting Started
35
49
 
36
50
  ```ts
@@ -171,8 +185,26 @@ const snapshot = bus.getClusterSnapshot();
171
185
  console.log(snapshot.workers, snapshot.routes, snapshot.assignedTopics);
172
186
  ```
173
187
 
188
+ ## FAQ
189
+
190
+ **Why is my subscription not receiving messages from the other tab?**
191
+ Cross-tab delivery for a topic only has one transport subscription (the owner). The owner fans out received publications to all tabs via BroadcastChannel `EVENT` messages, so if the receiving tab's browser disables BroadcastChannel or storage, it degrades to local-only mode. Check `bus.getStatus()` and `getClusterSnapshot().coordinated`.
192
+
193
+ **Does every tab open its own WebSocket?**
194
+ With the default `dedicated` mode, yes — each tab owns a connection through its own Worker. With `shared` (or `auto` in shared-capable browsers), same-origin tabs reuse one SharedWorker process while each tab's port keeps an independent session. Topic ownership is deduplicated across tabs either way, so popular topics are only subscribed once per cluster.
195
+
196
+ **What happens when the owning tab crashes?**
197
+ Ownership migrates. A graceful exit (pagehide) performs a strict handoff; an uncontrolled crash (killed tab, browser kill) is recovered via the heartbeat TTL — worst case `heartbeatIntervalMs + workerTtlMs` (≈13s with defaults).
198
+
199
+ **Do I need `centrifuge` installed?**
200
+ Only if you use the built-in Centrifuge backend (`cross-tab-worker-databus/centrifuge`). It is an optional peer dependency; the core package has zero runtime dependencies.
201
+
202
+ **How do I migrate from 0.1.x to 0.2.x?**
203
+ `centrifuge` moved from `dependencies` to an optional `peerDependency`. If you use the Centrifuge backend, add it to your own dependencies (`pnpm add centrifuge@^5.5.3`); no code changes are required. See the [0.2.0 changelog](./CHANGELOG.md).
204
+
174
205
  ## Development
175
206
 
207
+
176
208
  ```bash
177
209
  pnpm install
178
210
  pnpm check # typecheck + unit tests + build
package/README.zh.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # cross-tab-worker-databus
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/cross-tab-worker-databus)](https://www.npmjs.com/package/cross-tab-worker-databus)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
5
+
3
6
  > 中文 | [English](./README.md)
4
7
 
5
8
  框架无关的浏览器跨 Tab 数据总线。
@@ -31,6 +34,15 @@
31
34
  pnpm add cross-tab-worker-databus
32
35
  ```
33
36
 
37
+ 核心包零运行时依赖。Centrifuge transport(`cross-tab-worker-databus/centrifuge`)
38
+ 将 `centrifuge` 声明为可选 peer 依赖——仅在使用内置 Centrifuge 后端时安装:
39
+
40
+ ```bash
41
+ pnpm add cross-tab-worker-databus centrifuge
42
+ ```
43
+
44
+ 仅使用本地 BroadcastChannel 数据总线(不连 WebSocket 服务)的 Tab 无需安装 `centrifuge`。
45
+
34
46
  ## 快速接入
35
47
 
36
48
  ```ts
@@ -85,8 +97,26 @@ pnpm examples
85
97
  - [能力矩阵](./docs/zh/capabilities.md)
86
98
  - [变更日志](./CHANGELOG.md)
87
99
 
100
+ ## 常见问题(FAQ)
101
+
102
+ **为什么我的订阅收不到其他 Tab 的消息?**
103
+ 每个 Topic 的传输层订阅只有一个 owner,owner 收到消息后通过 BroadcastChannel `EVENT` 广播扇出给所有 Tab。如果接收方的浏览器禁用了 BroadcastChannel 或存储,会降级为仅本地模式。检查 `bus.getStatus()` 和 `getClusterSnapshot().coordinated`。
104
+
105
+ **每个 Tab 都会开一条 WebSocket 吗?**
106
+ 默认 `dedicated` 模式:是,每个 Tab 通过自己的 Worker 持有一条连接。`shared`(或 `auto` 在支持 SharedWorker 的浏览器下)模式:同源 Tab 复用一个 SharedWorker 进程,但每个 Tab 的 port 仍是独立会话。无论哪种模式,Topic ownership 都会跨 Tab 去重,热门 Topic 在整个集群内只订阅一次。
107
+
108
+ **owner 所在 Tab 崩溃了怎么办?**
109
+ Ownership 会迁移。优雅退出(pagehide)走严格交接;非受控崩溃(Tab 被杀、浏览器崩溃)通过心跳 TTL 兜底恢复——最坏情况 `heartbeatIntervalMs + workerTtlMs`(默认约 13 秒)。
110
+
111
+ **需要安装 `centrifuge` 吗?**
112
+ 只有使用内置 Centrifuge 后端(`cross-tab-worker-databus/centrifuge`)时才需要。它是可选 peer 依赖,核心包零运行时依赖。
113
+
114
+ **如何从 0.1.x 迁移到 0.2.x?**
115
+ `centrifuge` 从 `dependencies` 移为可选 `peerDependency`。如果使用 Centrifuge 后端,请把 `centrifuge@^5.5.3` 加入你自己的依赖(`pnpm add centrifuge@^5.5.3`);无需修改代码。详见 [0.2.0 changelog](./CHANGELOG.md)。
116
+
88
117
  ## 开发
89
118
 
119
+
90
120
  ```bash
91
121
  pnpm install
92
122
  pnpm check
@@ -19,58 +19,94 @@ export declare const DEFAULT_HEARTBEAT_INTERVAL_MS = 10000;
19
19
  * heartbeat interval. A live port PINGs every heartbeat interval, so a timeout of
20
20
  * several intervals tolerates throttled tabs without reaping healthy sessions. */
21
21
  export declare const DEFAULT_SESSION_TIMEOUT_MULTIPLIER = 3;
22
+ /** Default per-port session timeout in ms, derived from the heartbeat interval
23
+ * and the multiplier. Used as the fallback when a port sends no INIT config. */
24
+ export declare const DEFAULT_SESSION_TIMEOUT_MS: number;
22
25
  type WorkerUnsafeOption = 'eventsource' | 'fetch' | 'getData' | 'getToken' | 'networkEventTarget' | 'readableStream' | 'sockjs' | 'websocket';
23
- /** Centrifuge options safe to pass into a Worker; unsafe options are explicitly excluded. */
26
+ /** Centrifuge options safe to pass into a Worker; unsafe options are explicitly
27
+ * excluded and set to `never` so the compiler rejects them at the call site. */
24
28
  export type CentrifugeWorkerConfig = Omit<Partial<Options>, WorkerUnsafeOption> & {
25
29
  [Key in WorkerUnsafeOption]?: never;
26
30
  };
27
- /** Messages sent from the main thread to the Worker. */
28
- export type CentrifugeWorkerInput = {
31
+ /** Messages sent from the main thread to the Worker. All variants are
32
+ * structured-cloneable; `PUBLISH_BIN` carries an ArrayBuffer (transferable). */
33
+ export type CentrifugeWorkerInput =
34
+ /** Initial connection: URL + config. Sent once per backend creation.
35
+ * `transferable` enables ArrayBuffer zero-copy for subsequent PUBLISH_BIN.
36
+ * `heartbeatIntervalMs` overrides the SharedWorker PING cadence. */
37
+ {
29
38
  type: 'INIT';
30
39
  url: string;
31
40
  config: CentrifugeWorkerConfig;
32
41
  transferable?: boolean;
33
42
  heartbeatIntervalMs?: number;
34
- } | {
43
+ }
44
+ /** Subscribe to a channel. Idempotent — re-subscribing is a no-op. */
45
+ | {
35
46
  type: 'SUBSCRIBE';
36
47
  topic: string;
37
- } | {
48
+ }
49
+ /** Unsubscribe from a channel. Idempotent. */
50
+ | {
38
51
  type: 'UNSUBSCRIBE';
39
52
  topic: string;
40
- } | {
53
+ }
54
+ /** Publish a structured-cloneable payload to a channel. */
55
+ | {
41
56
  type: 'PUBLISH';
42
57
  topic: string;
43
58
  data: unknown;
44
- } | {
59
+ }
60
+ /** Publish an ArrayBuffer via Transferable (zero-copy when `transferable` is on). */
61
+ | {
45
62
  type: 'PUBLISH_BIN';
46
63
  topic: string;
47
64
  data: ArrayBuffer;
48
- } | {
65
+ }
66
+ /** Heartbeat from the main thread; the SharedWorker reaps silent ports. */
67
+ | {
49
68
  type: 'PING';
50
- } | {
69
+ }
70
+ /** Disconnect the client and clear all subscriptions. */
71
+ | {
51
72
  type: 'STOP';
52
73
  };
53
- /** Messages sent from the Worker back to the main thread. */
54
- export type CentrifugeWorkerOutput<TData = unknown> = {
74
+ /** Messages sent from the Worker back to the main thread. The main thread
75
+ * routes these to the DataBusTransportHandlers via handleOutput(). */
76
+ export type CentrifugeWorkerOutput<TData = unknown> =
77
+ /** Connection status changed. Maps Centrifuge states to the DataBus vocabulary. */
78
+ {
55
79
  type: 'STATUS';
56
80
  status: WorkerStatus;
57
- } | {
81
+ }
82
+ /** A JSON publication arrived. Routed to onMessage via handleOutput. */
83
+ | {
58
84
  type: 'MESSAGE';
59
85
  topic: string;
60
86
  data: TData;
61
- } | {
87
+ }
88
+ /** A binary publication arrived (Transferable). Routed to onMessage with the ArrayBuffer. */
89
+ | {
62
90
  type: 'MESSAGE_BIN';
63
91
  topic: string;
64
92
  data: ArrayBuffer;
65
- } | {
93
+ }
94
+ /** A non-fatal error occurred. Does not imply disconnection (the client retries internally). */
95
+ | {
66
96
  type: 'ERROR';
67
97
  error: SerializedWorkerError;
68
98
  };
69
- /** Error object serialized for cross-thread transfer (Error instances cannot be cloned via postMessage). */
99
+ /** Error object serialized for cross-thread transfer. Error instances cannot
100
+ * be structured-cloned via postMessage, so the Worker converts them to this
101
+ * shape and the main thread rebuilds an Error via deserializeWorkerError(). */
70
102
  export interface SerializedWorkerError {
103
+ /** The Error's `name` (e.g. 'TypeError', 'CentrifugeError'). */
71
104
  name: string;
105
+ /** The Error's `message`. */
72
106
  message: string;
107
+ /** The Error's `stack` if available (for debugging). */
73
108
  stack?: string;
109
+ /** Arbitrary context attached by the Worker (e.g. the failing operation). */
74
110
  context?: unknown;
75
111
  }
76
112
  export {};
@@ -1 +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"}
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;AACpD;gFACgF;AAChF,eAAO,MAAM,0BAA0B,QAC6B,CAAC;AAOrE,KAAK,kBAAkB,GACnB,aAAa,GACb,OAAO,GACP,SAAS,GACT,UAAU,GACV,oBAAoB,GACpB,gBAAgB,GAChB,QAAQ,GACR,WAAW,CAAC;AAEhB;gFACgF;AAChF,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;gFACgF;AAChF,MAAM,MAAM,qBAAqB;AAC/B;;oEAEoE;AAClE;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;AACrH,sEAAsE;GACpE;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACtC,8CAA8C;GAC5C;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACxC,2DAA2D;GACzD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE;AACnD,qFAAqF;GACnF;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE;AAC3D,2EAA2E;GACzE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;AAClB,yDAAyD;GACvD;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB;sEACsE;AACtE,MAAM,MAAM,sBAAsB,CAAC,KAAK,GAAG,OAAO;AAChD,mFAAmF;AACjF;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE;AAC1C,wEAAwE;GACtE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE;AACjD,6FAA6F;GAC3F;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE;AAC3D,gGAAgG;GAC9F;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,qBAAqB,CAAA;CAAE,CAAC;AAEpD;;+EAE+E;AAC/E,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB"}
@@ -13,11 +13,16 @@ export declare class CentrifugeSession<TData = unknown> {
13
13
  private readonly subscriptions;
14
14
  private transferable;
15
15
  constructor(sink: CentrifugeSessionSink<TData>);
16
- /** Dispatch an incoming Worker message to the matching operation. */
16
+ /** Dispatch an incoming Worker message to the matching operation.
17
+ * Unknown message types are ignored rather than thrown, so a future protocol
18
+ * extension adding a new variant cannot crash an older session. */
17
19
  handle(message: CentrifugeWorkerInput): void;
18
20
  /** Create the Centrifuge client, wire up lifecycle listeners, and connect. */
19
21
  private initialize;
20
- /** Subscribe to a Centrifuge channel. Reuses an existing subscription if one exists. */
22
+ /** Subscribe to a Centrifuge channel. Reuses an existing subscription if one exists.
23
+ * Listeners are only registered once per subscription object — a repeated
24
+ * SUBSCRIBE for an already-tracked topic skips the listener wiring entirely,
25
+ * avoiding the removeAllListeners + re-on churn on every duplicate message. */
21
26
  private subscribe;
22
27
  /** Unsubscribe from a Centrifuge channel and clean up the local reference.
23
28
  * Listeners are removed before unsubscribing so a late `unsubscribed` event
@@ -25,7 +30,10 @@ export declare class CentrifugeSession<TData = unknown> {
25
30
  private unsubscribe;
26
31
  /** Publish a message to the Centrifuge channel. */
27
32
  private publish;
28
- /** Forward a publication to the transport, using Transferable for binary data when enabled. */
33
+ /** Forward a publication to the transport. Binary payloads take the
34
+ * zero-copy `MESSAGE_BIN` path when `transferable` is enabled; everything
35
+ * else is structured-cloned via `MESSAGE`. An empty topic means the
36
+ * publication carried no channel info and is silently dropped. */
29
37
  private postPublication;
30
38
  /** Disconnect the client and clear all subscriptions. */
31
39
  private stop;
@@ -1 +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;IAuBlB,wFAAwF;IACxF,OAAO,CAAC,SAAS;IAmBjB;;gFAE4E;IAC5E,OAAO,CAAC,WAAW;IAUnB,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"}
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;;uEAEmE;IACnE,MAAM,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI;IAyB5C,8EAA8E;IAC9E,OAAO,CAAC,UAAU;IAuBlB;;;mFAG+E;IAC/E,OAAO,CAAC,SAAS;IA2BjB;;gFAE4E;IAC5E,OAAO,CAAC,WAAW;IAUnB,mDAAmD;IACnD,OAAO,CAAC,OAAO;IAKf;;;sEAGkE;IAClE,OAAO,CAAC,eAAe;IASvB,yDAAyD;IACzD,OAAO,CAAC,IAAI;IAOZ,2DAA2D;IAC3D,OAAO,CAAC,IAAI;IAIZ;;;;0DAIsD;IACtD,OAAO,CAAC,SAAS;CAGlB"}
@@ -88,6 +88,10 @@ export declare class CentrifugeWorkerTransport<TData = unknown> implements DataB
88
88
  * terminate the Worker (or close the SharedWorker port).
89
89
  */
90
90
  stop(): void;
91
+ /** Build the INIT payload sent to the Worker / local session. Optional fields
92
+ * are only included when they deviate from the defaults, so the Worker's own
93
+ * default-resolution logic kicks in for the common case. */
94
+ private buildInitInput;
91
95
  /** Create and initialise a dedicated Worker, then send the INIT message. */
92
96
  private startDedicatedWorker;
93
97
  /** Create and initialise a SharedWorker, open the MessagePort, and send the INIT message. */
@@ -96,11 +100,15 @@ export declare class CentrifugeWorkerTransport<TData = unknown> implements DataB
96
100
  private readonly handleMessage;
97
101
  /** Handle a message from the in-process CentrifugeSession (local fallback). */
98
102
  private readonly handleSessionOutput;
99
- /** Route a Worker output message to the appropriate handler callback. */
103
+ /** Route a Worker output message to the appropriate handler callback.
104
+ * Shared by the Worker message listener, the SharedWorker port listener,
105
+ * and the local-session sink — all three feed into this single dispatcher. */
100
106
  private handleOutput;
101
107
  /** Handle a Worker-level failure (crash, message decode error). Discards the
102
108
  * dead backend so a later start()/reopen can rebuild from scratch, and
103
- * signals an error status so the DataBus can trigger recovery. */
109
+ * signals an error status so the DataBus can trigger recovery.
110
+ * Only invoked when the generation guard confirms the failing backend is
111
+ * still current — late errors from a superseded Worker are silently dropped. */
104
112
  private onWorkerFailed;
105
113
  /** Remove every listener attached to the current SharedWorker and its port. */
106
114
  private detachSharedWorkerListeners;
@@ -1 +1 @@
1
- {"version":3,"file":"centrifuge.d.ts","sourceRoot":"","sources":["../src/centrifuge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"centrifuge.d.ts","sourceRoot":"","sources":["../src/centrifuge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAG/E,OAAO,KAAK,EAAiB,UAAU,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,KAAK,EACV,sBAAsB,EAIvB,MAAM,uBAAuB,CAAC;AAG/B,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC3F,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE/D,uEAAuE;AACvE,MAAM,WAAW,uBAAuB;IACtC,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,+DAA+D;IAC/D,OAAO,CAAC,EAAE,sBAAsB,CAAC;CAClC;AAED,gFAAgF;AAChF,MAAM,WAAW,gCAAgC;IAC/C,gFAAgF;IAChF,aAAa,CAAC,EAAE,MAAM,MAAM,CAAC;IAC7B,mCAAmC;IACnC,mBAAmB,CAAC,EAAE,MAAM,YAAY,CAAC;IACzC,+DAA+D;IAC/D,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,iEAAiE;IACjE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;6CAGyC;IACzC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,2FAA2F;AAC3F,MAAM,WAAW,8BAA8B,CAAC,KAAK,GAAG,OAAO,CAC7D,SAAQ,IAAI,CACR,sBAAsB,CAAC,uBAAuB,EAAE,KAAK,CAAC,EACtD,WAAW,GAAG,YAAY,GAAG,eAAe,GAAG,WAAW,CAC3D,EACD,gCAAgC;IAClC,2CAA2C;IAC3C,UAAU,EAAE,uBAAuB,CAAC;IACpC,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,qBAAa,yBAAyB,CAAC,KAAK,GAAG,OAAO,CACpD,YAAW,gBAAgB,CAAC,uBAAuB,EAAE,KAAK,CAAC;IAE3D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;IACvC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAmC;IACvE,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,IAAI,CAA4B;IACxC,OAAO,CAAC,eAAe,CAA+C;IACtE,OAAO,CAAC,YAAY,CAAyC;IAC7D,OAAO,CAAC,QAAQ,CAAgD;IAGhE,OAAO,CAAC,UAAU,CAAK;IAGvB,OAAO,CAAC,iBAAiB,CAAK;gBAElB,OAAO,GAAE,gCAAqC;IAS1D;;;OAGG;IACH,KAAK,CAAC,MAAM,EAAE,uBAAuB,EAAE,QAAQ,EAAE,wBAAwB,CAAC,KAAK,CAAC,GAAG,IAAI;IAwBvF,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI9B,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIhC;;;OAGG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI;IAQ3C;;;OAGG;IACH,IAAI,IAAI,IAAI;IAkBZ;;gEAE4D;IAC5D,OAAO,CAAC,cAAc;IAYtB,4EAA4E;IAC5E,OAAO,CAAC,oBAAoB;IAS5B,6FAA6F;IAC7F,OAAO,CAAC,iBAAiB;IAczB,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAE5B;IAEF,+EAA+E;IAC/E,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAElC;IAEF;;kFAE8E;IAC9E,OAAO,CAAC,YAAY;IAOpB;;;;oFAIgF;IAChF,OAAO,CAAC,cAAc;IAetB,+EAA+E;IAC/E,OAAO,CAAC,2BAA2B;IAMnC,sFAAsF;IACtF,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAGhC;IAEF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAG9B;IAEF,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAGtC;IAEF,wEAAwE;IACxE,OAAO,CAAC,YAAY;IAQpB;;;OAGG;IACH,OAAO,CAAC,IAAI;CAeb;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,GAAG,OAAO,EACrD,OAAO,EAAE,8BAA8B,CAAC,KAAK,CAAC,GAC7C,eAAe,CAAC,uBAAuB,EAAE,KAAK,CAAC,CAwBjD"}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CrossTabDataBus,
3
3
  selectWorkerBackend
4
- } from "./chunk-53INHVYO.js";
4
+ } from "./chunk-LBXREMZA.js";
5
5
 
6
6
  // src/centrifuge-session.ts
7
7
  import { Centrifuge } from "centrifuge";
@@ -12,14 +12,30 @@ var CentrifugeSession = class {
12
12
  client = null;
13
13
  subscriptions = /* @__PURE__ */ new Map();
14
14
  transferable = false;
15
- /** Dispatch an incoming Worker message to the matching operation. */
15
+ /** Dispatch an incoming Worker message to the matching operation.
16
+ * Unknown message types are ignored rather than thrown, so a future protocol
17
+ * extension adding a new variant cannot crash an older session. */
16
18
  handle(message) {
17
- if (message.type === "INIT") this.initialize(message.url, message.config, message.transferable === true);
18
- if (message.type === "SUBSCRIBE") this.subscribe(message.topic);
19
- if (message.type === "UNSUBSCRIBE") this.unsubscribe(message.topic);
20
- if (message.type === "PUBLISH") this.publish(message.topic, message.data);
21
- if (message.type === "PUBLISH_BIN") this.publish(message.topic, message.data);
22
- if (message.type === "STOP") this.stop();
19
+ switch (message.type) {
20
+ case "INIT":
21
+ this.initialize(message.url, message.config, message.transferable === true);
22
+ return;
23
+ case "SUBSCRIBE":
24
+ this.subscribe(message.topic);
25
+ return;
26
+ case "UNSUBSCRIBE":
27
+ this.unsubscribe(message.topic);
28
+ return;
29
+ case "PUBLISH":
30
+ case "PUBLISH_BIN":
31
+ this.publish(message.topic, message.data);
32
+ return;
33
+ case "STOP":
34
+ this.stop();
35
+ return;
36
+ default:
37
+ return;
38
+ }
23
39
  }
24
40
  /** Create the Centrifuge client, wire up lifecycle listeners, and connect. */
25
41
  initialize(url, config, transferable) {
@@ -40,9 +56,17 @@ var CentrifugeSession = class {
40
56
  });
41
57
  client.connect();
42
58
  }
43
- /** Subscribe to a Centrifuge channel. Reuses an existing subscription if one exists. */
59
+ /** Subscribe to a Centrifuge channel. Reuses an existing subscription if one exists.
60
+ * Listeners are only registered once per subscription object — a repeated
61
+ * SUBSCRIBE for an already-tracked topic skips the listener wiring entirely,
62
+ * avoiding the removeAllListeners + re-on churn on every duplicate message. */
44
63
  subscribe(topic) {
45
64
  if (!this.client) return this.postError(new Error("Centrifuge client is not initialized."));
65
+ const existing = this.subscriptions.get(topic);
66
+ if (existing) {
67
+ existing.subscribe();
68
+ return;
69
+ }
46
70
  let subscription = this.client.getSubscription(topic);
47
71
  if (!subscription) subscription = this.client.newSubscription(topic);
48
72
  subscription.removeAllListeners("publication");
@@ -73,7 +97,10 @@ var CentrifugeSession = class {
73
97
  if (!this.client) return this.postError(new Error("Centrifuge client is not initialized."));
74
98
  void this.client.publish(topic, data).catch((error) => this.postError(error));
75
99
  }
76
- /** Forward a publication to the transport, using Transferable for binary data when enabled. */
100
+ /** Forward a publication to the transport. Binary payloads take the
101
+ * zero-copy `MESSAGE_BIN` path when `transferable` is enabled; everything
102
+ * else is structured-cloned via `MESSAGE`. An empty topic means the
103
+ * publication carried no channel info and is silently dropped. */
77
104
  postPublication(topic, data) {
78
105
  if (!topic) return;
79
106
  if (this.transferable && data instanceof ArrayBuffer) {
@@ -105,12 +132,14 @@ var CentrifugeSession = class {
105
132
  function getPayloadTopic(data) {
106
133
  if (!data || typeof data !== "object") return "";
107
134
  const payload = data;
108
- const topic = payload.push?.channel ?? payload.channel;
135
+ const push = payload.push;
136
+ const nested = typeof push === "object" && push !== null ? push.channel : void 0;
137
+ const topic = nested ?? payload.channel;
109
138
  return typeof topic === "string" ? topic : "";
110
139
  }
140
+ var LIVE_STATES = /* @__PURE__ */ new Set(["connecting", "connected"]);
111
141
  function normalizeStatus(status) {
112
- if (status === "connecting" || status === "connected") return status;
113
- return "disconnected";
142
+ return LIVE_STATES.has(status) ? status : "disconnected";
114
143
  }
115
144
  function serializeError(error) {
116
145
  if (error instanceof Error) {
@@ -129,6 +158,8 @@ function serializeError(error) {
129
158
 
130
159
  // src/centrifuge-protocol.ts
131
160
  var DEFAULT_HEARTBEAT_INTERVAL_MS = 1e4;
161
+ var DEFAULT_SESSION_TIMEOUT_MULTIPLIER = 3;
162
+ var DEFAULT_SESSION_TIMEOUT_MS = DEFAULT_HEARTBEAT_INTERVAL_MS * DEFAULT_SESSION_TIMEOUT_MULTIPLIER;
132
163
 
133
164
  // src/centrifuge.ts
134
165
  var CentrifugeWorkerTransport = class {
@@ -170,13 +201,7 @@ var CentrifugeWorkerTransport = class {
170
201
  worker: this.workerFactory !== void 0,
171
202
  sharedWorker: this.sharedWorkerFactory !== void 0
172
203
  });
173
- const input = {
174
- type: "INIT",
175
- url: config.url,
176
- config: config.options ?? {},
177
- ...this.transferable ? { transferable: true } : {},
178
- ...this.heartbeatIntervalMs !== DEFAULT_HEARTBEAT_INTERVAL_MS ? { heartbeatIntervalMs: this.heartbeatIntervalMs } : {}
179
- };
204
+ const input = this.buildInitInput(config);
180
205
  if (backend === "shared") {
181
206
  this.startSharedWorker(input);
182
207
  this.backend = "shared";
@@ -229,6 +254,18 @@ var CentrifugeWorkerTransport = class {
229
254
  this.resetBackend();
230
255
  this.handlers = null;
231
256
  }
257
+ /** Build the INIT payload sent to the Worker / local session. Optional fields
258
+ * are only included when they deviate from the defaults, so the Worker's own
259
+ * default-resolution logic kicks in for the common case. */
260
+ buildInitInput(config) {
261
+ return {
262
+ type: "INIT",
263
+ url: config.url,
264
+ config: config.options ?? {},
265
+ ...this.transferable ? { transferable: true } : {},
266
+ ...this.heartbeatIntervalMs !== DEFAULT_HEARTBEAT_INTERVAL_MS ? { heartbeatIntervalMs: this.heartbeatIntervalMs } : {}
267
+ };
268
+ }
232
269
  /** Create and initialise a dedicated Worker, then send the INIT message. */
233
270
  startDedicatedWorker(input) {
234
271
  this.backendGeneration = ++this.generation;
@@ -260,7 +297,9 @@ var CentrifugeWorkerTransport = class {
260
297
  handleSessionOutput = (message) => {
261
298
  this.handleOutput(message);
262
299
  };
263
- /** Route a Worker output message to the appropriate handler callback. */
300
+ /** Route a Worker output message to the appropriate handler callback.
301
+ * Shared by the Worker message listener, the SharedWorker port listener,
302
+ * and the local-session sink — all three feed into this single dispatcher. */
264
303
  handleOutput(message) {
265
304
  if (message.type === "STATUS") this.handlers?.onStatus(message.status);
266
305
  if (message.type === "MESSAGE") this.handlers?.onMessage({ topic: message.topic, data: message.data });
@@ -269,7 +308,9 @@ var CentrifugeWorkerTransport = class {
269
308
  }
270
309
  /** Handle a Worker-level failure (crash, message decode error). Discards the
271
310
  * dead backend so a later start()/reopen can rebuild from scratch, and
272
- * signals an error status so the DataBus can trigger recovery. */
311
+ * signals an error status so the DataBus can trigger recovery.
312
+ * Only invoked when the generation guard confirms the failing backend is
313
+ * still current — late errors from a superseded Worker are silently dropped. */
273
314
  onWorkerFailed(message) {
274
315
  this.worker?.removeEventListener("message", this.handleMessage);
275
316
  this.worker?.removeEventListener("error", this.handleWorkerError);
@@ -325,13 +366,11 @@ var CentrifugeWorkerTransport = class {
325
366
  */
326
367
  post(message, transfer) {
327
368
  if (this.worker) {
328
- if (transfer) this.worker.postMessage(message, transfer);
329
- else this.worker.postMessage(message);
369
+ postToPortLike(this.worker, message, transfer);
330
370
  return;
331
371
  }
332
372
  if (this.port) {
333
- if (transfer) this.port.postMessage(message, transfer);
334
- else this.port.postMessage(message);
373
+ postToPortLike(this.port, message, transfer);
335
374
  return;
336
375
  }
337
376
  if (this.localSession) {
@@ -402,6 +441,10 @@ function assertStructuredCloneable(value) {
402
441
  );
403
442
  }
404
443
  }
444
+ function postToPortLike(target, message, transfer) {
445
+ if (transfer) target.postMessage(message, transfer);
446
+ else target.postMessage(message);
447
+ }
405
448
  function deserializeWorkerError(error) {
406
449
  const result = new Error(error.message);
407
450
  result.name = error.name;