cross-tab-worker-databus 0.20.71 → 0.20.86
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 +216 -2
- package/README.md +8 -0
- package/README.zh.md +4 -0
- package/dist/centrifuge-protocol.d.ts +59 -26
- package/dist/centrifuge-protocol.d.ts.map +1 -1
- package/dist/centrifuge-session.d.ts +9 -0
- package/dist/centrifuge-session.d.ts.map +1 -1
- package/dist/centrifuge.d.ts +12 -2
- package/dist/centrifuge.d.ts.map +1 -1
- package/dist/centrifuge.js +168 -88
- package/dist/centrifuge.js.map +3 -3
- package/dist/centrifuge.shared.worker.js +146 -38
- package/dist/centrifuge.shared.worker.js.map +3 -3
- package/dist/centrifuge.worker.js +140 -33
- package/dist/centrifuge.worker.js.map +3 -3
- package/dist/{chunk-D2SIT473.js → chunk-SDOV3UHG.js} +1304 -515
- package/dist/chunk-SDOV3UHG.js.map +7 -0
- package/dist/chunk-TZ7ZP7YD.js +175 -0
- package/dist/chunk-TZ7ZP7YD.js.map +7 -0
- package/dist/cjs/centrifuge.cjs +1608 -650
- package/dist/cjs/centrifuge.cjs.map +4 -4
- package/dist/cjs/hooks.cjs +37 -2
- package/dist/cjs/hooks.cjs.map +3 -3
- package/dist/cjs/index.cjs +1692 -747
- package/dist/cjs/index.cjs.map +4 -4
- package/dist/cjs/vue.cjs +45 -2
- package/dist/cjs/vue.cjs.map +3 -3
- package/dist/core/cluster.d.ts +40 -4
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/data-bus.d.ts +134 -77
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/core/dedup-manager.d.ts +98 -0
- package/dist/core/dedup-manager.d.ts.map +1 -0
- package/dist/core/environment.d.ts +43 -3
- package/dist/core/environment.d.ts.map +1 -1
- package/dist/core/replay-manager.d.ts +129 -0
- package/dist/core/replay-manager.d.ts.map +1 -0
- package/dist/core/replay-persistence.d.ts +2 -1
- package/dist/core/replay-persistence.d.ts.map +1 -1
- package/dist/core/replay-pruning.d.ts +21 -0
- package/dist/core/replay-pruning.d.ts.map +1 -0
- package/dist/core/routing.d.ts +22 -3
- package/dist/core/routing.d.ts.map +1 -1
- package/dist/core/storage-batch.d.ts +0 -4
- package/dist/core/storage-batch.d.ts.map +1 -1
- package/dist/core/trace.d.ts +55 -15
- package/dist/core/trace.d.ts.map +1 -1
- package/dist/core/types.d.ts +58 -9
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/version.d.ts +2 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/hooks.d.ts +12 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +28 -2
- package/dist/hooks.js.map +2 -2
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +263 -166
- package/dist/index.js.map +3 -3
- package/dist/utils/constants.d.ts +170 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/error-utils.d.ts +28 -0
- package/dist/utils/error-utils.d.ts.map +1 -0
- package/dist/utils/metadata.d.ts +16 -0
- package/dist/utils/metadata.d.ts.map +1 -0
- package/dist/utils/storage-utils.d.ts +24 -0
- package/dist/utils/storage-utils.d.ts.map +1 -0
- package/dist/utils/validation.d.ts +76 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/vue.d.ts +13 -1
- package/dist/vue.d.ts.map +1 -1
- package/dist/vue.js +36 -2
- package/dist/vue.js.map +2 -2
- package/dist/websocket.d.ts +6 -1
- package/dist/websocket.d.ts.map +1 -1
- package/dist/worker-mode.d.ts +6 -2
- package/dist/worker-mode.d.ts.map +1 -1
- package/docs/README.md +1 -0
- package/docs/api.md +135 -4
- package/docs/architecture.md +92 -1
- package/docs/benchmarks.md +24 -0
- package/docs/capabilities.md +24 -3
- package/docs/configuration.md +58 -0
- package/docs/getting-started.md +35 -0
- package/docs/release-checklist.md +28 -5
- package/docs/roadmap.md +81 -5
- package/docs/zh/README.md +2 -1
- package/docs/zh/api.md +134 -4
- package/docs/zh/architecture.md +61 -1
- package/docs/zh/benchmarks.md +24 -0
- package/docs/zh/capabilities.md +21 -3
- package/docs/zh/configuration.md +53 -0
- package/docs/zh/getting-started.md +35 -0
- package/docs/zh/release-checklist.md +29 -6
- package/docs/zh/roadmap.md +93 -5
- package/package.json +39 -16
- package/dist/chunk-D2SIT473.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,218 @@
|
|
|
1
|
-
|
|
1
|
+
## [0.20.86] - 2026-09-16
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- The configuration reference now documents the full public option surface: `replay.pruneStrategy` (previously absent entirely) and the replay options table (`maxPerTopic`, `persistence`, `retentionMs`, `pruneStrategy`, `retentionSweepMs`, `persistenceRetry`), plus a deduplication options table (`maxEntries`, `ttlMs`, `sweepMs`, `now`, `adaptiveTtl`) — in both languages. A guard derives the field list from the built declarations and fails when a `DataBusReplayOptions`/`DataBusDedupOptions` field is undocumented in either configuration reference. The `pruneStrategy` JSDoc no longer claims a default of `'both'` (the actual default is `'count'`).
|
|
5
|
+
- Property invariants for `selectActiveWorkers` / `selectRebalanceTarget`: the returned worker set is always a subset of the input, active selection stays within `maxActiveWorkers` and non-empty for a non-empty input, and neither helper throws on arbitrary corrupt worker records.
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- `ready()` no longer reports readiness for a restart that a later `stop()` canceled. The queued `start()` promise retains its documented resolve-on-cancellation behavior, but readiness now rejects with a clear lifecycle error instead of resolving against a stopped bus.
|
|
9
|
+
- A failed restart queued behind an in-flight `stop()` now remains observable through `ready()`. Previously the failure cleared `started`, so a later `ready()` without `initialConfig` was masked by the generic "requires initialConfig" error; it now resurfaces the actual transport startup failure and preserves the explicit manual-retry path.
|
|
10
|
+
- `publish()` and non-empty `publishBatch()` calls issued while `stop()` is in flight now report through `onError` instead of silently returning once `runTransport()` refuses to touch the stopping transport. Empty batches remain no-ops, publications already queued behind an in-flight open are still canceled by the stop (latest lifecycle intent wins), and page-hide suspension keeps its documented drop-without-defer behavior.
|
|
11
|
+
- `subscribe()` and `ready()` now honor the explicit-stop barrier. A subscription requested while `stop()` is settling reports through `onError` and returns a no-op cleanup instead of being erased by teardown or leaking into a later restart; `ready()` rejects rather than resolving against the stopping transport. A `start()` already queued behind that stop remains the newest lifecycle intent, so `ready()` continues to follow its restart promise.
|
|
12
|
+
- A `stop()` that arrives before a restart queued behind an earlier in-flight `stop()` could run now cancels that queued restart instead of being swallowed by the in-flight stop gate. The queued continuation is invalidated with a monotonic token, so the latest lifecycle intent always wins: `stop → start → stop` ends stopped with no extra transport open, while a later `start()` still queues a fresh restart with a higher token.
|
|
13
|
+
- Superseded asynchronous transport opens can no longer tear down or detach a newer page-hide/pageshow reopen. Each open now carries a lifecycle epoch, so callbacks, failures, success telemetry, and cleanup from an older open are ignored once a newer suspend/resume/stop transition owns the lifecycle; `stop()` also waits for any pending open or reopen even when `started` has already been cleared. Previously an initial open failing after a queued resume reset `started` to false and nulled `startPromise`, making `stop()` resolve immediately while the queued transport could still start afterwards.
|
|
14
|
+
- `start()` now serialises correctly with both page-hide suspension and an in-flight explicit stop. A hidden bus whose asynchronous `transport.stop()` was still settling returned that cleanup promise from `start()` and never reopened the transport; an in-flight `stop()` likewise allowed `start()` to observe the old ready state and resolve as a no-op before the bus finished stopping. Both paths now queue one fresh start after cleanup, share concurrent start/stop promises, and preserve the existing degraded manual-recovery behavior.
|
|
15
|
+
- Explicit `start()` now performs a manual transport recovery when the bus has already started but its automatic recovery budget is exhausted; it preserves the cluster, subscriptions, and replay state while resetting the failure/recovery ledger before reopening. Previously `start()` returned a resolved no-op in the degraded state even though the public health contract documents it as the manual retry path. Covered by a regression that exhausts automatic recovery, succeeds through `start()`, and verifies the health summary returns to healthy.
|
|
16
|
+
- IndexedDB replay persistence now settles every mutation when a transaction aborts, including connection-loss aborts that fire `onabort` without a preceding request error. Previously the serialized mutation queue could remain blocked forever, preventing later appends and clears from running. Loads now resolve only after `transaction.oncomplete`, so a request that succeeds before a later abort cannot be reported as a successful read. Request/transaction failures without an `error` object use operation-specific fallback messages.
|
|
17
|
+
- Replay age pruning is now position-independent and uses one shared policy for the in-memory rings and IndexedDB adapter. Previously an expired timestamped entry after a timestamp-less legacy entry (or after a non-expired entry) was never removed, and hydrated history was always truncated to `maxPerTopic` even when `pruneStrategy: 'age'` was configured. Timestamp-less entries are still preserved for compatibility, but are now capped by `maxPerTopic` under AGE so they cannot grow without bound; timestamped entries remain bounded by the retention window. Covered across live recording, hydration without a `clearBefore` adapter, and durable append paths.
|
|
18
|
+
|
|
19
|
+
## [0.20.85] - 2026-09-13
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- Seeded property suite (`tests/property.test.ts`) for the pure hot-path helpers and the stateful managers: `effectiveWorkerLoad`/`selectLeastLoadedWorker` totality and order-independence, `approximatePayloadBytes` finiteness (cycles included), `parseDataBusPublication` topic validity, `topicMatchesPattern` segment-boundary invariants, `serializeError` cloneability, `createOpaqueKey` shape, and `DedupManager`/`ReplayManager` bound invariants under long random operation sequences. Deterministic (fixed seeds), so failures reproduce and the suite is not flaky.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- Replay history is now bounded when `pruneStrategy: 'age'` is set without a `retentionMs`: neither the age pass nor the count cap applied, so the in-memory ring (and the persisted topic record) grew without limit while delivery stayed capped by `maxPerTopic`. The count cap now applies whenever there is no retention window to prune by. Pinned in both the in-memory manager and the IndexedDB adapter (mutation-checked).
|
|
26
|
+
- `serializeError` now guarantees its documented structured-cloneable result: a non-Error value was attached verbatim as `context`, so a function/symbol (or an object holding one) made the serialised error itself uncloneable and `postMessage` threw `DataCloneError` while reporting the original failure. Non-cloneable contexts are dropped (cloneable ones are still kept for diagnostics). Pinned by regression + property-suite cloneability invariants.
|
|
27
|
+
- `approximatePayloadBytes` no longer overflows the stack on deeply nested or cyclic payloads. Structured clone preserves cycles, so a cyclic publication can legitimately reach the replay-buffer byte estimate (`getDiagnostics().replay.bytes`) and the adaptive-load sampler; unbounded recursion there threw a `RangeError` and took down the diagnostics/reconcile path. The estimate is now depth-bounded (shallow results unchanged), pinned by a cycle/deep-nesting regression plus a finiteness invariant in the seeded property suite.
|
|
28
|
+
|
|
29
|
+
## [0.20.84] - 2026-09-13
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
- CI now enforces three gates that were documented but ran nowhere: `pnpm test:coverage` (the `vitest.config.ts` floors), `pnpm verify:compat`, and `pnpm verify:pack`. The `Release` workflow also runs `verify:compat` + `verify:pack` before publishing. Both workflows check out with `fetch-depth: 0` + `fetch-tags: true`, without which `verify:compat` fails to resolve its release-tag baseline. Documented in both release checklists.
|
|
33
|
+
- Focused `ReplayManager` unit suite (`tests/replay-manager.test.ts`): the disabled no-op contract, count/age/both pruning, wildcard replay fan-out, hydration with retention pre-pruning, append batch coalescing, `clear*` durable-failure propagation, retention-sweep coalescing and idempotence, and the persistence retry/backoff plus suspend-cancellation loop (86.95% -> 97.10% statements, 81.60% -> 96.00% branches).
|
|
34
|
+
- Additional IndexedDB replay adapter coverage: the unavailable-IndexedDB guard, ordering when a clear interleaves batch coalescing, the `onversionchange` connection drop that unblocks another tab's schema upgrade, transaction-construction failure on every mutation path, single-rejection semantics for a multi-read batched append (84.02% -> 93.29% statements), plus transaction-abort coverage — a quota-exceeded `put` aborts the whole transaction with no request error, so only `transaction.onerror` signals the failure (without it the append hangs), with a generic-message fallback when `transaction.error` is null.
|
|
35
|
+
- `CrossTabDataBus` lifecycle-contract coverage: `ready()` rejecting with the configuration error when no `initialConfig` exists and resurfacing the last transport failure once no start is in flight, `unsubscribe` no-ops for an unknown topic and an unregistered handler, `stop()` idempotence, and the third dispatch gate — an owning tab fans a publication out to a peer subscriber and records it *discarded* locally rather than counting it dispatched (mutation-checked).
|
|
36
|
+
- `WorkerClusterRuntime` publish-routing and lifecycle-guard coverage: wildcard publish-cache hits on repeat publishes and `publishBatch`, an empty batch as a no-op and a single-item batch delegating to `publish()` with metadata intact, `unsubscribe` on a topic with no route, `isActiveWorker()` eligibility before and after `stop()`, `start`/`stop` idempotence, and `hasLocalSubscriber` segment-boundary matching. Includes a two-runtime regression pinning the 0.20.58 fix: a `null` wildcard-cache entry must not short-circuit a publish whose topic is owned by a remote worker (mutation-checked). cluster.ts functions now 100%.
|
|
37
|
+
- `PortReaper` SharedWorker-shutdown coverage: `setTimeout`/`touch`/`remove` no-op for an untracked port (a STOP or INIT racing a reap must not resurrect it), duplicate `remove` clears the cadence timer once the last port goes, `dispose()` closes and stops every tracked session and is safe to repeat, `dispose()` keeps cleaning up after a target throws (a detached port must not strand the remaining WebSockets), and a non-finite/non-positive heartbeat falls back to the default session timeout. port-reaper.ts now 100% statements and functions; all three guards mutation-checked.
|
|
38
|
+
- `WebSocketTransport` frame-handling coverage: an empty `publishBatch` as a no-op, ArrayBuffer items embedded as byte arrays so a mixed batch stays one frame, a duplicate `start()` reusing the live socket instead of orphaning it, and non-string / non-object server frames ignored without an error report. websocket.ts 92.59 -> 96.29 statements, 85.54 -> 91.56 branches.
|
|
39
|
+
- `CentrifugeWorkerTransport` coverage: a duplicate `start()` reusing the live backend (a second Worker would mean two WebSocket connections), the SharedWorker-level failure and the port message-decode failure reported as distinct errors, a `channelToken` request satisfied from `getToken` when the provider has no `getChannelToken`, and a token request answered with `TOKEN_ERROR` rather than dropped when no `credentialProvider` is configured (a silent drop would hang the worker's connect). centrifuge.ts 93.12 -> 95.00 statements, 91.08 -> 93.06 branches; all four paths mutation-checked.
|
|
40
|
+
- `CentrifugeSession` coverage for the client-level `connected`/`disconnected`/`error` listeners, server-initiated `unsubscribed` cleanup, topic-less publication drops, the nested `push.channel` topic shape, and stale/duplicate token replies (85.18% -> 100% functions).
|
|
41
|
+
- Demo chaos toggles for lifecycle-failure coverage in a real browser: `#dropHandoffAck` drops outgoing `ROUTE_RELEASED` so a pagehide handoff can only converge through the TTL-gated stranded-handoff recovery, and `#simulateCrash` stops all outgoing coordination on a tab with no pagehide (zombie crash: heartbeats go stale, no handoff write, no worker-record removal) so survivors must re-elect through the heartbeat-TTL crash path. Both are covered by browser E2E (takeover timing corroborates the path taken: ~14 s recovery / ~13.6 s crash expiry vs 1–2 s graceful handoff), with exactly-once delivery asserted after each takeover. (A real renderer crash via CDP was evaluated and rejected: same-origin tabs share the renderer, so siblings die too.)
|
|
42
|
+
- Demo observability: the event feed now renders trace `reliability` events (route acknowledgments/migrations/recoveries, transport recovery attempts, persistence retries, dedup outcomes) with Chinese labels, so the new recovery diagnostics are visible in the product demo instead of only in the trace sink. Subscription (`subscription:subscribe/unsubscribe`) and cluster-coordination snapshots (`coordination`, counts only) render as bounded diagnostic rows too. Covered by a browser E2E asserting the acknowledgment, subscription, and coordination rows.
|
|
43
|
+
- Trace observability for handoff recovery: a re-election that recovers a stranded unconfirmed handoff now emits `reliability` trace events with `operation: 'route_migration_recovery'`, distinct from the routine graceful-handoff `route_migration`, so operators can tell recoveries apart from normal churn in the trace sink. Covered by unit tests on both paths plus a data-bus-level test proving the operation reaches the public trace sink end to end (mutation-checked); documented in the API reference (English and Chinese).
|
|
44
|
+
- Coverage-driven fault-injection suite for the storage utility layer (`tests/storage-utils.test.ts`): a fully-throwing storage backend and a corrupt-record backend pin every error-swallow branch of `readJson`/`writeJson`/`listKeys`/`readAllByPrefix` (storage-utils now 100% statements+branches).
|
|
45
|
+
- WebSocket transport regression coverage for two error paths: a Blob binary frame whose conversion rejects is isolated through `onError` (no crash, no delivery), and a binary publish whose encoded topic exceeds the 16-bit frame prefix reports "topic is too long" and sends nothing, with the exactly-0xffff boundary still framing successfully.
|
|
46
|
+
- Centrifuge transport coverage for factory-less degradation: with both Worker globals absent the transport resolves to the `local` backend without error (the documented SSR behavior), while an injected factory that throws surfaces the failure instead of degrading silently.
|
|
47
|
+
- Retention-cleanup regression coverage: a failing durable `clearBefore` pass is reported through `onError` once and does not wedge the pipeline (a later publication still flushes), and a cutoff queued while one pass is in flight is drained by the cleanup loop rather than dropped.
|
|
48
|
+
- IndexedDB replay persistence coverage for read-request failure paths: `load()` and `clearBefore()` reject with the request error and invalidate the connection; the fault-proxy transaction wrapper now forwards `oncomplete`/`onerror` so the previously untestable clear-path completion is exercisable.
|
|
49
|
+
- Dedup option validation coverage: invalid `maxEntries`/`ttlMs`/`sweepMs` and malformed `adaptiveTtl` bounds throw `TypeError` before the bus is constructed (validation.ts statements now 100%).
|
|
50
|
+
- `pnpm bench:trend` (`scripts/bench-trend.mjs`) generates a long-run browser benchmark trend doc (`docs/benchmarks.md` + Chinese mirror) from the archived `bench-results/` reports: per-metric latest/previous/delta plus an all-time best per latency. Both docs ship in the tarball and are indexed in the documentation READMEs; the release checklist (both languages) now includes the benchmark-regression gate step, and the Chinese checklist gained the previously missing browser-benchmark gate bullet (EN/ZH parity).
|
|
51
|
+
- Opt-in `loadWeighting` adaptive owner weighting (`messageRateWeight`, `byteRateWeight`, `scheduleLagWeight`): workers sample their own fan-out traffic and heartbeat scheduling overrun per window and publish it with the worker record; new-route owner selection adds the normalized rates and lag ratio to the topic count. Default (unset) keeps pure topic-count routing and existing routes stay sticky. `WorkerThroughputSample` gains `overrunMs`, a browser-native proxy for a starved event loop.
|
|
52
|
+
- Async credential refresh bridge for the Centrifuge worker: opt-in `credentialProvider` (`getToken` / `getChannelToken`) runs on the main thread, with the Worker requesting each fresh token over a `TOKEN_REQUEST` / `TOKEN_RESPONSE` / `TOKEN_ERROR` exchange. Function-valued Centrifuge options stay out of the structured-clone boundary; legacy configs keep byte-identical behavior; pending requests are settled on STOP.
|
|
53
|
+
- Synchronous trace visibility: `DataBusTraceReporter.getMetrics()` snapshots the current aggregation window without a sink or flush; `CrossTabDataBus.getMetrics()` and `getDiagnostics().metrics` expose it, and `getDiagnostics().trace` / `getHealthSummary().trace` report `asyncSink` mode and queued-event depth (back-pressure visibility).
|
|
54
|
+
- E2E: a real-Chromium scenario disables `window.BroadcastChannel` and verifies two tabs coordinate and deliver cross-tab over the localStorage storage-event channel; a Playwright `waitForSingleOwner` convergence helper replaces one-shot owner reads across the suite. A second fallback scenario drives a graceful `pagehide` handoff so the replacement route and REGISTRY nudge ride storage events and the standby takes ownership over the fallback plane.
|
|
55
|
+
- A 100-item × 3-tab `publishBatch` E2E asserts exactly-once fan-out per tab, guarding against the owner echo racing the EVENT broadcast and per-item frame decomposition on a larger burst.
|
|
56
|
+
- Getting-started guides (English and Chinese) gained an "Upgrading & Deprecation" section, the migration-guide payload behind the pre-1.0 deprecation policy in the release checklist.
|
|
57
|
+
- `getDiagnostics().replay` now reports `bytes`, the approximate in-memory footprint of the buffered replay rings (computed on demand, same sizing heuristic as adaptive load weighting).
|
|
58
|
+
- The demo's overview gains a live diagnostics row: the current trace-window throughput/dispatch P50 (`getMetrics()`) and the replay buffer footprint (`getDiagnostics().replay`), refreshed with the existing 1s render loop. A "负载加权" toggle (off by default) enables adaptive weighting in the demo and the workers table shows each worker's throughput sample (`msg/s` + scheduling-lag %) in a new 吞吐 column.
|
|
59
|
+
- QA: `verify:pack` now smoke-imports the full root public surface (12 functions incl. `effectiveWorkerLoad`, `approximatePayloadBytes`, `createIndexedDbReplayPersistence`) plus every subpath in ESM and CJS; a cluster integration test proves `scheduleLagWeight` steers a new route away from a scheduling-lagging worker; the `getMetrics()` test asserts full field parity with a flushed `message_metrics` event; README and Chinese roadmap feature lists were brought up to date with the recent additions.
|
|
60
|
+
- Packaging: the published tarball now enumerates the exact docs files (both languages) instead of the whole `docs/` directory, dropping the internal `docs/progress.md` tracking artifact from `npm pack` output.
|
|
61
|
+
- Security/CI infrastructure: a CodeQL workflow (javascript-typescript, push/PR/weekly) and a Dependabot config (weekly npm + GitHub Actions updates) were added; `verify:compat` now auto-derives its export-contract baseline from the latest release tag (with `COMPAT_BASE_TAG` override) instead of a hard-coded version.
|
|
62
|
+
- Dependency security: `pnpm audit` found two high dev-chain advisories (`glob <10.5.0`, `nanoid <3.3.18` via vitest/vite); pinned via `pnpm-workspace.yaml` overrides (the pnpm-v10 home for that setting) and the audit is clean.
|
|
63
|
+
- `verify:published` now smoke-imports the same full root public surface as `verify:pack`, so the release gate exercises the routing/observability exports end to end.
|
|
64
|
+
- Demo accessibility fixes (`examples/demo`): the run-mode segmented control is now an ARIA `radiogroup` with a `radio`/`aria-checked` state on each button, kept in sync by the click handler (previously only a CSS `active` class conveyed the selection, so assistive tech announced no selected mode at all and never announced a change). Its dangling `<label>` became a `<span id="modeSwitchLabel">` referenced via `aria-labelledby`, since a `<label>` with no form control has no accessible-name effect. Both `.state-table`s gained visually-hidden captions, and all eight `<th>` across the three tables gained `scope="col"` so cells are announced with their column header. Guarded by four browser E2E specs asserting no unnamed control, captions and column scopes on every table, and that `aria-checked` follows the selection. The group is also keyboard-operable per the radiogroup pattern: a roving tabindex makes it a single tab stop, and Arrow/Home/End move (and select) between options with a visible `:focus-visible` ring, which buttons previously lacked entirely.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
- `effectiveWorkerLoad` is now total against a corrupt base load: the earlier non-finite-score guard fell back to `worker.load` itself, so a stored load of `Infinity` (JSON `1e999`) or a malformed record leaked a non-finite score back into owner selection — the exact array-order dependence the guard was added to prevent. Found by a new seeded property suite (`tests/property.test.ts`) that also pins order-independent minimum selection, `parseDataBusPublication` topic validity, `topicMatchesPattern` segment-boundary invariants, and `createOpaqueKey` shape.
|
|
68
|
+
- DataBus transport error isolation is now pinned: a transport whose `publish` throws synchronously, or whose `subscribe` rejects, is funnelled into `onError` instead of escaping the caller or being swallowed (mutation-checked by removing the synchronous catch). `parseDataBusPublication` now also pins the null branch for primitive/null/undefined frames when no fallback topic is available.
|
|
69
|
+
- Removed a second unreachable branch: `ReplayManager.schedulePersistenceFlush`'s per-message `append` fallback could never run, because the only queuer (`record()`) pushes to the batch only when the backend advertises `appendBatch`. The batched append is now unconditional there, with the reachability constraint documented. Behaviour-preserving (643 unit tests unchanged).
|
|
70
|
+
- Removed an unreachable branch in `WorkerClusterRuntime`'s wildcard publish path: the cache-hit guard queried `assignedTopics` (keyed by the opaque topic key) with the plaintext pattern, so `assignedTopics.has(cachedPattern)` was always false and the branch could never run. The memoisation is now documented as a scan-skip marker only (only the first, scanning call may dispatch a local-wildcard publish locally; later calls route through `resolvePublishTarget`, honouring a concrete remote owner). Behaviour-preserving removal (640 unit tests unchanged) and cluster branch/line coverage rose (90.21 → 90.50 branches, 97.61 → 98.24 lines).
|
|
71
|
+
- `CrossTabDataBus.unsubscribe(topic)` without a handler — the documented whole-topic teardown — is now pinned: it clears every handler, tears the transport subscription down exactly once, drops the topic's replay history (no stale replay on re-subscribe), and makes the previously returned per-handler closers no-ops. It previously had no direct coverage (only the with-handler and unknown-topic forms were exercised). Mutation-checked both ways.
|
|
72
|
+
- Centrifuge publication metadata is now pinned for the partial cases: a `messageId`-only or `timestamp`-only publish sends an envelope that omits the absent key entirely instead of serialising `undefined` into the server payload.
|
|
73
|
+
- Error-path coverage added for the WebSocket transport's separate binary framing path (a binary publish on a closed socket is reported through `onError`; mutation-checked) and for the structured-clone guard's documented skip when a runtime lacks `structuredClone` (older browsers must proceed rather than fail with a spurious `TypeError`; mutation-checked). `BatchingStorageWriter` now also pins that at most one backoff retry timer stays pending while writes keep failing.
|
|
74
|
+
- `scripts/bench-browser.mjs` ran the *entire* benchmark at module scope, so nothing could import it — a test, or any other script — without spawning the demo server and launching a Chromium instance. Its environment inputs were also `Number(...)`-coerced with no validation, the same defect class as the `bench:compare` gate: `BENCH_MESSAGES=abc` became `NaN`, the publish loop never executed, and the run died on a 30s `waitForFunction` timeout with no hint of the cause; `BENCH_MESSAGES=0` made `perMessageMs` `0/0`, a `NaN` that `JSON.stringify` archives as `null` and so poisons the trend comparison; `BENCH_MODES=,` silently ran *zero* modes and archived an empty `results` array, leaving `bench:compare` with nothing to compare while still reporting OK; and `PORT=abc` produced `http://localhost:NaN/...` and a server that failed to listen, far from the actual mistake. The runtime body now lives in `main()` behind an `invokedDirectly` guard (the pattern the other release scripts already used), and `PORT` / `BENCH_MESSAGES` / `BENCH_MODES` are parsed by an exported, validated `parseBenchEnv` — an empty value still means "use the default", as before. Covered by `tests/bench-browser.test.ts`, and a source-hygiene guard now fails when a package.json entry point that a test imports runs at import time.
|
|
75
|
+
- The new API-reference guard broke CI while passing locally. `pnpm check` runs `typecheck && build && test`, so `tsc --noEmit` sees a fresh checkout with no `dist/` and rejected the guard's literal `import('../dist/index.js')` with TS2307 — the directory existed locally, so the failure was invisible until CI ran. The specifier is now non-literal (`` import(`../dist/${'index.js'}`) ``), the same pattern `tests/dual-format.test.ts` already used for exactly this reason, and a new source-hygiene guard rejects any literal dynamic `import()` of `../dist/` from a test file so the trap cannot be reintroduced silently.
|
|
76
|
+
- Each adapter entry (`/hooks`, `/vue`) exported the same four composable names, and the API reference documented the health composable for only *one* adapter per language: English described it under React and never mentioned that `/vue` exports it, while Chinese described it under Vue and never mentioned it under React — and the Chinese Vue heading carried the React name (`useCrossTabHealth`) over a body describing a Vue `Ref`. Both adapters are now documented in both languages, with the Vue binding named `useVueCrossTabHealth` to match the aliasing convention already used throughout that section. The h2/table-row/list-item parity guards could not see this, because both languages have the same shape — a new guard splits the reference on h2 and requires each entry's own section to document every export of that entry.
|
|
77
|
+
- The API reference (English and Chinese) did not document the whole public surface: four of the nineteen root exports — `DEFAULT_MAX_ACTIVE_WORKERS`, `approximatePayloadBytes`, `effectiveWorkerLoad`, and `getOrCreateTabId` — were absent from `docs/api.md`, and `CrossTabDataBus.publishBatch` had no entry at all (only the transport-side optional `publishBatch?` hook was described, while the `WorkerClusterRuntime` "Main methods" list also omitted it). All are documented now, including the non-obvious guarantees: `getOrCreateTabId` deliberately does not reuse the stored value when `window.opener` is present (a `window.open()` child inherits the opener's `sessionStorage`, so a blind reuse would give two live tabs one identity), and `effectiveWorkerLoad` always returns a finite score. A new guard derives the export list from the built entry point and fails the suite when a public export is undocumented in either language, so the reference can no longer drift behind the code.
|
|
78
|
+
- `effectiveWorkerLoad` could return a non-finite score, which made owner selection depend on the *order* of the worker array. A `windowMs` of `NaN` slipped past the `sample.windowMs <= 0` guard (because `NaN <= 0` is false), and a corrupt sample field (a `NaN` `messageCount`/`overrunMs`) or a non-finite weight divided through to `NaN`. `selectLeastLoadedWorker` compares `byLoad !== 0` — true for `NaN` — and `NaN < 0` is false, so a `NaN` worker won or lost purely by its index: the same worker set produced different owners depending on storage listing order. The score is now total (a non-finite window or result falls back to the raw topic count), pinned by an order-independence regression test.
|
|
79
|
+
- The cluster coordination options were entirely unvalidated, so `maxActiveWorkers`, `heartbeatIntervalMs`, `workerTtlMs`, `routeOwnerCacheMax`, and the `loadWeighting` weights all accepted values that silently broke routing: `heartbeatIntervalMs: 0`/`NaN` degenerated the heartbeat `setInterval` into a 0ms busy loop (the exact hazard the Centrifuge PING guard already exists for, and `Infinity` is rejected here because a Worker that never refreshes its heartbeat is pruned by its own TTL), a non-positive `workerTtlMs` pruned every peer on the first reconcile, and a non-positive `maxActiveWorkers`/`routeOwnerCacheMax` disabled ownership or caching outright. A new `assertClusterOptions` runs in the `WorkerClusterRuntime` constructor (so it covers `CrossTabDataBus` and the transport factories too), and the `loadWeighting` weights must be non-negative finite numbers — a negative weight inverted the documented policy by biasing new routes toward the *busiest* Worker.
|
|
80
|
+
- `scripts/verify-packed-consumer.mjs` polluted the checkout and leaked its scratch space on every run. It called a bare `npm pack`, so each run wrote `cross-tab-worker-databus-<version>.tgz` into the repository root — a stale archive per version, left behind on every push because CI runs the smoke each time (its sibling `verify-published-consumer.mjs` already used `--pack-destination`). Neither verifier removed its `mkdtempSync` root either, so every run also left ~3 MB of unpacked package in the OS temp directory (five had accumulated locally, ~16 MB). The pack now targets the temp root, and both scripts remove it in a `finally` (best-effort — a blocked delete warns rather than throwing, so it cannot turn a passing verification into a failed release gate).
|
|
81
|
+
- The same smoke verified only a hardcoded `['.', './hooks', './vue', './centrifuge']` subpath list, so the two Worker artifacts — `./centrifuge.worker` and `./centrifuge.shared.worker`, the entry points the built-in Worker factory resolves at runtime — were never checked inside the tarball at all. The sweep is now derived from the packed manifest (every declared target must exist, string and conditional entries alike), so a new entry point is covered on the commit that adds it. Verified end to end by pointing a worker export at a missing file and watching the smoke exit 1 with `missing export target ./dist/centrifuge.worker.broken.js for ./centrifuge.worker`.
|
|
82
|
+
- `scripts/bench-compare.mjs` did not validate `--fail-above-pct`: the value was `Number(...)`-coerced, so a typo produced `NaN`, and since every `pct > NaN` comparison is false the documented release gate **silently passed** for every metric (`[bench] OK: no metric regressed more than NaN%`). A missing, empty, non-numeric, or negative threshold now throws instead. The same pass made the "one report path" case an error rather than silently comparing the two most recent reports, and split the CLI into validated, exported `parseArgs` / `compareReports` / `findRegressions` helpers covered by `tests/bench-compare.test.ts`.
|
|
83
|
+
- `scripts/` was in the ESLint `ignores` list, so the release-critical tooling (the bench gate, the packed/published consumer verifiers, the demo servers) was never linted. It is now covered with Node globals and `no-console` allowed (browser globals scoped to `bench-browser.mjs`, whose Playwright callback really does run in the page). Enabling it surfaced two real findings, both fixed: a `preserve-caught-error` in `verify-version-compat.mjs` (the original error was discarded instead of attached as `cause`) and a misindented `console.log` in `serve-examples.mjs`. A guard fails if `scripts/**` returns to the ignore list.
|
|
84
|
+
- `scripts/bench-trend.mjs` stamped the generated trend doc with `new Date()`, so `docs/benchmarks.md` and its Chinese mirror changed every day even when no new benchmark report existed — a spurious diff that also implied fresh data. The stamp is now derived from the latest report's own `generatedAt` (falling back to the `browser-<ISO>.json` filename date), making regeneration a no-op when nothing new was archived. The generated header also claimed "prose is maintained by hand" while the script overwrites the entire file; corrected to say so. The renderer is now a pure exported `buildDocs(entries)`, covered by `tests/bench-trend.test.ts` (stamp source, byte-determinism, delta + all-time-best math, and rejection of an archive with fewer than two reports).
|
|
85
|
+
- `BatchingStorageWriter` carried its class JSDoc twice — the first block a truncated copy left over from an earlier edit — so only the second was attached to the class and the first was dead documentation. Removed, with a source-hygiene guard that fails when a JSDoc block is stacked directly on another whose body it prefixes (a file-level header followed by a member's own doc is not flagged).
|
|
86
|
+
- Documentation parity (English and Chinese): the English release checklist was missing the "Security and dependency scanning" section (CodeQL + Dependabot) that only the Chinese copy carried; the Chinese roadmap had lost its entire `0.11.0` delivered-scope section and its `0.13.0` candidates section had lost all four items; the Chinese configuration reference omitted the `recovery.cooldownMs` and `recovery.maxAttempts` rows; and the Chinese documentation index's demo link carried a doubled slash. Five new structural guards now compare every localized doc pair (h2 section count, markdown-table row count, list-item count), reject any empty section, and require every shipped doc to be enumerated in `package.json` — this drift class fails the suite instead of shipping silently.
|
|
87
|
+
- `dedup.adaptiveTtl` bounds validation accepted non-finite values: `NaN <= 0` and `maxMs < NaN` are both false, so `{ minMs: NaN }` (or an `Infinity` `maxMs`) passed construction and left `currentTtl()` returning `NaN`, silently disabling expiry instead of failing loudly. Bounds must now be finite positive numbers with `minMs <= maxMs`.
|
|
88
|
+
- Manual release dispatch derived the release identity from the wrong ref: on `workflow_dispatch`, `GITHUB_REF_NAME` is the selected **branch**, not the `tag` input, so the release job would create a GitHub release named `main` and compute the npm version from the branch (with notes extracted for `main`). The release workflow now resolves `${ inputs.tag || github.ref_name }` once into `RELEASE_TAG` and every step uses it. Guarded by a new `tests/workflows.test.ts`.
|
|
89
|
+
- The capabilities matrix (English and Chinese) had a broken table row: the "Optional ArrayBuffer Transferable transport" description was orphaned onto the next row, leaving a 3-cell row beside a 5-cell row so the whole matrix rendered with shifted columns. The description is back on its own row, and a new documentation guard now fails the suite whenever a markdown table mixes cell counts (splitting on unescaped pipes only, so `\|` inside a cell is not mistaken for a separator).
|
|
90
|
+
- CHANGELOG structure: the `[Unreleased]` section carried two `### Changed` headings, and the `[0.20.60]` version heading was an h1 (`#`) instead of h2 (`##`) — invisible to the Release workflow's `## [<version>]` notes match. Both fixed, with guards that fail on a repeated subheading within one version section and on any version heading not written as an h2.
|
|
91
|
+
- Release checklists (both languages) now include `pnpm test:coverage` in the Before-tagging run so the local dry run matches the CI gate set, and note that `verify:compat` needs `git fetch --tags` in a shallow clone (otherwise it fails with "no version tag found"). A documentation test now asserts each checklist documents the full gate set.
|
|
92
|
+
- Vue `useCrossTabDataBus` no longer leaks a bus when the component unmounts inside the async start window. `start()` awaits `stop()` before calling `create()`; an unmount landing in that window ran `stop()` without bumping the lifecycle generation, so the pending continuation still created a bus that nobody owned or stopped. `onBeforeUnmount` now supersedes the pending start. Pinned by a regression test (fails without the fix). The React adapter is unaffected — its `create()` is synchronous inside `useEffect`.
|
|
93
|
+
- Stranded-handoff recovery no longer lets concurrent survivors ping-pong route generations: only the deterministically elected owner performs the re-election (peers stand down and wait for its write), and projected loads spread multi-topic recoveries across survivors like the graceful handoff does. When the elected owner has no local subscription — so it would never reconcile the topic — the recovering peer writes the route and notifies it directly instead of standing down forever. Without this, divergent cross-tab views made survivors rewrite the same route every pass, dropping each other's confirmations and piling topics onto one worker. Covered by distribution and unsubscribed-owner regression tests; the multi-round soak pins convergence.
|
|
94
|
+
- Stranded unconfirmed handoffs no longer stall forever when the previous owner's `ROUTE_RELEASED` never arrives (dropped channel message under load, or a crash between the route write and the ACK): the reconcile loop re-elects a live owner once the previous owner is gone and the handoff has been stuck longer than a worker TTL (10 s default), rewriting the route with a fresh generation and clearing the handoff marker so the normal confirmation path completes. While the previous owner is still alive the new owner keeps waiting, and a fresh handoff is never mistaken for a stranded one (age gate), so the strict handoff keeps its no-overlap guarantee. Pinned by two regression tests (recovery after the TTL vs. continued waiting while the previous owner lives); architecture docs (EN+ZH) updated.
|
|
95
|
+
- Adaptive dedup TTL now takes effect on the hot path: the opportunistic per-message expiry used the fixed `ttlMs` while the sweep and `getStats()` used the adaptive window, so a burst that shrank the effective TTL toward `minMs` still retained IDs for the full fixed TTL where nearly all traffic flows. The hot path now expires against `currentTtl()`. Pinned by a direct `DedupManager` suite (`tests/dedup-manager.test.ts`: acceptance/suppression, TTL expiry, FIFO eviction, sweep lifecycle, adaptive shrink/relax/window-reset; fails without the fix).
|
|
96
|
+
|
|
97
|
+
### Changed
|
|
98
|
+
- The demo's config panel now shows the active chaos mode (`丢弃交接确认` / `模拟崩溃` / `未启用`): a tab that is dropping handoff ACKs or simulating a crash was previously visually indistinguishable from a healthy one. Both chaos gates are now read live at call time (previously `dropHandoffAck` was captured at bus creation), so a toggle takes effect immediately and the panel cannot show a mode that is not actually active. Both chaos E2E specs assert the row.
|
|
99
|
+
- The `Release` workflow now re-runs `pnpm lint` before publishing (a version tag can point at a commit that never passed CI's lint job), alongside the existing `verify:compat` / `verify:pack` release gates. Both release checklists document it, and a workflow guard asserts the step exists.
|
|
100
|
+
- `package.json`'s `files` list now enumerates every shipped doc explicitly, adding the localized `docs/zh/README.md` and `README.zh.md` (mirroring the already-listed `docs/README.md` / `README.md`). npm auto-includes `README*` / `LICENSE*` / `CHANGELOG*` regardless of `files`, so these two were shipped anyway — but a *non*-README doc left off the list would silently vanish from the tarball, so a guard now fails when a doc on disk is not published.
|
|
101
|
+
- Dependency upgrades: vitest 4 → 5 (with the matching coverage provider) and the scoped lint-config package 9 → 10 to match eslint 10. Vitest 5 rewrote the benchmarking API (`bench` is now a test-context fixture instead of a module-scope import), so all 25 benchmarks in `tests/bench/` were migrated to the `test(name, async ({ bench }) => { await bench(name, fn).run(); })` form; the suite reports identical hot-path numbers under the new runner.
|
|
102
|
+
- Dependency security: vitest upgraded 3.2 → 4.1.11 (with the matching coverage provider) to clear the GHSA-82fw-gwwq-j7x9 path-traversal advisory in the vitest mocker package; the audit is clean again and the full suite (typecheck, 460 unit, bench, e2e) is green on the new major. One test helper's mock-factory return type needed explicit callback typing under Vitest 4's tighter mock generics.
|
|
103
|
+
- Playwright E2E default timeout raised 60 s → 90 s so convergence waits (30–45 s) can legitimately stack with `HANDOFF_TIMEOUT_MS` (60 s) polls inside one test; the storage-event handoff test — the one suite whose 45 s convergence + 60 s handoff previously exceeded the old ceiling — now carries an explicit 120 s budget. This removes the documented flake class where a healthy but slow handoff poll died on the test-level timeout rather than its own.
|
|
104
|
+
- The demo's config panel shows the load-weighting toggle state (启用 消息/字节/滞后 vs 禁用 纯 Topic 数), and the routing benchmark suite gains a `weighted + lag` owner-selection baseline.
|
|
105
|
+
- The COORDINATION trace event is now emitted after each transport open (start and recovery) with the settled route list; previously it fired synchronously at the top of `start()` where subscription writes were still coalesced, so its `routes` field was always empty.
|
|
106
|
+
- The CI verify job now runs `pnpm audit` (dependency security gate) and the release checklists document the audit step.
|
|
107
|
+
- IndexedDB replay persistence coalesces concurrent `appendBatch` calls into a single read-modify-write transaction (regression: ten concurrent batches = one readwrite transaction) while preserving order against `clear`/`clearTopic`/`clearBefore`.
|
|
108
|
+
- The root export surface is pinned by a regression test, making pre-1.0 API additions/removals deliberate.
|
|
109
|
+
- Benchmarks: load-weighting scoring, `getMetrics` snapshot, and `publishBatch` batch-size sensitivity (10/50/100 per call) baselines.
|
|
110
|
+
- GitHub Actions bumped to current majors (checkout/setup-node/upload-artifact v4 → v7, pnpm/action-setup v4 → v6), dropping the Node 20 deprecation warning on the forced Node 24 action runtime.
|
|
111
|
+
- Browser E2E handoff tests (owner migration, multi-tab soak, BFCache) wait up to 60 s for a pagehide owner takeover, absorbing shared-runner scheduling jitter.
|
|
112
|
+
- The demo WebSocket hub attributes wire-frame counters per topic (`/debug/wsstats.topics`) so the single-frame `publishBatch` assertion is immune to concurrent tests on a parallel local run.
|
|
113
|
+
- Browser benchmark databus matrix now measures real dispatch work: auto-start defers `transport.start` to a microtask, so the emit/publish cases previously ran before the transport was live (messages dropped, publishes queued) and reported sub-millisecond no-ops. Cases now `await ready()` plus a bounded assignment poll and assert exact delivery counts; the `publishBatch` case uses an echo stub to measure the full route → publish → dispatch round-trip, and a `firstPacketMs` cold-dispatch baseline was added.
|
|
114
|
+
|
|
115
|
+
## [0.20.83] - 2026-09-05
|
|
116
|
+
|
|
117
|
+
### Added
|
|
118
|
+
- `useCrossTabHealth` edge-case coverage for both adapters (React and Vue): interval polling refreshes the health snapshot, detaching a stopped bus resets it to `null` and unsubscribes its status/error listeners, and unmounting never leaks the polling timer.
|
|
119
|
+
- Browser benchmarks now archive every run under `bench-results/` (gitignored), and a new `scripts/bench-compare.mjs` prints per-metric deltas between two runs so publish-throughput and hot-path regressions can be checked locally.
|
|
120
|
+
- The demo gains a "批量 10" button that publishes ten JSON items through `CrossTabDataBus.publishBatch` with per-item `messageId`s; the demo WebSocket hub counts `publish` vs `publishBatch` wire frames and exposes them via `/debug/wsstats`, and a new E2E asserts the burst travels as exactly one frame with no per-item publishes.
|
|
121
|
+
- Documented the `asyncSink: true` delivery semantics in the API reference (English and Chinese): microtask-batched FIFO delivery, unchanged error isolation, and the ordering boundary versus the synchronous sink.
|
|
122
|
+
- Added the `src/utils/` toolbox: shared string constants with literal-derived types, `publicationMetadata`, fault-tolerant storage primitives (`readJson`/`writeJson`/`listKeys`/`readAllByPrefix`), option-validation asserts, and Worker-boundary error serialization.
|
|
123
|
+
|
|
124
|
+
### Changed
|
|
125
|
+
- Every runtime string literal in the SDK is centralized in `utils/constants.ts`; status/role/action unions and cluster/worker/message/trace discriminants are derived from those constants so values and types cannot drift apart, and the repeated magic strings across source and tests were unified.
|
|
126
|
+
- Replay buffering and inbound deduplication moved out of `CrossTabDataBus` into self-contained `ReplayManager` and `DedupManager` classes with their own lifecycle; the DataBus delegates to them and deliberately keeps its lifecycle state machine in place (`data-bus.ts` shrank from ~1440 to ~1094 lines).
|
|
127
|
+
- The release checklist (English and Chinese) now documents the tagged-release workflow's blocking published-consumer verification (`verify:published`, 24×5 s retry budget) instead of treating it as a post-publication side note.
|
|
128
|
+
|
|
129
|
+
## [0.20.82] - 2026-09-05
|
|
130
|
+
|
|
131
|
+
### Added
|
|
132
|
+
- The default Playwright assertion ceiling rose to 20 s, covering every remaining poll on slow shared runners.
|
|
133
|
+
- Added non-cloneable-config coverage (symbols) with `cause` preservation for the Worker transport's structured-clone guard.
|
|
134
|
+
- Added a `/debug/connections` endpoint to the examples server and an E2E asserting that a closed shared-mode tab's WebSocket is dropped server-side.
|
|
135
|
+
|
|
136
|
+
## [0.20.81] - 2026-09-05
|
|
137
|
+
|
|
138
|
+
### Added
|
|
139
|
+
- `bench:browser` now also runs the data-bus hot-path matrix (wildcard dispatch, `publishBatch`, dedup, trace-and-publish) inside a real browser against the built ESM bundle, reported alongside the publish-throughput results for Node-vs-browser comparison.
|
|
140
|
+
- The E2E suite asserts the health summary end to end: the demo's health line reads healthy and `window.__bus.getHealthSummary()` reports `{ healthy: true, state: 'healthy' }` on a connected tab.
|
|
141
|
+
- Documented `createStorageEventChannel` in the API reference (English and Chinese) and added the transport-level `publishBatch` row to the capabilities matrix.
|
|
142
|
+
|
|
143
|
+
## [0.20.80] - 2026-09-05
|
|
144
|
+
|
|
145
|
+
### Added
|
|
146
|
+
- E2E failure artifacts now include Playwright traces (retain-on-failure) and first-retry videos; diagnostics retention extended to 14 days.
|
|
147
|
+
- Reload-style E2E tests converge the cluster (exactly one owner) before publishing, removing the standby re-subscription race behind intermittent CI failures.
|
|
148
|
+
- Documented the loss-and-recovery matrix for every coordination message in the architecture docs (English and Chinese).
|
|
149
|
+
|
|
150
|
+
### Changed
|
|
151
|
+
- The concurrent-burst E2E staggers publishes within each tab: hammering one transport with a same-tick burst can push it into a documented disconnect window where dropped publishes are expected.
|
|
152
|
+
|
|
153
|
+
## [0.20.79] - 2026-09-05
|
|
154
|
+
|
|
155
|
+
### Added
|
|
156
|
+
- The Release workflow now fails when the published-package consumer verification fails (retry budget raised to 24 × 5 s to cover registry propagation); releases without an npm token keep skipping the check.
|
|
157
|
+
- Added a regression pinning the full lost-handoff-ACK recovery chain: TTL cleanup of the orphaned route followed by ownership re-election when the original owner resumes.
|
|
158
|
+
|
|
159
|
+
## [0.20.78] - 2026-09-05
|
|
160
|
+
|
|
161
|
+
### Added
|
|
162
|
+
- The demo now shows the REAL transport backend (`getDiagnostics().transport.backend`) and the E2E suite asserts dedicated/shared tabs actually run on Worker backends — a silent fallback to the local session fails CI instead of passing unnoticed.
|
|
163
|
+
- Added a regression pinning the deferred channel close in `pause()` so queued handoff frames always flush.
|
|
164
|
+
- Documented the close-ordering invariant in the architecture docs and added health-summary/fallback quick-start sections to the getting-started guide (English and Chinese).
|
|
165
|
+
|
|
166
|
+
## [0.20.77] - 2026-09-05
|
|
167
|
+
|
|
168
|
+
### Fixed
|
|
169
|
+
- Worker-backend availability now honors runtime capability in addition to injected factories: without the fix, a browser consumer that did not pass an explicit `workerFactory`/`sharedWorkerFactory` silently degraded to the main-thread local session and never used the bundled Dedicated/Shared Workers.
|
|
170
|
+
|
|
171
|
+
### Added
|
|
172
|
+
- Added default-backend coverage for the bundled Dedicated/Shared Worker paths and the `uninitialized` diagnostics identity.
|
|
173
|
+
- Added storage-event channel loss-recovery regressions: a silently dropped delivery is recovered by the heartbeat reconcile loop.
|
|
174
|
+
- The demo now surfaces coordination-channel diagnostics and an opt-in `channelFallback: 'storage-event'` toggle.
|
|
175
|
+
|
|
176
|
+
## [0.20.76] - 2026-09-05
|
|
177
|
+
|
|
178
|
+
### Added
|
|
179
|
+
- Added an opt-in coordination channel fallback for environments without BroadcastChannel: `createBrowserEnvironment({ channelFallback: 'storage-event' })` installs a localStorage storage-event `ClusterChannel` (exported as `createStorageEventChannel`), preserving cross-tab owner coordination instead of degrading to local mode. Coordination payloads persist to localStorage under a dedicated key namespace — a documented security trade-off, which is why the fallback is opt-in.
|
|
180
|
+
|
|
181
|
+
## [0.20.75] - 2026-09-05
|
|
182
|
+
|
|
183
|
+
### Added
|
|
184
|
+
- Added hot-path performance gates to the unit suite: generous-ceiling assertions over wildcard/exact topic matching, opaque-key hashing, and worker selection loops, so catastrophic hot-path regressions fail CI while real benchmarking stays in `pnpm bench`.
|
|
185
|
+
- Extended IndexedDB replay persistence tests with scripted fault injection (transaction-construction failure and failing store requests), covering the adapter's invalidate-and-recover error paths.
|
|
186
|
+
|
|
187
|
+
## [0.20.74] - 2026-09-05
|
|
188
|
+
|
|
189
|
+
### Added
|
|
190
|
+
- Added an optional `publishBatch` to the `DataBusTransport` contract: transports that can pack many items into one wire frame (the bundled WebSocket transport sends a single `publishBatch` frame) are used for burst publications, with automatic per-item `publish` fallback for transports without batch support.
|
|
191
|
+
- Added `useCrossTabHealth` to the React and Vue adapters, mirroring `getHealthSummary()` with interval polling (default 1 s, `intervalMs: 0` for event-driven only) plus event-driven refreshes on status changes and errors.
|
|
192
|
+
- The demo WebSocket server accepts `publishBatch` frames and re-fans them out as individual timestamped publications.
|
|
193
|
+
|
|
194
|
+
### Fixed
|
|
195
|
+
- `getHealthSummary()` now accounts for the live transport status, so a transport reporting `error`/`disconnected` no longer reads as `healthy` while the `transportReady` flag has not yet been cleared.
|
|
196
|
+
|
|
197
|
+
## [0.20.73] - 2026-09-05
|
|
198
|
+
|
|
199
|
+
### Added
|
|
200
|
+
- Added unit-test coverage for the IndexedDB replay persistence adapter via `fake-indexeddb`: round-trip, count/age/both pruning, `appendBatch` grouping, mutation-queue serialization of concurrent same-topic appends, `clear`/`clearTopic`/`clearBefore` semantics, and recovery after a transient open failure.
|
|
201
|
+
- Added real-browser E2E coverage for concurrent multi-publisher bursts across three tabs and for a full connection re-apply (bus stop/start) that rejoins the cluster without duplicate ownership.
|
|
202
|
+
- Added a "Stability Invariants" section to the architecture docs (English and Chinese) consolidating the handoff, cleanup-ordering, recovery-budget, and BFCache guarantees pinned by the regression suite.
|
|
203
|
+
|
|
204
|
+
## [0.20.72] - 2026-09-05
|
|
205
|
+
|
|
206
|
+
### Added
|
|
207
|
+
- Added advanced hot-path benchmarks for `publishBatch`, wildcard routing, deduplication, replay pruning, `appendBatch` persistence, and asynchronous trace sinks.
|
|
208
|
+
- Added `CrossTabDataBus.getHealthSummary()` with a single-object readiness verdict (`healthy`, lifecycle-derived `state`, unified `lastFailure` ledger, transport identity, and recovery context) for dashboards and readiness probes.
|
|
209
|
+
- Added `getPersistenceStats()` and surfaced persistence failure counters in `getDiagnostics()`; transport diagnostics now include the live connection `status` and `suspended` flag.
|
|
210
|
+
- Added long-session stability regression coverage for owner-handoff ACK validation, repeated BFCache round-trips, recovery exhaustion reset, storage write backoff recovery, and replay persistence cleanup races.
|
|
211
|
+
|
|
212
|
+
### Fixed
|
|
213
|
+
- Stale `ROUTE_RELEASED` ACKs whose generation is older than the current route record are now rejected; the comparison was inverted, so a replayed ACK from an earlier handoff round could confirm a newer handoff.
|
|
214
|
+
- Replay persistence cleanup no longer resurrects cleared history: a queued batch flush is filtered for topics cleared via `unsubscribe`/`clearReplayTopic` and for entries pruned via `clearReplayBefore`.
|
|
215
|
+
- Diagnostics report the released SDK version, injected from `package.json` at build time, instead of a stale hardcoded constant.
|
|
2
216
|
|
|
3
217
|
## [0.20.71] - 2026-09-05
|
|
4
218
|
|
|
@@ -69,7 +283,7 @@
|
|
|
69
283
|
### Changed
|
|
70
284
|
- `WorkerClusterRuntime.onEvent` handler signature now includes a fourth `originTabId?: string` argument; existing call sites use `toMatchObject` so the extra argument does not break strict equality.
|
|
71
285
|
|
|
72
|
-
|
|
286
|
+
## [0.20.60] - 2026-09-04
|
|
73
287
|
|
|
74
288
|
### Added
|
|
75
289
|
- `publishBatch(topic, items)` on both `CrossTabDataBus` and `WorkerClusterRuntime` packs multiple items into a single BroadcastChannel postMessage so the receiving owner can dispatch them in one tick instead of one channel post per item.
|
package/README.md
CHANGED
|
@@ -20,6 +20,9 @@ By default each tab holds its own Dedicated Worker; when configured with `worker
|
|
|
20
20
|
- localStorage coordination writes are merged and flushed in batches; heartbeat and route confirmation use exponential backoff
|
|
21
21
|
- Existing Topic owners remain stable while alive; visibility changes do not move established subscriptions
|
|
22
22
|
- New Topics are assigned to the least-loaded eligible Worker
|
|
23
|
+
- Automatic owner recovery after an ungraceful exit: a lost handoff ACK or a crashed owner recovers through a TTL-gated re-election (bounded by `heartbeatIntervalMs + workerTtlMs`), and each route acknowledgment / migration / recovery is observable as a bounded `reliability` trace event
|
|
24
|
+
- Opt-in adaptive owner weighting (`loadWeighting`): traffic message/byte rates and heartbeat scheduling-lag steer NEW routes toward quieter, healthier Workers; existing routes stay sticky and the default remains pure topic-count routing ([configuration](./docs/configuration.md#adaptive-owner-weighting))
|
|
25
|
+
- Async credential refresh bridge for the Centrifuge worker (`credentialProvider`): the Worker requests each fresh `getToken`/`getChannelToken` from the main thread over a TOKEN_REQUEST/RESPONSE exchange, keeping function-valued options out of the structured-clone boundary
|
|
23
26
|
- Wildcard subscriptions: `chat.*` and `*` patterns match concrete topics at dispatch
|
|
24
27
|
- Transport-neutral publication metadata (`messageId`, `timestamp`) with canonical WebSocket/Centrifuge envelopes and legacy frame compatibility
|
|
25
28
|
- Optional durable replay retention (`replay.retentionMs`) and deduplication outcome metrics in trace snapshots
|
|
@@ -28,6 +31,11 @@ By default each tab holds its own Dedicated Worker; when configured with `worker
|
|
|
28
31
|
- Optional Vue 3 composables adapter (`cross-tab-worker-databus/vue`): lifecycle-safe bus, subscription, and status composables
|
|
29
32
|
- `pagehide` releases resources automatically; `pageshow` rebuilds the Worker and connection automatically
|
|
30
33
|
- Transport reconnect automatically restores the current owner's Topics
|
|
34
|
+
- Optional durable replay persistence with `appendBatch` bulk writes (IndexedDB transaction coalescing; prune strategies `count` / `age` / `both`)
|
|
35
|
+
- Transport-level batch publishing: `publishBatch` packs bursts into one wire frame where the backend supports it
|
|
36
|
+
- Single-object health verdict via `getHealthSummary()` with a unified failure ledger and recovery context
|
|
37
|
+
- Synchronous diagnostics: `getMetrics()` snapshots the current trace window (throughput, dispatch latency percentiles, dedup outcomes), `getDiagnostics().metrics/trace` ride in the diagnostics object, and `getDiagnostics().replay` reports the buffered replay footprint (`messages` + approximate `bytes`)
|
|
38
|
+
- Opt-in coordination fallback over localStorage storage events when BroadcastChannel is unavailable
|
|
31
39
|
- After a tab exits abnormally, automatic migration happens via heartbeat TTL
|
|
32
40
|
- Automatically degrades to local mode when BroadcastChannel or localStorage is unavailable
|
|
33
41
|
- The persistence layer does not store connection addresses, raw Topic text, or message content
|
package/README.zh.md
CHANGED
|
@@ -20,9 +20,13 @@
|
|
|
20
20
|
- localStorage 协调写入合并批量 flush;心跳和路由确认使用指数退避
|
|
21
21
|
- 已有 Topic 的 owner 存活时保持稳定,前后台切换不迁移已有订阅
|
|
22
22
|
- 新 Topic 分配给负载最低的候选 Worker
|
|
23
|
+
- 非正常退出后的自动 owner 恢复:交接 ACK 丢失或 owner 崩溃后由 TTL 门禁的重新选举接管(上界为 `heartbeatIntervalMs + workerTtlMs`),每次路由确认 / 迁移 / 恢复都会发出有界的 `reliability` trace 事件
|
|
24
|
+
- 可选自适应 owner 加权(`loadWeighting`):流量消息/字节速率与心跳调度滞后引导新 route 偏向更空闲、更健康的 Worker;已有 route 保持 sticky,默认仍为纯 Topic 数路由
|
|
25
|
+
- 面向 Centrifuge Worker 的异步凭证刷新桥(`credentialProvider`):Worker 通过 TOKEN_REQUEST/RESPONSE 交换向主线程请求每个新 `getToken` / `getChannelToken`,函数型选项不跨 structured-clone 边界
|
|
23
26
|
- 通配符订阅:`chat.*` 与 `*` pattern 在分发侧匹配具体 Topic
|
|
24
27
|
- 传输无关的 publication 元数据(`messageId`、`timestamp`),支持标准 WebSocket/Centrifuge envelope,并兼容旧帧格式
|
|
25
28
|
- 可选的 durable replay retention(`replay.retentionMs`),以及 trace snapshot 中的去重结果指标
|
|
29
|
+
- 同步诊断:`getMetrics()` 快照当前 trace 窗口(吞吐、分发延迟百分位、去重结果),`getDiagnostics().metrics/trace` 随诊断对象输出,`getDiagnostics().replay` 报告缓冲回放占用(`messages` + 近似 `bytes`)
|
|
26
30
|
- 内置零依赖的原生 WebSocket 传输(`createWebSocketDataBus`),适配普通 WebSocket 服务器
|
|
27
31
|
- 可选的 React hooks 适配层(`cross-tab-worker-databus/hooks`):StrictMode 安全的 bus 生命周期与自动清理订阅
|
|
28
32
|
- 可选的 Vue 3 composables 适配层(`cross-tab-worker-databus/vue`):安全管理 bus 生命周期、订阅和状态
|
|
@@ -5,9 +5,16 @@
|
|
|
5
5
|
* (dedicated or shared) that runs a centrifuge client. The worker is isolated
|
|
6
6
|
* from the main thread so that WebSocket lifecycle, token refresh, and binary
|
|
7
7
|
* data handling never block the UI.
|
|
8
|
+
*
|
|
9
|
+
* The message `type` discriminant values are derived from the constants in
|
|
10
|
+
* `utils/constants.ts` so main-thread and Worker sides reference the same
|
|
11
|
+
* values and can never drift apart.
|
|
8
12
|
*/
|
|
9
13
|
import type { Options } from 'centrifuge';
|
|
14
|
+
import type { CENTRIFUGE_INPUT_TYPE, CENTRIFUGE_OUTPUT_TYPE } from './utils/constants';
|
|
10
15
|
import type { WorkerStatus } from './core/types';
|
|
16
|
+
import type { SerializedWorkerError } from './utils/error-utils';
|
|
17
|
+
export type { SerializedWorkerError } from './utils/error-utils';
|
|
11
18
|
/**
|
|
12
19
|
* Default interval between main-thread PING heartbeats to a SharedWorker. The
|
|
13
20
|
* SharedWorker reaps a silent port after `SESSION_TIMEOUT_MULTIPLIER` intervals.
|
|
@@ -28,32 +35,52 @@ type WorkerUnsafeOption = 'eventsource' | 'fetch' | 'getData' | 'getToken' | 'ne
|
|
|
28
35
|
export type CentrifugeWorkerConfig = Omit<Partial<Options>, WorkerUnsafeOption> & {
|
|
29
36
|
[Key in WorkerUnsafeOption]?: never;
|
|
30
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* Async credential provider run on the main thread. Centrifuge client options
|
|
40
|
+
* are structured-cloned into the Worker, so function-valued `getToken` /
|
|
41
|
+
* `getChannelToken` cannot travel with the config. When a provider is set, the
|
|
42
|
+
* Worker asks the main thread for each fresh token over a TOKEN_REQUEST /
|
|
43
|
+
* TOKEN_RESPONSE exchange and this provider supplies it from application
|
|
44
|
+
* context (async token endpoints, SecureStorage, etc.).
|
|
45
|
+
*/
|
|
46
|
+
export interface CentrifugeCredentialProvider {
|
|
47
|
+
/** Return a fresh top-level connection token. Called on connect and refresh. */
|
|
48
|
+
getToken: () => MaybePromise<string | null | undefined>;
|
|
49
|
+
/** Return a channel token for a channel. Centrifuge falls back to `getToken`
|
|
50
|
+
* when channel tokens are not configured by the server. */
|
|
51
|
+
getChannelToken?: (channel: string) => MaybePromise<string | null | undefined>;
|
|
52
|
+
}
|
|
53
|
+
/** A value or a promise of a value. */
|
|
54
|
+
export type MaybePromise<T> = T | Promise<T>;
|
|
31
55
|
/** Messages sent from the main thread to the Worker. All variants are
|
|
32
56
|
* structured-cloneable; `PUBLISH_BIN` carries an ArrayBuffer (transferable). */
|
|
33
57
|
export type CentrifugeWorkerInput =
|
|
34
58
|
/** Initial connection: URL + config. Sent once per backend creation.
|
|
35
59
|
* `transferable` enables ArrayBuffer zero-copy for subsequent PUBLISH_BIN.
|
|
36
|
-
* `heartbeatIntervalMs` overrides the SharedWorker PING cadence.
|
|
60
|
+
* `heartbeatIntervalMs` overrides the SharedWorker PING cadence.
|
|
61
|
+
* `tokenBridge` asks the Worker to route getToken/getChannelToken back to
|
|
62
|
+
* the main thread instead of holding function-valued config. */
|
|
37
63
|
{
|
|
38
|
-
type:
|
|
64
|
+
type: typeof CENTRIFUGE_INPUT_TYPE.INIT;
|
|
39
65
|
url: string;
|
|
40
66
|
config: CentrifugeWorkerConfig;
|
|
41
67
|
transferable?: boolean;
|
|
42
68
|
heartbeatIntervalMs?: number;
|
|
69
|
+
tokenBridge?: boolean;
|
|
43
70
|
}
|
|
44
71
|
/** Subscribe to a channel. Idempotent — re-subscribing is a no-op. */
|
|
45
72
|
| {
|
|
46
|
-
type:
|
|
73
|
+
type: typeof CENTRIFUGE_INPUT_TYPE.SUBSCRIBE;
|
|
47
74
|
topic: string;
|
|
48
75
|
}
|
|
49
76
|
/** Unsubscribe from a channel. Idempotent. */
|
|
50
77
|
| {
|
|
51
|
-
type:
|
|
78
|
+
type: typeof CENTRIFUGE_INPUT_TYPE.UNSUBSCRIBE;
|
|
52
79
|
topic: string;
|
|
53
80
|
}
|
|
54
81
|
/** Publish a structured-cloneable payload to a channel. */
|
|
55
82
|
| {
|
|
56
|
-
type:
|
|
83
|
+
type: typeof CENTRIFUGE_INPUT_TYPE.PUBLISH;
|
|
57
84
|
topic: string;
|
|
58
85
|
data: unknown;
|
|
59
86
|
messageId?: string;
|
|
@@ -61,7 +88,7 @@ export type CentrifugeWorkerInput =
|
|
|
61
88
|
}
|
|
62
89
|
/** Publish an ArrayBuffer via Transferable (zero-copy when `transferable` is on). */
|
|
63
90
|
| {
|
|
64
|
-
type:
|
|
91
|
+
type: typeof CENTRIFUGE_INPUT_TYPE.PUBLISH_BIN;
|
|
65
92
|
topic: string;
|
|
66
93
|
data: ArrayBuffer;
|
|
67
94
|
messageId?: string;
|
|
@@ -69,23 +96,35 @@ export type CentrifugeWorkerInput =
|
|
|
69
96
|
}
|
|
70
97
|
/** Heartbeat from the main thread; the SharedWorker reaps silent ports. */
|
|
71
98
|
| {
|
|
72
|
-
type:
|
|
99
|
+
type: typeof CENTRIFUGE_INPUT_TYPE.PING;
|
|
73
100
|
}
|
|
74
101
|
/** Disconnect the client and clear all subscriptions. */
|
|
75
102
|
| {
|
|
76
|
-
type:
|
|
103
|
+
type: typeof CENTRIFUGE_INPUT_TYPE.STOP;
|
|
104
|
+
}
|
|
105
|
+
/** Fresh credential for an earlier TOKEN_REQUEST, resolved by requestId. */
|
|
106
|
+
| {
|
|
107
|
+
type: typeof CENTRIFUGE_INPUT_TYPE.TOKEN_RESPONSE;
|
|
108
|
+
requestId: number;
|
|
109
|
+
token: string;
|
|
110
|
+
}
|
|
111
|
+
/** A credential request failed (provider rejected or returned no token). */
|
|
112
|
+
| {
|
|
113
|
+
type: typeof CENTRIFUGE_INPUT_TYPE.TOKEN_ERROR;
|
|
114
|
+
requestId: number;
|
|
115
|
+
error: SerializedWorkerError;
|
|
77
116
|
};
|
|
78
117
|
/** Messages sent from the Worker back to the main thread. The main thread
|
|
79
118
|
* routes these to the DataBusTransportHandlers via handleOutput(). */
|
|
80
119
|
export type CentrifugeWorkerOutput<TData = unknown> =
|
|
81
120
|
/** Connection status changed. Maps Centrifuge states to the DataBus vocabulary. */
|
|
82
121
|
{
|
|
83
|
-
type:
|
|
122
|
+
type: typeof CENTRIFUGE_OUTPUT_TYPE.STATUS;
|
|
84
123
|
status: WorkerStatus;
|
|
85
124
|
}
|
|
86
125
|
/** A JSON publication arrived. Routed to onMessage via handleOutput. */
|
|
87
126
|
| {
|
|
88
|
-
type:
|
|
127
|
+
type: typeof CENTRIFUGE_OUTPUT_TYPE.MESSAGE;
|
|
89
128
|
topic: string;
|
|
90
129
|
data: TData;
|
|
91
130
|
messageId?: string;
|
|
@@ -93,7 +132,7 @@ export type CentrifugeWorkerOutput<TData = unknown> =
|
|
|
93
132
|
}
|
|
94
133
|
/** A binary publication arrived (Transferable). Routed to onMessage with the ArrayBuffer. */
|
|
95
134
|
| {
|
|
96
|
-
type:
|
|
135
|
+
type: typeof CENTRIFUGE_OUTPUT_TYPE.MESSAGE_BIN;
|
|
97
136
|
topic: string;
|
|
98
137
|
data: ArrayBuffer;
|
|
99
138
|
messageId?: string;
|
|
@@ -101,21 +140,15 @@ export type CentrifugeWorkerOutput<TData = unknown> =
|
|
|
101
140
|
}
|
|
102
141
|
/** A non-fatal error occurred. Does not imply disconnection (the client retries internally). */
|
|
103
142
|
| {
|
|
104
|
-
type:
|
|
143
|
+
type: typeof CENTRIFUGE_OUTPUT_TYPE.ERROR;
|
|
105
144
|
error: SerializedWorkerError;
|
|
106
|
-
};
|
|
107
|
-
/** Error object serialized for cross-thread transfer. Error instances cannot
|
|
108
|
-
* be structured-cloned via postMessage, so the Worker converts them to this
|
|
109
|
-
* shape and the main thread rebuilds an Error via deserializeWorkerError(). */
|
|
110
|
-
export interface SerializedWorkerError {
|
|
111
|
-
/** The Error's `name` (e.g. 'TypeError', 'CentrifugeError'). */
|
|
112
|
-
name: string;
|
|
113
|
-
/** The Error's `message`. */
|
|
114
|
-
message: string;
|
|
115
|
-
/** The Error's `stack` if available (for debugging). */
|
|
116
|
-
stack?: string;
|
|
117
|
-
/** Arbitrary context attached by the Worker (e.g. the failing operation). */
|
|
118
|
-
context?: unknown;
|
|
119
145
|
}
|
|
120
|
-
|
|
146
|
+
/** The Worker needs a fresh credential. The main thread resolves it via a
|
|
147
|
+
* matching TOKEN_RESPONSE / TOKEN_ERROR carrying the same `requestId`. */
|
|
148
|
+
| {
|
|
149
|
+
type: typeof CENTRIFUGE_OUTPUT_TYPE.TOKEN_REQUEST;
|
|
150
|
+
requestId: number;
|
|
151
|
+
kind: 'token' | 'channelToken';
|
|
152
|
+
channel?: string;
|
|
153
|
+
};
|
|
121
154
|
//# sourceMappingURL=centrifuge-protocol.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"centrifuge-protocol.d.ts","sourceRoot":"","sources":["../src/centrifuge-protocol.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"centrifuge-protocol.d.ts","sourceRoot":"","sources":["../src/centrifuge-protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,9 @@ export declare class CentrifugeSession<TData = unknown> {
|
|
|
12
12
|
private client;
|
|
13
13
|
private readonly subscriptions;
|
|
14
14
|
private transferable;
|
|
15
|
+
private tokenBridge;
|
|
16
|
+
private nextRequestId;
|
|
17
|
+
private readonly pendingTokenRequests;
|
|
15
18
|
constructor(sink: CentrifugeSessionSink<TData>);
|
|
16
19
|
/** Dispatch an incoming Worker message to the matching operation.
|
|
17
20
|
* Unknown message types are ignored rather than thrown, so a future protocol
|
|
@@ -37,6 +40,12 @@ export declare class CentrifugeSession<TData = unknown> {
|
|
|
37
40
|
private postPublication;
|
|
38
41
|
/** Disconnect the client and clear all subscriptions. */
|
|
39
42
|
private stop;
|
|
43
|
+
/** Issue a credential request to the main thread and await the response.
|
|
44
|
+
* Used as Centrifuge's `getToken` / `getChannelToken` when token bridging is
|
|
45
|
+
* enabled; resolved or rejected by a matching TOKEN_RESPONSE / TOKEN_ERROR. */
|
|
46
|
+
private requestToken;
|
|
47
|
+
private resolveToken;
|
|
48
|
+
private rejectToken;
|
|
40
49
|
/** Forward a message to the sink (the transport layer). */
|
|
41
50
|
private post;
|
|
42
51
|
/** Serialise and report an error. The Centrifuge client handles reconnection
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"centrifuge-session.d.ts","sourceRoot":"","sources":["../src/centrifuge-session.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAEV,qBAAqB,EACrB,sBAAsB,
|
|
1
|
+
{"version":3,"file":"centrifuge-session.d.ts","sourceRoot":"","sources":["../src/centrifuge-session.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAEV,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,uBAAuB,CAAC;AAK/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;AAgBD;;;GAGG;AACH,qBAAa,iBAAiB,CAAC,KAAK,GAAG,OAAO;IAQhC,OAAO,CAAC,QAAQ,CAAC,IAAI;IAPjC,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmC;IACjE,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAA0C;gBAElD,IAAI,EAAE,qBAAqB,CAAC,KAAK,CAAC;IAE/D;;uEAEmE;IACnE,MAAM,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI;IA+B5C,8EAA8E;IAC9E,OAAO,CAAC,UAAU;IAkClB;;;mFAG+E;IAC/E,OAAO,CAAC,SAAS;IA2BjB;;gFAE4E;IAC5E,OAAO,CAAC,WAAW;IAUnB,mDAAmD;IACnD,OAAO,CAAC,OAAO;IAgBf;;;sEAGkE;IAClE,OAAO,CAAC,eAAe;IAgBvB,yDAAyD;IACzD,OAAO,CAAC,IAAI;IAaZ;;mFAE+E;IAC/E,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,WAAW;IAOnB,2DAA2D;IAC3D,OAAO,CAAC,IAAI;IAIZ;;;;0DAIsD;IACtD,OAAO,CAAC,SAAS;CAGlB"}
|
package/dist/centrifuge.d.ts
CHANGED
|
@@ -14,9 +14,9 @@ import { CrossTabDataBus } from './core/data-bus';
|
|
|
14
14
|
import type { CrossTabDataBusOptions } from './core/data-bus';
|
|
15
15
|
import type { DataBusTransport, DataBusTransportHandlers } from './core/types';
|
|
16
16
|
import type { WorkerMode } from './worker-mode';
|
|
17
|
-
import type { CentrifugeWorkerConfig } from './centrifuge-protocol';
|
|
17
|
+
import type { CentrifugeCredentialProvider, CentrifugeWorkerConfig } from './centrifuge-protocol';
|
|
18
18
|
import type { DataBusPublishOptions } from './core/types';
|
|
19
|
-
export type { CentrifugeWorkerConfig, SerializedWorkerError } from './centrifuge-protocol';
|
|
19
|
+
export type { CentrifugeCredentialProvider, CentrifugeWorkerConfig, SerializedWorkerError } from './centrifuge-protocol';
|
|
20
20
|
export type { WorkerBackend, WorkerMode } from './worker-mode';
|
|
21
21
|
/** WebSocket connection parameters passed to the Centrifuge Worker. */
|
|
22
22
|
export interface CentrifugeDataBusConfig {
|
|
@@ -40,6 +40,11 @@ export interface CentrifugeWorkerTransportOptions {
|
|
|
40
40
|
* × this interval. Pass `Infinity` to disable heartbeats entirely. Defaults
|
|
41
41
|
* to `DEFAULT_HEARTBEAT_INTERVAL_MS`. */
|
|
42
42
|
heartbeatIntervalMs?: number;
|
|
43
|
+
/** Async credential provider. Runs on the main thread: the Worker cannot
|
|
44
|
+
* hold function-valued Centrifuge options (they are not structured-cloneable),
|
|
45
|
+
* so when a provider is set the Worker asks for each fresh token over a
|
|
46
|
+
* TOKEN_REQUEST / TOKEN_RESPONSE bridge and this provider supplies it. */
|
|
47
|
+
credentialProvider?: CentrifugeCredentialProvider;
|
|
43
48
|
}
|
|
44
49
|
/** Options for creating a fully-configured CrossTabDataBus with a Centrifuge transport. */
|
|
45
50
|
export interface CreateCentrifugeDataBusOptions<TData = unknown> extends Omit<CrossTabDataBusOptions<CentrifugeDataBusConfig, TData>, 'autoStart' | 'clusterKey' | 'initialConfig' | 'transport'>, CentrifugeWorkerTransportOptions {
|
|
@@ -63,6 +68,7 @@ export declare class CentrifugeWorkerTransport<TData = unknown> implements DataB
|
|
|
63
68
|
private readonly heartbeatIntervalMs;
|
|
64
69
|
private readonly workerFactory;
|
|
65
70
|
private readonly sharedWorkerFactory;
|
|
71
|
+
private readonly credentialProvider;
|
|
66
72
|
private backend;
|
|
67
73
|
private worker;
|
|
68
74
|
private sharedWorker;
|
|
@@ -107,6 +113,10 @@ export declare class CentrifugeWorkerTransport<TData = unknown> implements DataB
|
|
|
107
113
|
* Shared by the Worker message listener, the SharedWorker port listener,
|
|
108
114
|
* and the local-session sink — all three feed into this single dispatcher. */
|
|
109
115
|
private handleOutput;
|
|
116
|
+
/** Resolve a Worker credential request from the main thread: call the
|
|
117
|
+
* credentialProvider, then post the fresh token (or a serialized failure)
|
|
118
|
+
* back to the session that issued the request. */
|
|
119
|
+
private resolveTokenRequest;
|
|
110
120
|
/** Handle a Worker-level failure (crash, message decode error). Discards the
|
|
111
121
|
* dead backend so a later start()/reopen can rebuild from scratch, and
|
|
112
122
|
* signals an error status so the DataBus can trigger recovery.
|
package/dist/centrifuge.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
1
|
+
{"version":3,"file":"centrifuge.d.ts","sourceRoot":"","sources":["../src/centrifuge.ts"],"names":[],"mappings":"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"}
|