cross-tab-worker-databus 0.21.56 → 0.21.57

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.
@@ -204,7 +204,7 @@ export declare class WorkerClusterRuntime {
204
204
  * subscribers remain, deletes the route so the owning Worker can unsubscribe.
205
205
  */
206
206
  unsubscribe(topic: string): void;
207
- /** Remove this tab's subscriber record and, when it was the last one, delete
207
+ /** Remove this Runtime's subscriber record and, when it was the last one, delete
208
208
  * the route. Returns the topicKey (so callers like `unsubscribe` can reuse
209
209
  * it instead of re-hashing the topic to evict the reverse cache). */
210
210
  private releaseSubscription;
@@ -363,10 +363,60 @@ export declare class WorkerClusterRuntime {
363
363
  private sendControl;
364
364
  /** Post a message on the BroadcastChannel. Returns false on postMessage failure. */
365
365
  private send;
366
+ /**
367
+ * Whether any other live Worker record already carries `tabId`.
368
+ *
369
+ * Used once per document, from the constructor, to tell a browser-duplicated
370
+ * tab from this tab's own refresh: the copy inherits `sessionStorage` but not
371
+ * the opener, so the identity stored there is the only shared state and the
372
+ * Worker records are the only evidence that another live document holds it.
373
+ * Liveness is the same heartbeat window `readWorkers()` prunes by, so a record
374
+ * left behind by an abnormal exit is reported as live until it expires — the
375
+ * only false positive, and it re-mints one identity instead of letting two
376
+ * live tabs share one.
377
+ *
378
+ * The `!this.storage` leg is the degraded mode, and it earns its line for two
379
+ * reasons rather than one. It is what narrows the field for the scan below
380
+ * (`StorageLike | null` is rejected by `readAllByPrefix`), so the compiler
381
+ * holds it; and it is *not* what decides the verdict, because `listKeys()`
382
+ * catches its own failure and returns no records either way — measured by
383
+ * emptying this consequent, which leaves the suite green. What the inverted
384
+ * return does prove is the behaviour, not the deletion: returning `true` here
385
+ * fails 'keeps the inherited id when there is no storage to probe', since
386
+ * without `localStorage` there is no second document to collide with.
387
+ */
388
+ private isTabIdLiveElsewhere;
366
389
  /** Read all live worker records from storage, pruning stale entries past the TTL. */
367
390
  private readWorkers;
368
391
  /** Enumerate all tab IDs that have a subscriber record for `topicKey`. */
369
392
  private readSubscriberTabIds;
393
+ /**
394
+ * One scan of `topicKey`'s subscriber records, answering the two questions
395
+ * callers ask: which Tabs hold a subscription (emptiness checks, orphan-route
396
+ * pruning) and which **Runtimes** do (the stranded-handoff re-election).
397
+ *
398
+ * The second answer is the finer one and the reason this is one method rather
399
+ * than two scans: a tab-level answer lets a leftover record from a sibling
400
+ * Runtime speak for a Worker that never subscribed to the topic, and standing
401
+ * down on that proof stalls the recovery forever (see the call site).
402
+ *
403
+ * A legacy record — written before records carried `workerId` — proves only
404
+ * that *some* Runtime in its tab subscribes, so it contributes to the tab set
405
+ * and **not** to the worker set. Treating it as the whole tab, which is what
406
+ * the tab-level reader effectively did, is the conservative-looking choice
407
+ * that can stall; the cost of the other direction is one redundant
408
+ * `CONTROL/SUBSCRIBE` to a peer that is about to confirm anyway.
409
+ */
410
+ private readSubscriberIds;
411
+ /** Whether a subscriber record still belongs to a live Runtime.
412
+ *
413
+ * A record written by this version names its `workerId`, so liveness is the
414
+ * Worker set the reconcile pass already prunes by — which is what keeps two
415
+ * Runtimes in one tab independent. A record from an older version carries no
416
+ * `workerId`; it is honoured while its *tab* lives, the finest identity it
417
+ * has, and each Runtime replaces its own with a worker-keyed record on its
418
+ * next write. */
419
+ private isSubscriberRecordLive;
370
420
  /** Read the current route for `topicKey`. With a storage layer this returns
371
421
  * the durable route, or null when none is recorded. Without one (degraded
372
422
  * mode) it returns the synthesized local route from `buildLocalRoute`, which
@@ -387,9 +437,19 @@ export declare class WorkerClusterRuntime {
387
437
  private confirmRoute;
388
438
  /** Remove routes whose topic has no subscribers and whose TTL has expired. */
389
439
  private cleanupOrphanedRoutes;
390
- /** Remove subscriber records for tabs that are no longer active. */
440
+ /** Remove subscriber records whose owning Runtime (or, for a legacy record,
441
+ * whose tab) is no longer active. */
391
442
  private cleanupOrphanedSubscribers;
392
- /** Persist a subscriber record for this tab on `topicKey`. */
443
+ /** Persist a subscriber record for this Runtime on `topicKey`.
444
+ *
445
+ * Keyed by `workerId`, not `tabId`. A document may run several Runtimes that
446
+ * share one `tabId` on purpose (the market + notice pattern), and a tab-keyed
447
+ * record belonged to all of them at once: whichever unsubscribed first deleted
448
+ * the others' intent, the route then looked orphaned, and the owner dropped a
449
+ * transport subscription its surviving sibling still needed — a delivery gap
450
+ * until that sibling's next reconcile tick re-elected an owner. Duplicated
451
+ * tabs get the same protection even when the identity probe cannot see them,
452
+ * which is why the fix is here as well as in `isTabIdLiveElsewhere()`. */
393
453
  private writeSubscriber;
394
454
  /** Persist the current worker record with an updated heartbeat timestamp.
395
455
  * @param notify — when true, broadcast a REGISTRY nudge so peers reconcile
@@ -424,6 +484,7 @@ export declare class WorkerClusterRuntime {
424
484
  private rememberTopic;
425
485
  private workerStorageKey;
426
486
  private routeStorageKey;
487
+ /** `ownerId` is the Runtime's `workerId`; older versions used the `tabId`. */
427
488
  private subscriberStorageKey;
428
489
  private removeStorage;
429
490
  /** Force-flush any pending batched writes (used during shutdown/teardown). */
@@ -1 +1 @@
1
- {"version":3,"file":"cluster.d.ts","sourceRoot":"","sources":["../../src/core/cluster.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cluster.d.ts","sourceRoot":"","sources":["../../src/core/cluster.ts"],"names":[],"mappings":"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"}
@@ -135,6 +135,28 @@ export declare function canUseStorage(storage: StorageLike | null, probeKey: str
135
135
  * sessionStorage is scoped to the tab and survives refresh, so the same tab
136
136
  * retains its identity across the page lifecycle without coordination overhead.
137
137
  * Falls back to a random ID when sessionStorage is unavailable.
138
+ *
139
+ * A stored value is inherited by two different kinds of new document, and only
140
+ * one of them should keep it. A refresh (or a session restore) re-enters the
141
+ * *same* tab: nothing else is using the value, so it is reused. A tab the
142
+ * browser opened or duplicated is a *second* live document holding a copy, and
143
+ * two of them must not present one identity. The coordination plane does not
144
+ * distinguish them by subscriber *keys* (those are per Runtime, see
145
+ * `writeSubscriber()`), so a shared id does not lose a subscription — it makes
146
+ * a false one: the public snapshot, the diagnostics and the tab-level
147
+ * subscriber aggregation all report two documents as one.
148
+ *
149
+ * `window.open()` is visible (`window.opener`), but the browser's own
150
+ * "Duplicate tab" is not: it copies sessionStorage with no opener at all. That
151
+ * half needs evidence from outside the document, which `options` supplies; the
152
+ * caller's probe is consulted only on a document's first lookup, because later
153
+ * runtimes in the same document must keep the identity this document was issued
154
+ * (see `tabIdentityInitialized`).
138
155
  */
139
- export declare function getOrCreateTabId(environment: ClusterEnvironment, key?: string): string;
156
+ export declare function getOrCreateTabId(environment: ClusterEnvironment, key?: string, options?: {
157
+ /** Reports whether another live document already uses the inherited value.
158
+ * The cluster answers from its Worker records, which a duplicated tab cannot
159
+ * copy: they live in `localStorage` under a random `workerId`. */
160
+ isIdentityClaimedByPeer?: (candidate: string) => boolean;
161
+ }): string;
140
162
  //# sourceMappingURL=environment.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../src/core/environment.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../src/core/environment.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAA8D,MAAM,oBAAoB,CAAC;AAClH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,qFAAqF;AACrF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,KAAK,IAAI,IAAI,CAAC;IACd,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED,sGAAsG;AACtG,MAAM,WAAW,cAAc;IAC7B,gBAAgB,CAAC,IAAI,EAAE,OAAO,UAAU,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,oBAAoB,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IACvH,mBAAmB,CAAC,IAAI,EAAE,OAAO,UAAU,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,oBAAoB,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IAC1H,WAAW,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACjD,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,wEAAwE;IACxE,cAAc,EAAE,WAAW,GAAG,IAAI,CAAC;IACnC;;;;;;;;;;;;;;8EAc0E;IAC1E,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,MAAM,CAAC;IACvB,kEAAkE;IAClE,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,cAAc,GAAG,IAAI,CAAC;IACvD,4DAA4D;IAC5D,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;IACnE,wCAAwC;IACxC,aAAa,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,sDAAsD;IACtD,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;IAC7C,uDAAuD;IACvD,2BAA2B,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IAC5D,2DAA2D;IAC3D,8BAA8B,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IAC/D,wDAAwD;IACxD,mBAAmB,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IACpD,mDAAmD;IACnD,sBAAsB,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IACvD,iEAAiE;IACjE,mBAAmB,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IACpD,mDAAmD;IACnD,sBAAsB,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CACxD;AAsBD,qFAAqF;AACrF,MAAM,WAAW,kBAAkB;IACjC,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IACpH,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,KAAK,IAAI,GAAG,IAAI,CAAC;CACxH;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,GAAG,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChC,GAAG,cAAc,GAAG,IAAI,CAoHxB;AAQD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE;IACjD;;sEAEkE;IAClE,eAAe,CAAC,EAAE,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,OAAO,gBAAgB,CAAC,CAAC;CAC5E,GAAG,kBAAkB,CA8CrB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,IAAI,WAAW,CAUnG;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,kBAAkB,EAC/B,GAAG,SAAqB,EACxB,OAAO,CAAC,EAAE;IACR;;sEAEkE;IAClE,uBAAuB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;CAC1D,GACA,MAAM,CAyCR"}
@@ -140,6 +140,13 @@ export interface WorkerRoute {
140
140
  export interface TopicSubscriberRecord {
141
141
  /** The tab that holds this subscription. */
142
142
  tabId: string;
143
+ /** The Runtime that holds it. A document may run several (the market +
144
+ * notice pattern), and they share one `tabId` on purpose — so a record keyed
145
+ * and pruned by `tabId` alone is owned by all of them at once, and the first
146
+ * one to unsubscribe deletes the others' intent. Optional because records
147
+ * written before this field existed are still honoured while their tab
148
+ * lives. */
149
+ workerId?: string;
143
150
  /** Timestamp (ms) of the last subscriber write. */
144
151
  updatedAt: number;
145
152
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  selectRebalanceTarget,
21
21
  selectWorkerBackend,
22
22
  topicMatchesPattern
23
- } from "./chunk-VDCLCCMN.js";
23
+ } from "./chunk-ZH7VP3IY.js";
24
24
  import {
25
25
  DEFAULT_STORAGE_PREFIX,
26
26
  PRUNE_STRATEGY,
package/docs/api.md CHANGED
@@ -493,12 +493,14 @@ Generates a stable 128-bit hexadecimal opaque key. Used to avoid writing raw con
493
493
 
494
494
  Creates a default browser environment adapter, including storage, BroadcastChannel, timers, and page lifecycle events.
495
495
 
496
- ### `getOrCreateTabId(environment, key?)`
496
+ ### `getOrCreateTabId(environment, key?, options?)`
497
497
 
498
498
  Returns the stable identity of the current page/tab instance, reading it from session storage and creating one (`tab-<random>`) on first call, so a reloaded tab reclaims its routes instead of looking like a brand new one.
499
499
 
500
500
  The stored value is deliberately *not* reused when `window.opener` is present: `window.open()` clones the opener's `sessionStorage` into the child, so a blind reuse would make two live tabs share one identity. When storage is unavailable or throws, a fresh ID is generated instead.
501
501
 
502
+ The browser's own **Duplicate tab** copies `sessionStorage` too, and gives the copy *no* opener, so the opener check cannot see it. `options.isIdentityClaimedByPeer(candidate)` is the caller's evidence for that case: `WorkerClusterRuntime` answers it from this cluster's live Worker records, and a heartbeat-live record carrying the inherited value makes the duplicate take a fresh ID (persisted, so later Runtimes in the same document share it). The probe runs on a document's first lookup only, and never when an opener already settled the question — a refresh removes and flushes its own Worker record, so it keeps its identity; after an abnormal exit the stale record lives for up to `workerTtlMs`, and a reload inside that window takes a fresh ID instead.
503
+
502
504
  ### `selectWorkerBackend(mode, availability?)`
503
505
 
504
506
  Selects the actual backend based on `WorkerMode` and capability detection, returning `'shared' | 'dedicated' | 'local'`:
@@ -108,10 +108,10 @@ BroadcastChannel messages carry topic names, event types, and publication payloa
108
108
  ```text
109
109
  cross-tab-worker-databus:{clusterHash}:worker:{workerId}
110
110
  cross-tab-worker-databus:{clusterHash}:route:{topicKey}
111
- cross-tab-worker-databus:{clusterHash}:subscriber:{topicKey}:{tabId}
111
+ cross-tab-worker-databus:{clusterHash}:subscriber:{topicKey}:{workerId}
112
112
  ```
113
113
 
114
- Unlike the old single-JSON route table, subscribers use per-Tab independent keys. When Tab A and Tab B subscribe/unsubscribe concurrently, they do not perform a read-modify-write on the same `subscribers[]`, structurally reducing the probability of lost updates.
114
+ Unlike the old single-JSON route table, subscribers use one independent key per *Runtime* — the `workerId`, not the `tabId`, because a document may run several Runtimes that share one tab. When Tab A and Tab B subscribe/unsubscribe concurrently, they do not perform a read-modify-write on the same `subscribers[]`, structurally reducing the probability of lost updates; and when one of two Runtimes in the *same* tab unsubscribes, only its own record goes, so the sibling's route and transport subscription survive.
115
115
 
116
116
  ### Worker Record
117
117
 
@@ -217,7 +217,7 @@ topic
217
217
  └─ createOpaqueKey(topic) → topicKey
218
218
  ├─ route:{topicKey}
219
219
  │ └─ workerId / tabId / generation / confirmedAt
220
- └─ subscriber:{topicKey}:{tabId}
220
+ └─ subscriber:{topicKey}:{workerId}
221
221
 
222
222
  BroadcastChannel CONTROL
223
223
  └─ topic + topicKey + sourceWorkerId + targetWorkerId + action
@@ -288,7 +288,7 @@ sequenceDiagram
288
288
 
289
289
  App->>RuntimeA: subscribe(topic, handler)
290
290
  RuntimeA->>RuntimeA: derive topicKey
291
- RuntimeA->>Storage: write subscriber:{topicKey}:{tabId}
291
+ RuntimeA->>Storage: write subscriber:{topicKey}:{workerId}
292
292
  RuntimeA->>Storage: read or create route:{topicKey}
293
293
  RuntimeA->>Channel: CONTROL/SUBSCRIBE(topic, topicKey, targetWorkerId)
294
294
  Channel->>RuntimeB: deliver control message
@@ -300,7 +300,7 @@ sequenceDiagram
300
300
  RuntimeA->>App: invoke handler(payload)
301
301
  ```
302
302
 
303
- A second Tab subscribing to the same Topic adds only its own subscriber record; it does not create another transport subscription while the existing owner is alive. Unsubscribe removes the current Tab's subscriber record and, when that was the last one, the same Tab deletes the route and sends `CONTROL/UNSUBSCRIBE` to the worker the route named, which then drops its assignment and releases the transport subscription.
303
+ A second Tab subscribing to the same Topic adds only its own subscriber record; it does not create another transport subscription while the existing owner is alive. Unsubscribe removes the current Runtime's subscriber record and, when that was the last one, the same Runtime deletes the route and sends `CONTROL/UNSUBSCRIBE` to the worker the route named, which then drops its assignment and releases the transport subscription.
304
304
 
305
305
  ### Console diagnostics
306
306
 
@@ -350,7 +350,7 @@ Every keyed piece of state in the system — the full picture of what the previo
350
350
  | `knownTopics` | `WorkerClusterRuntime` | `topicKey` | Plaintext `topic` | FIFO scan capped at 500 that skips owned keys and the key being remembered, so an all-owned cache lets the cap slip | The reverse-lookup cache; also the only source of plaintext in storage-less mode |
351
351
  | Storage `worker:` | persisted | `clusterHash:…:worker:{workerId}` | JSON `WorkerRecord` | Heartbeat refresh; pruned after `workerTtlMs` | Cross-tab liveness discovery |
352
352
  | Storage `route:` | persisted | `clusterHash:…:route:{topicKey}` | JSON `WorkerRoute` | Created/stamped by subscriber; pruned when no subscribers + TTL expired | Cross-tab owner mapping |
353
- | Storage `subscriber:` | persisted | `clusterHash:…:subscriber:{topicKey}:{tabId}` | JSON `TopicSubscriberRecord` | Written per Tab subscription; pruned when the Tab dies | Cross-tab subscriber intent |
353
+ | Storage `subscriber:` | persisted | `clusterHash:…:subscriber:{topicKey}:{workerId}` | JSON `TopicSubscriberRecord` | Written per Runtime subscription; pruned when that Runtime dies (a legacy tab-keyed record is pruned with its Tab) | Cross-tab subscriber intent |
354
354
 
355
355
  **How the three in-memory topic forms relate** (`knownTopics` ↔ `assignedTopics` ↔ the four plaintext sets):
356
356
 
@@ -394,7 +394,7 @@ The owning Worker filters every publication it receives with `isAssigned(topic)`
394
394
  2. If a visible Tab exists, visible Workers are preferred when choosing an owner for a new Topic; when all Tabs are hidden, hidden Workers remain eligible.
395
395
  3. Sorted by `registeredAt, workerId`, up to 3 active Workers are selected as new-route candidates.
396
396
  4. An existing route remains sticky as long as its owner Worker record is alive, regardless of load, visibility, or whether the owner remains in the new-route candidate set.
397
- 5. A second Tab subscribing to an existing Topic only writes its subscriber record. It does not modify the route or call its own transport `subscribe`.
397
+ 5. A second Tab — or a second Runtime in the same Tab — subscribing to an existing Topic only writes its own subscriber record. It does not modify the route or call a transport `subscribe`.
398
398
  6. Only a Topic without a route, or a route whose owner has departed or expired by heartbeat TTL, is assigned to the least-loaded candidate Worker.
399
399
  7. A new route is considered unconfirmed until the owner writes `confirmedAt`; the subscriber will automatically resend the control message.
400
400
 
@@ -450,7 +450,7 @@ sequenceDiagram
450
450
 
451
451
  App->>Bus: subscribe(topic, handler)
452
452
  Bus->>Bus: First handler in this Tab?
453
- Bus->>Route: Write subscriber:{topicKey}:{tabId}
453
+ Bus->>Route: Write subscriber:{topicKey}:{workerId}
454
454
  Route-->>Bus: Current owner
455
455
  alt Owner does not exist or is invalid
456
456
  Bus->>Route: Write lowest-load owner
@@ -478,7 +478,7 @@ The system maintains four independent subscription-tracking sets. Understanding
478
478
  Application: subscribe(topic, handler)
479
479
  → topicHandlers 0→1
480
480
  → cluster.subscribe(topic) → subscribedTopics.add(topic)
481
- → write subscriber:{topicKey}:{tabId}
481
+ → write subscriber:{topicKey}:{workerId}
482
482
  → readRoute(topicKey)
483
483
  → if no route: selectLeastLoadedWorker, writeRoute, sendControl(SUBSCRIBE)
484
484
  → owner receives CONTROL/SUBSCRIBE
@@ -514,7 +514,7 @@ A publication travels publisher → current Topic owner → transport/server →
514
514
  2. The owner runs `transport.publish(topic, data)`. Because only the owner holds a real transport subscription to the topic, the server delivers the resulting publication back to exactly one Worker.
515
515
  3. The owner accepts a publication only while `isAssigned(topic)` still holds. Stale messages from an expired owner are discarded — keeping the fan-out single-sourced.
516
516
  4. The owner broadcasts `EVENT/DATABUS_PUBLICATION` over the BroadcastChannel and, if its own Tab also has a local subscription, dispatches once directly. BroadcastChannel never echoes to the sender, so there is no duplicate dispatch.
517
- 5. Every other Tab receives the `EVENT` but invokes its local handlers only when it holds a `subscriber:{topicKey}:{tabId}` record for that topic; Tabs without a local subscription drop the message.
517
+ 5. Every other Tab receives the `EVENT` but invokes its local handlers only when its own subscription intent covers that topic — the gate reads the in-memory subscription set, while the `subscriber:{topicKey}:{workerId}` record is what tells peers that intent exists; Tabs without a local subscription drop the message.
518
518
 
519
519
  Optional publication metadata (`messageId` and `timestamp`) follows the same path as the payload: `CONTROL/PUBLISH` → transport/server → `DataBusMessage` → `EVENT` fan-out. It is never written to coordination storage. Transports normalize legacy payloads and the canonical `DataBusPublicationEnvelope` before the three dispatch gates.
520
520
 
@@ -562,7 +562,7 @@ After the optional `messageId` deduplication gate, every accepted publication fr
562
562
 
563
563
  2. **`broadcastEvent('DATABUS_PUBLICATION', message)`** — called only after `isAssigned` passes. The owning Worker fans the message out to all tabs via BroadcastChannel `EVENT`. Each tab receives the event but does not dispatch yet — it must pass the inner gate.
564
564
 
565
- 3. **`hasLocalSubscriber(topic)`** — called on each tab receiving the `EVENT`. Only tabs that have a local subscriber record for this topic invoke the registered handler. Tabs without a local subscription drop the message silently.
565
+ 3. **`hasLocalSubscriber(topic)`** — called on each tab receiving the `EVENT`. It reads the in-memory subscription set (including wildcard patterns), not the durable subscriber record; only tabs whose own subscription covers this topic invoke the registered handler. Tabs without a local subscription drop the message silently.
566
566
 
567
567
  These three checks provide **at-most-once fan-out per accepted transport publication**:
568
568
  - The outer gate (`isAssigned`) prevents duplicate broadcasts from a stale owner.
@@ -583,8 +583,8 @@ Transport message → isAssigned(topic)? → Yes → broadcastEvent(EVENT)
583
583
 
584
584
  Cluster convergence is driven on two timelines:
585
585
 
586
- - **Heartbeat + reconcile loop** (default `3000 ms`, `heartbeatIntervalMs`). On every tick each Worker refreshes its own record and runs a reconcile pass: prunes Workers past `workerTtlMs`, orphaned subscribers whose Tab is no longer active, and orphaned routes that have no subscribers and exceed the TTL; repairs a confirmed route that names this Worker after its assignment was cleared (see the invariant below); recomputes its own active/standby role; re-writes its subscriber records; and re-sends `CONTROL/SUBSCRIBE` for any route that still lacks `confirmedAt` — which also recovers control messages lost on the channel.
587
- - **`REGISTRY` nudge**. Writes to Worker records and routes broadcast a `REGISTRY` message so every peer reconciles immediately rather than waiting for the next heartbeat. A subscriber-only write does not: `subscribe()` on a topic whose owner is still live writes this Tab's subscriber record and returns.
586
+ - **Heartbeat + reconcile loop** (default `3000 ms`, `heartbeatIntervalMs`). On every tick each Worker refreshes its own record and runs a reconcile pass: prunes Workers past `workerTtlMs`, orphaned subscribers whose Runtime (or, for a legacy record, whose Tab) is no longer active, and orphaned routes that have no subscribers and exceed the TTL; repairs a confirmed route that names this Worker after its assignment was cleared (see the invariant below); recomputes its own active/standby role; re-writes its subscriber records; and re-sends `CONTROL/SUBSCRIBE` for any route that still lacks `confirmedAt` — which also recovers control messages lost on the channel.
587
+ - **`REGISTRY` nudge**. Writes to Worker records and routes broadcast a `REGISTRY` message so every peer reconciles immediately rather than waiting for the next heartbeat. A subscriber-only write does not: `subscribe()` on a topic whose owner is still live writes this Runtime's subscriber record and returns.
588
588
 
589
589
  Heartbeat writes are not announced, so a stale record is only noticed within one heartbeat interval. The worst case for failing to detect a dead owner is `heartbeatIntervalMs + workerTtlMs` (default about 13 s); see [TTL Message-Loss Window](./configuration.md#ttl-message-loss-window) for the trade-offs.
590
590
 
@@ -592,11 +592,13 @@ Heartbeat writes are not announced, so a stale record is only noticed within one
592
592
 
593
593
  On normal close or entry into BFCache, `pagehide` pauses the Runtime: it deletes Worker and subscriber records, yields the actual owner, and closes the underlying transport, but retains the business subscription intent in memory. After the page is restored via `pageshow`, DataBus rebuilds the Worker/connection, and the Runtime automatically re-registers, restores subscriber records, and reconciles Topics, without requiring the business to re-call `subscribe`.
594
594
 
595
- #### Tab identity and `window.open`
595
+ #### Tab identity: refreshes, `window.open`, and duplicated tabs
596
596
 
597
- Each Runtime stores its `tabId` in `sessionStorage` so refreshes retain identity; every Runtime gets a random-suffixed `workerId`. Browsers may clone the opener's `sessionStorage` when `window.open()` creates a page, causing two physical tabs to share a `tabId` and collide on subscriber or diagnostic keys.
597
+ Each Runtime stores its `tabId` in `sessionStorage` so refreshes retain identity; every Runtime gets a random-suffixed `workerId`. Two kinds of new document inherit that stored value, and only one of them may keep it. A refresh re-enters the *same* tab, so the value is unclaimed and is reused. A browser-duplicated tab (`window.open()`, or the browser's own Duplicate tab) is a *second* live document holding a copy, and two of them must not present one identity: `tabId` is the identity the public snapshot reports and one half of the key the diagnostic records use, so a copy would speak for the original. (Subscriber records are keyed per Runtime, so the coordination plane no longer *depends* on that uniqueness — the guard exists so that the identity itself is true.)
598
598
 
599
- Applications should use `noopener` when opening a new tab. As a safety net, the SDK discards a copied sessionStorage id when an opener is detected and generates a fresh one. Inspection records are keyed by `tabId + workerId`, preventing Worker restart or handoff snapshots from overwriting one another.
599
+ The two sources of evidence are different in kind. `window.open()` is visible as `window.opener`, so the SDK discards a copied `sessionStorage` id when an opener is present and generates a fresh one; applications are still encouraged to use `noopener`. The browser's Duplicate tab carries no opener at all, so the SDK probes this cluster's Worker records instead — they live in `localStorage` under a random `workerId` and cannot be copied with the session. A heartbeat-live record carrying the inherited id means another live document is using it, and the duplicate mints a fresh id that is persisted for later Runtimes in the same document.
600
+
601
+ That probe runs once per document, from the constructor. A refresh is recognised because `pause()` removes the departing document's Worker record and flushes that removal before the page goes away; after an abnormal exit the record survives for up to `workerTtlMs`, so a reload inside that window takes a fresh id (the stale record is then pruned by the normal TTL path). Inspection records are keyed by `tabId + workerId`, preventing Worker restart or handoff snapshots from overwriting one another.
600
602
 
601
603
  `visibilitychange` does not remove business subscriptions or migrate established routes. A hidden Tab keeps the Topics it already owns and can still receive data broadcast by other owners. Visibility only affects candidate selection when a new Topic needs its first owner.
602
604
 
@@ -2,20 +2,20 @@
2
2
 
3
3
  # Browser Benchmark Trend
4
4
 
5
- > Data through 2026-10-25, from the 95 archived `bench-results/browser-*.json` reports (run `pnpm bench:browser` to add one; regenerate this doc with `node scripts/bench-trend.mjs`).
5
+ > Data through 2026-10-30, from the 97 archived `bench-results/browser-*.json` reports (run `pnpm bench:browser` to add one; regenerate this doc with `node scripts/bench-trend.mjs`).
6
6
 
7
7
  The comparison baseline for release gating is `pnpm bench:compare --fail-above-pct 50`, which compares the newest report against the median of the same metric in the preceding reports *and* requires the new value to exceed the highest of those samples (50% ceiling absorbs shared-runner noise; a single previous report was a fragile baseline because the in-page hot-path metrics alternate between a fast and a slow mode on identical code — and a median over an alternating sample is only stable while the modes are mixed, so a percentage alone can still fail on nothing but a mode shift. Metrics excused by that second leg are listed as "within-baseline, not gated" rather than passing silently). This doc records the long-run picture: values are per-metric latencies where lower is better, and the last column is the best run inside the most recent 5 reports — not an all-time record, because the in-page matrix changed measurement semantics in early September 2026 and older reports are not comparable.
8
8
 
9
9
  <!-- BENCH-TREND:BEGIN (machine-generated table) -->
10
10
  | Metric | Previous (ms) | Latest (ms) | Δ | Best of last 5 runs (ms) |
11
11
  |---|---|---|---|---|
12
- | publish per-message (ms, lower is better) — dedicated | 40.396 | 50.3047 | +9.91 | 40.396 |
13
- | publish per-message (ms, lower is better) — shared | 36.1461 | 36.1657 | +0.02 | 33.8229 |
14
- | wildcard dispatch ×1000 (ms, lower is better) | 9.5 | 6.2 | -3.30 | 6 |
15
- | publishBatch ×1000 (ms, lower is better) | 5.5 | 4.9 | -0.60 | 3.9 |
16
- | dedup ×1000 (ms, lower is better) | 14.6 | 13.2 | -1.40 | 13.2 |
17
- | trace + publish ×1000 (ms, lower is better) | 5.2 | 5.5 | +0.30 | 5.2 |
18
- | first-packet cold dispatch (ms, lower is better) | 0 | 0 | +0.00 | 0 |
12
+ | publish per-message (ms, lower is better) — dedicated | 39.1206 | 37.9266 | -1.19 | 37.9266 |
13
+ | publish per-message (ms, lower is better) — shared | 34.1177 | 34.5476 | +0.43 | 33.8229 |
14
+ | wildcard dispatch ×1000 (ms, lower is better) | 6.7 | 6.9 | +0.20 | 6 |
15
+ | publishBatch ×1000 (ms, lower is better) | 4.3 | 4.1 | -0.20 | 3.9 |
16
+ | dedup ×1000 (ms, lower is better) | 16.1 | 16.7 | +0.60 | 13.2 |
17
+ | trace + publish ×1000 (ms, lower is better) | 5.4 | 5.1 | -0.30 | 5.1 |
18
+ | first-packet cold dispatch (ms, lower is better) | 0 | 0.1 | +0.10 | 0 |
19
19
  <!-- BENCH-TREND:END -->
20
20
 
21
21
  Notes:
@@ -273,11 +273,11 @@ When non-clonable data is passed, `CentrifugeWorkerTransport` will throw a clear
273
273
 
274
274
  ## Storage Data Boundaries
275
275
 
276
- The coordination records this library writes itself — `{clusterHash}:worker:{workerId}`, `{clusterHash}:route:{topicKey}`, `{clusterHash}:subscriber:{topicKey}:{tabId}`, and the tab-id key — hold only:
276
+ The coordination records this library writes itself — `{clusterHash}:worker:{workerId}`, `{clusterHash}:route:{topicKey}`, `{clusterHash}:subscriber:{topicKey}:{workerId}`, and the tab-id key — hold only:
277
277
 
278
278
  - Worker and Tab identity, role, status, visibility, the owned-topic `load` figure, an optional rolling throughput sample, the advertised protocol version, and the heartbeat / first-registration timestamps
279
279
  - The opaque Topic key, its owner's Worker and Tab identity, the last-update and generation numbers, and — during a graceful handoff — the previous owner and the owner's confirmation timestamp
280
- - A subscriber record's Tab ID and its last-update timestamp
280
+ - A subscriber record's Tab ID, the Runtime's Worker ID (absent in a record written by an older version), and its last-update timestamp
281
281
  - The tab's own persisted id, which is what lets a reload reclaim its routes — written to `sessionStorage`, not to the cluster's `localStorage` namespace
282
282
 
283
283
  Those records never hold:
package/docs/roadmap.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Roadmap
2
2
 
3
- 0.21.55 was released on October 25, 2026. The project is intentionally continuing through reliability-focused releases before a 1.0.0 stability freeze.
3
+ 0.21.56 was released on October 25, 2026. The project is intentionally continuing through reliability-focused releases before a 1.0.0 stability freeze.
4
4
 
5
5
  ## 0.21.44 delivered scope
6
6
 
package/docs/zh/api.md CHANGED
@@ -492,12 +492,14 @@ useVueCrossTabSubscription(bus, 'chat.*', message => console.log(message.data));
492
492
 
493
493
  创建默认浏览器环境适配器,包含 storage、BroadcastChannel、定时器和页面生命周期事件。
494
494
 
495
- ### `getOrCreateTabId(environment, key?)`
495
+ ### `getOrCreateTabId(environment, key?, options?)`
496
496
 
497
497
  返回当前页面/标签页实例的稳定标识:优先从 sessionStorage 读取,首次调用时创建(`tab-<random>`)并写回,因此刷新后的标签页会认领原有 route,而不是被当成一个全新标签页。
498
498
 
499
499
  存在 `window.opener` 时**刻意不**复用已存储的值:`window.open()` 会把 opener 的 `sessionStorage` 克隆给子页面,盲目复用会让两个存活的标签页共用一个身份。storage 不可用或抛错时改为生成新 ID。
500
500
 
501
+ 浏览器自带的**复制标签页(Duplicate tab)**同样会复制 `sessionStorage`,而且复制出来的页面**没有** opener,因此 opener 检查看不到它。`options.isIdentityClaimedByPeer(candidate)` 就是调用方对这一情形的证据:`WorkerClusterRuntime` 用本集群存活的 Worker 记录来回答,若存在一条心跳仍新鲜、且带着被继承 id 的记录,复制出的标签页就改领一个新 ID(并写回,同一 document 内后续 Runtime 共用)。该探测只在一个 document 的首次查找时执行,opener 已给出结论时也不执行——刷新会在 `pause()` 里删除并 flush 自己的 Worker 记录,因此保留原身份;异常退出留下的记录会存活至多一个 `workerTtlMs`,在这个窗口内刷新则会领到新 ID。
502
+
501
503
  ### `selectWorkerBackend(mode, availability?)`
502
504
 
503
505
  按 `WorkerMode` 和能力检测选择实际后端,返回 `'shared' | 'dedicated' | 'local'`:
@@ -98,10 +98,10 @@ BroadcastChannel 消息以明文传输 Topic 名称、事件类型和 publicatio
98
98
  ```text
99
99
  cross-tab-worker-databus:{clusterHash}:worker:{workerId}
100
100
  cross-tab-worker-databus:{clusterHash}:route:{topicKey}
101
- cross-tab-worker-databus:{clusterHash}:subscriber:{topicKey}:{tabId}
101
+ cross-tab-worker-databus:{clusterHash}:subscriber:{topicKey}:{workerId}
102
102
  ```
103
103
 
104
- 与旧的单 JSON 路由表不同,subscriber 使用按 Tab 独立的 key。当 Tab A 和 Tab B 并发订阅/退订时,它们不会对同一个 `subscribers[]` 做读-改-写,从结构上降低丢失更新的概率。
104
+ 与旧的单 JSON 路由表不同,subscriber 使用**每个 Runtime** 一条独立 key——是 `workerId` 而不是 `tabId`,因为一个 document 可以运行多个共享同一 Tab 的 Runtime。当 Tab A 和 Tab B 并发订阅/退订时,它们不会对同一个 `subscribers[]` 做读-改-写,从结构上降低丢失更新的概率;同一 Tab 内两个 Runtime 之一退订时,删除的也只是它自己的记录,兄弟 Runtime 的 route 与 transport 订阅继续有效。
105
105
 
106
106
  ### Worker 记录
107
107
 
@@ -182,7 +182,7 @@ topic
182
182
  └─ createOpaqueKey(topic) → topicKey
183
183
  ├─ route:{topicKey}
184
184
  │ └─ workerId / tabId / generation / confirmedAt
185
- └─ subscriber:{topicKey}:{tabId}
185
+ └─ subscriber:{topicKey}:{workerId}
186
186
 
187
187
  BroadcastChannel CONTROL
188
188
  └─ topic + topicKey + sourceWorkerId + targetWorkerId + action
@@ -252,7 +252,7 @@ sequenceDiagram
252
252
 
253
253
  App->>RuntimeA: subscribe(topic, handler)
254
254
  RuntimeA->>RuntimeA: 计算 topicKey
255
- RuntimeA->>Storage: 写 subscriber:{topicKey}:{tabId}
255
+ RuntimeA->>Storage: 写 subscriber:{topicKey}:{workerId}
256
256
  RuntimeA->>Storage: 读取或创建 route:{topicKey}
257
257
  RuntimeA->>Channel: CONTROL/SUBSCRIBE(topic, topicKey, targetWorkerId)
258
258
  Channel->>RuntimeB: 投递控制消息
@@ -266,7 +266,7 @@ sequenceDiagram
266
266
  RuntimeA->>App: 调用 handler(payload)
267
267
  ```
268
268
 
269
- 第二个 Tab 订阅同一 Topic 时,只新增自己的 `subscriber:{topicKey}:{tabId}`;只要现有 route 的 owner 仍存活,就不会再次建立一条 transport 订阅。退订时删除当前 Tab 的 subscriber 记录;若它已是最后一条,同一个 Tab 顺带删除 route,并向 route 记录的 worker 发出 `CONTROL/UNSUBSCRIBE`——收到的一方就此放弃 ownership 并释放 transport 订阅。
269
+ 第二个 Tab 订阅同一 Topic 时,只新增自己的 `subscriber:{topicKey}:{workerId}`;只要现有 route 的 owner 仍存活,就不会再次建立一条 transport 订阅。退订时删除当前 Runtime 的 subscriber 记录;若它已是最后一条,同一个 Runtime 顺带删除 route,并向 route 记录的 worker 发出 `CONTROL/UNSUBSCRIBE`——收到的一方就此放弃 ownership 并释放 transport 订阅。
270
270
 
271
271
  ### 控制台排查
272
272
 
@@ -313,7 +313,7 @@ console.table(__bus.getClusterSnapshot().routes)
313
313
  | `knownTopics` | `WorkerClusterRuntime` | `topicKey` | 明文 `topic` | FIFO 扫描上限 500,会跳过持有的 key 与本次正在记忆的 key,因此所有条目都被持有时上限会失守 | 反查缓存;也是无 storage 模式下明文的唯一来源 |
314
314
  | 存储 `worker:` | 持久化 | `clusterHash:…:worker:{workerId}` | JSON `WorkerRecord` | 心跳刷新;超过 `workerTtlMs` 被清理 | 跨 Tab 存活发现 |
315
315
  | 存储 `route:` | 持久化 | `clusterHash:…:route:{topicKey}` | JSON `WorkerRoute` | 由订阅方创建/盖章;无订阅者且 TTL 过期时清理 | 跨 Tab owner 映射 |
316
- | 存储 `subscriber:` | 持久化 | `clusterHash:…:subscriber:{topicKey}:{tabId}` | JSON `TopicSubscriberRecord` | 每次 Tab 订阅写入;Tab 死亡时清理 | 跨 Tab 订阅意图 |
316
+ | 存储 `subscriber:` | 持久化 | `clusterHash:…:subscriber:{topicKey}:{workerId}` | JSON `TopicSubscriberRecord` | 每次 Runtime 订阅写入;该 Runtime 死亡时清理(旧版按 Tab 落键的记录随 Tab 清理) | 跨 Tab 订阅意图 |
317
317
 
318
318
  **三种内存 Topic 形态的关系**(`knownTopics` ↔ `assignedTopics` ↔ 四个明文集合):
319
319
 
@@ -388,7 +388,7 @@ sequenceDiagram
388
388
 
389
389
  App->>Bus: subscribe(topic, handler)
390
390
  Bus->>Bus: 当前 Tab 第一个 handler?
391
- Bus->>Route: 写 subscriber:{topicKey}:{tabId}
391
+ Bus->>Route: 写 subscriber:{topicKey}:{workerId}
392
392
  Route-->>Bus: 当前 owner
393
393
  alt owner 不存在或无效
394
394
  Bus->>Route: 写最低负载 owner
@@ -416,7 +416,7 @@ sequenceDiagram
416
416
  应用层: subscribe(topic, handler)
417
417
  → topicHandlers 0→1
418
418
  → cluster.subscribe(topic) → subscribedTopics.add(topic)
419
- → 写 subscriber:{topicKey}:{tabId}
419
+ → 写 subscriber:{topicKey}:{workerId}
420
420
  → readRoute(topicKey)
421
421
  → 无 route: 选择最低负载 Worker,写 route,sendControl(SUBSCRIBE)
422
422
  → owner 收到 CONTROL/SUBSCRIBE
@@ -452,7 +452,7 @@ sequenceDiagram
452
452
  2. owner 执行 `transport.publish(topic, data)`。由于只有 owner 持有该 Topic 的真实 transport 订阅,服务器只会把这条 publication 回推给唯一一个 Worker。
453
453
  3. owner 仅当 `isAssigned(topic)` 仍成立时才接受该 publication;过期 owner 的陈旧消息被丢弃——保证扇出路径只有单一消息源。
454
454
  4. owner 通过 BroadcastChannel 广播 `EVENT/DATABUS_PUBLICATION`;自身 Tab 也有本地订阅时直接分发一次。BroadcastChannel 从不把消息回传给发送者,因此不会重复分发。
455
- 5. 其余每个 Tab 收到 `EVENT` 后,仅当自己持有该 Topic 的 `subscriber:{topicKey}:{tabId}` 记录时才调用本地 handler;没有本地订阅的 Tab 直接丢弃。
455
+ 5. 其余每个 Tab 收到 `EVENT` 后,仅当自己的订阅意图覆盖该 Topic 时才调用本地 handler——这道门读的是内存中的订阅集合,而 `subscriber:{topicKey}:{workerId}` 记录只是告诉其他 peer 这份意图存在;没有本地订阅的 Tab 直接丢弃。
456
456
 
457
457
  可选的 publication 元数据(`messageId` 与 `timestamp`)与 payload 走同一条路径:`CONTROL/PUBLISH` → transport/服务器 → `DataBusMessage` → `EVENT` 扇出。它不会写入协调存储。各 transport 会在三处分发门之前,把旧 payload 格式与标准 `DataBusPublicationEnvelope` 统一规范化。
458
458
 
@@ -493,7 +493,7 @@ SDK 当前刻意不在 Service Worker 中承载实时 transport。Service Worker
493
493
 
494
494
  1. **`isAssigned(topic)`** — 在 owner Worker 上检查(`handleTransportMessage`)。如果该 topic 已不再分配给此 worker(比如前一个 ownership 窗口的过期消息),立即丢弃。这是外层关卡:防止非 owner 广播。
495
495
  2. **`broadcastEvent('DATABUS_PUBLICATION', message)`** — 仅当 `isAssigned` 通过后调用。owner Worker 通过 BroadcastChannel `EVENT` 将消息扇出到所有 Tab。每个 Tab 收到事件但暂不分发——必须通过内层关卡。
496
- 3. **`hasLocalSubscriber(topic)`** — 在收到 `EVENT` 的每个 Tab 上检查。仅当该 Tab 有该 topic 的本地 subscriber 记录时才调用已注册的 handler。无本地订阅的 Tab 静默丢弃。
496
+ 3. **`hasLocalSubscriber(topic)`** — 在收到 `EVENT` 的每个 Tab 上检查。它读的是内存中的订阅集合(含通配 pattern),不是持久化的 subscriber 记录;仅当自己的订阅覆盖该 topic 时才调用已注册的 handler。无本地订阅的 Tab 静默丢弃。
497
497
 
498
498
  这三道关卡提供的是**每次已接受的 transport publication 至多扇出一次**:
499
499
  - 外层关卡(`isAssigned`)防止过期 owner 重复广播。
@@ -523,11 +523,13 @@ Transport 消息 → isAssigned(topic)? → 是 → broadcastEvent(EVENT)
523
523
 
524
524
  正常关闭或进入 BFCache 时,`pagehide` 暂停 Runtime:删除 Worker 和 subscriber 记录、让出实际 owner、关闭底层 transport,但在内存中保留业务订阅意图。`pageshow` 恢复页面后,DataBus 重建 Worker/连接,Runtime 自动重新注册、恢复 subscriber 记录并协调 Topic,业务无需重新调用 `subscribe`。
525
525
 
526
- #### Tab 身份与 `window.open`
526
+ #### Tab 身份:刷新、`window.open` 与复制标签页
527
527
 
528
- 每个 Runtime 使用 `sessionStorage` 保存 `tabId`,刷新页面时保持稳定;`workerId` 则在每次 Runtime 创建时使用随机后缀生成。浏览器通过 `window.open()` 打开页面时,可能先复制 opener 的 `sessionStorage`,导致两个物理 Tab 暂时拿到相同 `tabId`,进而发生 subscriber key 和诊断记录覆盖。
528
+ 每个 Runtime 使用 `sessionStorage` 保存 `tabId`,刷新页面时保持稳定;`workerId` 则在每次 Runtime 创建时使用随机后缀生成。继承这份存储值的新 document 有两种,但只有一种应当保留它。刷新是**同一个** Tab 重新进入页面,值无人占用,因此复用。浏览器复制出的标签页(`window.open()` 或浏览器自带的 Duplicate tab)则是**第二个**存活 document 持有一份副本,两者不能呈现同一个身份:subscriber 记录以 `tabId` 为键,副本会在每次写入时覆盖原标签页的记录、并在退订时把它们删掉。
529
529
 
530
- 应用打开新 Tab 时应使用 `noopener`。SDK 也会在检测到 opener 时丢弃被复制的 sessionStorage tab id,并生成新的 id,作为应用侧遗漏 `noopener` 时的兜底。诊断记录按 `tabId + workerId` 保存,避免 Worker 重启或 handoff 窗口内的新旧快照互相覆盖。
530
+ 两种线索性质不同。`window.open()` 会留下 `window.opener`,SDK 因此在检测到 opener 时丢弃被复制的 `sessionStorage` id 并重新生成;应用侧仍建议使用 `noopener`。浏览器自带的 Duplicate tab 完全没有 opener,SDK 改为探测本集群的 Worker 记录——它们以随机的 `workerId` 存在 `localStorage` 中,不会随 session 一起被复制。若存在一条心跳仍新鲜、且带着被继承 id 的记录,说明另一个存活 document 正在使用它,复制出的标签页就改领一个新 id,并写回以供水同 document 内后续 Runtime 共用。
531
+
532
+ 该探测每个 document 只执行一次,位置在构造函数中。刷新之所以能被识别,是因为 `pause()` 会删除离开的 document 的 Worker 记录并在页面消失前 flush 这次删除;异常退出留下的记录会存活至多一个 `workerTtlMs`,在这个窗口内刷新会领到新 id(随后由正常的 TTL 路径清理该陈旧记录)。诊断记录按 `tabId + workerId` 保存,避免 Worker 重启或 handoff 窗口内的新旧快照互相覆盖。
531
533
 
532
534
  `visibilitychange` 不删除业务订阅,也不迁移已经建立的 route。隐藏 Tab 继续持有自己已有的 Topic,也仍能收到其他 owner 广播的数据。可见性只影响全新 Topic 首次选择 owner 时的候选集合。
533
535
 
@@ -2,20 +2,20 @@
2
2
 
3
3
  # 浏览器基准趋势
4
4
 
5
- > 数据截至 2026-10-25,基于 95 份归档的 `bench-results/browser-*.json` 报告(运行 `pnpm bench:browser` 追加一份;用 `node scripts/bench-trend.mjs` 重新生成本文档)。
5
+ > 数据截至 2026-10-30,基于 97 份归档的 `bench-results/browser-*.json` 报告(运行 `pnpm bench:browser` 追加一份;用 `node scripts/bench-trend.mjs` 重新生成本文档)。
6
6
 
7
7
  发布门禁的对比基线是 `pnpm bench:compare --fail-above-pct 50`:将最新报告与其之前若干报告中同一指标的中位数比较,*并且*要求新值超过这些样本中的最大值(50% 上限用于吸收共享 runner 的噪声;此前只看"上一份报告"并不稳定,因为页内热路径指标在同一份代码上会在快/慢两档之间来回跳;而在两档交替的样本上,中位数只有在这两档混合分布时才稳定,因此单看百分比仍可能纯粹因为档位切换而失败)。被第二个条件放过的指标会显式列在 “within-baseline, not gated” 里,而不是悄悄算作通过。本文记录长期趋势:数值为逐指标延迟,越低越好;最后一列是最近 5 份报告内的最优值,而不是历史纪录——页内基准矩阵在 2026 年 9 月初变更过测量语义,更早的报告不可比。
8
8
 
9
9
  <!-- BENCH-TREND:BEGIN (machine-generated table) -->
10
10
  | 指标 | 上次 (ms) | 本次 (ms) | Δ | 近 5 次最优 (ms) |
11
11
  |---|---|---|---|---|
12
- | publish per-message (ms, lower is better) — dedicated | 40.396 | 50.3047 | +9.91 | 40.396 |
13
- | publish per-message (ms, lower is better) — shared | 36.1461 | 36.1657 | +0.02 | 33.8229 |
14
- | wildcard dispatch ×1000 (ms, lower is better) | 9.5 | 6.2 | -3.30 | 6 |
15
- | publishBatch ×1000 (ms, lower is better) | 5.5 | 4.9 | -0.60 | 3.9 |
16
- | dedup ×1000 (ms, lower is better) | 14.6 | 13.2 | -1.40 | 13.2 |
17
- | trace + publish ×1000 (ms, lower is better) | 5.2 | 5.5 | +0.30 | 5.2 |
18
- | first-packet cold dispatch (ms, lower is better) | 0 | 0 | +0.00 | 0 |
12
+ | publish per-message (ms, lower is better) — dedicated | 39.1206 | 37.9266 | -1.19 | 37.9266 |
13
+ | publish per-message (ms, lower is better) — shared | 34.1177 | 34.5476 | +0.43 | 33.8229 |
14
+ | wildcard dispatch ×1000 (ms, lower is better) | 6.7 | 6.9 | +0.20 | 6 |
15
+ | publishBatch ×1000 (ms, lower is better) | 4.3 | 4.1 | -0.20 | 3.9 |
16
+ | dedup ×1000 (ms, lower is better) | 16.1 | 16.7 | +0.60 | 13.2 |
17
+ | trace + publish ×1000 (ms, lower is better) | 5.4 | 5.1 | -0.30 | 5.1 |
18
+ | first-packet cold dispatch (ms, lower is better) | 0 | 0.1 | +0.10 | 0 |
19
19
  <!-- BENCH-TREND:END -->
20
20
 
21
21
  说明: