Bloque I5 — session broadcastChannel: false explicit opt-out

`EngineSessionOptions.broadcastChannel` now accepts `string | false`
in addition to `undefined`. Passing `false` skips the
`BroadcastChannel` setup entirely — useful for:

- privacy-strict modes that don't want any cross-tab signal
- tests that want deterministic identity (no cross-tab race) without
  having to rely on `BroadcastChannel` being undefined
- SSR / Worker environments

Storage-driven sync via `localStorage`'s `storage` event still runs
when the storage adapter exposes `onChange` — only the explicit
channel post is skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
master
dev 5 months ago
parent b1e73f4586
commit e9eb69dbd4

@ -123,20 +123,29 @@ export function createEngineSession<TUser, TCredential = undefined, TData = unde
}
// ── Cross-tab broadcast (event + generation only, never tokens) ─────────
//
// `broadcastChannel: false` opts out — useful for privacy-strict
// modes, tests that need deterministic identity (no cross-tab race),
// and SSR / Worker environments that don't expose BroadcastChannel.
// Storage-driven sync via `localStorage`'s `storage` event still
// runs (see `wireSessionStorageSync` above) — only the explicit
// channel post is skipped.
const channelName = options.broadcastChannel ?? SESSION_DEFAULT_BROADCAST_CHANNEL;
broadcastSync = createSessionBroadcastSync<
TUser,
TCredential,
TData,
SessionChange<TUser, TCredential, TData>['event']
>({
channelName,
storage,
applyExternalChange: (next) => {
if (next !== null || state.current !== null) state.applyExternalChange(next);
}
});
if (options.broadcastChannel !== false) {
const channelName = options.broadcastChannel ?? SESSION_DEFAULT_BROADCAST_CHANNEL;
broadcastSync = createSessionBroadcastSync<
TUser,
TCredential,
TData,
SessionChange<TUser, TCredential, TData>['event']
>({
channelName,
storage,
applyExternalChange: (next) => {
if (next !== null || state.current !== null) state.applyExternalChange(next);
}
});
}
if (broadcastSync !== undefined) detachers.push(() => broadcastSync.detach());
// ── Public API ──────────────────────────────────────────────────────────

@ -507,7 +507,23 @@ export interface EngineSessionOptions<TUser, TCredential = undefined, TData = un
* App-level orchestration may translate those events into `app.*` events.
*/
bus?: EventPublisher<SessEventMap>;
broadcastChannel?: string;
/**
* Cross-tab synchronization through `BroadcastChannel`. When the
* runtime creates the engine, it opens a channel on this name and
* publishes `{ type: 'session.broadcast', event, generation }` —
* tokens and credentials NEVER cross the channel; receivers re-read
* the latest snapshot from `storage` instead.
*
* - `undefined` (default) → use `SESSION_DEFAULT_BROADCAST_CHANNEL`.
* - A string → custom channel name (handy for multi-tenant browsers
* that need isolated channels per app instance).
* - `false` → broadcast disabled. Use this in privacy-strict modes,
* in tests that want deterministic identity (no cross-tab races),
* or in environments without `BroadcastChannel` (Workers, SSR).
* Storage-driven sync via `localStorage`'s `storage` event still
* works — only the explicit channel post is skipped.
*/
broadcastChannel?: string | false;
}
// ============================================================================

Loading…
Cancel
Save

Powered by TurnKey Linux.