cross-tab-worker-databus 0.21.35 → 0.21.36

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 CHANGED
@@ -1,3 +1,24 @@
1
+ ## [0.21.36] - 2026-09-25
2
+
3
+ Two shipped comments said things the code contradicts — one counted three listeners where the repository registers one handler twice, and the other quoted a test total that had already gone stale twice *while telling the reader how to re-derive it*. Fourteen numeric claims across `src/` were counted against the tree to find them, and thirteen surfaces of prose and gates were corrected or given a check on the way. No library behavior, export, option or storage key changed.
4
+
5
+ ### Fixed
6
+
7
+ - **A declaration comment on a `private` member counted three listeners where there are two call sites.** `src/centrifuge.ts`'s `handleOutput` doc read "Shared by the Worker message listener, the SharedWorker port listener, and the local-session sink — all three feed into this single dispatcher". Measured: dedicated Worker and SharedWorker `MessagePort` are attached to the *same* `handleMessage` function (`addEventListener('message', …)` fires twice for one handler), so three sources reach it through two call sites — the sentence was right about feeds and wrong about functions, and a reader grepping for the third listener lands back on the first and concludes code is missing. It now carries both counts and the reason. This ships wider than it looks: `tsc` emits a `private` member and keeps its JSDoc, so the text was found in five artifacts — `dist/centrifuge.d.ts`, `dist/centrifuge.js`, both CJS bundles and their maps — which is why `AGENTS.md` now says plainly that `private` is not an exemption from the shipped-prose surface.
8
+ - **A count beside its own lookup instruction was deleted rather than refreshed.** `src/core/trace.ts`'s `sink`-default note said "the same deletion re-run here: all 944 tests pass"; the suite is 954. That figure had replaced "884" two releases earlier, in the very sentence that added "take the denominator from `pnpm test`" — so the site held the re-derive recipe and a stale product of it at once, which is the form least likely to be checked, because the next reader is the person who just edited the number. The suite total is not a property of the claim (which is "2 events → 2 `TypeError`s"), so the value is gone and only the lookup remains. The same sweep counted fourteen numeric claims against the tree and twelve held exactly — `confirmRoute()`'s three call sites, `assignedTopics`' three writers, `pendingStop`'s four writes and two non-null writes, `transportReady = false`'s five sites, `startPromise`'s four installs of ten assignments, `percentileMs`' six calls, the reaper's three statements in one `try`, `validation.ts`' twenty-five-value vector enumerated in the sentence that counts it, and `centrifuge@5.7.4`'s twelve `emit('error')` sites inside `BaseSubscription` at lines 667–2673 against 23 in the file, where the range qualifier is what makes the claim true. The dated copies of the corrected figure in the roadmap and progress logs stay: they record what a mutant did at their own release.
9
+
10
+ ### Documentation
11
+
12
+ - **`CONTRIBUTING.md` had never been read against the repository, and four of its claims were contradicted by it.** Coverage thresholds were stated as 85/80/90/85 against `vitest.config.ts`'s 98/96/98/99 — understating a blocking gate is the direction that manufactures a contributor who is green locally and red in CI, so the line now names the config as the source of truth; `pnpm check` was glossed as "typecheck + build + 221 unit tests" and omitted that it runs the perf gates and *not* lint; the release section told contributors to run `npm version` and then `git push origin main --tags`, i.e. to tag the local HEAD and push to the protected branch directly, which are the two operations this repository prohibits, and it now reads fetch → fast-forward → tag the merged commit → push that one tag; and `[Unreleased]` was prescribed in four places (two in this file, the PR template, and both roadmap languages) although the convention is abandoned — `## [Unreleased]` appears zero times in `CHANGELOG.md` and the last commit that touched the marker is `0794966`, at the `0.21.17` prep. All four copies now describe the versioned section and point at `docs/release-checklist.md` as authoritative.
13
+ - **The publish-lag recipe named a parameter it never said how to choose.** Three surfaces told a reader to re-derive the ack→record gap from a release run's jobs endpoint with the attempt left blank, and one glossed it as "attempt 1 is the failed one" — true of exactly one tag in the series. The plain endpoint returns the *latest* attempt, so on the one re-run release the obvious short form answers a different question: -177.0 s and a 1.0 s consumer step, against the 310.0 s gap and 364 s failure that attempt 1 holds. Both checklists (en + zh) and the release workflow comment now state where to read the attempt number from, and the same sweep widened the series from nine releases to every tag the repository has cut: 44 non-negative gaps with a maximum of 310.0 s, so the 720 s registry-propagation budget is untouched, while "the registry records minutes after the ack" is scoped to the `0.20.92`-onward window because five older releases recorded within ±0.6 s and three of them *before* the ack returned.
14
+ - **The documentation index described a generator more loosely than the generator describes itself.** `docs/README.md` said the benchmark trend is built "from `bench-results/`" where `scripts/bench-trend.mjs` reads only `browser-*.json`; both languages now name the subset. The other ten index rows were read against their targets and hold — including the capabilities row, whose three tiers appear in no heading at all and only in the status legend plus table cells, which is why the check had to read bodies rather than grep headings.
15
+
16
+ ### Added
17
+
18
+ - **`AGENTS.md`'s directory map is now complete and gated.** It named 16 of the 43 files under `tests/` while keeping a one-line-per-file shape, so every harness, prose gate, release gate and bench file added after the block was written was invisible to the session that reads it first. It covers all tracked `src`/`tests`/`e2e` files plus a line per code directory, and `tests/documentation.test.ts`'s new case enforces it — with the section's *edges* pinned, not only its contents, because a fence extraction that slips one position makes "every name appears in the block" true of a document that merely discusses those files elsewhere. Five mutants, each dying to a different assertion.
19
+ - **A prose gate can no longer scan a build artifact.** `listDocumentationFiles()` walks a directory as it is on disk with no extension filter, so the privacy gate was regex-scanning the 1.13 MB React bundle and 1.75 MB map that `pnpm build:examples` leaves in gitignored `examples/react/vendor/` — and only on machines that had built it, since CI's `verify` job never does and the `browser` job that does runs no vitest. A vendored scoped-package specifier would therefore have reddened a docs-only PR at home while the merge gate stayed green. The scope is now the repository's own index, and the predicate has a case on fabricated input so it reads identically on a clean and a built tree.
20
+ - **`CONTRIBUTING.md` joined three prose gates, and each was probed separately rather than credited as one widening.** Doctored with one violation of each kind and run with the array entry, then with only that entry removed: the empty-section and relative-link sweeps go `FAILED(1)` → `PASSED(21)`, so those additions are preventive coverage that provably fires; the citation sweep has no citation in the file to fire on, and is reported as preventive only.
21
+
1
22
  ## [0.21.35] - 2026-09-25
2
23
 
3
24
  Two shipped comments cited a test by a fixture string instead of its name, and that class of citation now has a gate; the `AGENTS.md` copy of the `knownTopics` cap rule was the last one still stating a bound the code refuses; the release gate that decides "npm never recorded this version" now asks npm instead of whatever registry the machine is configured to use; and finishing the sweep of one of this release's own corrections turned up three sentences it had broken or left behind in the Chinese mirror. No library behavior, export, option or key changed.
@@ -115,8 +115,10 @@ export declare class CentrifugeWorkerTransport<TData = unknown> implements DataB
115
115
  /** Handle a message from the in-process CentrifugeSession (local fallback). */
116
116
  private readonly handleSessionOutput;
117
117
  /** Route a Worker output message to the appropriate handler callback.
118
- * Shared by the Worker message listener, the SharedWorker port listener,
119
- * and the local-session sink — all three feed into this single dispatcher. */
118
+ * Three message sources reach it — the dedicated Worker, the SharedWorker
119
+ * MessagePort, and the in-process session sink — but through two call sites: the
120
+ * first two share this one `handleMessage` handler, registered on the Worker and
121
+ * on the port, and only the session path arrives through its own wrapper. */
120
122
  private handleOutput;
121
123
  /** Resolve a Worker credential request from the main thread: call the
122
124
  * credentialProvider, then post the fresh token (or a serialized failure)
@@ -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":"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"}
@@ -7,7 +7,7 @@ import {
7
7
  publicationMetadata,
8
8
  selectWorkerBackend,
9
9
  serializeError
10
- } from "./chunk-EVVTH6BQ.js";
10
+ } from "./chunk-AWCFYLIT.js";
11
11
  import {
12
12
  CENTRIFUGE_INPUT_TYPE,
13
13
  CENTRIFUGE_OUTPUT_TYPE,
@@ -424,8 +424,10 @@ var CentrifugeWorkerTransport = class {
424
424
  this.handleOutput(message);
425
425
  };
426
426
  /** Route a Worker output message to the appropriate handler callback.
427
- * Shared by the Worker message listener, the SharedWorker port listener,
428
- * and the local-session sink — all three feed into this single dispatcher. */
427
+ * Three message sources reach it — the dedicated Worker, the SharedWorker
428
+ * MessagePort, and the in-process session sink — but through two call sites: the
429
+ * first two share this one `handleMessage` handler, registered on the Worker and
430
+ * on the port, and only the session path arrives through its own wrapper. */
429
431
  handleOutput(message) {
430
432
  if (message.type === CENTRIFUGE_OUTPUT_TYPE.STATUS) this.handlers?.onStatus(message.status);
431
433
  if (message.type === CENTRIFUGE_OUTPUT_TYPE.MESSAGE) this.handlers?.onMessage({ topic: message.topic, data: message.data, ...publicationMetadata(message.messageId, message.timestamp) });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/centrifuge-session.ts", "../src/centrifuge-protocol.ts", "../src/centrifuge.ts"],
4
- "sourcesContent": ["/**\n * CentrifugeSession \u2014 a reusable wrapper around a single Centrifuge client.\n *\n * Provides a structured-clone-safe message protocol (INIT/SUBSCRIBE/UNSUBSCRIBE/\n * PUBLISH/STOP) so the same session class can run inside a Dedicated Worker,\n * a SharedWorker port, or directly on the main thread as a local fallback.\n */\nimport { Centrifuge } from 'centrifuge';\nimport { parseDataBusPublication } from './core/publication';\nimport type {\n PublicationContext,\n StateContext,\n Subscription,\n SubscriptionErrorContext\n} from 'centrifuge';\nimport type {\n CentrifugeWorkerConfig,\n CentrifugeWorkerInput,\n CentrifugeWorkerOutput\n} from './centrifuge-protocol';\nimport { CENTRIFUGE_INPUT_TYPE, CENTRIFUGE_OUTPUT_TYPE, WORKER_STATUS } from './utils/constants';\nimport { publicationMetadata } from './utils/metadata';\nimport { deserializeWorkerError, serializeError } from './utils/error-utils';\n\n/** Callback interface for posting messages back to the transport layer. */\nexport interface CentrifugeSessionSink<TData = unknown> {\n post(message: CentrifugeWorkerOutput<TData>, transfer?: ArrayBuffer[]): void;\n}\n\n/** A pending getToken/getChannelToken request awaiting a main-thread response. */\ninterface PendingTokenRequest {\n resolve: (token: string) => void;\n reject: (error: unknown) => void;\n}\n\n/** Centrifuge client options for token-bridge mode: the structured-clone-safe\n * subset plus the function-valued credential hooks this session installs.\n * Against the pinned SDK (5.7.4) `getToken` is the only one that is a real\n * client option (`build/types.d.ts:126`, and the same hook serves renewal);\n * `getChannelToken` appears nowhere in the package's types or build, and\n * subscriptions are created with `newSubscription(topic)` and no per-\n * subscription options \u2014 so that hook is never invoked, and the `'channelToken'`\n * request kind is produced only by the test fake. It stays because the Worker\n * protocol carries the kind: an SDK that later names it would need no change on\n * this side. Used only inside the session, never across the Worker boundary. */\ntype CentrifugeBridgedOptions = Omit<CentrifugeWorkerConfig, 'getToken' | 'getChannelToken'> & {\n getToken: () => Promise<string>;\n getChannelToken: (channel: string) => Promise<string>;\n};\n\n/**\n * Stateful Centrifuge client wrapper shared by Dedicated Worker, SharedWorker\n * ports and the main-thread local fallback. Each session owns one connection.\n */\nexport class CentrifugeSession<TData = unknown> {\n private client: Centrifuge | null = null;\n private readonly subscriptions = new Map<string, Subscription>();\n private transferable = false;\n private tokenBridge = false;\n private nextRequestId = 1;\n // Bumped on every initialize/stop. Async client callbacks capture the value\n // from the connection that created them so a stopped client cannot emit into\n // a later session instance (including the main-thread local fallback).\n private lifecycle = 0;\n private readonly pendingTokenRequests = new Map<number, PendingTokenRequest>();\n\n constructor(private readonly sink: CentrifugeSessionSink<TData>) {}\n\n /** Dispatch an incoming Worker message to the matching operation.\n * Unknown message types are ignored rather than thrown, so a future protocol\n * extension adding a new variant cannot crash an older session. */\n handle(message: CentrifugeWorkerInput): void {\n switch (message.type) {\n case CENTRIFUGE_INPUT_TYPE.INIT:\n this.initialize(message.url, message.config, message.transferable === true, message.tokenBridge === true);\n return;\n case CENTRIFUGE_INPUT_TYPE.SUBSCRIBE:\n this.subscribe(message.topic);\n return;\n case CENTRIFUGE_INPUT_TYPE.UNSUBSCRIBE:\n this.unsubscribe(message.topic);\n return;\n case CENTRIFUGE_INPUT_TYPE.PUBLISH:\n case CENTRIFUGE_INPUT_TYPE.PUBLISH_BIN:\n // Binary and JSON publish share the same Centrifuge client call; the\n // transport layer decides whether to transfer the ArrayBuffer.\n this.publish(message.topic, message.data, message.messageId, message.timestamp);\n return;\n case CENTRIFUGE_INPUT_TYPE.STOP:\n this.stop();\n return;\n case CENTRIFUGE_INPUT_TYPE.TOKEN_RESPONSE:\n this.resolveToken(message.requestId, message.token);\n return;\n case CENTRIFUGE_INPUT_TYPE.TOKEN_ERROR:\n this.rejectToken(message.requestId, deserializeWorkerError(message.error));\n return;\n default:\n return;\n }\n }\n\n /** Create the Centrifuge client, wire up lifecycle listeners, and connect. */\n private initialize(url: string, config: CentrifugeWorkerConfig, transferable: boolean, tokenBridge: boolean): void {\n if (this.client) return;\n const lifecycle = ++this.lifecycle;\n this.transferable = transferable;\n this.tokenBridge = tokenBridge;\n const clientOptions: CentrifugeWorkerConfig | CentrifugeBridgedOptions = tokenBridge\n ? {\n ...config,\n // Route credential fetches back to the main thread, where the\n // application's credentialProvider lives (config must stay\n // structured-cloneable, so functions cannot cross the boundary).\n getToken: () => this.requestToken('token', lifecycle),\n getChannelToken: (channel: string) => this.requestToken('channelToken', lifecycle, channel)\n }\n : config;\n const client = new Centrifuge(url, clientOptions);\n this.client = client;\n client.on('state', (context: StateContext) => {\n if (this.lifecycle !== lifecycle) return;\n this.post({ type: CENTRIFUGE_OUTPUT_TYPE.STATUS, status: normalizeStatus(context.newState) });\n });\n client.on('connected', () => {\n if (this.lifecycle !== lifecycle) return;\n this.post({ type: CENTRIFUGE_OUTPUT_TYPE.STATUS, status: WORKER_STATUS.CONNECTED });\n });\n client.on('disconnected', () => {\n if (this.lifecycle !== lifecycle) return;\n this.post({ type: CENTRIFUGE_OUTPUT_TYPE.STATUS, status: WORKER_STATUS.DISCONNECTED });\n });\n client.on('error', context => {\n if (this.lifecycle !== lifecycle) return;\n this.postError(context);\n });\n // Client-level publications are only for server-side subscriptions (where\n // no client Subscription object exists). For topics we have an active\n // subscription for, the subscription-level 'publication' listener handles\n // dispatch \u2014 skip here to avoid delivering the same message twice.\n client.on('publication', (context: PublicationContext) => {\n if (this.lifecycle !== lifecycle) return;\n const topic = context.channel || getPayloadTopic(context.data);\n if (!topic || this.subscriptions.has(topic)) return;\n this.postPublication(topic, context.data);\n });\n client.connect();\n }\n\n /** Subscribe to a Centrifuge channel. Reuses an existing subscription if one exists.\n * Listeners are only registered once per subscription object \u2014 a repeated\n * SUBSCRIBE for an already-tracked topic skips the listener wiring entirely,\n * avoiding the removeAllListeners + re-on churn on every duplicate message. */\n private subscribe(topic: string): void {\n if (!this.client) return this.postError(new Error('Centrifuge client is not initialized.'));\n const lifecycle = this.lifecycle;\n // If we already track this subscription, it already has our listeners \u2014\n // a duplicate SUBSCRIBE is a no-op (idempotent), matching the transport\n // contract. Only a fresh subscription needs listener wiring.\n const existing = this.subscriptions.get(topic);\n if (existing) {\n existing.subscribe();\n return;\n }\n let subscription = this.client.getSubscription(topic);\n if (!subscription) subscription = this.client.newSubscription(topic);\n // Scoped to the three events this method wires, so the client's own `state`\n // wiring is untouched \u2014 but `error` is NOT internal-listener-free: a no-op\n // `error` listener is installed on every subscription precisely\n // \"to avoid unhandled exception in EventEmitter for non-set error handler\",\n // and not by `Subscription` itself \u2014 that class body contains no `on('error')`\n // call at all (measured on the pinned copy), so the guard runs in the\n // `BaseSubscription` constructor every subscription reaches through `super`.\n // (centrifuge 5.7.4, `build/index.js`. The line number this used to cite \u2014 762\n // \u2014 matches neither the pinned copy, where the guard sits at 763-764, nor the\n // 5.7.0 copy in the store, where it is at 748; whether it drifted or was\n // mis-copied is not recoverable from here. That is the argument for quoting\n // the dependency's own comment as the locator instead of a number), and the\n // bundled emitter throws on an `error` emit with no listener\n // (`throw er; // Unhandled 'error' event` in the same file).\n // This call removes that guard,\n // and the `subscription.on('error', \u2026)` re-installation below puts one back for\n // as long as this session holds the subscription \u2014 three statements later (the\n // `unsubscribed` removal, the `subscriptions` set, the `publication` wiring), all\n // of them inside this same synchronous task, so no emit can land in between.\n // `unsubscribe()` removes both \u2014 this handler and the constructor's no-op \u2014 and\n // adds nothing back, which is settled rather than open. All twelve `emit('error')`\n // sites in `BaseSubscription` (its class body is lines 667-2673 of that file, and\n // the twelve were counted in it) sit behind a\n // `_isSubscribing()`/`_isSubscribed()` test except one, the\n // `badConfiguration` emit in `_getSubscriptionToken`; and after\n // `_setUnsubscribed` has moved `state` to `Unsubscribed` no path re-enters it\n // \u2014 the resubscribe timer's callback re-tests `_isSubscribing()` before it\n // does anything, the client's reconnect pass (`_sendSubscribeCommands`)\n // drives only subscriptions whose state is already `Subscribing`, and\n // `_refresh()`, the one entry into `_getSubscriptionToken` with no state test\n // of its own, is reachable only through `_refreshTimeout`, which\n // `_setUnsubscribed` clears via `_clearSubscribedState()`. So a throw would\n // need the listeners gone while the object is still live, and\n // `unsubscribe()` does not leave it live. `MapSubscription` and\n // `SharedPollSubscription` have their own emit sites but never carry this\n // session's listeners \u2014 `newSubscription` constructs a plain `Subscription`.\n subscription.removeAllListeners('publication');\n subscription.removeAllListeners('error');\n subscription.removeAllListeners('unsubscribed');\n this.subscriptions.set(topic, subscription);\n subscription.on('publication', context => {\n if (this.lifecycle !== lifecycle) return;\n this.postPublication(topic, context.data);\n });\n subscription.on('error', (context: SubscriptionErrorContext) => {\n if (this.lifecycle !== lifecycle) return;\n this.postError(context);\n });\n subscription.on('unsubscribed', () => {\n if (this.lifecycle === lifecycle) this.subscriptions.delete(topic);\n });\n subscription.subscribe();\n }\n\n /** Unsubscribe from a Centrifuge channel and clean up the local reference.\n * Listeners are removed before unsubscribing so a late `unsubscribed` event\n * cannot delete a subscription that a subsequent `subscribe()` re-added. */\n private unsubscribe(topic: string): void {\n const subscription = this.subscriptions.get(topic) ?? this.client?.getSubscription(topic);\n if (!subscription) return;\n subscription.removeAllListeners('publication');\n subscription.removeAllListeners('error');\n subscription.removeAllListeners('unsubscribed');\n this.subscriptions.delete(topic);\n subscription.unsubscribe();\n }\n\n /** Publish a message to the Centrifuge channel. */\n private publish(topic: string, data: unknown, messageId?: string, timestamp?: number): void {\n if (!this.client) return this.postError(new Error('Centrifuge client is not initialized.'));\n const lifecycle = this.lifecycle;\n // Centrifuge's payload is application-defined. Preserve legacy payloads;\n // when an ID is requested, send a small metadata envelope that compatible\n // servers can echo back for end-to-end deduplication.\n const hasMetadata = messageId !== undefined || timestamp !== undefined;\n const payload = hasMetadata\n ? {\n data,\n ...(messageId === undefined ? {} : { messageId }),\n ...(timestamp === undefined ? {} : { timestamp })\n }\n : data;\n void this.client.publish(topic, payload).catch(error => {\n if (this.lifecycle === lifecycle) this.postError(error);\n });\n }\n\n /** Forward a publication to the transport. Binary payloads take the\n * zero-copy `MESSAGE_BIN` path when `transferable` is enabled; everything\n * else is structured-cloned via `MESSAGE`. An empty topic means the\n * publication carried no channel info and is silently dropped.\n *\n * That drop is uncovered and dominated: the connection-level caller tests the\n * same emptiness before calling, and the subscription-level caller reads the\n * topic out of a key this session only ever populates from a SUBSCRIBE frame.\n * Measured \u2014 deleting this line leaves the whole suite green, and the frame\n * it withholds has nowhere to land anyway: the only handler registry is the\n * DataBus's `topicHandlers`, and every write to it sits behind\n * `assertPublicTopic` (at the head of `data-bus.ts`'s `subscribe()`, `publish()`\n * and `publishBatch()`), so no `''` key can\n * exist \u2014 while `topicMatchesPattern` answers false for an empty topic against\n * both `*` and `prefix.*`, so a wildcard handler cannot see it either. Neither\n * of those is a reason to delete this guard: it is the only one *at this layer*,\n * because neither the session nor the transport calls `assertPublicTopic` \u2014 a\n * SUBSCRIBE frame naming `''` creates a real Centrifuge subscription\n * (`newSubscription` does not validate a channel), so the undeliverable\n * publication would otherwise be posted back to the main thread. Kept as the\n * boundary statement, not counted as a closed leg. */\n private postPublication(topic: string, data: unknown): void {\n if (!topic) return;\n if (this.transferable && data instanceof ArrayBuffer) {\n this.post({ type: CENTRIFUGE_OUTPUT_TYPE.MESSAGE_BIN, topic, data }, [data]);\n return;\n }\n const publication = parseDataBusPublication<TData>(data, topic);\n if (!publication) return;\n this.post({\n type: CENTRIFUGE_OUTPUT_TYPE.MESSAGE,\n topic: publication.topic,\n data: publication.data,\n ...publicationMetadata(publication.messageId, publication.timestamp)\n });\n }\n\n /** Disconnect the client and clear all subscriptions. */\n private stop(): void {\n // Invalidate callbacks from the connection being stopped before disconnect\n // so its synchronous or queued events cannot leak into a later session.\n this.lifecycle += 1;\n // Settle every in-flight credential request so a stop cannot leave the\n // Worker awaiting a main-thread response forever.\n for (const [, pending] of this.pendingTokenRequests) {\n pending.reject(new Error('Centrifuge session stopped before the credential was resolved.'));\n }\n this.pendingTokenRequests.clear();\n this.client?.disconnect();\n this.subscriptions.clear();\n this.client = null;\n this.post({ type: CENTRIFUGE_OUTPUT_TYPE.STATUS, status: WORKER_STATUS.DISCONNECTED });\n }\n\n /** Issue a credential request to the main thread and await the response.\n * Used as Centrifuge's `getToken` / `getChannelToken` when token bridging is\n * enabled; resolved or rejected by a matching TOKEN_RESPONSE / TOKEN_ERROR. */\n private requestToken(kind: 'token' | 'channelToken', lifecycle: number, channel?: string): Promise<string> {\n // The provider is installed on a specific Centrifuge client. An old client\n // must not be able to route a late credential request into a replacement\n // session merely because it retained the callback closure.\n if (this.lifecycle !== lifecycle) {\n return Promise.reject(new Error('Centrifuge client lifecycle has ended before the credential was requested.'));\n }\n const requestId = this.nextRequestId;\n this.nextRequestId += 1;\n return new Promise<string>((resolve, reject) => {\n this.pendingTokenRequests.set(requestId, { resolve, reject });\n this.post({\n type: CENTRIFUGE_OUTPUT_TYPE.TOKEN_REQUEST,\n requestId,\n kind,\n ...(channel === undefined ? {} : { channel })\n });\n });\n }\n\n private resolveToken(requestId: number, token: string): void {\n const pending = this.pendingTokenRequests.get(requestId);\n if (!pending) return;\n this.pendingTokenRequests.delete(requestId);\n pending.resolve(token);\n }\n\n private rejectToken(requestId: number, error: unknown): void {\n const pending = this.pendingTokenRequests.get(requestId);\n if (!pending) return;\n this.pendingTokenRequests.delete(requestId);\n pending.reject(error);\n }\n\n /** Forward a message to the sink (the transport layer). */\n private post(message: CentrifugeWorkerOutput<TData>, transfer?: ArrayBuffer[]): void {\n this.sink.post(message, transfer);\n }\n\n /** Serialise and report an error. The Centrifuge client handles reconnection\n * internally, so a transient error should not trigger a `STATUS: error` that\n * would cause `selectActiveWorkers()` to exclude this worker from routing.\n * Fatal errors are distinguished by the client eventually emitting\n * `disconnected` without a subsequent `connected`. */\n private postError(error: unknown): void {\n this.post({ type: CENTRIFUGE_OUTPUT_TYPE.ERROR, error: serializeError(error) });\n }\n}\n\n/** Extract a topic from a Centrifuge publication payload if one is present.\n * Handles both direct `channel` fields and the nested `push.channel` shape\n * that Centrifugo uses for some server-side push types. */\nfunction getPayloadTopic(data: unknown): string {\n if (!data || typeof data !== 'object') return '';\n const payload = data as Record<string, unknown>;\n // Prefer nested push.channel (server-side push) then fall back to top-level channel.\n const push = payload.push;\n const nested = typeof push === 'object' && push !== null ? (push as Record<string, unknown>).channel : undefined;\n const topic = nested ?? payload.channel;\n return typeof topic === 'string' ? topic : '';\n}\n\n/** Map a Centrifuge state string to the DataBus's status vocabulary.\n * 'connecting' and 'connected' pass through; anything else (e.g. 'reconnecting',\n * 'disconnected') maps to 'disconnected'. */\nconst LIVE_STATES = new Set<string>([WORKER_STATUS.CONNECTING, WORKER_STATUS.CONNECTED]);\nfunction normalizeStatus(status: string): 'connecting' | 'connected' | 'disconnected' {\n return LIVE_STATES.has(status) ? (status as 'connecting' | 'connected') : 'disconnected';\n}\n", "/**\n * Worker-thread protocol for Centrifuge WebSocket transport.\n *\n * Defines the message types exchanged between the main thread and a Web Worker\n * (dedicated or shared) that runs a centrifuge client. The worker is isolated\n * from the main thread so that WebSocket lifecycle, token refresh, and binary\n * data handling never block the UI.\n *\n * The message `type` discriminant values are derived from the constants in\n * `utils/constants.ts` so main-thread and Worker sides reference the same\n * values and can never drift apart.\n */\nimport type { Options } from 'centrifuge';\nimport type { CENTRIFUGE_INPUT_TYPE, CENTRIFUGE_OUTPUT_TYPE } from './utils/constants';\nimport type { WorkerStatus } from './core/types';\nimport type { SerializedWorkerError } from './utils/error-utils';\n\nexport type { SerializedWorkerError } from './utils/error-utils';\n\n/**\n * Default interval between main-thread PING heartbeats to a SharedWorker. The\n * SharedWorker reaps a silent port after `SESSION_TIMEOUT_MULTIPLIER` intervals.\n * Shared here so the main thread (heartbeat sender) and the SharedWorker (reaper)\n * always agree on the cadence.\n */\nexport const DEFAULT_HEARTBEAT_INTERVAL_MS = 10_000;\n/** SharedWorker per-port session timeout, expressed as a multiple of that port's\n * heartbeat interval. A live port PINGs every heartbeat interval, so a timeout of\n * several intervals tolerates throttled tabs without reaping healthy sessions. */\nexport const DEFAULT_SESSION_TIMEOUT_MULTIPLIER = 3;\n/** Default per-port session timeout in ms, derived from the heartbeat interval\n * and the multiplier. Used as the fallback when a port sends no INIT config. */\nexport const DEFAULT_SESSION_TIMEOUT_MS =\n DEFAULT_HEARTBEAT_INTERVAL_MS * DEFAULT_SESSION_TIMEOUT_MULTIPLIER;\n\n// These options reference browser APIs (WebSocket, EventSource, fetch, \u2026) that\n// do not exist inside a Worker \u2014 the worker imports its own WebSocket. Nothing\n// strips them at runtime: `buildInitInput` forwards `config.options` verbatim,\n// so this exclusion is compile-time only. A value forced through with a cast is\n// caught by the runtime gate that does exist, `assertStructuredCloneable` on the\n// same options, which throws a TypeError from the transport before any backend\n// is created \u2014 a function-valued option would otherwise die at the structured-\n// clone boundary with a DataCloneError instead.\ntype WorkerUnsafeOption =\n | 'eventsource'\n | 'fetch'\n | 'getData'\n | 'getToken'\n | 'networkEventTarget'\n | 'readableStream'\n | 'sockjs'\n | 'websocket';\n\n/** Centrifuge options safe to pass into a Worker; unsafe options are explicitly\n * excluded and set to `never` so the compiler rejects them at the call site. */\nexport type CentrifugeWorkerConfig = Omit<Partial<Options>, WorkerUnsafeOption> & {\n [Key in WorkerUnsafeOption]?: never;\n};\n\n/**\n * Async credential provider run on the main thread. Centrifuge client options\n * are structured-cloned into the Worker, so function-valued `getToken` /\n * `getChannelToken` cannot travel with the config. When a provider is set, the\n * Worker asks the main thread for each fresh token over a TOKEN_REQUEST /\n * TOKEN_RESPONSE exchange and this provider supplies it from application\n * context (async token endpoints, SecureStorage, etc.).\n */\nexport interface CentrifugeCredentialProvider {\n /** Return a fresh top-level connection token. Called on connect and refresh. */\n getToken: () => MaybePromise<string | null | undefined>;\n /** Return a channel token for a channel. Centrifuge falls back to `getToken`\n * when channel tokens are not configured by the server. */\n getChannelToken?: (channel: string) => MaybePromise<string | null | undefined>;\n}\n\n/** A value or a promise of a value. */\nexport type MaybePromise<T> = T | Promise<T>;\n\n/** Messages sent from the main thread to the Worker. All variants are\n * structured-cloneable; `PUBLISH_BIN` carries an ArrayBuffer (transferable). */\nexport type CentrifugeWorkerInput =\n /** Initial connection: URL + config. Sent once per backend creation.\n * `transferable` enables ArrayBuffer zero-copy for subsequent PUBLISH_BIN.\n * `heartbeatIntervalMs` overrides the SharedWorker PING cadence.\n * `tokenBridge` asks the Worker to route getToken/getChannelToken back to\n * the main thread instead of holding function-valued config. */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.INIT; url: string; config: CentrifugeWorkerConfig; transferable?: boolean; heartbeatIntervalMs?: number; tokenBridge?: boolean }\n /** Subscribe to a channel. Idempotent \u2014 re-subscribing is a no-op. */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.SUBSCRIBE; topic: string }\n /** Unsubscribe from a channel. Idempotent. */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.UNSUBSCRIBE; topic: string }\n /** Publish a structured-cloneable payload to a channel. */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.PUBLISH; topic: string; data: unknown; messageId?: string; timestamp?: number }\n /** Publish an ArrayBuffer via Transferable (zero-copy when `transferable` is on). */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.PUBLISH_BIN; topic: string; data: ArrayBuffer; messageId?: string; timestamp?: number }\n /** Heartbeat from the main thread; the SharedWorker reaps silent ports. */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.PING }\n /** Disconnect the client and clear all subscriptions. */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.STOP }\n /** Fresh credential for an earlier TOKEN_REQUEST, resolved by requestId. */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.TOKEN_RESPONSE; requestId: number; token: string }\n /** A credential request failed (provider rejected or returned no token). */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.TOKEN_ERROR; requestId: number; error: SerializedWorkerError };\n\n/** Messages sent from the Worker back to the main thread. The main thread\n * routes these to the DataBusTransportHandlers via handleOutput(). */\nexport type CentrifugeWorkerOutput<TData = unknown> =\n /** Connection status changed. Maps Centrifuge states to the DataBus vocabulary. */\n | { type: typeof CENTRIFUGE_OUTPUT_TYPE.STATUS; status: WorkerStatus }\n /** A JSON publication arrived. Routed to onMessage via handleOutput. */\n | { type: typeof CENTRIFUGE_OUTPUT_TYPE.MESSAGE; topic: string; data: TData; messageId?: string; timestamp?: number }\n /** A binary publication arrived (Transferable). Routed to onMessage with the ArrayBuffer. */\n | { type: typeof CENTRIFUGE_OUTPUT_TYPE.MESSAGE_BIN; topic: string; data: ArrayBuffer; messageId?: string; timestamp?: number }\n /** A non-fatal error occurred. Does not imply disconnection (the client retries internally). */\n | { type: typeof CENTRIFUGE_OUTPUT_TYPE.ERROR; error: SerializedWorkerError }\n /** The Worker needs a fresh credential. The main thread resolves it via a\n * matching TOKEN_RESPONSE / TOKEN_ERROR carrying the same `requestId`. */\n | { type: typeof CENTRIFUGE_OUTPUT_TYPE.TOKEN_REQUEST; requestId: number; kind: 'token' | 'channelToken'; channel?: string }\n /** The SharedWorker's reaper is about to close this port, so its session and\n * WebSocket are going away with it. Sent as the last message on the port: a tab\n * whose heartbeat was merely starved (a long task, background throttling) is\n * alive and can rebuild, but only if it learns that its backend is gone. */\n | { type: typeof CENTRIFUGE_OUTPUT_TYPE.SESSION_REAPED };\n", "/**\n * Centrifuge WebSocket transport that runs inside a Web Worker.\n *\n * Supports three backends. Which one is tried first depends on `workerMode`:\n * - `dedicated` (the default): Dedicated Worker \u2192 SharedWorker \u2192 in-process\n * - `shared` / `auto`: SharedWorker \u2192 Dedicated Worker \u2192 in-process\n *\n * 1. SharedWorker \u2014 one WebSocket per tab session, hosted in a shared process\n * 2. Dedicated Worker \u2014 one WebSocket per tab\n * 3. In-process (local) \u2014 Centrifuge runs on the main thread (fallback when\n * neither Worker type is available, e.g. in non-browser environments)\n *\n * Binary data (ArrayBuffer) can be transferred via Transferable when the\n * `transferable` option is enabled, avoiding structured-clone overhead.\n */\n\nimport { CrossTabDataBus } from './core/data-bus';\nimport type { CrossTabDataBusOptions } from './core/data-bus';\nimport type { DataBusTransport, DataBusTransportHandlers } from './core/types';\nimport { CentrifugeSession } from './centrifuge-session';\nimport { selectWorkerBackend } from './worker-mode';\nimport type { WorkerBackend, WorkerMode } from './worker-mode';\nimport type {\n CentrifugeCredentialProvider,\n CentrifugeWorkerConfig,\n CentrifugeWorkerInput,\n CentrifugeWorkerOutput\n} from './centrifuge-protocol';\nimport type { DataBusPublishOptions } from './core/types';\nimport { DEFAULT_HEARTBEAT_INTERVAL_MS } from './centrifuge-protocol';\nimport { CENTRIFUGE_INPUT_TYPE, CENTRIFUGE_OUTPUT_TYPE, DEFAULT_STORAGE_PREFIX, WORKER_BACKEND, WORKER_MODE, WORKER_STATUS } from './utils/constants';\nimport { deserializeWorkerError, serializeError } from './utils/error-utils';\nimport { publicationMetadata } from './utils/metadata';\nimport { assertHeartbeatInterval, assertStructuredCloneable } from './utils/validation';\n\nexport type { CentrifugeCredentialProvider, CentrifugeWorkerConfig, SerializedWorkerError } from './centrifuge-protocol';\nexport type { WorkerBackend, WorkerMode } from './worker-mode';\n\n/** WebSocket connection parameters passed to the Centrifuge Worker. */\nexport interface CentrifugeDataBusConfig {\n /** Centrifuge server WebSocket URL. */\n url: string;\n /** Centrifuge client options (token, channel params, etc.). */\n options?: CentrifugeWorkerConfig;\n}\n\n/** Options for configuring the Worker backend (dedicated, shared, or local). */\nexport interface CentrifugeWorkerTransportOptions {\n /** Custom dedicated Worker factory. Used for testing or bundler integration. */\n workerFactory?: () => Worker;\n /** Custom SharedWorker factory. */\n sharedWorkerFactory?: () => SharedWorker;\n /** Preferred Worker mode: 'dedicated', 'shared', or 'auto'. */\n workerMode?: WorkerMode;\n /** Enable transferable (ArrayBuffer) support for binary data. */\n transferable?: boolean;\n /** Interval (ms) between PING heartbeats sent to the SharedWorker. The\n * SharedWorker reaps a silent port after `DEFAULT_SESSION_TIMEOUT_MULTIPLIER`\n * \u00D7 this interval. Pass `Infinity` to disable heartbeats entirely. Defaults\n * to `DEFAULT_HEARTBEAT_INTERVAL_MS`. */\n heartbeatIntervalMs?: number;\n /** Async credential provider. Runs on the main thread: the Worker cannot\n * hold function-valued Centrifuge options (they are not structured-cloneable),\n * so when a provider is set the Worker asks for each fresh token over a\n * TOKEN_REQUEST / TOKEN_RESPONSE bridge and this provider supplies it. */\n credentialProvider?: CentrifugeCredentialProvider;\n}\n\n/** Options for creating a fully-configured CrossTabDataBus with a Centrifuge transport. */\nexport interface CreateCentrifugeDataBusOptions<TData = unknown>\n extends Omit<\n CrossTabDataBusOptions<CentrifugeDataBusConfig, TData>,\n 'autoStart' | 'clusterKey' | 'initialConfig' | 'transport'\n >,\n CentrifugeWorkerTransportOptions {\n /** Centrifuge connection configuration. */\n connection: CentrifugeDataBusConfig;\n /** Cluster key for cross-tab coordination. Defaults to the connection URL. */\n clusterKey?: string;\n}\n\n/**\n * Transport layer that runs a Centrifuge WebSocket client inside a Web Worker.\n *\n * Delegates the actual WebSocket connection to a Worker (dedicated or shared)\n * or falls back to an in-process CentrifugeSession. On the two Worker backends\n * the Worker is isolated from the main thread, so WebSocket lifecycle, token\n * refresh, and binary data handling stay off the UI thread. On the `local`\n * fallback there is no Worker: the same work runs on the main thread, which is\n * the point of the fallback (it keeps the transport usable where no Worker API\n * exists) rather than a property it preserves.\n */\nexport class CentrifugeWorkerTransport<TData = unknown>\n implements DataBusTransport<CentrifugeDataBusConfig, TData>\n{\n readonly diagnosticsName = 'centrifuge';\n private readonly workerMode: WorkerMode;\n private readonly transferable: boolean;\n private readonly heartbeatIntervalMs: number;\n private readonly workerFactory: (() => Worker) | undefined;\n private readonly sharedWorkerFactory: (() => SharedWorker) | undefined;\n private readonly credentialProvider: CentrifugeCredentialProvider | undefined;\n private backend: WorkerBackend | null = null;\n private worker: Worker | null = null;\n private sharedWorker: SharedWorker | null = null;\n private port: MessagePort | null = null;\n private heartbeatHandle: ReturnType<typeof setInterval> | null = null;\n private localSession: CentrifugeSession<TData> | null = null;\n private handlers: DataBusTransportHandlers<TData> | null = null;\n // Monotonically increasing counter, bumped when a *Worker* backend is created\n // (`startDedicatedWorker`, `startSharedWorker`) and when the transport stops.\n // The in-process `localSession` creation does not bump it, which is why\n // `isCurrentBackend()` compares worker/port/localSession identity as well.\n // Only the asynchronous credential bridge reads the counter: a provider may\n // settle after the backend it was answering is gone. Worker error events need\n // no such check, but not for the reason this note used to give \u2014 the top of\n // `stop()` bumps the counter and only then removes the listeners, so the\n // generation moves on *before* the removal, not after, while `onWorkerFailed()`\n // removes them without touching the counter at all. The containment in both\n // cases is that they happen in one synchronous task, together with\n // `handlers = null`, and no message or error event can be delivered to this\n // object mid-task.\n private generation = 0;\n\n get diagnosticsBackend(): string {\n return this.backend ?? 'uninitialized';\n }\n\n constructor(options: CentrifugeWorkerTransportOptions = {}) {\n this.workerMode = options.workerMode ?? WORKER_MODE.DEDICATED;\n this.transferable = options.transferable ?? false;\n this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;\n assertHeartbeatInterval(this.heartbeatIntervalMs);\n this.workerFactory = options.workerFactory;\n this.sharedWorkerFactory = options.sharedWorkerFactory;\n this.credentialProvider = options.credentialProvider;\n }\n\n /**\n * Start the transport: select a backend, initialise the Worker (or local\n * session), and send the INIT message with connection parameters.\n */\n start(config: CentrifugeDataBusConfig, handlers: DataBusTransportHandlers<TData>): void {\n if (this.backend) return;\n assertStructuredCloneable(config.options ?? {});\n this.handlers = handlers;\n // Runtime capability OR an injected factory counts as availability; a\n // factory-less browser must use the bundled default Workers instead of\n // silently degrading to the local session.\n const backend = selectWorkerBackend(this.workerMode, {\n worker: this.workerFactory !== undefined || typeof Worker !== 'undefined',\n sharedWorker: this.sharedWorkerFactory !== undefined || typeof SharedWorker !== 'undefined'\n });\n const input = this.buildInitInput(config);\n if (backend === WORKER_BACKEND.SHARED) {\n this.startSharedWorker(input);\n this.backend = WORKER_BACKEND.SHARED;\n return;\n }\n if (backend === WORKER_BACKEND.DEDICATED) {\n this.startDedicatedWorker(input);\n this.backend = WORKER_BACKEND.DEDICATED;\n return;\n }\n this.localSession = new CentrifugeSession<TData>({ post: this.handleSessionOutput });\n this.localSession.handle(input);\n this.backend = WORKER_BACKEND.LOCAL;\n }\n\n subscribe(topic: string): void {\n this.post({ type: CENTRIFUGE_INPUT_TYPE.SUBSCRIBE, topic });\n }\n\n unsubscribe(topic: string): void {\n this.post({ type: CENTRIFUGE_INPUT_TYPE.UNSUBSCRIBE, topic });\n }\n\n /**\n * Publish data to `topic`. Binary data (ArrayBuffer) is sent via Transferable\n * when `transferable` is enabled, avoiding a structured-clone cycle.\n */\n publish(topic: string, data: unknown, options?: DataBusPublishOptions): void {\n if (this.transferable && data instanceof ArrayBuffer) {\n this.post({ type: CENTRIFUGE_INPUT_TYPE.PUBLISH_BIN, topic, data, ...publicationMetadata(options?.messageId, options?.timestamp) }, [data]);\n return;\n }\n this.post({ type: CENTRIFUGE_INPUT_TYPE.PUBLISH, topic, data, ...publicationMetadata(options?.messageId, options?.timestamp) });\n }\n\n /**\n * Gracefully stop the transport: send STOP, clean up event listeners, and\n * terminate the Worker (or close the SharedWorker port).\n */\n stop(): void {\n if (!this.backend) return;\n this.generation++;\n this.post({ type: CENTRIFUGE_INPUT_TYPE.STOP });\n this.clearHeartbeat();\n if (this.worker) {\n this.worker.removeEventListener('message', this.handleMessage);\n this.worker.removeEventListener('error', this.handleWorkerError);\n this.worker.terminate();\n }\n if (this.sharedWorker) {\n this.detachSharedWorkerListeners();\n this.port?.close();\n }\n this.resetBackend();\n this.handlers = null;\n }\n\n /** Build the INIT payload sent to the Worker / local session. Optional fields\n * are only included when they deviate from the defaults, so the Worker's own\n * default-resolution logic kicks in for the common case. */\n private buildInitInput(config: CentrifugeDataBusConfig): CentrifugeWorkerInput {\n return {\n type: CENTRIFUGE_INPUT_TYPE.INIT,\n url: config.url,\n config: config.options ?? {},\n ...(this.transferable ? { transferable: true } : {}),\n ...(this.credentialProvider ? { tokenBridge: true } : {}),\n ...(this.heartbeatIntervalMs !== DEFAULT_HEARTBEAT_INTERVAL_MS\n ? { heartbeatIntervalMs: this.heartbeatIntervalMs }\n : {})\n };\n }\n\n /** Create and initialise a dedicated Worker, then send the INIT message. */\n private startDedicatedWorker(input: CentrifugeWorkerInput): void {\n this.generation += 1;\n const worker = (this.workerFactory ?? createDefaultWorker)();\n this.worker = worker;\n worker.addEventListener('message', this.handleMessage);\n worker.addEventListener('error', this.handleWorkerError);\n worker.postMessage(input);\n }\n\n /** Create and initialise a SharedWorker, open the MessagePort, and send the INIT message. */\n private startSharedWorker(input: CentrifugeWorkerInput): void {\n this.generation += 1;\n const shared = (this.sharedWorkerFactory ?? createDefaultSharedWorker)();\n this.sharedWorker = shared;\n const port = shared.port;\n this.port = port;\n port.addEventListener('message', this.handleMessage);\n port.addEventListener('messageerror', this.handlePortError);\n shared.addEventListener('error', this.handleSharedWorkerError);\n port.start();\n port.postMessage(input);\n this.startHeartbeat();\n }\n\n /** Handle a message event from the Worker (dedicated or shared). */\n private readonly handleMessage = (event: MessageEvent<CentrifugeWorkerOutput<TData>>) => {\n this.handleOutput(event.data);\n };\n\n /** Handle a message from the in-process CentrifugeSession (local fallback). */\n private readonly handleSessionOutput = (message: CentrifugeWorkerOutput<TData>) => {\n this.handleOutput(message);\n };\n\n /** Route a Worker output message to the appropriate handler callback.\n * Shared by the Worker message listener, the SharedWorker port listener,\n * and the local-session sink \u2014 all three feed into this single dispatcher. */\n private handleOutput(message: CentrifugeWorkerOutput<TData>): void {\n if (message.type === CENTRIFUGE_OUTPUT_TYPE.STATUS) this.handlers?.onStatus(message.status);\n if (message.type === CENTRIFUGE_OUTPUT_TYPE.MESSAGE) this.handlers?.onMessage({ topic: message.topic, data: message.data, ...publicationMetadata(message.messageId, message.timestamp) });\n if (message.type === CENTRIFUGE_OUTPUT_TYPE.MESSAGE_BIN) this.handlers?.onMessage({ topic: message.topic, data: message.data as TData, ...publicationMetadata(message.messageId, message.timestamp) });\n if (message.type === CENTRIFUGE_OUTPUT_TYPE.ERROR) this.handlers?.onError(deserializeWorkerError(message.error));\n if (message.type === CENTRIFUGE_OUTPUT_TYPE.TOKEN_REQUEST) this.resolveTokenRequest(message.requestId, message.kind, message.channel);\n // The SharedWorker is closing this port because our PING heartbeat went silent\n // for longer than its session timeout \u2014 a long synchronous task, or the\n // throttling a backgrounded tab gets. Nothing else can tell us: a MessagePort\n // has no close event, and posting into the closed end succeeds and is\n // discarded. Treating it as any other backend loss is what makes the bus report\n // it and lets the existing cooldown recovery rebuild the port; measured in a\n // real browser before this existed, a 34 s stall left a shared-mode tab owning\n // its topic with zero server-side subscribers while reporting\n // `state: healthy` / `status: connected` / `transportReady: true`.\n if (message.type === CENTRIFUGE_OUTPUT_TYPE.SESSION_REAPED) {\n this.onWorkerFailed(\"The SharedWorker ended this tab's session after its port heartbeat went silent.\");\n }\n }\n\n /** Resolve a Worker credential request from the main thread: call the\n * credentialProvider, then post the fresh token (or a serialized failure)\n * back to the session that issued the request. */\n private resolveTokenRequest(requestId: number, kind: 'token' | 'channelToken', channel: string | undefined): void {\n // Credential providers may be asynchronous. Capture the backend identity\n // before awaiting so a stop/start that replaces the Worker cannot receive\n // a late token (or error) belonging to the superseded session. Request IDs\n // restart at 1 for a fresh CentrifugeSession, so without this guard a stale\n // reply can incorrectly satisfy the replacement session's request.\n const generation = this.generation;\n const worker = this.worker;\n const port = this.port;\n const localSession = this.localSession;\n const isCurrentBackend = () =>\n this.generation === generation &&\n this.worker === worker &&\n this.port === port &&\n this.localSession === localSession;\n const provider = this.credentialProvider;\n let value: string | null | Promise<string | null | undefined> | undefined;\n try {\n value = kind === 'channelToken' && provider?.getChannelToken\n ? provider.getChannelToken(channel ?? '')\n : provider?.getToken?.();\n } catch (error) {\n // This arm never yields, but the provider is application code and can\n // stop or replace the transport synchronously before throwing. Without\n // the check `post()` then addresses a backend that no longer exists.\n if (isCurrentBackend()) {\n this.post({ type: CENTRIFUGE_INPUT_TYPE.TOKEN_ERROR, requestId, error: serializeError(error) });\n }\n return;\n }\n Promise.resolve(value).then(\n token => {\n if (!isCurrentBackend()) return;\n if (typeof token !== 'string' || token.length === 0) {\n this.post({\n type: CENTRIFUGE_INPUT_TYPE.TOKEN_ERROR,\n requestId,\n error: serializeError(new Error(`The credential provider returned no ${kind}.`))\n });\n return;\n }\n this.post({ type: CENTRIFUGE_INPUT_TYPE.TOKEN_RESPONSE, requestId, token });\n },\n error => {\n if (!isCurrentBackend()) return;\n this.post({ type: CENTRIFUGE_INPUT_TYPE.TOKEN_ERROR, requestId, error: serializeError(error) });\n }\n );\n }\n\n /** Handle a Worker-level failure (crash, message decode error). Discards the\n * dead backend so a later start()/reopen can rebuild from scratch, and\n * signals an error status so the DataBus can trigger recovery. A superseded\n * backend cannot reach this: its listeners are removed before the transport\n * moves on, which is what keeps late Worker errors from tearing down a fresh\n * session (pinned by 'ignores error events from a superseded worker'). */\n private onWorkerFailed(message: string): void {\n // Remove the message listener and release the old backend before recovery\n // reopens, so late messages from the failed Worker/port cannot be routed\n // into the freshly started session.\n this.worker?.removeEventListener('message', this.handleMessage);\n this.worker?.removeEventListener('error', this.handleWorkerError);\n this.worker?.terminate();\n this.detachSharedWorkerListeners();\n this.port?.close();\n this.clearHeartbeat();\n this.resetBackend();\n this.handlers?.onError(new Error(message));\n this.handlers?.onStatus(WORKER_STATUS.ERROR);\n }\n\n /** Remove every listener attached to the current SharedWorker and its port. */\n private detachSharedWorkerListeners(): void {\n this.port?.removeEventListener('message', this.handleMessage);\n this.port?.removeEventListener('messageerror', this.handlePortError);\n this.sharedWorker?.removeEventListener('error', this.handleSharedWorkerError);\n }\n\n /** Periodically ping the SharedWorker so its session reaper can detect a dead tab. */\n private startHeartbeat(): void {\n // Uncovered and dominated. The only caller is `startSharedWorker()`, which\n // `start()` reaches past its own `if (this.backend) return` guard, and\n // `backend` becomes null only through `stop()` and `onWorkerFailed()` \u2014 both\n // of which call `clearHeartbeat()` first. Measured: deleting this line leaves\n // the whole suite green. It stays because `clearHeartbeat()` can only ever\n // drop the one handle it holds, so a double-arm would leak an interval that\n // nothing can clear afterwards \u2014 doubled PINGs at the SharedWorker for the\n // rest of the page, which reads to the reaper as a tab that never goes away.\n if (this.heartbeatHandle !== null) return;\n // Infinite disables the heartbeat (e.g. for environment where the\n // SharedWorker reaper is not needed).\n if (this.heartbeatIntervalMs === Infinity) return;\n this.heartbeatHandle = setInterval(() => {\n this.post({ type: CENTRIFUGE_INPUT_TYPE.PING });\n }, this.heartbeatIntervalMs);\n }\n\n private clearHeartbeat(): void {\n if (this.heartbeatHandle !== null) clearInterval(this.heartbeatHandle);\n this.heartbeatHandle = null;\n }\n\n private readonly handleWorkerError = () => {\n this.onWorkerFailed('Centrifuge worker failed.');\n };\n\n private readonly handlePortError = () => {\n this.onWorkerFailed('Centrifuge shared worker message decoding failed.');\n };\n\n private readonly handleSharedWorkerError = () => {\n this.onWorkerFailed('Centrifuge shared worker failed.');\n };\n\n /** Clear the Worker/port/backend references after a failure or stop. */\n private resetBackend(): void {\n this.worker = null;\n this.sharedWorker = null;\n this.port = null;\n this.backend = null;\n this.localSession = null;\n }\n\n /**\n * Post a message to the active backend. Accepts optional Transferable buffers\n * for zero-copy ArrayBuffer transfer.\n */\n private post(message: CentrifugeWorkerInput, transfer?: ArrayBuffer[]): void {\n if (this.worker) {\n postToPortLike(this.worker, message, transfer);\n return;\n }\n if (this.port) {\n postToPortLike(this.port, message, transfer);\n return;\n }\n if (this.localSession) {\n this.localSession.handle(message);\n return;\n }\n throw new Error('CentrifugeWorkerTransport.start() must be called first.');\n }\n}\n\n/**\n * Create a fully-configured CrossTabDataBus with a Centrifuge WebSocket transport.\n *\n * This is the primary entry point for consumers. It wires up the transport,\n * cluster coordination, and lifecycle management:\n *\n * ```ts\n * const bus = createCentrifugeDataBus({\n * connection: { url: 'wss://example.com/connection/websocket', options: { token: '\u2026' } },\n * trace: { enabled: true, sink: event => console.log(event) },\n * });\n * ```\n */\nexport function createCentrifugeDataBus<TData = unknown>(\n options: CreateCentrifugeDataBusOptions<TData>\n): CrossTabDataBus<CentrifugeDataBusConfig, TData> {\n const {\n clusterKey,\n connection,\n credentialProvider,\n heartbeatIntervalMs,\n sharedWorkerFactory,\n transferable,\n workerFactory,\n workerMode,\n ...dataBusOptions\n } = options;\n return new CrossTabDataBus({\n ...dataBusOptions,\n autoStart: true,\n clusterKey: clusterKey ?? connection.url,\n initialConfig: connection,\n transport: new CentrifugeWorkerTransport<TData>({\n ...(workerFactory ? { workerFactory } : {}),\n ...(sharedWorkerFactory ? { sharedWorkerFactory } : {}),\n ...(transferable === undefined ? {} : { transferable }),\n ...(workerMode ? { workerMode } : {}),\n ...(heartbeatIntervalMs === undefined ? {} : { heartbeatIntervalMs }),\n ...(credentialProvider ? { credentialProvider } : {})\n })\n });\n}\n\n/** Create the default dedicated Worker hosting the Centrifuge client. There is\n * no `typeof Worker` guard: `start()` asks `selectWorkerBackend` with\n * `worker: workerFactory !== undefined || typeof Worker !== 'undefined'`, that\n * function never returns a Worker backend its availability flags deny, and these\n * factories are only reached for a backend it did return. The degradation is\n * pinned where it is decided \u2014 `tests/worker-mode.test.ts` on the selector, and\n * \"falls back to the local session when the platform lacks both Worker APIs\" on\n * the transport \u2014 so a guard here could only ever re-check a fact the caller has\n * already established, and no runtime can reach it with the global missing.\n *\n * The `catch` below reads as uncovered in `pnpm test:coverage` and is not: it is\n * the CommonJS half of the module-format pair, and the report's `include` is\n * `src/**`, while the artifact that actually has no usable `import.meta.url` is\n * `dist/cjs/centrifuge.cjs`. `tests/dual-format.test.ts` asserts both sides \u2014\n * this message under CJS and a constructed Worker under ESM \u2014 so the leg is\n * pinned through the built output rather than missing. Treat that test as its\n * coverage. */\nfunction createDefaultWorker(): Worker {\n let workerUrl: URL;\n try {\n workerUrl = new URL('./centrifuge.worker.js', import.meta.url);\n } catch {\n throw new Error(\n 'The default Centrifuge Worker URL is unavailable in this module format; provide workerFactory explicitly.'\n );\n }\n return new Worker(workerUrl, {\n name: DEFAULT_STORAGE_PREFIX,\n type: 'module'\n });\n}\n\n/** Create the default SharedWorker. Each connecting port within the SharedWorker\n * creates its own CentrifugeSession with an independent WebSocket connection,\n * so refreshing or stopping one tab does not affect the others. Like its\n * dedicated twin it carries no `typeof SharedWorker` guard \u2014 the availability\n * flag in `start()` is what decides this is reachable \u2014 and like its twin the\n * `catch` below is pinned through the built artifacts (the CJS half of\n * `tests/dual-format.test.ts`) rather than being uncovered. */\nfunction createDefaultSharedWorker(): SharedWorker {\n let workerUrl: URL;\n try {\n workerUrl = new URL('./centrifuge.shared.worker.js', import.meta.url);\n } catch {\n throw new Error(\n 'The default Centrifuge SharedWorker URL is unavailable in this module format; provide sharedWorkerFactory explicitly.'\n );\n }\n return new SharedWorker(workerUrl, {\n name: `${DEFAULT_STORAGE_PREFIX}-shared`,\n type: 'module'\n });\n}\n\n/** Post `message` to a Worker or MessagePort, forwarding `transfer` buffers\n * when present. Both targets share the `postMessage(message, transfer?)`\n * signature, so a single helper eliminates the duplicated if/else at each\n * call site. */\nfunction postToPortLike(\n target: Pick<Worker, 'postMessage'>,\n message: CentrifugeWorkerInput,\n transfer?: ArrayBuffer[]\n): void {\n if (transfer) target.postMessage(message, transfer as Transferable[]);\n else target.postMessage(message);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * CentrifugeSession \u2014 a reusable wrapper around a single Centrifuge client.\n *\n * Provides a structured-clone-safe message protocol (INIT/SUBSCRIBE/UNSUBSCRIBE/\n * PUBLISH/STOP) so the same session class can run inside a Dedicated Worker,\n * a SharedWorker port, or directly on the main thread as a local fallback.\n */\nimport { Centrifuge } from 'centrifuge';\nimport { parseDataBusPublication } from './core/publication';\nimport type {\n PublicationContext,\n StateContext,\n Subscription,\n SubscriptionErrorContext\n} from 'centrifuge';\nimport type {\n CentrifugeWorkerConfig,\n CentrifugeWorkerInput,\n CentrifugeWorkerOutput\n} from './centrifuge-protocol';\nimport { CENTRIFUGE_INPUT_TYPE, CENTRIFUGE_OUTPUT_TYPE, WORKER_STATUS } from './utils/constants';\nimport { publicationMetadata } from './utils/metadata';\nimport { deserializeWorkerError, serializeError } from './utils/error-utils';\n\n/** Callback interface for posting messages back to the transport layer. */\nexport interface CentrifugeSessionSink<TData = unknown> {\n post(message: CentrifugeWorkerOutput<TData>, transfer?: ArrayBuffer[]): void;\n}\n\n/** A pending getToken/getChannelToken request awaiting a main-thread response. */\ninterface PendingTokenRequest {\n resolve: (token: string) => void;\n reject: (error: unknown) => void;\n}\n\n/** Centrifuge client options for token-bridge mode: the structured-clone-safe\n * subset plus the function-valued credential hooks this session installs.\n * Against the pinned SDK (5.7.4) `getToken` is the only one that is a real\n * client option (`build/types.d.ts:126`, and the same hook serves renewal);\n * `getChannelToken` appears nowhere in the package's types or build, and\n * subscriptions are created with `newSubscription(topic)` and no per-\n * subscription options \u2014 so that hook is never invoked, and the `'channelToken'`\n * request kind is produced only by the test fake. It stays because the Worker\n * protocol carries the kind: an SDK that later names it would need no change on\n * this side. Used only inside the session, never across the Worker boundary. */\ntype CentrifugeBridgedOptions = Omit<CentrifugeWorkerConfig, 'getToken' | 'getChannelToken'> & {\n getToken: () => Promise<string>;\n getChannelToken: (channel: string) => Promise<string>;\n};\n\n/**\n * Stateful Centrifuge client wrapper shared by Dedicated Worker, SharedWorker\n * ports and the main-thread local fallback. Each session owns one connection.\n */\nexport class CentrifugeSession<TData = unknown> {\n private client: Centrifuge | null = null;\n private readonly subscriptions = new Map<string, Subscription>();\n private transferable = false;\n private tokenBridge = false;\n private nextRequestId = 1;\n // Bumped on every initialize/stop. Async client callbacks capture the value\n // from the connection that created them so a stopped client cannot emit into\n // a later session instance (including the main-thread local fallback).\n private lifecycle = 0;\n private readonly pendingTokenRequests = new Map<number, PendingTokenRequest>();\n\n constructor(private readonly sink: CentrifugeSessionSink<TData>) {}\n\n /** Dispatch an incoming Worker message to the matching operation.\n * Unknown message types are ignored rather than thrown, so a future protocol\n * extension adding a new variant cannot crash an older session. */\n handle(message: CentrifugeWorkerInput): void {\n switch (message.type) {\n case CENTRIFUGE_INPUT_TYPE.INIT:\n this.initialize(message.url, message.config, message.transferable === true, message.tokenBridge === true);\n return;\n case CENTRIFUGE_INPUT_TYPE.SUBSCRIBE:\n this.subscribe(message.topic);\n return;\n case CENTRIFUGE_INPUT_TYPE.UNSUBSCRIBE:\n this.unsubscribe(message.topic);\n return;\n case CENTRIFUGE_INPUT_TYPE.PUBLISH:\n case CENTRIFUGE_INPUT_TYPE.PUBLISH_BIN:\n // Binary and JSON publish share the same Centrifuge client call; the\n // transport layer decides whether to transfer the ArrayBuffer.\n this.publish(message.topic, message.data, message.messageId, message.timestamp);\n return;\n case CENTRIFUGE_INPUT_TYPE.STOP:\n this.stop();\n return;\n case CENTRIFUGE_INPUT_TYPE.TOKEN_RESPONSE:\n this.resolveToken(message.requestId, message.token);\n return;\n case CENTRIFUGE_INPUT_TYPE.TOKEN_ERROR:\n this.rejectToken(message.requestId, deserializeWorkerError(message.error));\n return;\n default:\n return;\n }\n }\n\n /** Create the Centrifuge client, wire up lifecycle listeners, and connect. */\n private initialize(url: string, config: CentrifugeWorkerConfig, transferable: boolean, tokenBridge: boolean): void {\n if (this.client) return;\n const lifecycle = ++this.lifecycle;\n this.transferable = transferable;\n this.tokenBridge = tokenBridge;\n const clientOptions: CentrifugeWorkerConfig | CentrifugeBridgedOptions = tokenBridge\n ? {\n ...config,\n // Route credential fetches back to the main thread, where the\n // application's credentialProvider lives (config must stay\n // structured-cloneable, so functions cannot cross the boundary).\n getToken: () => this.requestToken('token', lifecycle),\n getChannelToken: (channel: string) => this.requestToken('channelToken', lifecycle, channel)\n }\n : config;\n const client = new Centrifuge(url, clientOptions);\n this.client = client;\n client.on('state', (context: StateContext) => {\n if (this.lifecycle !== lifecycle) return;\n this.post({ type: CENTRIFUGE_OUTPUT_TYPE.STATUS, status: normalizeStatus(context.newState) });\n });\n client.on('connected', () => {\n if (this.lifecycle !== lifecycle) return;\n this.post({ type: CENTRIFUGE_OUTPUT_TYPE.STATUS, status: WORKER_STATUS.CONNECTED });\n });\n client.on('disconnected', () => {\n if (this.lifecycle !== lifecycle) return;\n this.post({ type: CENTRIFUGE_OUTPUT_TYPE.STATUS, status: WORKER_STATUS.DISCONNECTED });\n });\n client.on('error', context => {\n if (this.lifecycle !== lifecycle) return;\n this.postError(context);\n });\n // Client-level publications are only for server-side subscriptions (where\n // no client Subscription object exists). For topics we have an active\n // subscription for, the subscription-level 'publication' listener handles\n // dispatch \u2014 skip here to avoid delivering the same message twice.\n client.on('publication', (context: PublicationContext) => {\n if (this.lifecycle !== lifecycle) return;\n const topic = context.channel || getPayloadTopic(context.data);\n if (!topic || this.subscriptions.has(topic)) return;\n this.postPublication(topic, context.data);\n });\n client.connect();\n }\n\n /** Subscribe to a Centrifuge channel. Reuses an existing subscription if one exists.\n * Listeners are only registered once per subscription object \u2014 a repeated\n * SUBSCRIBE for an already-tracked topic skips the listener wiring entirely,\n * avoiding the removeAllListeners + re-on churn on every duplicate message. */\n private subscribe(topic: string): void {\n if (!this.client) return this.postError(new Error('Centrifuge client is not initialized.'));\n const lifecycle = this.lifecycle;\n // If we already track this subscription, it already has our listeners \u2014\n // a duplicate SUBSCRIBE is a no-op (idempotent), matching the transport\n // contract. Only a fresh subscription needs listener wiring.\n const existing = this.subscriptions.get(topic);\n if (existing) {\n existing.subscribe();\n return;\n }\n let subscription = this.client.getSubscription(topic);\n if (!subscription) subscription = this.client.newSubscription(topic);\n // Scoped to the three events this method wires, so the client's own `state`\n // wiring is untouched \u2014 but `error` is NOT internal-listener-free: a no-op\n // `error` listener is installed on every subscription precisely\n // \"to avoid unhandled exception in EventEmitter for non-set error handler\",\n // and not by `Subscription` itself \u2014 that class body contains no `on('error')`\n // call at all (measured on the pinned copy), so the guard runs in the\n // `BaseSubscription` constructor every subscription reaches through `super`.\n // (centrifuge 5.7.4, `build/index.js`. The line number this used to cite \u2014 762\n // \u2014 matches neither the pinned copy, where the guard sits at 763-764, nor the\n // 5.7.0 copy in the store, where it is at 748; whether it drifted or was\n // mis-copied is not recoverable from here. That is the argument for quoting\n // the dependency's own comment as the locator instead of a number), and the\n // bundled emitter throws on an `error` emit with no listener\n // (`throw er; // Unhandled 'error' event` in the same file).\n // This call removes that guard,\n // and the `subscription.on('error', \u2026)` re-installation below puts one back for\n // as long as this session holds the subscription \u2014 three statements later (the\n // `unsubscribed` removal, the `subscriptions` set, the `publication` wiring), all\n // of them inside this same synchronous task, so no emit can land in between.\n // `unsubscribe()` removes both \u2014 this handler and the constructor's no-op \u2014 and\n // adds nothing back, which is settled rather than open. All twelve `emit('error')`\n // sites in `BaseSubscription` (its class body is lines 667-2673 of that file, and\n // the twelve were counted in it) sit behind a\n // `_isSubscribing()`/`_isSubscribed()` test except one, the\n // `badConfiguration` emit in `_getSubscriptionToken`; and after\n // `_setUnsubscribed` has moved `state` to `Unsubscribed` no path re-enters it\n // \u2014 the resubscribe timer's callback re-tests `_isSubscribing()` before it\n // does anything, the client's reconnect pass (`_sendSubscribeCommands`)\n // drives only subscriptions whose state is already `Subscribing`, and\n // `_refresh()`, the one entry into `_getSubscriptionToken` with no state test\n // of its own, is reachable only through `_refreshTimeout`, which\n // `_setUnsubscribed` clears via `_clearSubscribedState()`. So a throw would\n // need the listeners gone while the object is still live, and\n // `unsubscribe()` does not leave it live. `MapSubscription` and\n // `SharedPollSubscription` have their own emit sites but never carry this\n // session's listeners \u2014 `newSubscription` constructs a plain `Subscription`.\n subscription.removeAllListeners('publication');\n subscription.removeAllListeners('error');\n subscription.removeAllListeners('unsubscribed');\n this.subscriptions.set(topic, subscription);\n subscription.on('publication', context => {\n if (this.lifecycle !== lifecycle) return;\n this.postPublication(topic, context.data);\n });\n subscription.on('error', (context: SubscriptionErrorContext) => {\n if (this.lifecycle !== lifecycle) return;\n this.postError(context);\n });\n subscription.on('unsubscribed', () => {\n if (this.lifecycle === lifecycle) this.subscriptions.delete(topic);\n });\n subscription.subscribe();\n }\n\n /** Unsubscribe from a Centrifuge channel and clean up the local reference.\n * Listeners are removed before unsubscribing so a late `unsubscribed` event\n * cannot delete a subscription that a subsequent `subscribe()` re-added. */\n private unsubscribe(topic: string): void {\n const subscription = this.subscriptions.get(topic) ?? this.client?.getSubscription(topic);\n if (!subscription) return;\n subscription.removeAllListeners('publication');\n subscription.removeAllListeners('error');\n subscription.removeAllListeners('unsubscribed');\n this.subscriptions.delete(topic);\n subscription.unsubscribe();\n }\n\n /** Publish a message to the Centrifuge channel. */\n private publish(topic: string, data: unknown, messageId?: string, timestamp?: number): void {\n if (!this.client) return this.postError(new Error('Centrifuge client is not initialized.'));\n const lifecycle = this.lifecycle;\n // Centrifuge's payload is application-defined. Preserve legacy payloads;\n // when an ID is requested, send a small metadata envelope that compatible\n // servers can echo back for end-to-end deduplication.\n const hasMetadata = messageId !== undefined || timestamp !== undefined;\n const payload = hasMetadata\n ? {\n data,\n ...(messageId === undefined ? {} : { messageId }),\n ...(timestamp === undefined ? {} : { timestamp })\n }\n : data;\n void this.client.publish(topic, payload).catch(error => {\n if (this.lifecycle === lifecycle) this.postError(error);\n });\n }\n\n /** Forward a publication to the transport. Binary payloads take the\n * zero-copy `MESSAGE_BIN` path when `transferable` is enabled; everything\n * else is structured-cloned via `MESSAGE`. An empty topic means the\n * publication carried no channel info and is silently dropped.\n *\n * That drop is uncovered and dominated: the connection-level caller tests the\n * same emptiness before calling, and the subscription-level caller reads the\n * topic out of a key this session only ever populates from a SUBSCRIBE frame.\n * Measured \u2014 deleting this line leaves the whole suite green, and the frame\n * it withholds has nowhere to land anyway: the only handler registry is the\n * DataBus's `topicHandlers`, and every write to it sits behind\n * `assertPublicTopic` (at the head of `data-bus.ts`'s `subscribe()`, `publish()`\n * and `publishBatch()`), so no `''` key can\n * exist \u2014 while `topicMatchesPattern` answers false for an empty topic against\n * both `*` and `prefix.*`, so a wildcard handler cannot see it either. Neither\n * of those is a reason to delete this guard: it is the only one *at this layer*,\n * because neither the session nor the transport calls `assertPublicTopic` \u2014 a\n * SUBSCRIBE frame naming `''` creates a real Centrifuge subscription\n * (`newSubscription` does not validate a channel), so the undeliverable\n * publication would otherwise be posted back to the main thread. Kept as the\n * boundary statement, not counted as a closed leg. */\n private postPublication(topic: string, data: unknown): void {\n if (!topic) return;\n if (this.transferable && data instanceof ArrayBuffer) {\n this.post({ type: CENTRIFUGE_OUTPUT_TYPE.MESSAGE_BIN, topic, data }, [data]);\n return;\n }\n const publication = parseDataBusPublication<TData>(data, topic);\n if (!publication) return;\n this.post({\n type: CENTRIFUGE_OUTPUT_TYPE.MESSAGE,\n topic: publication.topic,\n data: publication.data,\n ...publicationMetadata(publication.messageId, publication.timestamp)\n });\n }\n\n /** Disconnect the client and clear all subscriptions. */\n private stop(): void {\n // Invalidate callbacks from the connection being stopped before disconnect\n // so its synchronous or queued events cannot leak into a later session.\n this.lifecycle += 1;\n // Settle every in-flight credential request so a stop cannot leave the\n // Worker awaiting a main-thread response forever.\n for (const [, pending] of this.pendingTokenRequests) {\n pending.reject(new Error('Centrifuge session stopped before the credential was resolved.'));\n }\n this.pendingTokenRequests.clear();\n this.client?.disconnect();\n this.subscriptions.clear();\n this.client = null;\n this.post({ type: CENTRIFUGE_OUTPUT_TYPE.STATUS, status: WORKER_STATUS.DISCONNECTED });\n }\n\n /** Issue a credential request to the main thread and await the response.\n * Used as Centrifuge's `getToken` / `getChannelToken` when token bridging is\n * enabled; resolved or rejected by a matching TOKEN_RESPONSE / TOKEN_ERROR. */\n private requestToken(kind: 'token' | 'channelToken', lifecycle: number, channel?: string): Promise<string> {\n // The provider is installed on a specific Centrifuge client. An old client\n // must not be able to route a late credential request into a replacement\n // session merely because it retained the callback closure.\n if (this.lifecycle !== lifecycle) {\n return Promise.reject(new Error('Centrifuge client lifecycle has ended before the credential was requested.'));\n }\n const requestId = this.nextRequestId;\n this.nextRequestId += 1;\n return new Promise<string>((resolve, reject) => {\n this.pendingTokenRequests.set(requestId, { resolve, reject });\n this.post({\n type: CENTRIFUGE_OUTPUT_TYPE.TOKEN_REQUEST,\n requestId,\n kind,\n ...(channel === undefined ? {} : { channel })\n });\n });\n }\n\n private resolveToken(requestId: number, token: string): void {\n const pending = this.pendingTokenRequests.get(requestId);\n if (!pending) return;\n this.pendingTokenRequests.delete(requestId);\n pending.resolve(token);\n }\n\n private rejectToken(requestId: number, error: unknown): void {\n const pending = this.pendingTokenRequests.get(requestId);\n if (!pending) return;\n this.pendingTokenRequests.delete(requestId);\n pending.reject(error);\n }\n\n /** Forward a message to the sink (the transport layer). */\n private post(message: CentrifugeWorkerOutput<TData>, transfer?: ArrayBuffer[]): void {\n this.sink.post(message, transfer);\n }\n\n /** Serialise and report an error. The Centrifuge client handles reconnection\n * internally, so a transient error should not trigger a `STATUS: error` that\n * would cause `selectActiveWorkers()` to exclude this worker from routing.\n * Fatal errors are distinguished by the client eventually emitting\n * `disconnected` without a subsequent `connected`. */\n private postError(error: unknown): void {\n this.post({ type: CENTRIFUGE_OUTPUT_TYPE.ERROR, error: serializeError(error) });\n }\n}\n\n/** Extract a topic from a Centrifuge publication payload if one is present.\n * Handles both direct `channel` fields and the nested `push.channel` shape\n * that Centrifugo uses for some server-side push types. */\nfunction getPayloadTopic(data: unknown): string {\n if (!data || typeof data !== 'object') return '';\n const payload = data as Record<string, unknown>;\n // Prefer nested push.channel (server-side push) then fall back to top-level channel.\n const push = payload.push;\n const nested = typeof push === 'object' && push !== null ? (push as Record<string, unknown>).channel : undefined;\n const topic = nested ?? payload.channel;\n return typeof topic === 'string' ? topic : '';\n}\n\n/** Map a Centrifuge state string to the DataBus's status vocabulary.\n * 'connecting' and 'connected' pass through; anything else (e.g. 'reconnecting',\n * 'disconnected') maps to 'disconnected'. */\nconst LIVE_STATES = new Set<string>([WORKER_STATUS.CONNECTING, WORKER_STATUS.CONNECTED]);\nfunction normalizeStatus(status: string): 'connecting' | 'connected' | 'disconnected' {\n return LIVE_STATES.has(status) ? (status as 'connecting' | 'connected') : 'disconnected';\n}\n", "/**\n * Worker-thread protocol for Centrifuge WebSocket transport.\n *\n * Defines the message types exchanged between the main thread and a Web Worker\n * (dedicated or shared) that runs a centrifuge client. The worker is isolated\n * from the main thread so that WebSocket lifecycle, token refresh, and binary\n * data handling never block the UI.\n *\n * The message `type` discriminant values are derived from the constants in\n * `utils/constants.ts` so main-thread and Worker sides reference the same\n * values and can never drift apart.\n */\nimport type { Options } from 'centrifuge';\nimport type { CENTRIFUGE_INPUT_TYPE, CENTRIFUGE_OUTPUT_TYPE } from './utils/constants';\nimport type { WorkerStatus } from './core/types';\nimport type { SerializedWorkerError } from './utils/error-utils';\n\nexport type { SerializedWorkerError } from './utils/error-utils';\n\n/**\n * Default interval between main-thread PING heartbeats to a SharedWorker. The\n * SharedWorker reaps a silent port after `SESSION_TIMEOUT_MULTIPLIER` intervals.\n * Shared here so the main thread (heartbeat sender) and the SharedWorker (reaper)\n * always agree on the cadence.\n */\nexport const DEFAULT_HEARTBEAT_INTERVAL_MS = 10_000;\n/** SharedWorker per-port session timeout, expressed as a multiple of that port's\n * heartbeat interval. A live port PINGs every heartbeat interval, so a timeout of\n * several intervals tolerates throttled tabs without reaping healthy sessions. */\nexport const DEFAULT_SESSION_TIMEOUT_MULTIPLIER = 3;\n/** Default per-port session timeout in ms, derived from the heartbeat interval\n * and the multiplier. Used as the fallback when a port sends no INIT config. */\nexport const DEFAULT_SESSION_TIMEOUT_MS =\n DEFAULT_HEARTBEAT_INTERVAL_MS * DEFAULT_SESSION_TIMEOUT_MULTIPLIER;\n\n// These options reference browser APIs (WebSocket, EventSource, fetch, \u2026) that\n// do not exist inside a Worker \u2014 the worker imports its own WebSocket. Nothing\n// strips them at runtime: `buildInitInput` forwards `config.options` verbatim,\n// so this exclusion is compile-time only. A value forced through with a cast is\n// caught by the runtime gate that does exist, `assertStructuredCloneable` on the\n// same options, which throws a TypeError from the transport before any backend\n// is created \u2014 a function-valued option would otherwise die at the structured-\n// clone boundary with a DataCloneError instead.\ntype WorkerUnsafeOption =\n | 'eventsource'\n | 'fetch'\n | 'getData'\n | 'getToken'\n | 'networkEventTarget'\n | 'readableStream'\n | 'sockjs'\n | 'websocket';\n\n/** Centrifuge options safe to pass into a Worker; unsafe options are explicitly\n * excluded and set to `never` so the compiler rejects them at the call site. */\nexport type CentrifugeWorkerConfig = Omit<Partial<Options>, WorkerUnsafeOption> & {\n [Key in WorkerUnsafeOption]?: never;\n};\n\n/**\n * Async credential provider run on the main thread. Centrifuge client options\n * are structured-cloned into the Worker, so function-valued `getToken` /\n * `getChannelToken` cannot travel with the config. When a provider is set, the\n * Worker asks the main thread for each fresh token over a TOKEN_REQUEST /\n * TOKEN_RESPONSE exchange and this provider supplies it from application\n * context (async token endpoints, SecureStorage, etc.).\n */\nexport interface CentrifugeCredentialProvider {\n /** Return a fresh top-level connection token. Called on connect and refresh. */\n getToken: () => MaybePromise<string | null | undefined>;\n /** Return a channel token for a channel. Centrifuge falls back to `getToken`\n * when channel tokens are not configured by the server. */\n getChannelToken?: (channel: string) => MaybePromise<string | null | undefined>;\n}\n\n/** A value or a promise of a value. */\nexport type MaybePromise<T> = T | Promise<T>;\n\n/** Messages sent from the main thread to the Worker. All variants are\n * structured-cloneable; `PUBLISH_BIN` carries an ArrayBuffer (transferable). */\nexport type CentrifugeWorkerInput =\n /** Initial connection: URL + config. Sent once per backend creation.\n * `transferable` enables ArrayBuffer zero-copy for subsequent PUBLISH_BIN.\n * `heartbeatIntervalMs` overrides the SharedWorker PING cadence.\n * `tokenBridge` asks the Worker to route getToken/getChannelToken back to\n * the main thread instead of holding function-valued config. */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.INIT; url: string; config: CentrifugeWorkerConfig; transferable?: boolean; heartbeatIntervalMs?: number; tokenBridge?: boolean }\n /** Subscribe to a channel. Idempotent \u2014 re-subscribing is a no-op. */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.SUBSCRIBE; topic: string }\n /** Unsubscribe from a channel. Idempotent. */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.UNSUBSCRIBE; topic: string }\n /** Publish a structured-cloneable payload to a channel. */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.PUBLISH; topic: string; data: unknown; messageId?: string; timestamp?: number }\n /** Publish an ArrayBuffer via Transferable (zero-copy when `transferable` is on). */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.PUBLISH_BIN; topic: string; data: ArrayBuffer; messageId?: string; timestamp?: number }\n /** Heartbeat from the main thread; the SharedWorker reaps silent ports. */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.PING }\n /** Disconnect the client and clear all subscriptions. */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.STOP }\n /** Fresh credential for an earlier TOKEN_REQUEST, resolved by requestId. */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.TOKEN_RESPONSE; requestId: number; token: string }\n /** A credential request failed (provider rejected or returned no token). */\n | { type: typeof CENTRIFUGE_INPUT_TYPE.TOKEN_ERROR; requestId: number; error: SerializedWorkerError };\n\n/** Messages sent from the Worker back to the main thread. The main thread\n * routes these to the DataBusTransportHandlers via handleOutput(). */\nexport type CentrifugeWorkerOutput<TData = unknown> =\n /** Connection status changed. Maps Centrifuge states to the DataBus vocabulary. */\n | { type: typeof CENTRIFUGE_OUTPUT_TYPE.STATUS; status: WorkerStatus }\n /** A JSON publication arrived. Routed to onMessage via handleOutput. */\n | { type: typeof CENTRIFUGE_OUTPUT_TYPE.MESSAGE; topic: string; data: TData; messageId?: string; timestamp?: number }\n /** A binary publication arrived (Transferable). Routed to onMessage with the ArrayBuffer. */\n | { type: typeof CENTRIFUGE_OUTPUT_TYPE.MESSAGE_BIN; topic: string; data: ArrayBuffer; messageId?: string; timestamp?: number }\n /** A non-fatal error occurred. Does not imply disconnection (the client retries internally). */\n | { type: typeof CENTRIFUGE_OUTPUT_TYPE.ERROR; error: SerializedWorkerError }\n /** The Worker needs a fresh credential. The main thread resolves it via a\n * matching TOKEN_RESPONSE / TOKEN_ERROR carrying the same `requestId`. */\n | { type: typeof CENTRIFUGE_OUTPUT_TYPE.TOKEN_REQUEST; requestId: number; kind: 'token' | 'channelToken'; channel?: string }\n /** The SharedWorker's reaper is about to close this port, so its session and\n * WebSocket are going away with it. Sent as the last message on the port: a tab\n * whose heartbeat was merely starved (a long task, background throttling) is\n * alive and can rebuild, but only if it learns that its backend is gone. */\n | { type: typeof CENTRIFUGE_OUTPUT_TYPE.SESSION_REAPED };\n", "/**\n * Centrifuge WebSocket transport that runs inside a Web Worker.\n *\n * Supports three backends. Which one is tried first depends on `workerMode`:\n * - `dedicated` (the default): Dedicated Worker \u2192 SharedWorker \u2192 in-process\n * - `shared` / `auto`: SharedWorker \u2192 Dedicated Worker \u2192 in-process\n *\n * 1. SharedWorker \u2014 one WebSocket per tab session, hosted in a shared process\n * 2. Dedicated Worker \u2014 one WebSocket per tab\n * 3. In-process (local) \u2014 Centrifuge runs on the main thread (fallback when\n * neither Worker type is available, e.g. in non-browser environments)\n *\n * Binary data (ArrayBuffer) can be transferred via Transferable when the\n * `transferable` option is enabled, avoiding structured-clone overhead.\n */\n\nimport { CrossTabDataBus } from './core/data-bus';\nimport type { CrossTabDataBusOptions } from './core/data-bus';\nimport type { DataBusTransport, DataBusTransportHandlers } from './core/types';\nimport { CentrifugeSession } from './centrifuge-session';\nimport { selectWorkerBackend } from './worker-mode';\nimport type { WorkerBackend, WorkerMode } from './worker-mode';\nimport type {\n CentrifugeCredentialProvider,\n CentrifugeWorkerConfig,\n CentrifugeWorkerInput,\n CentrifugeWorkerOutput\n} from './centrifuge-protocol';\nimport type { DataBusPublishOptions } from './core/types';\nimport { DEFAULT_HEARTBEAT_INTERVAL_MS } from './centrifuge-protocol';\nimport { CENTRIFUGE_INPUT_TYPE, CENTRIFUGE_OUTPUT_TYPE, DEFAULT_STORAGE_PREFIX, WORKER_BACKEND, WORKER_MODE, WORKER_STATUS } from './utils/constants';\nimport { deserializeWorkerError, serializeError } from './utils/error-utils';\nimport { publicationMetadata } from './utils/metadata';\nimport { assertHeartbeatInterval, assertStructuredCloneable } from './utils/validation';\n\nexport type { CentrifugeCredentialProvider, CentrifugeWorkerConfig, SerializedWorkerError } from './centrifuge-protocol';\nexport type { WorkerBackend, WorkerMode } from './worker-mode';\n\n/** WebSocket connection parameters passed to the Centrifuge Worker. */\nexport interface CentrifugeDataBusConfig {\n /** Centrifuge server WebSocket URL. */\n url: string;\n /** Centrifuge client options (token, channel params, etc.). */\n options?: CentrifugeWorkerConfig;\n}\n\n/** Options for configuring the Worker backend (dedicated, shared, or local). */\nexport interface CentrifugeWorkerTransportOptions {\n /** Custom dedicated Worker factory. Used for testing or bundler integration. */\n workerFactory?: () => Worker;\n /** Custom SharedWorker factory. */\n sharedWorkerFactory?: () => SharedWorker;\n /** Preferred Worker mode: 'dedicated', 'shared', or 'auto'. */\n workerMode?: WorkerMode;\n /** Enable transferable (ArrayBuffer) support for binary data. */\n transferable?: boolean;\n /** Interval (ms) between PING heartbeats sent to the SharedWorker. The\n * SharedWorker reaps a silent port after `DEFAULT_SESSION_TIMEOUT_MULTIPLIER`\n * \u00D7 this interval. Pass `Infinity` to disable heartbeats entirely. Defaults\n * to `DEFAULT_HEARTBEAT_INTERVAL_MS`. */\n heartbeatIntervalMs?: number;\n /** Async credential provider. Runs on the main thread: the Worker cannot\n * hold function-valued Centrifuge options (they are not structured-cloneable),\n * so when a provider is set the Worker asks for each fresh token over a\n * TOKEN_REQUEST / TOKEN_RESPONSE bridge and this provider supplies it. */\n credentialProvider?: CentrifugeCredentialProvider;\n}\n\n/** Options for creating a fully-configured CrossTabDataBus with a Centrifuge transport. */\nexport interface CreateCentrifugeDataBusOptions<TData = unknown>\n extends Omit<\n CrossTabDataBusOptions<CentrifugeDataBusConfig, TData>,\n 'autoStart' | 'clusterKey' | 'initialConfig' | 'transport'\n >,\n CentrifugeWorkerTransportOptions {\n /** Centrifuge connection configuration. */\n connection: CentrifugeDataBusConfig;\n /** Cluster key for cross-tab coordination. Defaults to the connection URL. */\n clusterKey?: string;\n}\n\n/**\n * Transport layer that runs a Centrifuge WebSocket client inside a Web Worker.\n *\n * Delegates the actual WebSocket connection to a Worker (dedicated or shared)\n * or falls back to an in-process CentrifugeSession. On the two Worker backends\n * the Worker is isolated from the main thread, so WebSocket lifecycle, token\n * refresh, and binary data handling stay off the UI thread. On the `local`\n * fallback there is no Worker: the same work runs on the main thread, which is\n * the point of the fallback (it keeps the transport usable where no Worker API\n * exists) rather than a property it preserves.\n */\nexport class CentrifugeWorkerTransport<TData = unknown>\n implements DataBusTransport<CentrifugeDataBusConfig, TData>\n{\n readonly diagnosticsName = 'centrifuge';\n private readonly workerMode: WorkerMode;\n private readonly transferable: boolean;\n private readonly heartbeatIntervalMs: number;\n private readonly workerFactory: (() => Worker) | undefined;\n private readonly sharedWorkerFactory: (() => SharedWorker) | undefined;\n private readonly credentialProvider: CentrifugeCredentialProvider | undefined;\n private backend: WorkerBackend | null = null;\n private worker: Worker | null = null;\n private sharedWorker: SharedWorker | null = null;\n private port: MessagePort | null = null;\n private heartbeatHandle: ReturnType<typeof setInterval> | null = null;\n private localSession: CentrifugeSession<TData> | null = null;\n private handlers: DataBusTransportHandlers<TData> | null = null;\n // Monotonically increasing counter, bumped when a *Worker* backend is created\n // (`startDedicatedWorker`, `startSharedWorker`) and when the transport stops.\n // The in-process `localSession` creation does not bump it, which is why\n // `isCurrentBackend()` compares worker/port/localSession identity as well.\n // Only the asynchronous credential bridge reads the counter: a provider may\n // settle after the backend it was answering is gone. Worker error events need\n // no such check, but not for the reason this note used to give \u2014 the top of\n // `stop()` bumps the counter and only then removes the listeners, so the\n // generation moves on *before* the removal, not after, while `onWorkerFailed()`\n // removes them without touching the counter at all. The containment in both\n // cases is that they happen in one synchronous task, together with\n // `handlers = null`, and no message or error event can be delivered to this\n // object mid-task.\n private generation = 0;\n\n get diagnosticsBackend(): string {\n return this.backend ?? 'uninitialized';\n }\n\n constructor(options: CentrifugeWorkerTransportOptions = {}) {\n this.workerMode = options.workerMode ?? WORKER_MODE.DEDICATED;\n this.transferable = options.transferable ?? false;\n this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;\n assertHeartbeatInterval(this.heartbeatIntervalMs);\n this.workerFactory = options.workerFactory;\n this.sharedWorkerFactory = options.sharedWorkerFactory;\n this.credentialProvider = options.credentialProvider;\n }\n\n /**\n * Start the transport: select a backend, initialise the Worker (or local\n * session), and send the INIT message with connection parameters.\n */\n start(config: CentrifugeDataBusConfig, handlers: DataBusTransportHandlers<TData>): void {\n if (this.backend) return;\n assertStructuredCloneable(config.options ?? {});\n this.handlers = handlers;\n // Runtime capability OR an injected factory counts as availability; a\n // factory-less browser must use the bundled default Workers instead of\n // silently degrading to the local session.\n const backend = selectWorkerBackend(this.workerMode, {\n worker: this.workerFactory !== undefined || typeof Worker !== 'undefined',\n sharedWorker: this.sharedWorkerFactory !== undefined || typeof SharedWorker !== 'undefined'\n });\n const input = this.buildInitInput(config);\n if (backend === WORKER_BACKEND.SHARED) {\n this.startSharedWorker(input);\n this.backend = WORKER_BACKEND.SHARED;\n return;\n }\n if (backend === WORKER_BACKEND.DEDICATED) {\n this.startDedicatedWorker(input);\n this.backend = WORKER_BACKEND.DEDICATED;\n return;\n }\n this.localSession = new CentrifugeSession<TData>({ post: this.handleSessionOutput });\n this.localSession.handle(input);\n this.backend = WORKER_BACKEND.LOCAL;\n }\n\n subscribe(topic: string): void {\n this.post({ type: CENTRIFUGE_INPUT_TYPE.SUBSCRIBE, topic });\n }\n\n unsubscribe(topic: string): void {\n this.post({ type: CENTRIFUGE_INPUT_TYPE.UNSUBSCRIBE, topic });\n }\n\n /**\n * Publish data to `topic`. Binary data (ArrayBuffer) is sent via Transferable\n * when `transferable` is enabled, avoiding a structured-clone cycle.\n */\n publish(topic: string, data: unknown, options?: DataBusPublishOptions): void {\n if (this.transferable && data instanceof ArrayBuffer) {\n this.post({ type: CENTRIFUGE_INPUT_TYPE.PUBLISH_BIN, topic, data, ...publicationMetadata(options?.messageId, options?.timestamp) }, [data]);\n return;\n }\n this.post({ type: CENTRIFUGE_INPUT_TYPE.PUBLISH, topic, data, ...publicationMetadata(options?.messageId, options?.timestamp) });\n }\n\n /**\n * Gracefully stop the transport: send STOP, clean up event listeners, and\n * terminate the Worker (or close the SharedWorker port).\n */\n stop(): void {\n if (!this.backend) return;\n this.generation++;\n this.post({ type: CENTRIFUGE_INPUT_TYPE.STOP });\n this.clearHeartbeat();\n if (this.worker) {\n this.worker.removeEventListener('message', this.handleMessage);\n this.worker.removeEventListener('error', this.handleWorkerError);\n this.worker.terminate();\n }\n if (this.sharedWorker) {\n this.detachSharedWorkerListeners();\n this.port?.close();\n }\n this.resetBackend();\n this.handlers = null;\n }\n\n /** Build the INIT payload sent to the Worker / local session. Optional fields\n * are only included when they deviate from the defaults, so the Worker's own\n * default-resolution logic kicks in for the common case. */\n private buildInitInput(config: CentrifugeDataBusConfig): CentrifugeWorkerInput {\n return {\n type: CENTRIFUGE_INPUT_TYPE.INIT,\n url: config.url,\n config: config.options ?? {},\n ...(this.transferable ? { transferable: true } : {}),\n ...(this.credentialProvider ? { tokenBridge: true } : {}),\n ...(this.heartbeatIntervalMs !== DEFAULT_HEARTBEAT_INTERVAL_MS\n ? { heartbeatIntervalMs: this.heartbeatIntervalMs }\n : {})\n };\n }\n\n /** Create and initialise a dedicated Worker, then send the INIT message. */\n private startDedicatedWorker(input: CentrifugeWorkerInput): void {\n this.generation += 1;\n const worker = (this.workerFactory ?? createDefaultWorker)();\n this.worker = worker;\n worker.addEventListener('message', this.handleMessage);\n worker.addEventListener('error', this.handleWorkerError);\n worker.postMessage(input);\n }\n\n /** Create and initialise a SharedWorker, open the MessagePort, and send the INIT message. */\n private startSharedWorker(input: CentrifugeWorkerInput): void {\n this.generation += 1;\n const shared = (this.sharedWorkerFactory ?? createDefaultSharedWorker)();\n this.sharedWorker = shared;\n const port = shared.port;\n this.port = port;\n port.addEventListener('message', this.handleMessage);\n port.addEventListener('messageerror', this.handlePortError);\n shared.addEventListener('error', this.handleSharedWorkerError);\n port.start();\n port.postMessage(input);\n this.startHeartbeat();\n }\n\n /** Handle a message event from the Worker (dedicated or shared). */\n private readonly handleMessage = (event: MessageEvent<CentrifugeWorkerOutput<TData>>) => {\n this.handleOutput(event.data);\n };\n\n /** Handle a message from the in-process CentrifugeSession (local fallback). */\n private readonly handleSessionOutput = (message: CentrifugeWorkerOutput<TData>) => {\n this.handleOutput(message);\n };\n\n /** Route a Worker output message to the appropriate handler callback.\n * Three message sources reach it \u2014 the dedicated Worker, the SharedWorker\n * MessagePort, and the in-process session sink \u2014 but through two call sites: the\n * first two share this one `handleMessage` handler, registered on the Worker and\n * on the port, and only the session path arrives through its own wrapper. */\n private handleOutput(message: CentrifugeWorkerOutput<TData>): void {\n if (message.type === CENTRIFUGE_OUTPUT_TYPE.STATUS) this.handlers?.onStatus(message.status);\n if (message.type === CENTRIFUGE_OUTPUT_TYPE.MESSAGE) this.handlers?.onMessage({ topic: message.topic, data: message.data, ...publicationMetadata(message.messageId, message.timestamp) });\n if (message.type === CENTRIFUGE_OUTPUT_TYPE.MESSAGE_BIN) this.handlers?.onMessage({ topic: message.topic, data: message.data as TData, ...publicationMetadata(message.messageId, message.timestamp) });\n if (message.type === CENTRIFUGE_OUTPUT_TYPE.ERROR) this.handlers?.onError(deserializeWorkerError(message.error));\n if (message.type === CENTRIFUGE_OUTPUT_TYPE.TOKEN_REQUEST) this.resolveTokenRequest(message.requestId, message.kind, message.channel);\n // The SharedWorker is closing this port because our PING heartbeat went silent\n // for longer than its session timeout \u2014 a long synchronous task, or the\n // throttling a backgrounded tab gets. Nothing else can tell us: a MessagePort\n // has no close event, and posting into the closed end succeeds and is\n // discarded. Treating it as any other backend loss is what makes the bus report\n // it and lets the existing cooldown recovery rebuild the port; measured in a\n // real browser before this existed, a 34 s stall left a shared-mode tab owning\n // its topic with zero server-side subscribers while reporting\n // `state: healthy` / `status: connected` / `transportReady: true`.\n if (message.type === CENTRIFUGE_OUTPUT_TYPE.SESSION_REAPED) {\n this.onWorkerFailed(\"The SharedWorker ended this tab's session after its port heartbeat went silent.\");\n }\n }\n\n /** Resolve a Worker credential request from the main thread: call the\n * credentialProvider, then post the fresh token (or a serialized failure)\n * back to the session that issued the request. */\n private resolveTokenRequest(requestId: number, kind: 'token' | 'channelToken', channel: string | undefined): void {\n // Credential providers may be asynchronous. Capture the backend identity\n // before awaiting so a stop/start that replaces the Worker cannot receive\n // a late token (or error) belonging to the superseded session. Request IDs\n // restart at 1 for a fresh CentrifugeSession, so without this guard a stale\n // reply can incorrectly satisfy the replacement session's request.\n const generation = this.generation;\n const worker = this.worker;\n const port = this.port;\n const localSession = this.localSession;\n const isCurrentBackend = () =>\n this.generation === generation &&\n this.worker === worker &&\n this.port === port &&\n this.localSession === localSession;\n const provider = this.credentialProvider;\n let value: string | null | Promise<string | null | undefined> | undefined;\n try {\n value = kind === 'channelToken' && provider?.getChannelToken\n ? provider.getChannelToken(channel ?? '')\n : provider?.getToken?.();\n } catch (error) {\n // This arm never yields, but the provider is application code and can\n // stop or replace the transport synchronously before throwing. Without\n // the check `post()` then addresses a backend that no longer exists.\n if (isCurrentBackend()) {\n this.post({ type: CENTRIFUGE_INPUT_TYPE.TOKEN_ERROR, requestId, error: serializeError(error) });\n }\n return;\n }\n Promise.resolve(value).then(\n token => {\n if (!isCurrentBackend()) return;\n if (typeof token !== 'string' || token.length === 0) {\n this.post({\n type: CENTRIFUGE_INPUT_TYPE.TOKEN_ERROR,\n requestId,\n error: serializeError(new Error(`The credential provider returned no ${kind}.`))\n });\n return;\n }\n this.post({ type: CENTRIFUGE_INPUT_TYPE.TOKEN_RESPONSE, requestId, token });\n },\n error => {\n if (!isCurrentBackend()) return;\n this.post({ type: CENTRIFUGE_INPUT_TYPE.TOKEN_ERROR, requestId, error: serializeError(error) });\n }\n );\n }\n\n /** Handle a Worker-level failure (crash, message decode error). Discards the\n * dead backend so a later start()/reopen can rebuild from scratch, and\n * signals an error status so the DataBus can trigger recovery. A superseded\n * backend cannot reach this: its listeners are removed before the transport\n * moves on, which is what keeps late Worker errors from tearing down a fresh\n * session (pinned by 'ignores error events from a superseded worker'). */\n private onWorkerFailed(message: string): void {\n // Remove the message listener and release the old backend before recovery\n // reopens, so late messages from the failed Worker/port cannot be routed\n // into the freshly started session.\n this.worker?.removeEventListener('message', this.handleMessage);\n this.worker?.removeEventListener('error', this.handleWorkerError);\n this.worker?.terminate();\n this.detachSharedWorkerListeners();\n this.port?.close();\n this.clearHeartbeat();\n this.resetBackend();\n this.handlers?.onError(new Error(message));\n this.handlers?.onStatus(WORKER_STATUS.ERROR);\n }\n\n /** Remove every listener attached to the current SharedWorker and its port. */\n private detachSharedWorkerListeners(): void {\n this.port?.removeEventListener('message', this.handleMessage);\n this.port?.removeEventListener('messageerror', this.handlePortError);\n this.sharedWorker?.removeEventListener('error', this.handleSharedWorkerError);\n }\n\n /** Periodically ping the SharedWorker so its session reaper can detect a dead tab. */\n private startHeartbeat(): void {\n // Uncovered and dominated. The only caller is `startSharedWorker()`, which\n // `start()` reaches past its own `if (this.backend) return` guard, and\n // `backend` becomes null only through `stop()` and `onWorkerFailed()` \u2014 both\n // of which call `clearHeartbeat()` first. Measured: deleting this line leaves\n // the whole suite green. It stays because `clearHeartbeat()` can only ever\n // drop the one handle it holds, so a double-arm would leak an interval that\n // nothing can clear afterwards \u2014 doubled PINGs at the SharedWorker for the\n // rest of the page, which reads to the reaper as a tab that never goes away.\n if (this.heartbeatHandle !== null) return;\n // Infinite disables the heartbeat (e.g. for environment where the\n // SharedWorker reaper is not needed).\n if (this.heartbeatIntervalMs === Infinity) return;\n this.heartbeatHandle = setInterval(() => {\n this.post({ type: CENTRIFUGE_INPUT_TYPE.PING });\n }, this.heartbeatIntervalMs);\n }\n\n private clearHeartbeat(): void {\n if (this.heartbeatHandle !== null) clearInterval(this.heartbeatHandle);\n this.heartbeatHandle = null;\n }\n\n private readonly handleWorkerError = () => {\n this.onWorkerFailed('Centrifuge worker failed.');\n };\n\n private readonly handlePortError = () => {\n this.onWorkerFailed('Centrifuge shared worker message decoding failed.');\n };\n\n private readonly handleSharedWorkerError = () => {\n this.onWorkerFailed('Centrifuge shared worker failed.');\n };\n\n /** Clear the Worker/port/backend references after a failure or stop. */\n private resetBackend(): void {\n this.worker = null;\n this.sharedWorker = null;\n this.port = null;\n this.backend = null;\n this.localSession = null;\n }\n\n /**\n * Post a message to the active backend. Accepts optional Transferable buffers\n * for zero-copy ArrayBuffer transfer.\n */\n private post(message: CentrifugeWorkerInput, transfer?: ArrayBuffer[]): void {\n if (this.worker) {\n postToPortLike(this.worker, message, transfer);\n return;\n }\n if (this.port) {\n postToPortLike(this.port, message, transfer);\n return;\n }\n if (this.localSession) {\n this.localSession.handle(message);\n return;\n }\n throw new Error('CentrifugeWorkerTransport.start() must be called first.');\n }\n}\n\n/**\n * Create a fully-configured CrossTabDataBus with a Centrifuge WebSocket transport.\n *\n * This is the primary entry point for consumers. It wires up the transport,\n * cluster coordination, and lifecycle management:\n *\n * ```ts\n * const bus = createCentrifugeDataBus({\n * connection: { url: 'wss://example.com/connection/websocket', options: { token: '\u2026' } },\n * trace: { enabled: true, sink: event => console.log(event) },\n * });\n * ```\n */\nexport function createCentrifugeDataBus<TData = unknown>(\n options: CreateCentrifugeDataBusOptions<TData>\n): CrossTabDataBus<CentrifugeDataBusConfig, TData> {\n const {\n clusterKey,\n connection,\n credentialProvider,\n heartbeatIntervalMs,\n sharedWorkerFactory,\n transferable,\n workerFactory,\n workerMode,\n ...dataBusOptions\n } = options;\n return new CrossTabDataBus({\n ...dataBusOptions,\n autoStart: true,\n clusterKey: clusterKey ?? connection.url,\n initialConfig: connection,\n transport: new CentrifugeWorkerTransport<TData>({\n ...(workerFactory ? { workerFactory } : {}),\n ...(sharedWorkerFactory ? { sharedWorkerFactory } : {}),\n ...(transferable === undefined ? {} : { transferable }),\n ...(workerMode ? { workerMode } : {}),\n ...(heartbeatIntervalMs === undefined ? {} : { heartbeatIntervalMs }),\n ...(credentialProvider ? { credentialProvider } : {})\n })\n });\n}\n\n/** Create the default dedicated Worker hosting the Centrifuge client. There is\n * no `typeof Worker` guard: `start()` asks `selectWorkerBackend` with\n * `worker: workerFactory !== undefined || typeof Worker !== 'undefined'`, that\n * function never returns a Worker backend its availability flags deny, and these\n * factories are only reached for a backend it did return. The degradation is\n * pinned where it is decided \u2014 `tests/worker-mode.test.ts` on the selector, and\n * \"falls back to the local session when the platform lacks both Worker APIs\" on\n * the transport \u2014 so a guard here could only ever re-check a fact the caller has\n * already established, and no runtime can reach it with the global missing.\n *\n * The `catch` below reads as uncovered in `pnpm test:coverage` and is not: it is\n * the CommonJS half of the module-format pair, and the report's `include` is\n * `src/**`, while the artifact that actually has no usable `import.meta.url` is\n * `dist/cjs/centrifuge.cjs`. `tests/dual-format.test.ts` asserts both sides \u2014\n * this message under CJS and a constructed Worker under ESM \u2014 so the leg is\n * pinned through the built output rather than missing. Treat that test as its\n * coverage. */\nfunction createDefaultWorker(): Worker {\n let workerUrl: URL;\n try {\n workerUrl = new URL('./centrifuge.worker.js', import.meta.url);\n } catch {\n throw new Error(\n 'The default Centrifuge Worker URL is unavailable in this module format; provide workerFactory explicitly.'\n );\n }\n return new Worker(workerUrl, {\n name: DEFAULT_STORAGE_PREFIX,\n type: 'module'\n });\n}\n\n/** Create the default SharedWorker. Each connecting port within the SharedWorker\n * creates its own CentrifugeSession with an independent WebSocket connection,\n * so refreshing or stopping one tab does not affect the others. Like its\n * dedicated twin it carries no `typeof SharedWorker` guard \u2014 the availability\n * flag in `start()` is what decides this is reachable \u2014 and like its twin the\n * `catch` below is pinned through the built artifacts (the CJS half of\n * `tests/dual-format.test.ts`) rather than being uncovered. */\nfunction createDefaultSharedWorker(): SharedWorker {\n let workerUrl: URL;\n try {\n workerUrl = new URL('./centrifuge.shared.worker.js', import.meta.url);\n } catch {\n throw new Error(\n 'The default Centrifuge SharedWorker URL is unavailable in this module format; provide sharedWorkerFactory explicitly.'\n );\n }\n return new SharedWorker(workerUrl, {\n name: `${DEFAULT_STORAGE_PREFIX}-shared`,\n type: 'module'\n });\n}\n\n/** Post `message` to a Worker or MessagePort, forwarding `transfer` buffers\n * when present. Both targets share the `postMessage(message, transfer?)`\n * signature, so a single helper eliminates the duplicated if/else at each\n * call site. */\nfunction postToPortLike(\n target: Pick<Worker, 'postMessage'>,\n message: CentrifugeWorkerInput,\n transfer?: ArrayBuffer[]\n): void {\n if (transfer) target.postMessage(message, transfer as Transferable[]);\n else target.postMessage(message);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2475,7 +2475,7 @@ var SDK_VERSION = (
2475
2475
  // declaration's type is `string | undefined` and the export's is `string`
2476
2476
  // (dropping the conditional is TS2322, measured). Recorded here so the leg reads
2477
2477
  // as a shape the harness cannot reach rather than as a case nobody exercised.
2478
- true ? "0.21.35" : ""
2478
+ true ? "0.21.36" : ""
2479
2479
  );
2480
2480
 
2481
2481
  // src/core/data-bus.ts
@@ -3715,4 +3715,4 @@ export {
3715
3715
  parseDataBusPublication,
3716
3716
  selectWorkerBackend
3717
3717
  };
3718
- //# sourceMappingURL=chunk-EVVTH6BQ.js.map
3718
+ //# sourceMappingURL=chunk-AWCFYLIT.js.map