@gwakko/shared-websocket 0.12.1 → 0.12.2

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/README.md CHANGED
@@ -239,6 +239,7 @@ Incoming: WebSocket.onmessage
239
239
  | `protocols` | `string[]` | `[]` | WebSocket subprotocols |
240
240
  | `reconnect` | `boolean` | `true` | Auto-reconnect |
241
241
  | `reconnectMaxDelay` | `number` | `30000` | Max backoff (ms) |
242
+ | `reconnectMaxRetries` | `number` | `Infinity` | Max attempts before giving up |
242
243
  | `heartbeatInterval` | `number` | `30000` | Ping interval (ms) |
243
244
  | `sendBuffer` | `number` | `100` | Buffered messages during reconnect |
244
245
  | `auth` | `() => string` | — | Token callback (each connect) |
@@ -4,6 +4,8 @@ interface SharedSocketOptions {
4
4
  protocols?: string[];
5
5
  reconnect?: boolean;
6
6
  reconnectMaxDelay?: number;
7
+ /** Max reconnect attempts before giving up (default: Infinity). */
8
+ reconnectMaxRetries?: number;
7
9
  heartbeatInterval?: number;
8
10
  sendBuffer?: number;
9
11
  auth?: () => string | Promise<string>;
@@ -26,6 +28,7 @@ export declare class SharedSocket implements Disposable {
26
28
  private reconnectTimer;
27
29
  private onMessageFns;
28
30
  private onStateChangeFns;
31
+ private reconnectAttempts;
29
32
  private readonly opts;
30
33
  constructor(url: string, options?: SharedSocketOptions);
31
34
  get state(): SocketState;
@@ -238,12 +238,13 @@ function* backoff(base = 1e3, max = 3e4) {
238
238
 
239
239
  // src/SharedSocket.ts
240
240
  var SharedSocket = (_class3 = class {
241
- constructor(url, options = {}) {;_class3.prototype.__init11.call(this);_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);_class3.prototype.__init14.call(this);_class3.prototype.__init15.call(this);_class3.prototype.__init16.call(this);_class3.prototype.__init17.call(this);_class3.prototype.__init18.call(this);
241
+ constructor(url, options = {}) {;_class3.prototype.__init11.call(this);_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);_class3.prototype.__init14.call(this);_class3.prototype.__init15.call(this);_class3.prototype.__init16.call(this);_class3.prototype.__init17.call(this);_class3.prototype.__init18.call(this);_class3.prototype.__init19.call(this);
242
242
  this.url = url;
243
243
  this.opts = {
244
244
  protocols: _nullishCoalesce(options.protocols, () => ( [])),
245
245
  reconnect: _nullishCoalesce(options.reconnect, () => ( true)),
246
246
  reconnectMaxDelay: _nullishCoalesce(options.reconnectMaxDelay, () => ( 3e4)),
247
+ reconnectMaxRetries: _nullishCoalesce(options.reconnectMaxRetries, () => ( Infinity)),
247
248
  heartbeatInterval: _nullishCoalesce(options.heartbeatInterval, () => ( 3e4)),
248
249
  sendBuffer: _nullishCoalesce(options.sendBuffer, () => ( 100)),
249
250
  auth: options.auth,
@@ -266,6 +267,7 @@ var SharedSocket = (_class3 = class {
266
267
  __init16() {this.reconnectTimer = null}
267
268
  __init17() {this.onMessageFns = /* @__PURE__ */ new Set()}
268
269
  __init18() {this.onStateChangeFns = /* @__PURE__ */ new Set()}
270
+ __init19() {this.reconnectAttempts = 0}
269
271
 
270
272
  get state() {
271
273
  return this._state;
@@ -276,6 +278,7 @@ var SharedSocket = (_class3 = class {
276
278
  const connectUrl = await this.buildUrl();
277
279
  this.ws = new WebSocket(connectUrl, this.opts.protocols);
278
280
  this.ws.onopen = () => {
281
+ this.reconnectAttempts = 0;
279
282
  this.setState("connected");
280
283
  this.flushBuffer();
281
284
  this.startHeartbeat();
@@ -333,6 +336,11 @@ var SharedSocket = (_class3 = class {
333
336
  return () => this.onStateChangeFns.delete(fn);
334
337
  }
335
338
  reconnect() {
339
+ this.reconnectAttempts++;
340
+ if (this.reconnectAttempts > this.opts.reconnectMaxRetries) {
341
+ this.setState("closed");
342
+ return;
343
+ }
336
344
  this.setState("reconnecting");
337
345
  const gen = backoff(1e3, this.opts.reconnectMaxDelay);
338
346
  const attempt = () => {
@@ -397,16 +405,16 @@ var SharedSocket = (_class3 = class {
397
405
 
398
406
  // src/WorkerSocket.ts
399
407
  var WorkerSocket = (_class4 = class {
400
- constructor(url, options = {}) {;_class4.prototype.__init19.call(this);_class4.prototype.__init20.call(this);_class4.prototype.__init21.call(this);_class4.prototype.__init22.call(this);
408
+ constructor(url, options = {}) {;_class4.prototype.__init20.call(this);_class4.prototype.__init21.call(this);_class4.prototype.__init22.call(this);_class4.prototype.__init23.call(this);
401
409
  this.url = url;
402
410
  this.options = options;
403
411
  }
404
412
 
405
413
 
406
- __init19() {this.worker = null}
407
- __init20() {this._state = "closed"}
408
- __init21() {this.onMessageFns = /* @__PURE__ */ new Set()}
409
- __init22() {this.onStateChangeFns = /* @__PURE__ */ new Set()}
414
+ __init20() {this.worker = null}
415
+ __init21() {this._state = "closed"}
416
+ __init22() {this.onMessageFns = /* @__PURE__ */ new Set()}
417
+ __init23() {this.onStateChangeFns = /* @__PURE__ */ new Set()}
410
418
  get state() {
411
419
  return this._state;
412
420
  }
@@ -501,9 +509,9 @@ var WorkerSocket = (_class4 = class {
501
509
  }, _class4);
502
510
 
503
511
  // src/SubscriptionManager.ts
504
- var SubscriptionManager = (_class5 = class {constructor() { _class5.prototype.__init23.call(this);_class5.prototype.__init24.call(this); }
505
- __init23() {this.handlers = /* @__PURE__ */ new Map()}
506
- __init24() {this.lastMessages = /* @__PURE__ */ new Map()}
512
+ var SubscriptionManager = (_class5 = class {constructor() { _class5.prototype.__init24.call(this);_class5.prototype.__init25.call(this); }
513
+ __init24() {this.handlers = /* @__PURE__ */ new Map()}
514
+ __init25() {this.lastMessages = /* @__PURE__ */ new Map()}
507
515
  on(event, handler) {
508
516
  let set = this.handlers.get(event);
509
517
  if (!set) {
@@ -601,7 +609,7 @@ var NOOP_LOGGER = {
601
609
  }
602
610
  };
603
611
  var SharedWebSocket = (_class6 = class {
604
- constructor(url, options = {}) {;_class6.prototype.__init25.call(this);_class6.prototype.__init26.call(this);_class6.prototype.__init27.call(this);_class6.prototype.__init28.call(this);_class6.prototype.__init29.call(this);_class6.prototype.__init30.call(this);_class6.prototype.__init31.call(this);_class6.prototype.__init32.call(this);_class6.prototype.__init33.call(this);_class6.prototype.__init34.call(this);_class6.prototype.__init35.call(this);_class6.prototype.__init36.call(this);
612
+ constructor(url, options = {}) {;_class6.prototype.__init26.call(this);_class6.prototype.__init27.call(this);_class6.prototype.__init28.call(this);_class6.prototype.__init29.call(this);_class6.prototype.__init30.call(this);_class6.prototype.__init31.call(this);_class6.prototype.__init32.call(this);_class6.prototype.__init33.call(this);_class6.prototype.__init34.call(this);_class6.prototype.__init35.call(this);_class6.prototype.__init36.call(this);_class6.prototype.__init37.call(this);
605
613
  this.url = url;
606
614
  this.options = options;
607
615
  this.proto = { ...DEFAULT_PROTOCOL, ...options.events };
@@ -703,21 +711,21 @@ var SharedWebSocket = (_class6 = class {
703
711
 
704
712
 
705
713
 
706
- __init25() {this.socket = null}
707
- __init26() {this.subs = new SubscriptionManager()}
708
- __init27() {this.syncStore = /* @__PURE__ */ new Map()}
714
+ __init26() {this.socket = null}
715
+ __init27() {this.subs = new SubscriptionManager()}
716
+ __init28() {this.syncStore = /* @__PURE__ */ new Map()}
709
717
 
710
- __init28() {this.cleanups = []}
711
- __init29() {this.disposed = false}
718
+ __init29() {this.cleanups = []}
719
+ __init30() {this.disposed = false}
712
720
 
713
721
 
714
- __init30() {this.outgoingMiddleware = []}
715
- __init31() {this.incomingMiddleware = []}
716
- __init32() {this.serializers = /* @__PURE__ */ new Map()}
717
- __init33() {this.deserializers = /* @__PURE__ */ new Map()}
718
- __init34() {this._isAuthenticated = false}
719
- __init35() {this.authChannels = /* @__PURE__ */ new Map()}
720
- __init36() {this.authTopics = /* @__PURE__ */ new Set()}
722
+ __init31() {this.outgoingMiddleware = []}
723
+ __init32() {this.incomingMiddleware = []}
724
+ __init33() {this.serializers = /* @__PURE__ */ new Map()}
725
+ __init34() {this.deserializers = /* @__PURE__ */ new Map()}
726
+ __init35() {this._isAuthenticated = false}
727
+ __init36() {this.authChannels = /* @__PURE__ */ new Map()}
728
+ __init37() {this.authTopics = /* @__PURE__ */ new Set()}
721
729
  get connected() {
722
730
  return _optionalChain([this, 'access', _30 => _30.socket, 'optionalAccess', _31 => _31.state]) === "connected" || !this.coordinator.isLeader;
723
731
  }
@@ -1087,6 +1095,7 @@ var SharedWebSocket = (_class6 = class {
1087
1095
  protocols: this.options.protocols,
1088
1096
  reconnect: this.options.reconnect,
1089
1097
  reconnectMaxDelay: this.options.reconnectMaxDelay,
1098
+ reconnectMaxRetries: this.options.reconnectMaxRetries,
1090
1099
  heartbeatInterval: this.options.heartbeatInterval,
1091
1100
  sendBuffer: this.options.sendBuffer,
1092
1101
  pingPayload: this.proto.ping
@@ -1214,4 +1223,4 @@ var SharedWebSocket = (_class6 = class {
1214
1223
 
1215
1224
 
1216
1225
  exports.MessageBus = MessageBus; exports.TabCoordinator = TabCoordinator; exports.SharedSocket = SharedSocket; exports.WorkerSocket = WorkerSocket; exports.SubscriptionManager = SubscriptionManager; exports.SharedWebSocket = SharedWebSocket;
1217
- //# sourceMappingURL=chunk-MP3K5IEI.cjs.map
1226
+ //# sourceMappingURL=chunk-ADGLL3J2.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/gwakko/Projects/shared-websocket/dist/chunk-ADGLL3J2.cjs","../src/utils/id.ts","../src/MessageBus.ts","../src/TabCoordinator.ts","../src/utils/backoff.ts","../src/SharedSocket.ts","../src/WorkerSocket.ts","../src/SubscriptionManager.ts","../src/SharedWebSocket.ts"],"names":[],"mappings":"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","file":"/Users/gwakko/Projects/shared-websocket/dist/chunk-ADGLL3J2.cjs","sourcesContent":[null,"export function generateId(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n","import './utils/disposable';\nimport { generateId } from './utils/id';\nimport type { BusMessage, Unsubscribe } from './types';\n\ntype Listener = (msg: BusMessage) => void;\n\nexport class MessageBus implements Disposable {\n private channel: BroadcastChannel;\n private listeners = new Map<string, Set<Listener>>();\n private pendingRequests = new Map<string, { resolve: (v: unknown) => void; reject: (e: Error) => void; timer: ReturnType<typeof setTimeout> }>();\n\n constructor(\n channelName: string,\n private readonly tabId: string,\n ) {\n this.channel = new BroadcastChannel(channelName);\n this.channel.onmessage = (ev: MessageEvent<BusMessage>) => {\n this.handleMessage(ev.data);\n };\n }\n\n subscribe<T>(topic: string, fn: (data: T) => void): Unsubscribe {\n const wrapper: Listener = (msg) => {\n if (msg.source !== this.tabId) fn(msg.data as T);\n };\n this.addListener(topic, wrapper);\n return () => this.removeListener(topic, wrapper);\n }\n\n publish<T>(topic: string, data: T): void {\n this.postMessage({ topic, type: 'publish', data });\n }\n\n broadcast<T>(topic: string, data: T): void {\n const msg = this.createMessage(topic, 'broadcast', data);\n this.channel.postMessage(msg);\n // Also deliver to self\n this.handleMessage(msg);\n }\n\n async request<T, R>(topic: string, data: T, timeout = 5000): Promise<R> {\n const msg = this.createMessage(topic, 'request', data);\n return new Promise<R>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pendingRequests.delete(msg.id);\n reject(new Error(`MessageBus.request: timeout for topic \"${topic}\"`));\n }, timeout);\n this.pendingRequests.set(msg.id, { resolve: resolve as (v: unknown) => void, reject, timer });\n this.channel.postMessage(msg);\n });\n }\n\n respond<T, R>(topic: string, fn: (data: T) => R | Promise<R>): Unsubscribe {\n const wrapper: Listener = async (msg) => {\n if (msg.type !== 'request' || msg.source === this.tabId) return;\n const result = await fn(msg.data as T);\n this.postMessage({ topic, type: 'response', data: { requestId: msg.id, result } });\n };\n this.addListener(topic, wrapper);\n return () => this.removeListener(topic, wrapper);\n }\n\n private handleMessage(msg: BusMessage): void {\n // Handle response to pending request\n if (msg.type === 'response') {\n const payload = msg.data as { requestId: string; result: unknown };\n const pending = this.pendingRequests.get(payload.requestId);\n if (pending) {\n clearTimeout(pending.timer);\n this.pendingRequests.delete(payload.requestId);\n pending.resolve(payload.result);\n return;\n }\n }\n\n const listeners = this.listeners.get(msg.topic);\n if (listeners) {\n for (const fn of listeners) fn(msg);\n }\n }\n\n private postMessage(partial: Pick<BusMessage, 'topic' | 'type' | 'data'>): void {\n this.channel.postMessage(this.createMessage(partial.topic, partial.type, partial.data));\n }\n\n private createMessage(topic: string, type: BusMessage['type'], data: unknown): BusMessage {\n return { id: generateId(), source: this.tabId, topic, type, data, timestamp: Date.now() };\n }\n\n private addListener(topic: string, fn: Listener): void {\n let set = this.listeners.get(topic);\n if (!set) {\n set = new Set();\n this.listeners.set(topic, set);\n }\n set.add(fn);\n }\n\n private removeListener(topic: string, fn: Listener): void {\n this.listeners.get(topic)?.delete(fn);\n }\n\n [Symbol.dispose](): void {\n for (const pending of this.pendingRequests.values()) {\n clearTimeout(pending.timer);\n pending.reject(new Error('MessageBus disposed'));\n }\n this.pendingRequests.clear();\n this.listeners.clear();\n this.channel.close();\n }\n}\n","import './utils/disposable';\nimport { MessageBus } from './MessageBus';\nimport type { Unsubscribe } from './types';\n\ninterface CoordinatorOptions {\n electionTimeout?: number; // ms to wait for rejection (default 200)\n heartbeatInterval?: number; // ms between heartbeats (default 2000)\n leaderTimeout?: number; // ms without heartbeat to trigger election (default 5000)\n}\n\nexport class TabCoordinator implements Disposable {\n private _isLeader = false;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private leaderCheckTimer: ReturnType<typeof setInterval> | null = null;\n private lastHeartbeat = 0;\n private disposed = false;\n\n private onBecomeLeaderFns = new Set<() => void>();\n private onLoseLeadershipFns = new Set<() => void>();\n private cleanups: Unsubscribe[] = [];\n\n private readonly electionTimeout: number;\n private readonly heartbeatInterval: number;\n private readonly leaderTimeout: number;\n\n constructor(\n private readonly bus: MessageBus,\n private readonly tabId: string,\n options: CoordinatorOptions = {},\n ) {\n this.electionTimeout = options.electionTimeout ?? 200;\n this.heartbeatInterval = options.heartbeatInterval ?? 2000;\n this.leaderTimeout = options.leaderTimeout ?? 5000;\n\n // Listen for election requests — reject if we are leader\n this.cleanups.push(\n this.bus.subscribe<{ tabId: string }>('coord:election', () => {\n if (this._isLeader) {\n this.bus.publish('coord:reject', { tabId: this.tabId });\n }\n }),\n );\n\n // Listen for heartbeats\n this.cleanups.push(\n this.bus.subscribe<{ tabId: string }>('coord:heartbeat', () => {\n this.lastHeartbeat = Date.now();\n }),\n );\n\n // Listen for abdication\n this.cleanups.push(\n this.bus.subscribe('coord:abdicate', () => {\n if (!this._isLeader && !this.disposed) {\n this.elect();\n }\n }),\n );\n }\n\n get isLeader(): boolean {\n return this._isLeader;\n }\n\n async elect(): Promise<void> {\n if (this.disposed) return;\n\n return new Promise<void>((resolve) => {\n let rejected = false;\n\n const unsub = this.bus.subscribe('coord:reject', () => {\n rejected = true;\n unsub();\n // We are follower — start monitoring leader heartbeat\n this.startLeaderCheck();\n resolve();\n });\n\n this.bus.publish('coord:election', { tabId: this.tabId });\n\n setTimeout(() => {\n unsub();\n if (!rejected && !this.disposed) {\n this.becomeLeader();\n }\n resolve();\n }, this.electionTimeout);\n });\n }\n\n abdicate(): void {\n if (!this._isLeader) return;\n this._isLeader = false;\n this.stopHeartbeat();\n this.bus.publish('coord:abdicate', { tabId: this.tabId });\n for (const fn of this.onLoseLeadershipFns) fn();\n }\n\n onBecomeLeader(fn: () => void): Unsubscribe {\n this.onBecomeLeaderFns.add(fn);\n return () => this.onBecomeLeaderFns.delete(fn);\n }\n\n onLoseLeadership(fn: () => void): Unsubscribe {\n this.onLoseLeadershipFns.add(fn);\n return () => this.onLoseLeadershipFns.delete(fn);\n }\n\n private becomeLeader(): void {\n this._isLeader = true;\n this.stopLeaderCheck();\n this.startHeartbeat();\n for (const fn of this.onBecomeLeaderFns) fn();\n }\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n this.bus.publish('coord:heartbeat', { tabId: this.tabId });\n }, this.heartbeatInterval);\n // Send immediately\n this.bus.publish('coord:heartbeat', { tabId: this.tabId });\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private startLeaderCheck(): void {\n this.stopLeaderCheck();\n this.lastHeartbeat = Date.now();\n this.leaderCheckTimer = setInterval(() => {\n if (Date.now() - this.lastHeartbeat > this.leaderTimeout && !this.disposed) {\n this.stopLeaderCheck();\n this.elect();\n }\n }, 1000);\n }\n\n private stopLeaderCheck(): void {\n if (this.leaderCheckTimer) {\n clearInterval(this.leaderCheckTimer);\n this.leaderCheckTimer = null;\n }\n }\n\n [Symbol.dispose](): void {\n this.disposed = true;\n if (this._isLeader) {\n this.abdicate();\n }\n this.stopHeartbeat();\n this.stopLeaderCheck();\n for (const unsub of this.cleanups) unsub();\n this.cleanups = [];\n this.onBecomeLeaderFns.clear();\n this.onLoseLeadershipFns.clear();\n }\n}\n","/** Exponential backoff generator with jitter. */\nexport function* backoff(base = 1000, max = 30_000): Generator<number> {\n let delay = base;\n while (true) {\n const jitter = delay * 0.25 * (Math.random() * 2 - 1);\n yield Math.min(delay + jitter, max);\n delay = Math.min(delay * 2, max);\n }\n}\n","import './utils/disposable';\nimport { backoff } from './utils/backoff';\nimport type { SocketState, Unsubscribe, EventHandler } from './types';\n\ninterface SharedSocketOptions {\n protocols?: string[];\n reconnect?: boolean;\n reconnectMaxDelay?: number;\n /** Max reconnect attempts before giving up (default: Infinity). */\n reconnectMaxRetries?: number;\n heartbeatInterval?: number;\n sendBuffer?: number;\n auth?: () => string | Promise<string>;\n authToken?: string;\n authParam?: string;\n /** Heartbeat payload (default: { type: \"ping\" }). */\n pingPayload?: unknown;\n /** Custom serializer (default: JSON.stringify). */\n serialize?: (data: unknown) => string | ArrayBuffer | Blob;\n /** Custom deserializer (default: JSON.parse). */\n deserialize?: (raw: string | ArrayBuffer) => unknown;\n}\n\nexport class SharedSocket implements Disposable {\n private ws: WebSocket | null = null;\n private _state: SocketState = 'closed';\n private buffer: unknown[] = [];\n private disposed = false;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n private onMessageFns = new Set<EventHandler>();\n private onStateChangeFns = new Set<(state: SocketState) => void>();\n\n private reconnectAttempts = 0;\n\n private readonly opts: Required<Omit<SharedSocketOptions, 'auth' | 'authToken' | 'authParam' | 'pingPayload' | 'serialize' | 'deserialize'>> & {\n auth?: () => string | Promise<string>;\n authToken?: string;\n authParam: string;\n pingPayload: unknown;\n serialize: (data: unknown) => string | ArrayBuffer | Blob;\n deserialize: (raw: string | ArrayBuffer) => unknown;\n };\n\n constructor(\n private url: string,\n options: SharedSocketOptions = {},\n ) {\n this.opts = {\n protocols: options.protocols ?? [],\n reconnect: options.reconnect ?? true,\n reconnectMaxDelay: options.reconnectMaxDelay ?? 30_000,\n reconnectMaxRetries: options.reconnectMaxRetries ?? Infinity,\n heartbeatInterval: options.heartbeatInterval ?? 30_000,\n sendBuffer: options.sendBuffer ?? 100,\n auth: options.auth,\n authToken: options.authToken,\n authParam: options.authParam ?? 'token',\n pingPayload: options.pingPayload ?? { type: 'ping' },\n serialize: options.serialize ?? ((data: unknown) => JSON.stringify(data)),\n deserialize: options.deserialize ?? ((raw: string | ArrayBuffer) => {\n if (typeof raw === 'string') return JSON.parse(raw);\n // ArrayBuffer → decode as UTF-8 then parse\n return JSON.parse(new TextDecoder().decode(raw));\n }),\n };\n }\n\n get state(): SocketState {\n return this._state;\n }\n\n async connect(): Promise<void> {\n if (this.disposed) return;\n\n this.setState('connecting');\n\n const connectUrl = await this.buildUrl();\n this.ws = new WebSocket(connectUrl, this.opts.protocols);\n\n this.ws.onopen = () => {\n this.reconnectAttempts = 0;\n this.setState('connected');\n this.flushBuffer();\n this.startHeartbeat();\n };\n\n this.ws.onmessage = (ev: MessageEvent) => {\n let data: unknown;\n try {\n data = this.opts.deserialize(ev.data as string | ArrayBuffer);\n } catch {\n data = ev.data;\n }\n for (const fn of this.onMessageFns) fn(data);\n };\n\n this.ws.onclose = () => {\n this.stopHeartbeat();\n if (!this.disposed && this.opts.reconnect) {\n this.reconnect();\n } else {\n this.setState('closed');\n }\n };\n\n this.ws.onerror = () => {\n // onclose will fire after onerror\n };\n }\n\n disconnect(): void {\n this.disposed = true;\n this.stopHeartbeat();\n this.clearReconnect();\n\n if (this.ws) {\n this.ws.onclose = null;\n this.ws.onmessage = null;\n this.ws.onerror = null;\n if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {\n this.ws.close(1000, 'client disconnect');\n }\n this.ws = null;\n }\n\n this.setState('closed');\n }\n\n send(data: unknown): void {\n if (this._state === 'connected' && this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(this.opts.serialize(data));\n } else if (this._state === 'reconnecting' || this._state === 'connecting') {\n if (this.buffer.length < this.opts.sendBuffer) {\n this.buffer.push(data);\n }\n }\n }\n\n onMessage(fn: EventHandler): Unsubscribe {\n this.onMessageFns.add(fn);\n return () => this.onMessageFns.delete(fn);\n }\n\n onStateChange(fn: (state: SocketState) => void): Unsubscribe {\n this.onStateChangeFns.add(fn);\n return () => this.onStateChangeFns.delete(fn);\n }\n\n private reconnect(): void {\n this.reconnectAttempts++;\n\n if (this.reconnectAttempts > this.opts.reconnectMaxRetries) {\n this.setState('closed');\n return;\n }\n\n this.setState('reconnecting');\n const gen = backoff(1000, this.opts.reconnectMaxDelay);\n\n const attempt = () => {\n if (this.disposed) return;\n const delay = gen.next().value;\n this.reconnectTimer = setTimeout(() => {\n if (!this.disposed) this.connect();\n }, delay);\n };\n\n attempt();\n }\n\n private flushBuffer(): void {\n const pending = this.buffer.splice(0);\n for (const item of pending) {\n this.send(item);\n }\n }\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(this.opts.serialize(this.opts.pingPayload));\n }\n }, this.opts.heartbeatInterval);\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private clearReconnect(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n\n private async buildUrl(): Promise<string> {\n // Resolve token: callback > static > none\n let token: string | undefined;\n if (this.opts.auth) {\n token = await this.opts.auth();\n } else if (this.opts.authToken) {\n token = this.opts.authToken;\n }\n\n if (!token) return this.url;\n\n // WebSocket URLs (ws://, wss://) are not fully supported by URL API.\n // Convert to http(s) for parsing, then back to ws(s).\n const httpUrl = this.url.replace(/^ws(s?):\\/\\//, 'http$1://');\n const parsed = new URL(httpUrl);\n parsed.searchParams.set(this.opts.authParam, token);\n\n return parsed.toString().replace(/^http(s?):\\/\\//, 'ws$1://');\n }\n\n private setState(state: SocketState): void {\n this._state = state;\n for (const fn of this.onStateChangeFns) fn(state);\n }\n\n [Symbol.dispose](): void {\n this.disconnect();\n this.onMessageFns.clear();\n this.onStateChangeFns.clear();\n this.buffer = [];\n }\n}\n","import './utils/disposable';\nimport type { SocketState, Unsubscribe, EventHandler } from './types';\n\n/**\n * WorkerSocket — WebSocket running inside a Web Worker.\n *\n * Same interface as SharedSocket, but WebSocket lives off main thread.\n * Benefits: heartbeat timers and JSON parsing don't block UI rendering.\n *\n * Use when:\n * - High message rate (50+ msgs/sec)\n * - Heavy JSON payloads\n * - UI does complex rendering that could block main thread\n *\n * Don't use when:\n * - Low message rate (simple chat, notifications)\n * - Bundle size matters (adds worker file)\n * - Debugging (Worker DevTools is less convenient)\n */\nexport class WorkerSocket implements Disposable {\n private worker: Worker | null = null;\n private _state: SocketState = 'closed';\n\n private onMessageFns = new Set<EventHandler>();\n private onStateChangeFns = new Set<(state: SocketState) => void>();\n\n constructor(\n private url: string,\n private options: {\n protocols?: string[];\n reconnect?: boolean;\n reconnectMaxDelay?: number;\n heartbeatInterval?: number;\n sendBuffer?: number;\n workerUrl?: string | URL;\n } = {},\n ) {}\n\n get state(): SocketState {\n return this._state;\n }\n\n connect(): void {\n // Create worker from inline blob if no workerUrl provided\n const workerUrl = this.options.workerUrl ?? this.createWorkerBlob();\n\n this.worker = new Worker(workerUrl, { type: 'module' });\n\n this.worker.onmessage = (ev: MessageEvent) => {\n const msg = ev.data;\n\n switch (msg.type) {\n case 'state':\n this._state = msg.state;\n for (const fn of this.onStateChangeFns) fn(msg.state);\n break;\n\n case 'message':\n for (const fn of this.onMessageFns) fn(msg.data);\n break;\n\n case 'open':\n // State already set via 'state' message\n break;\n\n case 'close':\n break;\n\n case 'error':\n console.error('WorkerSocket error:', msg.message);\n break;\n }\n };\n\n this.worker.postMessage({\n type: 'connect',\n url: this.url,\n protocols: this.options.protocols ?? [],\n reconnect: this.options.reconnect ?? true,\n reconnectMaxDelay: this.options.reconnectMaxDelay ?? 30_000,\n heartbeatInterval: this.options.heartbeatInterval ?? 30_000,\n bufferSize: this.options.sendBuffer ?? 100,\n });\n }\n\n send(data: unknown): void {\n this.worker?.postMessage({ type: 'send', data });\n }\n\n disconnect(): void {\n this.worker?.postMessage({ type: 'disconnect' });\n setTimeout(() => {\n this.worker?.terminate();\n this.worker = null;\n }, 100);\n this._state = 'closed';\n }\n\n onMessage(fn: EventHandler): Unsubscribe {\n this.onMessageFns.add(fn);\n return () => this.onMessageFns.delete(fn);\n }\n\n onStateChange(fn: (state: SocketState) => void): Unsubscribe {\n this.onStateChangeFns.add(fn);\n return () => this.onStateChangeFns.delete(fn);\n }\n\n private createWorkerBlob(): URL {\n // Inline the worker code as a blob URL\n // In production, use a bundler (Vite, webpack) to handle worker imports\n const code = `\n let ws = null, state = 'closed', buffer = [], disposed = false;\n let heartbeatTimer = null, reconnectTimer = null;\n let url = '', protocols = [], shouldReconnect = true;\n let maxDelay = 30000, hbInterval = 30000, maxBuf = 100, delay = 1000;\n\n function setState(s) { state = s; self.postMessage({ type: 'state', state: s }); }\n function connect() {\n if (disposed) return;\n setState('connecting');\n ws = new WebSocket(url, protocols);\n ws.onopen = () => { setState('connected'); delay = 1000; self.postMessage({ type: 'open' }); flush(); startHB(); };\n ws.onmessage = (e) => { let d; try { d = JSON.parse(e.data); } catch { d = e.data; } self.postMessage({ type: 'message', data: d }); };\n ws.onclose = (e) => { stopHB(); self.postMessage({ type: 'close', code: e.code, reason: e.reason }); if (!disposed && shouldReconnect && e.code !== 1000) reconnect(); else setState('closed'); };\n ws.onerror = () => { self.postMessage({ type: 'error', message: 'error' }); };\n }\n function send(d) { if (state === 'connected' && ws?.readyState === 1) ws.send(JSON.stringify(d)); else if (buffer.length < maxBuf) buffer.push(d); }\n function flush() { const p = buffer.splice(0); p.forEach(send); }\n function startHB() { stopHB(); heartbeatTimer = setInterval(() => { if (ws?.readyState === 1) ws.send('{\"type\":\"ping\"}'); }, hbInterval); }\n function stopHB() { if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; } }\n function reconnect() { setState('reconnecting'); const j = delay * 0.25 * (Math.random() * 2 - 1); reconnectTimer = setTimeout(() => { if (!disposed) connect(); }, Math.min(delay + j, maxDelay)); delay = Math.min(delay * 2, maxDelay); }\n self.onmessage = (e) => {\n const c = e.data;\n if (c.type === 'connect') { url = c.url; protocols = c.protocols || []; shouldReconnect = c.reconnect ?? true; maxDelay = c.reconnectMaxDelay || 30000; hbInterval = c.heartbeatInterval || 30000; maxBuf = c.bufferSize || 100; connect(); }\n if (c.type === 'send') send(c.data);\n if (c.type === 'disconnect') { disposed = true; stopHB(); if (reconnectTimer) clearTimeout(reconnectTimer); if (ws) { ws.onclose = null; if (ws.readyState < 2) ws.close(1000); ws = null; } buffer = []; setState('closed'); }\n };\n `;\n const blob = new Blob([code], { type: 'application/javascript' });\n return new URL(URL.createObjectURL(blob));\n }\n\n [Symbol.dispose](): void {\n this.disconnect();\n this.onMessageFns.clear();\n this.onStateChangeFns.clear();\n }\n}\n","import './utils/disposable';\nimport type { EventHandler, Unsubscribe } from './types';\n\nexport class SubscriptionManager implements Disposable {\n private handlers = new Map<string, Set<EventHandler>>();\n private lastMessages = new Map<string, unknown>();\n\n on(event: string, handler: EventHandler): Unsubscribe {\n let set = this.handlers.get(event);\n if (!set) {\n set = new Set();\n this.handlers.set(event, set);\n }\n set.add(handler);\n return () => set!.delete(handler);\n }\n\n once(event: string, handler: EventHandler): Unsubscribe {\n const wrapper: EventHandler = (data) => {\n unsub();\n handler(data);\n };\n const unsub = this.on(event, wrapper);\n return unsub;\n }\n\n off(event: string, handler?: EventHandler): void {\n if (handler) {\n this.handlers.get(event)?.delete(handler);\n } else {\n this.handlers.delete(event);\n }\n }\n\n emit(event: string, data: unknown): void {\n this.lastMessages.set(event, data);\n const set = this.handlers.get(event);\n if (set) {\n for (const fn of set) fn(data);\n }\n }\n\n getLastMessage(event: string): unknown | undefined {\n return this.lastMessages.get(event);\n }\n\n async *stream(event: string, signal?: AbortSignal): AsyncGenerator<unknown> {\n const queue: unknown[] = [];\n let resolve: (() => void) | null = null;\n let done = false;\n\n const unsub = this.on(event, (data) => {\n queue.push(data);\n resolve?.();\n });\n\n const onAbort = () => {\n done = true;\n resolve?.();\n };\n signal?.addEventListener('abort', onAbort);\n\n try {\n while (!done) {\n if (queue.length > 0) {\n yield queue.shift()!;\n } else {\n await new Promise<void>((r) => { resolve = r; });\n resolve = null;\n }\n }\n } finally {\n unsub();\n signal?.removeEventListener('abort', onAbort);\n }\n }\n\n offAll(): void {\n this.handlers.clear();\n this.lastMessages.clear();\n }\n\n [Symbol.dispose](): void {\n this.offAll();\n }\n}\n","import './utils/disposable';\nimport { generateId } from './utils/id';\nimport { MessageBus } from './MessageBus';\nimport { TabCoordinator } from './TabCoordinator';\nimport { SharedSocket } from './SharedSocket';\nimport { WorkerSocket } from './WorkerSocket';\nimport { SubscriptionManager } from './SubscriptionManager';\nimport type { SharedWebSocketOptions, TabRole, Unsubscribe, EventHandler, Channel, EventProtocol, EventMap, Logger, Middleware } from './types';\n\nconst DEFAULT_PROTOCOL: EventProtocol = {\n eventField: 'event',\n dataField: 'data',\n channelJoin: '$channel:join',\n channelLeave: '$channel:leave',\n ping: { type: 'ping' },\n defaultEvent: 'message',\n topicSubscribe: '$topic:subscribe',\n topicUnsubscribe: '$topic:unsubscribe',\n authLogin: '$auth:login',\n authLogout: '$auth:logout',\n authRevoked: '$auth:revoked',\n};\n\nconst NOOP_LOGGER: Logger = {\n debug() {},\n info() {},\n warn() {},\n error() {},\n};\n\n/** Common interface for both SharedSocket and WorkerSocket. */\ninterface SocketAdapter {\n readonly state: string;\n connect(): void;\n send(data: unknown): void;\n disconnect(): void;\n onMessage(fn: EventHandler): Unsubscribe;\n onStateChange(fn: (state: string) => void): Unsubscribe;\n [Symbol.dispose](): void;\n}\n\n/**\n * SharedWebSocket — shares ONE WebSocket connection across browser tabs.\n *\n * @typeParam TEvents - Event map for type-safe subscriptions.\n *\n * @example\n * // Typed events\n * type Events = {\n * 'chat.message': { text: string; userId: string };\n * 'order.created': { id: string; total: number };\n * };\n * const ws = new SharedWebSocket<Events>(url);\n * ws.on('chat.message', (msg) => msg.text); // ← msg: { text, userId }\n */\nexport class SharedWebSocket<TEvents extends EventMap = EventMap> implements Disposable {\n private bus: MessageBus;\n private coordinator: TabCoordinator;\n private socket: SocketAdapter | null = null;\n private subs = new SubscriptionManager();\n private syncStore = new Map<string, unknown>();\n private tabId: string;\n private cleanups: Unsubscribe[] = [];\n private disposed = false;\n private readonly proto: EventProtocol;\n private readonly log: Logger;\n private outgoingMiddleware: Middleware[] = [];\n private incomingMiddleware: Middleware[] = [];\n private serializers = new Map<string, (data: unknown) => unknown>();\n private deserializers = new Map<string, (data: unknown) => unknown>();\n private _isAuthenticated = false;\n private authChannels = new Map<string, Channel>();\n private authTopics = new Set<string>();\n\n constructor(\n private readonly url: string,\n private readonly options: SharedWebSocketOptions<TEvents> = {} as SharedWebSocketOptions<TEvents>,\n ) {\n this.proto = { ...DEFAULT_PROTOCOL, ...options.events };\n this.log = options.debug ? (options.logger ?? console) : NOOP_LOGGER;\n this.tabId = generateId();\n this.log.debug('[SharedWS] init', { tabId: this.tabId, url });\n this.bus = new MessageBus('shared-ws', this.tabId);\n this.coordinator = new TabCoordinator(this.bus, this.tabId, {\n electionTimeout: options.electionTimeout,\n heartbeatInterval: options.leaderHeartbeat,\n leaderTimeout: options.leaderTimeout,\n });\n\n // When ANY tab receives a WS message via bus → emit to local subscribers\n this.cleanups.push(\n this.bus.subscribe<{ event: string; data: unknown }>('ws:message', (msg) => {\n this.subs.emit(msg.event, msg.data);\n }),\n );\n\n // Leader listens for send requests from followers\n this.cleanups.push(\n this.bus.subscribe<{ event: string; data: unknown }>('ws:send', (msg) => {\n if (this.coordinator.isLeader && this.socket) {\n this.socket.send({ [this.proto.eventField]: msg.event, [this.proto.dataField]: msg.data });\n }\n }),\n );\n\n // Sync across tabs\n this.cleanups.push(\n this.bus.subscribe<{ key: string; value: unknown }>('ws:sync', (msg) => {\n this.syncStore.set(msg.key, msg.value);\n this.subs.emit(`sync:${msg.key}`, msg.value);\n }),\n );\n\n // Leader lifecycle\n this.coordinator.onBecomeLeader(() => {\n this.handleBecomeLeader();\n this.bus.broadcast('ws:lifecycle', { type: 'leader', isLeader: true });\n });\n this.coordinator.onLoseLeadership(() => {\n this.handleLoseLeadership();\n this.bus.broadcast('ws:lifecycle', { type: 'leader', isLeader: false });\n });\n\n // Lifecycle events from bus (all tabs receive)\n this.cleanups.push(\n this.bus.subscribe<{ type: string; isLeader?: boolean; error?: unknown; authenticated?: boolean }>('ws:lifecycle', (msg) => {\n switch (msg.type) {\n case 'connect':\n this.subs.emit('$lifecycle:connect', undefined);\n break;\n case 'disconnect':\n this.subs.emit('$lifecycle:disconnect', undefined);\n break;\n case 'reconnecting':\n this.subs.emit('$lifecycle:reconnecting', undefined);\n break;\n case 'leader':\n this.subs.emit('$lifecycle:leader', msg.isLeader);\n break;\n case 'error':\n this.subs.emit('$lifecycle:error', msg.error);\n break;\n case 'auth': {\n this._isAuthenticated = !!msg.authenticated;\n if (!msg.authenticated) {\n this.authChannels.clear();\n this.authTopics.clear();\n }\n this.subs.emit('$lifecycle:auth', msg.authenticated);\n break;\n }\n }\n }),\n );\n\n // Track tab visibility\n if (typeof document !== 'undefined') {\n const onVisibilityChange = () => {\n const active = !document.hidden;\n this.subs.emit('$lifecycle:active', active);\n this.log.debug('[SharedWS]', active ? '👁 tab active' : '👁 tab hidden');\n };\n document.addEventListener('visibilitychange', onVisibilityChange);\n this.cleanups.push(() => document.removeEventListener('visibilitychange', onVisibilityChange));\n }\n\n // Handle server-initiated auth revocation\n this.cleanups.push(\n this.subs.on(this.proto.authRevoked, () => {\n if (this.coordinator.isLeader) {\n for (const [, ch] of this.authChannels) ch.leave();\n for (const topic of this.authTopics) this.unsubscribe(topic);\n }\n this.authChannels.clear();\n this.authTopics.clear();\n this._isAuthenticated = false;\n this.syncStore.delete('$auth:token');\n this.subs.emit('$lifecycle:auth', false);\n this.log.warn('[SharedWS] auth revoked by server');\n }),\n );\n\n // Cleanup on tab close\n if (typeof window !== 'undefined') {\n const onBeforeUnload = () => this[Symbol.dispose]();\n window.addEventListener('beforeunload', onBeforeUnload);\n this.cleanups.push(() => window.removeEventListener('beforeunload', onBeforeUnload));\n }\n }\n\n get connected(): boolean {\n return this.socket?.state === 'connected' || !this.coordinator.isLeader;\n }\n\n get tabRole(): TabRole {\n return this.coordinator.isLeader ? 'leader' : 'follower';\n }\n\n /** Whether the user is authenticated via runtime auth. */\n get isAuthenticated(): boolean {\n return this._isAuthenticated;\n }\n\n /** Whether this tab is currently visible/focused. */\n get isActive(): boolean {\n return typeof document !== 'undefined' ? !document.hidden : true;\n }\n\n /** Start leader election and connect. */\n async connect(): Promise<void> {\n await this.coordinator.elect();\n }\n\n // ─── Lifecycle Hooks ─────────────────────────────────\n\n /** Called when WebSocket connection opens (broadcast to all tabs). */\n onConnect(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:connect', fn);\n }\n\n /** Called when WebSocket connection closes (broadcast to all tabs). */\n onDisconnect(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:disconnect', fn);\n }\n\n /** Called when WebSocket starts reconnecting (broadcast to all tabs). */\n onReconnecting(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:reconnecting', fn);\n }\n\n /** Called when this tab becomes leader or loses leadership. */\n onLeaderChange(fn: (isLeader: boolean) => void): Unsubscribe {\n return this.subs.on('$lifecycle:leader', fn as EventHandler);\n }\n\n /** Called on WebSocket or network error (broadcast to all tabs). */\n onError(fn: (error: unknown) => void): Unsubscribe {\n return this.subs.on('$lifecycle:error', fn as EventHandler);\n }\n\n /** Called when this tab becomes visible/focused. */\n onActive(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:active', ((isActive: unknown) => {\n if (isActive === true) fn();\n }) as EventHandler);\n }\n\n /** Called when this tab goes to background/hidden. */\n onInactive(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:active', ((isActive: unknown) => {\n if (isActive === false) fn();\n }) as EventHandler);\n }\n\n /** Called on any visibility change. */\n onVisibilityChange(fn: (isActive: boolean) => void): Unsubscribe {\n return this.subs.on('$lifecycle:active', fn as EventHandler);\n }\n\n // ─── Authentication ──────────────────────────────────\n\n /**\n * Authenticate on an existing connection. Sends auth event to server,\n * syncs auth state across all tabs. Use for login after guest connection.\n *\n * @example\n * const token = await loginApi(email, password);\n * ws.authenticate(token);\n *\n * @example\n * // React — via useSocketAuth hook\n * const { authenticate } = useSocketAuth();\n * authenticate(token);\n */\n authenticate(token: string): void {\n this._isAuthenticated = true;\n this.syncStore.set('$auth:token', token);\n this.bus.broadcast('ws:sync', { key: '$auth:token', value: token });\n this.send(this.proto.authLogin, { token });\n this.bus.broadcast('ws:lifecycle', { type: 'auth', authenticated: true });\n this.log.info('[SharedWS] authenticated');\n }\n\n /**\n * Deauthenticate — notifies server, auto-leaves all auth-required channels\n * and topics, syncs state across tabs. Connection stays open for public events.\n *\n * @example\n * ws.deauthenticate(); // connection stays open, auth subscriptions cleaned up\n */\n deauthenticate(): void {\n // Leave auth channels and unsubscribe auth topics\n for (const [, ch] of this.authChannels) ch.leave();\n this.authChannels.clear();\n for (const topic of this.authTopics) this.unsubscribe(topic);\n this.authTopics.clear();\n\n this._isAuthenticated = false;\n this.send(this.proto.authLogout, {});\n this.syncStore.delete('$auth:token');\n this.bus.broadcast('ws:sync', { key: '$auth:token', value: undefined });\n this.bus.broadcast('ws:lifecycle', { type: 'auth', authenticated: false });\n this.log.info('[SharedWS] deauthenticated');\n }\n\n /**\n * Called when auth state changes (authenticate, deauthenticate, or server revocation).\n *\n * @example\n * ws.onAuthChange((authenticated) => {\n * if (!authenticated) router.push('/login');\n * });\n */\n onAuthChange(fn: (authenticated: boolean) => void): Unsubscribe {\n return this.subs.on('$lifecycle:auth', fn as EventHandler);\n }\n\n // ─── Middleware ───────────────────────────────────────\n\n /**\n * Add middleware to transform messages before send or after receive.\n * Return null from middleware to drop the message.\n *\n * @example\n * // Add timestamp to every outgoing message\n * ws.use('outgoing', (msg) => ({ ...msg, timestamp: Date.now() }));\n *\n * @example\n * // Decrypt incoming messages\n * ws.use('incoming', (msg) => ({ ...msg, data: decrypt(msg.data) }));\n *\n * @example\n * // Drop messages from blocked users\n * ws.use('incoming', (msg) => blockedUsers.has(msg.userId) ? null : msg);\n */\n use(direction: 'outgoing' | 'incoming', fn: Middleware): this {\n if (direction === 'outgoing') {\n this.outgoingMiddleware.push(fn);\n } else {\n this.incomingMiddleware.push(fn);\n }\n return this;\n }\n\n // ─── Per-Event Serialization ─────────────────────────\n\n /**\n * Register a custom serializer for a specific event.\n * The data is transformed before outgoing middleware and global serialize.\n *\n * @example\n * // Binary for file uploads, JSON for everything else\n * ws.serializer('file.upload', (data) => new Blob([data as ArrayBuffer]));\n *\n * @example\n * // Protobuf for specific event\n * ws.serializer('trading.order', (data) => OrderProto.encode(data).finish());\n */\n serializer(event: string, fn: (data: unknown) => unknown): this {\n this.serializers.set(event, fn);\n return this;\n }\n\n /**\n * Register a custom deserializer for a specific event.\n * The data is transformed after global deserialize and before incoming middleware.\n *\n * @example\n * ws.deserializer('file.download', (data) => new Uint8Array(data as ArrayBuffer));\n *\n * @example\n * // Protobuf for specific event\n * ws.deserializer('trading.tick', (data) => TickProto.decode(data as Uint8Array));\n */\n deserializer(event: string, fn: (data: unknown) => unknown): this {\n this.deserializers.set(event, fn);\n return this;\n }\n\n // ─── Event Subscription ──────────────────────────────\n\n /** Subscribe to server events (works in ALL tabs). Type-safe with EventMap. */\n on<K extends string & keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): Unsubscribe;\n on(event: string, handler: EventHandler<unknown>): Unsubscribe;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n on(event: string, handler: (data: any) => void): Unsubscribe {\n return this.subs.on(event, handler);\n }\n\n once<K extends string & keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): Unsubscribe;\n once(event: string, handler: EventHandler<unknown>): Unsubscribe;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n once(event: string, handler: (data: any) => void): Unsubscribe {\n return this.subs.once(event, handler);\n }\n\n off(event: string, handler?: EventHandler): void {\n this.subs.off(event, handler);\n }\n\n /** Async generator for consuming events. Type-safe with EventMap. */\n stream<K extends string & keyof TEvents>(event: K, signal?: AbortSignal): AsyncGenerator<TEvents[K]>;\n stream(event: string, signal?: AbortSignal): AsyncGenerator<unknown>;\n stream(event: string, signal?: AbortSignal): AsyncGenerator<unknown> {\n return this.subs.stream(event, signal);\n }\n\n /** Send message to server (auto-routed through leader). Type-safe with EventMap. */\n send<K extends string & keyof TEvents>(event: K, data: TEvents[K]): void;\n send(event: string, data: unknown): void;\n send(event: string, data: unknown): void {\n // Per-event serializer transforms data before building payload\n const eventSerializer = this.serializers.get(event);\n const serializedData = eventSerializer ? eventSerializer(data) : data;\n\n let payload: unknown = { [this.proto.eventField]: event, [this.proto.dataField]: serializedData };\n\n for (const mw of this.outgoingMiddleware) {\n payload = mw(payload);\n if (payload === null) {\n this.log.debug('[SharedWS] ✗ outgoing dropped by middleware', event);\n return;\n }\n }\n\n this.log.debug('[SharedWS] → send', event, data);\n\n if (this.coordinator.isLeader && this.socket) {\n this.socket.send(payload);\n } else {\n this.bus.publish('ws:send', { event, data });\n }\n }\n\n /** Request/response through server via leader. */\n async request<T>(event: string, data: unknown, timeout = 5000): Promise<T> {\n return this.bus.request('ws:request', { event, data }, timeout);\n }\n\n /** Sync state across tabs (no server roundtrip). */\n sync<T>(key: string, value: T): void {\n this.syncStore.set(key, value);\n this.bus.broadcast('ws:sync', { key, value });\n }\n\n getSync<T>(key: string): T | undefined {\n return this.syncStore.get(key) as T | undefined;\n }\n\n onSync<T>(key: string, fn: (value: T) => void): Unsubscribe {\n return this.subs.on(`sync:${key}`, fn as EventHandler);\n }\n\n /**\n * Subscribe to a private/scoped channel. Returns a channel handle with\n * scoped on/send/stream methods. Sends join on subscribe, leave on unsubscribe.\n *\n * @example\n * const chat = ws.channel('chat:room_123');\n * chat.on('message', (msg) => render(msg));\n * chat.send('message', { text: 'Hello' });\n * chat.leave(); // sends leave + unsubscribes\n *\n * @example\n * // Private notifications for tenant\n * const notifications = ws.channel(`tenant:${tenantId}:notifications`);\n * notifications.on('alert', (alert) => showToast(alert));\n */\n channel(name: string, options?: { auth?: boolean }): Channel {\n // Notify server about channel subscription\n this.send(this.proto.channelJoin, { channel: name });\n\n const self = this;\n const unsubs: Unsubscribe[] = [];\n const isAuth = options?.auth ?? false;\n\n const ch: Channel = {\n name,\n on(event: string, handler: EventHandler): Unsubscribe {\n const unsub = self.subs.on(`${name}:${event}`, handler);\n unsubs.push(unsub);\n return unsub;\n },\n once(event: string, handler: EventHandler): Unsubscribe {\n const unsub = self.subs.once(`${name}:${event}`, handler);\n unsubs.push(unsub);\n return unsub;\n },\n send(event: string, data: unknown): void {\n self.send(`${name}:${event}`, data);\n },\n stream(event: string, signal?: AbortSignal): AsyncGenerator<unknown> {\n return self.subs.stream(`${name}:${event}`, signal);\n },\n leave(): void {\n self.send(self.proto.channelLeave, { channel: name });\n for (const unsub of unsubs) unsub();\n unsubs.length = 0;\n if (isAuth) self.authChannels.delete(name);\n },\n };\n\n if (isAuth) {\n this.authChannels.set(name, ch);\n }\n\n return ch;\n }\n\n // ─── Topics ──────────────────────────────────────────\n\n /**\n * Subscribe to a server-side topic. Server will start sending events for this topic.\n * Sends topicSubscribe event (default: \"$topic:subscribe\").\n *\n * @example\n * ws.subscribe('notifications:orders');\n * ws.subscribe('notifications:payments');\n * ws.subscribe(`user:${userId}:mentions`);\n */\n subscribe(topic: string, options?: { auth?: boolean }): void {\n this.send(this.proto.topicSubscribe, { topic });\n if (options?.auth) {\n this.authTopics.add(topic);\n }\n this.log.debug('[SharedWS] subscribe topic', topic);\n }\n\n /**\n * Unsubscribe from a server-side topic.\n * Sends topicUnsubscribe event (default: \"$topic:unsubscribe\").\n */\n unsubscribe(topic: string): void {\n this.send(this.proto.topicUnsubscribe, { topic });\n this.authTopics.delete(topic);\n this.log.debug('[SharedWS] unsubscribe topic', topic);\n }\n\n // ─── Push Notifications ─────────────────────────────\n\n /**\n * Subscribe to an event and show notifications.\n *\n * **target** controls which tab(s) display the notification:\n * - `'active'` — only the currently visible tab (default for render)\n * - `'leader'` — only the leader tab (default for browser Notification)\n * - `'all'` — every tab (for critical alerts)\n *\n * @example\n * // Custom render — sonner toast on active tab only\n * ws.push('notification', {\n * render: (n) => toast(n.title),\n * target: 'active', // default for render\n * });\n *\n * @example\n * // Critical alert — show in ALL tabs\n * ws.push('payment.failed', {\n * render: (n) => toast.error('Payment failed!'),\n * target: 'all',\n * });\n *\n * @example\n * // Browser Notification — only from leader\n * ws.push('order.created', {\n * title: (order) => `New Order #${order.id}`,\n * target: 'leader', // default for browser Notification\n * });\n *\n * @example\n * // Both render + native with different targets\n * ws.push('order.created', {\n * render: (order) => toast(`Order #${order.id}`), // active tab\n * title: (order) => `New Order #${order.id}`, // leader → native\n * });\n */\n push<T = unknown>(\n event: string,\n config: {\n /** Custom render function — you decide how to display. */\n render?: (data: T) => void;\n /** Title for browser Notification API. */\n title?: string | ((data: T) => string);\n /** Body for browser Notification API. */\n body?: string | ((data: T) => string);\n /** Icon URL for browser Notification. */\n icon?: string;\n /** Tag for browser Notification deduplication. */\n tag?: string | ((data: T) => string);\n /**\n * Which tab(s) show the notification:\n * - `'active'` — only the visible/focused tab (default for render)\n * - `'leader'` — only the leader tab (default for browser Notification)\n * - `'all'` — every tab (critical alerts)\n */\n target?: 'active' | 'leader' | 'all';\n /** Called when browser Notification is clicked. */\n onClick?: (data: T) => void;\n },\n ): Unsubscribe {\n const useNativeNotification = !!config.title;\n\n // Default target: 'active' for render, 'leader' for native\n const renderTarget = config.target ?? 'active';\n const nativeTarget = config.target ?? 'leader';\n\n if (useNativeNotification && typeof Notification !== 'undefined' && Notification.permission === 'default') {\n Notification.requestPermission();\n }\n\n return this.on(event, ((data: unknown) => {\n const typed = data as T;\n const isVisible = typeof document !== 'undefined' && !document.hidden;\n const isLeader = this.tabRole === 'leader';\n\n // Custom render\n if (config.render) {\n const shouldRender =\n renderTarget === 'all' ||\n (renderTarget === 'active' && isVisible) ||\n (renderTarget === 'leader' && isLeader);\n\n if (shouldRender) {\n config.render(typed);\n this.log.debug('[SharedWS] 🔔 render', event, `(target: ${renderTarget})`);\n }\n }\n\n // Browser Notification API\n if (useNativeNotification && typeof Notification !== 'undefined' && Notification.permission === 'granted') {\n const shouldNotify =\n nativeTarget === 'all' ||\n (nativeTarget === 'leader' && isLeader) ||\n (nativeTarget === 'active' && isVisible);\n\n // Native notifications make sense when tab is hidden\n if (shouldNotify && !isVisible) {\n const title = typeof config.title === 'function' ? config.title(typed) : config.title!;\n const body = typeof config.body === 'function' ? config.body(typed) : config.body;\n const tag = typeof config.tag === 'function' ? config.tag(typed) : config.tag;\n\n const notif = new Notification(title, { body, icon: config.icon, tag });\n\n if (config.onClick) {\n const handler = config.onClick;\n notif.onclick = () => {\n handler(typed);\n window.focus();\n };\n }\n\n this.log.debug('[SharedWS] 🔔 native', title, `(target: ${nativeTarget})`);\n }\n }\n }) as EventHandler);\n }\n\n disconnect(): void {\n this[Symbol.dispose]();\n }\n\n private createSocket(): SocketAdapter {\n const socketOptions = {\n protocols: this.options.protocols,\n reconnect: this.options.reconnect,\n reconnectMaxDelay: this.options.reconnectMaxDelay,\n reconnectMaxRetries: this.options.reconnectMaxRetries,\n heartbeatInterval: this.options.heartbeatInterval,\n sendBuffer: this.options.sendBuffer,\n pingPayload: this.proto.ping,\n };\n\n if (this.options.useWorker) {\n // WebSocket runs in a Web Worker — main thread stays free\n return new WorkerSocket(this.url, {\n ...socketOptions,\n workerUrl: this.options.workerUrl,\n });\n }\n\n // WebSocket runs in main thread (default)\n return new SharedSocket(this.url, {\n ...socketOptions,\n auth: this.options.auth,\n authToken: this.options.authToken,\n authParam: this.options.authParam,\n serialize: this.options.serialize,\n deserialize: this.options.deserialize,\n });\n }\n\n private handleBecomeLeader(): void {\n this.log.info('[SharedWS] 👑 became leader');\n this.socket = this.createSocket();\n\n this.socket.onMessage((raw: unknown) => {\n let data: unknown = raw;\n for (const mw of this.incomingMiddleware) {\n data = mw(data);\n if (data === null) {\n this.log.debug('[SharedWS] ✗ incoming dropped by middleware');\n return;\n }\n }\n\n const msg = data as Record<string, unknown> | null | undefined;\n const event = (msg?.[this.proto.eventField] as string) ?? this.proto.defaultEvent;\n let payload = msg?.[this.proto.dataField] ?? data;\n\n // Per-event deserializer transforms data after global deserialize\n const eventDeserializer = this.deserializers.get(event);\n if (eventDeserializer) {\n payload = eventDeserializer(payload);\n }\n\n this.log.debug('[SharedWS] ← recv', event, payload);\n this.bus.broadcast('ws:message', { event, data: payload });\n });\n\n this.socket.onStateChange((state: string) => {\n this.log.info('[SharedWS]', state === 'connected' ? '✓ connected' : state === 'reconnecting' ? '🔄 reconnecting' : `state: ${state}`);\n switch (state) {\n case 'connected':\n this.bus.broadcast('ws:lifecycle', { type: 'connect' });\n this.reAuthenticateOnReconnect();\n break;\n case 'closed':\n this.bus.broadcast('ws:lifecycle', { type: 'disconnect' });\n break;\n case 'reconnecting':\n this.bus.broadcast('ws:lifecycle', { type: 'reconnecting' });\n break;\n }\n });\n\n this.cleanups.push(\n this.bus.respond<{ event: string; data: unknown }, unknown>('ws:request', async (req) => {\n return new Promise((resolve) => {\n const unsub = this.socket!.onMessage((response: unknown) => {\n const res = response as Record<string, unknown> | undefined;\n if (res?.[this.proto.eventField] === req.event || res?.requestId) {\n unsub();\n resolve(res?.[this.proto.dataField] ?? response);\n }\n });\n this.socket!.send({ event: req.event, data: req.data });\n });\n }),\n );\n\n this.socket.connect();\n }\n\n private reAuthenticateOnReconnect(): void {\n if (!this._isAuthenticated || !this.socket) return;\n\n const token = this.syncStore.get('$auth:token') as string | undefined;\n if (token) {\n this.socket.send({\n [this.proto.eventField]: this.proto.authLogin,\n [this.proto.dataField]: { token },\n });\n this.log.debug('[SharedWS] re-authenticated after reconnect');\n }\n\n // Re-join auth channels\n for (const name of this.authChannels.keys()) {\n this.socket.send({\n [this.proto.eventField]: this.proto.channelJoin,\n [this.proto.dataField]: { channel: name },\n });\n }\n\n // Re-subscribe auth topics\n for (const topic of this.authTopics) {\n this.socket.send({\n [this.proto.eventField]: this.proto.topicSubscribe,\n [this.proto.dataField]: { topic },\n });\n }\n }\n\n private handleLoseLeadership(): void {\n if (this.socket) {\n this.socket[Symbol.dispose]();\n this.socket = null;\n }\n }\n\n [Symbol.dispose](): void {\n if (this.disposed) return;\n this.disposed = true;\n\n this.coordinator[Symbol.dispose]();\n\n if (this.socket) {\n this.socket[Symbol.dispose]();\n this.socket = null;\n }\n\n for (const unsub of this.cleanups) unsub();\n this.cleanups = [];\n this.subs[Symbol.dispose]();\n this.bus[Symbol.dispose]();\n this.syncStore.clear();\n this.authChannels.clear();\n this.authTopics.clear();\n }\n}\n"]}
@@ -244,6 +244,7 @@ var SharedSocket = class {
244
244
  protocols: options.protocols ?? [],
245
245
  reconnect: options.reconnect ?? true,
246
246
  reconnectMaxDelay: options.reconnectMaxDelay ?? 3e4,
247
+ reconnectMaxRetries: options.reconnectMaxRetries ?? Infinity,
247
248
  heartbeatInterval: options.heartbeatInterval ?? 3e4,
248
249
  sendBuffer: options.sendBuffer ?? 100,
249
250
  auth: options.auth,
@@ -266,6 +267,7 @@ var SharedSocket = class {
266
267
  reconnectTimer = null;
267
268
  onMessageFns = /* @__PURE__ */ new Set();
268
269
  onStateChangeFns = /* @__PURE__ */ new Set();
270
+ reconnectAttempts = 0;
269
271
  opts;
270
272
  get state() {
271
273
  return this._state;
@@ -276,6 +278,7 @@ var SharedSocket = class {
276
278
  const connectUrl = await this.buildUrl();
277
279
  this.ws = new WebSocket(connectUrl, this.opts.protocols);
278
280
  this.ws.onopen = () => {
281
+ this.reconnectAttempts = 0;
279
282
  this.setState("connected");
280
283
  this.flushBuffer();
281
284
  this.startHeartbeat();
@@ -333,6 +336,11 @@ var SharedSocket = class {
333
336
  return () => this.onStateChangeFns.delete(fn);
334
337
  }
335
338
  reconnect() {
339
+ this.reconnectAttempts++;
340
+ if (this.reconnectAttempts > this.opts.reconnectMaxRetries) {
341
+ this.setState("closed");
342
+ return;
343
+ }
336
344
  this.setState("reconnecting");
337
345
  const gen = backoff(1e3, this.opts.reconnectMaxDelay);
338
346
  const attempt = () => {
@@ -1087,6 +1095,7 @@ var SharedWebSocket = class {
1087
1095
  protocols: this.options.protocols,
1088
1096
  reconnect: this.options.reconnect,
1089
1097
  reconnectMaxDelay: this.options.reconnectMaxDelay,
1098
+ reconnectMaxRetries: this.options.reconnectMaxRetries,
1090
1099
  heartbeatInterval: this.options.heartbeatInterval,
1091
1100
  sendBuffer: this.options.sendBuffer,
1092
1101
  pingPayload: this.proto.ping
@@ -1214,4 +1223,4 @@ export {
1214
1223
  SubscriptionManager,
1215
1224
  SharedWebSocket
1216
1225
  };
1217
- //# sourceMappingURL=chunk-INJYCCW7.js.map
1226
+ //# sourceMappingURL=chunk-FZIIMO67.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/id.ts","../src/MessageBus.ts","../src/TabCoordinator.ts","../src/utils/backoff.ts","../src/SharedSocket.ts","../src/WorkerSocket.ts","../src/SubscriptionManager.ts","../src/SharedWebSocket.ts"],"sourcesContent":["export function generateId(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n","import './utils/disposable';\nimport { generateId } from './utils/id';\nimport type { BusMessage, Unsubscribe } from './types';\n\ntype Listener = (msg: BusMessage) => void;\n\nexport class MessageBus implements Disposable {\n private channel: BroadcastChannel;\n private listeners = new Map<string, Set<Listener>>();\n private pendingRequests = new Map<string, { resolve: (v: unknown) => void; reject: (e: Error) => void; timer: ReturnType<typeof setTimeout> }>();\n\n constructor(\n channelName: string,\n private readonly tabId: string,\n ) {\n this.channel = new BroadcastChannel(channelName);\n this.channel.onmessage = (ev: MessageEvent<BusMessage>) => {\n this.handleMessage(ev.data);\n };\n }\n\n subscribe<T>(topic: string, fn: (data: T) => void): Unsubscribe {\n const wrapper: Listener = (msg) => {\n if (msg.source !== this.tabId) fn(msg.data as T);\n };\n this.addListener(topic, wrapper);\n return () => this.removeListener(topic, wrapper);\n }\n\n publish<T>(topic: string, data: T): void {\n this.postMessage({ topic, type: 'publish', data });\n }\n\n broadcast<T>(topic: string, data: T): void {\n const msg = this.createMessage(topic, 'broadcast', data);\n this.channel.postMessage(msg);\n // Also deliver to self\n this.handleMessage(msg);\n }\n\n async request<T, R>(topic: string, data: T, timeout = 5000): Promise<R> {\n const msg = this.createMessage(topic, 'request', data);\n return new Promise<R>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pendingRequests.delete(msg.id);\n reject(new Error(`MessageBus.request: timeout for topic \"${topic}\"`));\n }, timeout);\n this.pendingRequests.set(msg.id, { resolve: resolve as (v: unknown) => void, reject, timer });\n this.channel.postMessage(msg);\n });\n }\n\n respond<T, R>(topic: string, fn: (data: T) => R | Promise<R>): Unsubscribe {\n const wrapper: Listener = async (msg) => {\n if (msg.type !== 'request' || msg.source === this.tabId) return;\n const result = await fn(msg.data as T);\n this.postMessage({ topic, type: 'response', data: { requestId: msg.id, result } });\n };\n this.addListener(topic, wrapper);\n return () => this.removeListener(topic, wrapper);\n }\n\n private handleMessage(msg: BusMessage): void {\n // Handle response to pending request\n if (msg.type === 'response') {\n const payload = msg.data as { requestId: string; result: unknown };\n const pending = this.pendingRequests.get(payload.requestId);\n if (pending) {\n clearTimeout(pending.timer);\n this.pendingRequests.delete(payload.requestId);\n pending.resolve(payload.result);\n return;\n }\n }\n\n const listeners = this.listeners.get(msg.topic);\n if (listeners) {\n for (const fn of listeners) fn(msg);\n }\n }\n\n private postMessage(partial: Pick<BusMessage, 'topic' | 'type' | 'data'>): void {\n this.channel.postMessage(this.createMessage(partial.topic, partial.type, partial.data));\n }\n\n private createMessage(topic: string, type: BusMessage['type'], data: unknown): BusMessage {\n return { id: generateId(), source: this.tabId, topic, type, data, timestamp: Date.now() };\n }\n\n private addListener(topic: string, fn: Listener): void {\n let set = this.listeners.get(topic);\n if (!set) {\n set = new Set();\n this.listeners.set(topic, set);\n }\n set.add(fn);\n }\n\n private removeListener(topic: string, fn: Listener): void {\n this.listeners.get(topic)?.delete(fn);\n }\n\n [Symbol.dispose](): void {\n for (const pending of this.pendingRequests.values()) {\n clearTimeout(pending.timer);\n pending.reject(new Error('MessageBus disposed'));\n }\n this.pendingRequests.clear();\n this.listeners.clear();\n this.channel.close();\n }\n}\n","import './utils/disposable';\nimport { MessageBus } from './MessageBus';\nimport type { Unsubscribe } from './types';\n\ninterface CoordinatorOptions {\n electionTimeout?: number; // ms to wait for rejection (default 200)\n heartbeatInterval?: number; // ms between heartbeats (default 2000)\n leaderTimeout?: number; // ms without heartbeat to trigger election (default 5000)\n}\n\nexport class TabCoordinator implements Disposable {\n private _isLeader = false;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private leaderCheckTimer: ReturnType<typeof setInterval> | null = null;\n private lastHeartbeat = 0;\n private disposed = false;\n\n private onBecomeLeaderFns = new Set<() => void>();\n private onLoseLeadershipFns = new Set<() => void>();\n private cleanups: Unsubscribe[] = [];\n\n private readonly electionTimeout: number;\n private readonly heartbeatInterval: number;\n private readonly leaderTimeout: number;\n\n constructor(\n private readonly bus: MessageBus,\n private readonly tabId: string,\n options: CoordinatorOptions = {},\n ) {\n this.electionTimeout = options.electionTimeout ?? 200;\n this.heartbeatInterval = options.heartbeatInterval ?? 2000;\n this.leaderTimeout = options.leaderTimeout ?? 5000;\n\n // Listen for election requests — reject if we are leader\n this.cleanups.push(\n this.bus.subscribe<{ tabId: string }>('coord:election', () => {\n if (this._isLeader) {\n this.bus.publish('coord:reject', { tabId: this.tabId });\n }\n }),\n );\n\n // Listen for heartbeats\n this.cleanups.push(\n this.bus.subscribe<{ tabId: string }>('coord:heartbeat', () => {\n this.lastHeartbeat = Date.now();\n }),\n );\n\n // Listen for abdication\n this.cleanups.push(\n this.bus.subscribe('coord:abdicate', () => {\n if (!this._isLeader && !this.disposed) {\n this.elect();\n }\n }),\n );\n }\n\n get isLeader(): boolean {\n return this._isLeader;\n }\n\n async elect(): Promise<void> {\n if (this.disposed) return;\n\n return new Promise<void>((resolve) => {\n let rejected = false;\n\n const unsub = this.bus.subscribe('coord:reject', () => {\n rejected = true;\n unsub();\n // We are follower — start monitoring leader heartbeat\n this.startLeaderCheck();\n resolve();\n });\n\n this.bus.publish('coord:election', { tabId: this.tabId });\n\n setTimeout(() => {\n unsub();\n if (!rejected && !this.disposed) {\n this.becomeLeader();\n }\n resolve();\n }, this.electionTimeout);\n });\n }\n\n abdicate(): void {\n if (!this._isLeader) return;\n this._isLeader = false;\n this.stopHeartbeat();\n this.bus.publish('coord:abdicate', { tabId: this.tabId });\n for (const fn of this.onLoseLeadershipFns) fn();\n }\n\n onBecomeLeader(fn: () => void): Unsubscribe {\n this.onBecomeLeaderFns.add(fn);\n return () => this.onBecomeLeaderFns.delete(fn);\n }\n\n onLoseLeadership(fn: () => void): Unsubscribe {\n this.onLoseLeadershipFns.add(fn);\n return () => this.onLoseLeadershipFns.delete(fn);\n }\n\n private becomeLeader(): void {\n this._isLeader = true;\n this.stopLeaderCheck();\n this.startHeartbeat();\n for (const fn of this.onBecomeLeaderFns) fn();\n }\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n this.bus.publish('coord:heartbeat', { tabId: this.tabId });\n }, this.heartbeatInterval);\n // Send immediately\n this.bus.publish('coord:heartbeat', { tabId: this.tabId });\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private startLeaderCheck(): void {\n this.stopLeaderCheck();\n this.lastHeartbeat = Date.now();\n this.leaderCheckTimer = setInterval(() => {\n if (Date.now() - this.lastHeartbeat > this.leaderTimeout && !this.disposed) {\n this.stopLeaderCheck();\n this.elect();\n }\n }, 1000);\n }\n\n private stopLeaderCheck(): void {\n if (this.leaderCheckTimer) {\n clearInterval(this.leaderCheckTimer);\n this.leaderCheckTimer = null;\n }\n }\n\n [Symbol.dispose](): void {\n this.disposed = true;\n if (this._isLeader) {\n this.abdicate();\n }\n this.stopHeartbeat();\n this.stopLeaderCheck();\n for (const unsub of this.cleanups) unsub();\n this.cleanups = [];\n this.onBecomeLeaderFns.clear();\n this.onLoseLeadershipFns.clear();\n }\n}\n","/** Exponential backoff generator with jitter. */\nexport function* backoff(base = 1000, max = 30_000): Generator<number> {\n let delay = base;\n while (true) {\n const jitter = delay * 0.25 * (Math.random() * 2 - 1);\n yield Math.min(delay + jitter, max);\n delay = Math.min(delay * 2, max);\n }\n}\n","import './utils/disposable';\nimport { backoff } from './utils/backoff';\nimport type { SocketState, Unsubscribe, EventHandler } from './types';\n\ninterface SharedSocketOptions {\n protocols?: string[];\n reconnect?: boolean;\n reconnectMaxDelay?: number;\n /** Max reconnect attempts before giving up (default: Infinity). */\n reconnectMaxRetries?: number;\n heartbeatInterval?: number;\n sendBuffer?: number;\n auth?: () => string | Promise<string>;\n authToken?: string;\n authParam?: string;\n /** Heartbeat payload (default: { type: \"ping\" }). */\n pingPayload?: unknown;\n /** Custom serializer (default: JSON.stringify). */\n serialize?: (data: unknown) => string | ArrayBuffer | Blob;\n /** Custom deserializer (default: JSON.parse). */\n deserialize?: (raw: string | ArrayBuffer) => unknown;\n}\n\nexport class SharedSocket implements Disposable {\n private ws: WebSocket | null = null;\n private _state: SocketState = 'closed';\n private buffer: unknown[] = [];\n private disposed = false;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n private onMessageFns = new Set<EventHandler>();\n private onStateChangeFns = new Set<(state: SocketState) => void>();\n\n private reconnectAttempts = 0;\n\n private readonly opts: Required<Omit<SharedSocketOptions, 'auth' | 'authToken' | 'authParam' | 'pingPayload' | 'serialize' | 'deserialize'>> & {\n auth?: () => string | Promise<string>;\n authToken?: string;\n authParam: string;\n pingPayload: unknown;\n serialize: (data: unknown) => string | ArrayBuffer | Blob;\n deserialize: (raw: string | ArrayBuffer) => unknown;\n };\n\n constructor(\n private url: string,\n options: SharedSocketOptions = {},\n ) {\n this.opts = {\n protocols: options.protocols ?? [],\n reconnect: options.reconnect ?? true,\n reconnectMaxDelay: options.reconnectMaxDelay ?? 30_000,\n reconnectMaxRetries: options.reconnectMaxRetries ?? Infinity,\n heartbeatInterval: options.heartbeatInterval ?? 30_000,\n sendBuffer: options.sendBuffer ?? 100,\n auth: options.auth,\n authToken: options.authToken,\n authParam: options.authParam ?? 'token',\n pingPayload: options.pingPayload ?? { type: 'ping' },\n serialize: options.serialize ?? ((data: unknown) => JSON.stringify(data)),\n deserialize: options.deserialize ?? ((raw: string | ArrayBuffer) => {\n if (typeof raw === 'string') return JSON.parse(raw);\n // ArrayBuffer → decode as UTF-8 then parse\n return JSON.parse(new TextDecoder().decode(raw));\n }),\n };\n }\n\n get state(): SocketState {\n return this._state;\n }\n\n async connect(): Promise<void> {\n if (this.disposed) return;\n\n this.setState('connecting');\n\n const connectUrl = await this.buildUrl();\n this.ws = new WebSocket(connectUrl, this.opts.protocols);\n\n this.ws.onopen = () => {\n this.reconnectAttempts = 0;\n this.setState('connected');\n this.flushBuffer();\n this.startHeartbeat();\n };\n\n this.ws.onmessage = (ev: MessageEvent) => {\n let data: unknown;\n try {\n data = this.opts.deserialize(ev.data as string | ArrayBuffer);\n } catch {\n data = ev.data;\n }\n for (const fn of this.onMessageFns) fn(data);\n };\n\n this.ws.onclose = () => {\n this.stopHeartbeat();\n if (!this.disposed && this.opts.reconnect) {\n this.reconnect();\n } else {\n this.setState('closed');\n }\n };\n\n this.ws.onerror = () => {\n // onclose will fire after onerror\n };\n }\n\n disconnect(): void {\n this.disposed = true;\n this.stopHeartbeat();\n this.clearReconnect();\n\n if (this.ws) {\n this.ws.onclose = null;\n this.ws.onmessage = null;\n this.ws.onerror = null;\n if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {\n this.ws.close(1000, 'client disconnect');\n }\n this.ws = null;\n }\n\n this.setState('closed');\n }\n\n send(data: unknown): void {\n if (this._state === 'connected' && this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(this.opts.serialize(data));\n } else if (this._state === 'reconnecting' || this._state === 'connecting') {\n if (this.buffer.length < this.opts.sendBuffer) {\n this.buffer.push(data);\n }\n }\n }\n\n onMessage(fn: EventHandler): Unsubscribe {\n this.onMessageFns.add(fn);\n return () => this.onMessageFns.delete(fn);\n }\n\n onStateChange(fn: (state: SocketState) => void): Unsubscribe {\n this.onStateChangeFns.add(fn);\n return () => this.onStateChangeFns.delete(fn);\n }\n\n private reconnect(): void {\n this.reconnectAttempts++;\n\n if (this.reconnectAttempts > this.opts.reconnectMaxRetries) {\n this.setState('closed');\n return;\n }\n\n this.setState('reconnecting');\n const gen = backoff(1000, this.opts.reconnectMaxDelay);\n\n const attempt = () => {\n if (this.disposed) return;\n const delay = gen.next().value;\n this.reconnectTimer = setTimeout(() => {\n if (!this.disposed) this.connect();\n }, delay);\n };\n\n attempt();\n }\n\n private flushBuffer(): void {\n const pending = this.buffer.splice(0);\n for (const item of pending) {\n this.send(item);\n }\n }\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(this.opts.serialize(this.opts.pingPayload));\n }\n }, this.opts.heartbeatInterval);\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private clearReconnect(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n\n private async buildUrl(): Promise<string> {\n // Resolve token: callback > static > none\n let token: string | undefined;\n if (this.opts.auth) {\n token = await this.opts.auth();\n } else if (this.opts.authToken) {\n token = this.opts.authToken;\n }\n\n if (!token) return this.url;\n\n // WebSocket URLs (ws://, wss://) are not fully supported by URL API.\n // Convert to http(s) for parsing, then back to ws(s).\n const httpUrl = this.url.replace(/^ws(s?):\\/\\//, 'http$1://');\n const parsed = new URL(httpUrl);\n parsed.searchParams.set(this.opts.authParam, token);\n\n return parsed.toString().replace(/^http(s?):\\/\\//, 'ws$1://');\n }\n\n private setState(state: SocketState): void {\n this._state = state;\n for (const fn of this.onStateChangeFns) fn(state);\n }\n\n [Symbol.dispose](): void {\n this.disconnect();\n this.onMessageFns.clear();\n this.onStateChangeFns.clear();\n this.buffer = [];\n }\n}\n","import './utils/disposable';\nimport type { SocketState, Unsubscribe, EventHandler } from './types';\n\n/**\n * WorkerSocket — WebSocket running inside a Web Worker.\n *\n * Same interface as SharedSocket, but WebSocket lives off main thread.\n * Benefits: heartbeat timers and JSON parsing don't block UI rendering.\n *\n * Use when:\n * - High message rate (50+ msgs/sec)\n * - Heavy JSON payloads\n * - UI does complex rendering that could block main thread\n *\n * Don't use when:\n * - Low message rate (simple chat, notifications)\n * - Bundle size matters (adds worker file)\n * - Debugging (Worker DevTools is less convenient)\n */\nexport class WorkerSocket implements Disposable {\n private worker: Worker | null = null;\n private _state: SocketState = 'closed';\n\n private onMessageFns = new Set<EventHandler>();\n private onStateChangeFns = new Set<(state: SocketState) => void>();\n\n constructor(\n private url: string,\n private options: {\n protocols?: string[];\n reconnect?: boolean;\n reconnectMaxDelay?: number;\n heartbeatInterval?: number;\n sendBuffer?: number;\n workerUrl?: string | URL;\n } = {},\n ) {}\n\n get state(): SocketState {\n return this._state;\n }\n\n connect(): void {\n // Create worker from inline blob if no workerUrl provided\n const workerUrl = this.options.workerUrl ?? this.createWorkerBlob();\n\n this.worker = new Worker(workerUrl, { type: 'module' });\n\n this.worker.onmessage = (ev: MessageEvent) => {\n const msg = ev.data;\n\n switch (msg.type) {\n case 'state':\n this._state = msg.state;\n for (const fn of this.onStateChangeFns) fn(msg.state);\n break;\n\n case 'message':\n for (const fn of this.onMessageFns) fn(msg.data);\n break;\n\n case 'open':\n // State already set via 'state' message\n break;\n\n case 'close':\n break;\n\n case 'error':\n console.error('WorkerSocket error:', msg.message);\n break;\n }\n };\n\n this.worker.postMessage({\n type: 'connect',\n url: this.url,\n protocols: this.options.protocols ?? [],\n reconnect: this.options.reconnect ?? true,\n reconnectMaxDelay: this.options.reconnectMaxDelay ?? 30_000,\n heartbeatInterval: this.options.heartbeatInterval ?? 30_000,\n bufferSize: this.options.sendBuffer ?? 100,\n });\n }\n\n send(data: unknown): void {\n this.worker?.postMessage({ type: 'send', data });\n }\n\n disconnect(): void {\n this.worker?.postMessage({ type: 'disconnect' });\n setTimeout(() => {\n this.worker?.terminate();\n this.worker = null;\n }, 100);\n this._state = 'closed';\n }\n\n onMessage(fn: EventHandler): Unsubscribe {\n this.onMessageFns.add(fn);\n return () => this.onMessageFns.delete(fn);\n }\n\n onStateChange(fn: (state: SocketState) => void): Unsubscribe {\n this.onStateChangeFns.add(fn);\n return () => this.onStateChangeFns.delete(fn);\n }\n\n private createWorkerBlob(): URL {\n // Inline the worker code as a blob URL\n // In production, use a bundler (Vite, webpack) to handle worker imports\n const code = `\n let ws = null, state = 'closed', buffer = [], disposed = false;\n let heartbeatTimer = null, reconnectTimer = null;\n let url = '', protocols = [], shouldReconnect = true;\n let maxDelay = 30000, hbInterval = 30000, maxBuf = 100, delay = 1000;\n\n function setState(s) { state = s; self.postMessage({ type: 'state', state: s }); }\n function connect() {\n if (disposed) return;\n setState('connecting');\n ws = new WebSocket(url, protocols);\n ws.onopen = () => { setState('connected'); delay = 1000; self.postMessage({ type: 'open' }); flush(); startHB(); };\n ws.onmessage = (e) => { let d; try { d = JSON.parse(e.data); } catch { d = e.data; } self.postMessage({ type: 'message', data: d }); };\n ws.onclose = (e) => { stopHB(); self.postMessage({ type: 'close', code: e.code, reason: e.reason }); if (!disposed && shouldReconnect && e.code !== 1000) reconnect(); else setState('closed'); };\n ws.onerror = () => { self.postMessage({ type: 'error', message: 'error' }); };\n }\n function send(d) { if (state === 'connected' && ws?.readyState === 1) ws.send(JSON.stringify(d)); else if (buffer.length < maxBuf) buffer.push(d); }\n function flush() { const p = buffer.splice(0); p.forEach(send); }\n function startHB() { stopHB(); heartbeatTimer = setInterval(() => { if (ws?.readyState === 1) ws.send('{\"type\":\"ping\"}'); }, hbInterval); }\n function stopHB() { if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; } }\n function reconnect() { setState('reconnecting'); const j = delay * 0.25 * (Math.random() * 2 - 1); reconnectTimer = setTimeout(() => { if (!disposed) connect(); }, Math.min(delay + j, maxDelay)); delay = Math.min(delay * 2, maxDelay); }\n self.onmessage = (e) => {\n const c = e.data;\n if (c.type === 'connect') { url = c.url; protocols = c.protocols || []; shouldReconnect = c.reconnect ?? true; maxDelay = c.reconnectMaxDelay || 30000; hbInterval = c.heartbeatInterval || 30000; maxBuf = c.bufferSize || 100; connect(); }\n if (c.type === 'send') send(c.data);\n if (c.type === 'disconnect') { disposed = true; stopHB(); if (reconnectTimer) clearTimeout(reconnectTimer); if (ws) { ws.onclose = null; if (ws.readyState < 2) ws.close(1000); ws = null; } buffer = []; setState('closed'); }\n };\n `;\n const blob = new Blob([code], { type: 'application/javascript' });\n return new URL(URL.createObjectURL(blob));\n }\n\n [Symbol.dispose](): void {\n this.disconnect();\n this.onMessageFns.clear();\n this.onStateChangeFns.clear();\n }\n}\n","import './utils/disposable';\nimport type { EventHandler, Unsubscribe } from './types';\n\nexport class SubscriptionManager implements Disposable {\n private handlers = new Map<string, Set<EventHandler>>();\n private lastMessages = new Map<string, unknown>();\n\n on(event: string, handler: EventHandler): Unsubscribe {\n let set = this.handlers.get(event);\n if (!set) {\n set = new Set();\n this.handlers.set(event, set);\n }\n set.add(handler);\n return () => set!.delete(handler);\n }\n\n once(event: string, handler: EventHandler): Unsubscribe {\n const wrapper: EventHandler = (data) => {\n unsub();\n handler(data);\n };\n const unsub = this.on(event, wrapper);\n return unsub;\n }\n\n off(event: string, handler?: EventHandler): void {\n if (handler) {\n this.handlers.get(event)?.delete(handler);\n } else {\n this.handlers.delete(event);\n }\n }\n\n emit(event: string, data: unknown): void {\n this.lastMessages.set(event, data);\n const set = this.handlers.get(event);\n if (set) {\n for (const fn of set) fn(data);\n }\n }\n\n getLastMessage(event: string): unknown | undefined {\n return this.lastMessages.get(event);\n }\n\n async *stream(event: string, signal?: AbortSignal): AsyncGenerator<unknown> {\n const queue: unknown[] = [];\n let resolve: (() => void) | null = null;\n let done = false;\n\n const unsub = this.on(event, (data) => {\n queue.push(data);\n resolve?.();\n });\n\n const onAbort = () => {\n done = true;\n resolve?.();\n };\n signal?.addEventListener('abort', onAbort);\n\n try {\n while (!done) {\n if (queue.length > 0) {\n yield queue.shift()!;\n } else {\n await new Promise<void>((r) => { resolve = r; });\n resolve = null;\n }\n }\n } finally {\n unsub();\n signal?.removeEventListener('abort', onAbort);\n }\n }\n\n offAll(): void {\n this.handlers.clear();\n this.lastMessages.clear();\n }\n\n [Symbol.dispose](): void {\n this.offAll();\n }\n}\n","import './utils/disposable';\nimport { generateId } from './utils/id';\nimport { MessageBus } from './MessageBus';\nimport { TabCoordinator } from './TabCoordinator';\nimport { SharedSocket } from './SharedSocket';\nimport { WorkerSocket } from './WorkerSocket';\nimport { SubscriptionManager } from './SubscriptionManager';\nimport type { SharedWebSocketOptions, TabRole, Unsubscribe, EventHandler, Channel, EventProtocol, EventMap, Logger, Middleware } from './types';\n\nconst DEFAULT_PROTOCOL: EventProtocol = {\n eventField: 'event',\n dataField: 'data',\n channelJoin: '$channel:join',\n channelLeave: '$channel:leave',\n ping: { type: 'ping' },\n defaultEvent: 'message',\n topicSubscribe: '$topic:subscribe',\n topicUnsubscribe: '$topic:unsubscribe',\n authLogin: '$auth:login',\n authLogout: '$auth:logout',\n authRevoked: '$auth:revoked',\n};\n\nconst NOOP_LOGGER: Logger = {\n debug() {},\n info() {},\n warn() {},\n error() {},\n};\n\n/** Common interface for both SharedSocket and WorkerSocket. */\ninterface SocketAdapter {\n readonly state: string;\n connect(): void;\n send(data: unknown): void;\n disconnect(): void;\n onMessage(fn: EventHandler): Unsubscribe;\n onStateChange(fn: (state: string) => void): Unsubscribe;\n [Symbol.dispose](): void;\n}\n\n/**\n * SharedWebSocket — shares ONE WebSocket connection across browser tabs.\n *\n * @typeParam TEvents - Event map for type-safe subscriptions.\n *\n * @example\n * // Typed events\n * type Events = {\n * 'chat.message': { text: string; userId: string };\n * 'order.created': { id: string; total: number };\n * };\n * const ws = new SharedWebSocket<Events>(url);\n * ws.on('chat.message', (msg) => msg.text); // ← msg: { text, userId }\n */\nexport class SharedWebSocket<TEvents extends EventMap = EventMap> implements Disposable {\n private bus: MessageBus;\n private coordinator: TabCoordinator;\n private socket: SocketAdapter | null = null;\n private subs = new SubscriptionManager();\n private syncStore = new Map<string, unknown>();\n private tabId: string;\n private cleanups: Unsubscribe[] = [];\n private disposed = false;\n private readonly proto: EventProtocol;\n private readonly log: Logger;\n private outgoingMiddleware: Middleware[] = [];\n private incomingMiddleware: Middleware[] = [];\n private serializers = new Map<string, (data: unknown) => unknown>();\n private deserializers = new Map<string, (data: unknown) => unknown>();\n private _isAuthenticated = false;\n private authChannels = new Map<string, Channel>();\n private authTopics = new Set<string>();\n\n constructor(\n private readonly url: string,\n private readonly options: SharedWebSocketOptions<TEvents> = {} as SharedWebSocketOptions<TEvents>,\n ) {\n this.proto = { ...DEFAULT_PROTOCOL, ...options.events };\n this.log = options.debug ? (options.logger ?? console) : NOOP_LOGGER;\n this.tabId = generateId();\n this.log.debug('[SharedWS] init', { tabId: this.tabId, url });\n this.bus = new MessageBus('shared-ws', this.tabId);\n this.coordinator = new TabCoordinator(this.bus, this.tabId, {\n electionTimeout: options.electionTimeout,\n heartbeatInterval: options.leaderHeartbeat,\n leaderTimeout: options.leaderTimeout,\n });\n\n // When ANY tab receives a WS message via bus → emit to local subscribers\n this.cleanups.push(\n this.bus.subscribe<{ event: string; data: unknown }>('ws:message', (msg) => {\n this.subs.emit(msg.event, msg.data);\n }),\n );\n\n // Leader listens for send requests from followers\n this.cleanups.push(\n this.bus.subscribe<{ event: string; data: unknown }>('ws:send', (msg) => {\n if (this.coordinator.isLeader && this.socket) {\n this.socket.send({ [this.proto.eventField]: msg.event, [this.proto.dataField]: msg.data });\n }\n }),\n );\n\n // Sync across tabs\n this.cleanups.push(\n this.bus.subscribe<{ key: string; value: unknown }>('ws:sync', (msg) => {\n this.syncStore.set(msg.key, msg.value);\n this.subs.emit(`sync:${msg.key}`, msg.value);\n }),\n );\n\n // Leader lifecycle\n this.coordinator.onBecomeLeader(() => {\n this.handleBecomeLeader();\n this.bus.broadcast('ws:lifecycle', { type: 'leader', isLeader: true });\n });\n this.coordinator.onLoseLeadership(() => {\n this.handleLoseLeadership();\n this.bus.broadcast('ws:lifecycle', { type: 'leader', isLeader: false });\n });\n\n // Lifecycle events from bus (all tabs receive)\n this.cleanups.push(\n this.bus.subscribe<{ type: string; isLeader?: boolean; error?: unknown; authenticated?: boolean }>('ws:lifecycle', (msg) => {\n switch (msg.type) {\n case 'connect':\n this.subs.emit('$lifecycle:connect', undefined);\n break;\n case 'disconnect':\n this.subs.emit('$lifecycle:disconnect', undefined);\n break;\n case 'reconnecting':\n this.subs.emit('$lifecycle:reconnecting', undefined);\n break;\n case 'leader':\n this.subs.emit('$lifecycle:leader', msg.isLeader);\n break;\n case 'error':\n this.subs.emit('$lifecycle:error', msg.error);\n break;\n case 'auth': {\n this._isAuthenticated = !!msg.authenticated;\n if (!msg.authenticated) {\n this.authChannels.clear();\n this.authTopics.clear();\n }\n this.subs.emit('$lifecycle:auth', msg.authenticated);\n break;\n }\n }\n }),\n );\n\n // Track tab visibility\n if (typeof document !== 'undefined') {\n const onVisibilityChange = () => {\n const active = !document.hidden;\n this.subs.emit('$lifecycle:active', active);\n this.log.debug('[SharedWS]', active ? '👁 tab active' : '👁 tab hidden');\n };\n document.addEventListener('visibilitychange', onVisibilityChange);\n this.cleanups.push(() => document.removeEventListener('visibilitychange', onVisibilityChange));\n }\n\n // Handle server-initiated auth revocation\n this.cleanups.push(\n this.subs.on(this.proto.authRevoked, () => {\n if (this.coordinator.isLeader) {\n for (const [, ch] of this.authChannels) ch.leave();\n for (const topic of this.authTopics) this.unsubscribe(topic);\n }\n this.authChannels.clear();\n this.authTopics.clear();\n this._isAuthenticated = false;\n this.syncStore.delete('$auth:token');\n this.subs.emit('$lifecycle:auth', false);\n this.log.warn('[SharedWS] auth revoked by server');\n }),\n );\n\n // Cleanup on tab close\n if (typeof window !== 'undefined') {\n const onBeforeUnload = () => this[Symbol.dispose]();\n window.addEventListener('beforeunload', onBeforeUnload);\n this.cleanups.push(() => window.removeEventListener('beforeunload', onBeforeUnload));\n }\n }\n\n get connected(): boolean {\n return this.socket?.state === 'connected' || !this.coordinator.isLeader;\n }\n\n get tabRole(): TabRole {\n return this.coordinator.isLeader ? 'leader' : 'follower';\n }\n\n /** Whether the user is authenticated via runtime auth. */\n get isAuthenticated(): boolean {\n return this._isAuthenticated;\n }\n\n /** Whether this tab is currently visible/focused. */\n get isActive(): boolean {\n return typeof document !== 'undefined' ? !document.hidden : true;\n }\n\n /** Start leader election and connect. */\n async connect(): Promise<void> {\n await this.coordinator.elect();\n }\n\n // ─── Lifecycle Hooks ─────────────────────────────────\n\n /** Called when WebSocket connection opens (broadcast to all tabs). */\n onConnect(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:connect', fn);\n }\n\n /** Called when WebSocket connection closes (broadcast to all tabs). */\n onDisconnect(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:disconnect', fn);\n }\n\n /** Called when WebSocket starts reconnecting (broadcast to all tabs). */\n onReconnecting(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:reconnecting', fn);\n }\n\n /** Called when this tab becomes leader or loses leadership. */\n onLeaderChange(fn: (isLeader: boolean) => void): Unsubscribe {\n return this.subs.on('$lifecycle:leader', fn as EventHandler);\n }\n\n /** Called on WebSocket or network error (broadcast to all tabs). */\n onError(fn: (error: unknown) => void): Unsubscribe {\n return this.subs.on('$lifecycle:error', fn as EventHandler);\n }\n\n /** Called when this tab becomes visible/focused. */\n onActive(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:active', ((isActive: unknown) => {\n if (isActive === true) fn();\n }) as EventHandler);\n }\n\n /** Called when this tab goes to background/hidden. */\n onInactive(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:active', ((isActive: unknown) => {\n if (isActive === false) fn();\n }) as EventHandler);\n }\n\n /** Called on any visibility change. */\n onVisibilityChange(fn: (isActive: boolean) => void): Unsubscribe {\n return this.subs.on('$lifecycle:active', fn as EventHandler);\n }\n\n // ─── Authentication ──────────────────────────────────\n\n /**\n * Authenticate on an existing connection. Sends auth event to server,\n * syncs auth state across all tabs. Use for login after guest connection.\n *\n * @example\n * const token = await loginApi(email, password);\n * ws.authenticate(token);\n *\n * @example\n * // React — via useSocketAuth hook\n * const { authenticate } = useSocketAuth();\n * authenticate(token);\n */\n authenticate(token: string): void {\n this._isAuthenticated = true;\n this.syncStore.set('$auth:token', token);\n this.bus.broadcast('ws:sync', { key: '$auth:token', value: token });\n this.send(this.proto.authLogin, { token });\n this.bus.broadcast('ws:lifecycle', { type: 'auth', authenticated: true });\n this.log.info('[SharedWS] authenticated');\n }\n\n /**\n * Deauthenticate — notifies server, auto-leaves all auth-required channels\n * and topics, syncs state across tabs. Connection stays open for public events.\n *\n * @example\n * ws.deauthenticate(); // connection stays open, auth subscriptions cleaned up\n */\n deauthenticate(): void {\n // Leave auth channels and unsubscribe auth topics\n for (const [, ch] of this.authChannels) ch.leave();\n this.authChannels.clear();\n for (const topic of this.authTopics) this.unsubscribe(topic);\n this.authTopics.clear();\n\n this._isAuthenticated = false;\n this.send(this.proto.authLogout, {});\n this.syncStore.delete('$auth:token');\n this.bus.broadcast('ws:sync', { key: '$auth:token', value: undefined });\n this.bus.broadcast('ws:lifecycle', { type: 'auth', authenticated: false });\n this.log.info('[SharedWS] deauthenticated');\n }\n\n /**\n * Called when auth state changes (authenticate, deauthenticate, or server revocation).\n *\n * @example\n * ws.onAuthChange((authenticated) => {\n * if (!authenticated) router.push('/login');\n * });\n */\n onAuthChange(fn: (authenticated: boolean) => void): Unsubscribe {\n return this.subs.on('$lifecycle:auth', fn as EventHandler);\n }\n\n // ─── Middleware ───────────────────────────────────────\n\n /**\n * Add middleware to transform messages before send or after receive.\n * Return null from middleware to drop the message.\n *\n * @example\n * // Add timestamp to every outgoing message\n * ws.use('outgoing', (msg) => ({ ...msg, timestamp: Date.now() }));\n *\n * @example\n * // Decrypt incoming messages\n * ws.use('incoming', (msg) => ({ ...msg, data: decrypt(msg.data) }));\n *\n * @example\n * // Drop messages from blocked users\n * ws.use('incoming', (msg) => blockedUsers.has(msg.userId) ? null : msg);\n */\n use(direction: 'outgoing' | 'incoming', fn: Middleware): this {\n if (direction === 'outgoing') {\n this.outgoingMiddleware.push(fn);\n } else {\n this.incomingMiddleware.push(fn);\n }\n return this;\n }\n\n // ─── Per-Event Serialization ─────────────────────────\n\n /**\n * Register a custom serializer for a specific event.\n * The data is transformed before outgoing middleware and global serialize.\n *\n * @example\n * // Binary for file uploads, JSON for everything else\n * ws.serializer('file.upload', (data) => new Blob([data as ArrayBuffer]));\n *\n * @example\n * // Protobuf for specific event\n * ws.serializer('trading.order', (data) => OrderProto.encode(data).finish());\n */\n serializer(event: string, fn: (data: unknown) => unknown): this {\n this.serializers.set(event, fn);\n return this;\n }\n\n /**\n * Register a custom deserializer for a specific event.\n * The data is transformed after global deserialize and before incoming middleware.\n *\n * @example\n * ws.deserializer('file.download', (data) => new Uint8Array(data as ArrayBuffer));\n *\n * @example\n * // Protobuf for specific event\n * ws.deserializer('trading.tick', (data) => TickProto.decode(data as Uint8Array));\n */\n deserializer(event: string, fn: (data: unknown) => unknown): this {\n this.deserializers.set(event, fn);\n return this;\n }\n\n // ─── Event Subscription ──────────────────────────────\n\n /** Subscribe to server events (works in ALL tabs). Type-safe with EventMap. */\n on<K extends string & keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): Unsubscribe;\n on(event: string, handler: EventHandler<unknown>): Unsubscribe;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n on(event: string, handler: (data: any) => void): Unsubscribe {\n return this.subs.on(event, handler);\n }\n\n once<K extends string & keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): Unsubscribe;\n once(event: string, handler: EventHandler<unknown>): Unsubscribe;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n once(event: string, handler: (data: any) => void): Unsubscribe {\n return this.subs.once(event, handler);\n }\n\n off(event: string, handler?: EventHandler): void {\n this.subs.off(event, handler);\n }\n\n /** Async generator for consuming events. Type-safe with EventMap. */\n stream<K extends string & keyof TEvents>(event: K, signal?: AbortSignal): AsyncGenerator<TEvents[K]>;\n stream(event: string, signal?: AbortSignal): AsyncGenerator<unknown>;\n stream(event: string, signal?: AbortSignal): AsyncGenerator<unknown> {\n return this.subs.stream(event, signal);\n }\n\n /** Send message to server (auto-routed through leader). Type-safe with EventMap. */\n send<K extends string & keyof TEvents>(event: K, data: TEvents[K]): void;\n send(event: string, data: unknown): void;\n send(event: string, data: unknown): void {\n // Per-event serializer transforms data before building payload\n const eventSerializer = this.serializers.get(event);\n const serializedData = eventSerializer ? eventSerializer(data) : data;\n\n let payload: unknown = { [this.proto.eventField]: event, [this.proto.dataField]: serializedData };\n\n for (const mw of this.outgoingMiddleware) {\n payload = mw(payload);\n if (payload === null) {\n this.log.debug('[SharedWS] ✗ outgoing dropped by middleware', event);\n return;\n }\n }\n\n this.log.debug('[SharedWS] → send', event, data);\n\n if (this.coordinator.isLeader && this.socket) {\n this.socket.send(payload);\n } else {\n this.bus.publish('ws:send', { event, data });\n }\n }\n\n /** Request/response through server via leader. */\n async request<T>(event: string, data: unknown, timeout = 5000): Promise<T> {\n return this.bus.request('ws:request', { event, data }, timeout);\n }\n\n /** Sync state across tabs (no server roundtrip). */\n sync<T>(key: string, value: T): void {\n this.syncStore.set(key, value);\n this.bus.broadcast('ws:sync', { key, value });\n }\n\n getSync<T>(key: string): T | undefined {\n return this.syncStore.get(key) as T | undefined;\n }\n\n onSync<T>(key: string, fn: (value: T) => void): Unsubscribe {\n return this.subs.on(`sync:${key}`, fn as EventHandler);\n }\n\n /**\n * Subscribe to a private/scoped channel. Returns a channel handle with\n * scoped on/send/stream methods. Sends join on subscribe, leave on unsubscribe.\n *\n * @example\n * const chat = ws.channel('chat:room_123');\n * chat.on('message', (msg) => render(msg));\n * chat.send('message', { text: 'Hello' });\n * chat.leave(); // sends leave + unsubscribes\n *\n * @example\n * // Private notifications for tenant\n * const notifications = ws.channel(`tenant:${tenantId}:notifications`);\n * notifications.on('alert', (alert) => showToast(alert));\n */\n channel(name: string, options?: { auth?: boolean }): Channel {\n // Notify server about channel subscription\n this.send(this.proto.channelJoin, { channel: name });\n\n const self = this;\n const unsubs: Unsubscribe[] = [];\n const isAuth = options?.auth ?? false;\n\n const ch: Channel = {\n name,\n on(event: string, handler: EventHandler): Unsubscribe {\n const unsub = self.subs.on(`${name}:${event}`, handler);\n unsubs.push(unsub);\n return unsub;\n },\n once(event: string, handler: EventHandler): Unsubscribe {\n const unsub = self.subs.once(`${name}:${event}`, handler);\n unsubs.push(unsub);\n return unsub;\n },\n send(event: string, data: unknown): void {\n self.send(`${name}:${event}`, data);\n },\n stream(event: string, signal?: AbortSignal): AsyncGenerator<unknown> {\n return self.subs.stream(`${name}:${event}`, signal);\n },\n leave(): void {\n self.send(self.proto.channelLeave, { channel: name });\n for (const unsub of unsubs) unsub();\n unsubs.length = 0;\n if (isAuth) self.authChannels.delete(name);\n },\n };\n\n if (isAuth) {\n this.authChannels.set(name, ch);\n }\n\n return ch;\n }\n\n // ─── Topics ──────────────────────────────────────────\n\n /**\n * Subscribe to a server-side topic. Server will start sending events for this topic.\n * Sends topicSubscribe event (default: \"$topic:subscribe\").\n *\n * @example\n * ws.subscribe('notifications:orders');\n * ws.subscribe('notifications:payments');\n * ws.subscribe(`user:${userId}:mentions`);\n */\n subscribe(topic: string, options?: { auth?: boolean }): void {\n this.send(this.proto.topicSubscribe, { topic });\n if (options?.auth) {\n this.authTopics.add(topic);\n }\n this.log.debug('[SharedWS] subscribe topic', topic);\n }\n\n /**\n * Unsubscribe from a server-side topic.\n * Sends topicUnsubscribe event (default: \"$topic:unsubscribe\").\n */\n unsubscribe(topic: string): void {\n this.send(this.proto.topicUnsubscribe, { topic });\n this.authTopics.delete(topic);\n this.log.debug('[SharedWS] unsubscribe topic', topic);\n }\n\n // ─── Push Notifications ─────────────────────────────\n\n /**\n * Subscribe to an event and show notifications.\n *\n * **target** controls which tab(s) display the notification:\n * - `'active'` — only the currently visible tab (default for render)\n * - `'leader'` — only the leader tab (default for browser Notification)\n * - `'all'` — every tab (for critical alerts)\n *\n * @example\n * // Custom render — sonner toast on active tab only\n * ws.push('notification', {\n * render: (n) => toast(n.title),\n * target: 'active', // default for render\n * });\n *\n * @example\n * // Critical alert — show in ALL tabs\n * ws.push('payment.failed', {\n * render: (n) => toast.error('Payment failed!'),\n * target: 'all',\n * });\n *\n * @example\n * // Browser Notification — only from leader\n * ws.push('order.created', {\n * title: (order) => `New Order #${order.id}`,\n * target: 'leader', // default for browser Notification\n * });\n *\n * @example\n * // Both render + native with different targets\n * ws.push('order.created', {\n * render: (order) => toast(`Order #${order.id}`), // active tab\n * title: (order) => `New Order #${order.id}`, // leader → native\n * });\n */\n push<T = unknown>(\n event: string,\n config: {\n /** Custom render function — you decide how to display. */\n render?: (data: T) => void;\n /** Title for browser Notification API. */\n title?: string | ((data: T) => string);\n /** Body for browser Notification API. */\n body?: string | ((data: T) => string);\n /** Icon URL for browser Notification. */\n icon?: string;\n /** Tag for browser Notification deduplication. */\n tag?: string | ((data: T) => string);\n /**\n * Which tab(s) show the notification:\n * - `'active'` — only the visible/focused tab (default for render)\n * - `'leader'` — only the leader tab (default for browser Notification)\n * - `'all'` — every tab (critical alerts)\n */\n target?: 'active' | 'leader' | 'all';\n /** Called when browser Notification is clicked. */\n onClick?: (data: T) => void;\n },\n ): Unsubscribe {\n const useNativeNotification = !!config.title;\n\n // Default target: 'active' for render, 'leader' for native\n const renderTarget = config.target ?? 'active';\n const nativeTarget = config.target ?? 'leader';\n\n if (useNativeNotification && typeof Notification !== 'undefined' && Notification.permission === 'default') {\n Notification.requestPermission();\n }\n\n return this.on(event, ((data: unknown) => {\n const typed = data as T;\n const isVisible = typeof document !== 'undefined' && !document.hidden;\n const isLeader = this.tabRole === 'leader';\n\n // Custom render\n if (config.render) {\n const shouldRender =\n renderTarget === 'all' ||\n (renderTarget === 'active' && isVisible) ||\n (renderTarget === 'leader' && isLeader);\n\n if (shouldRender) {\n config.render(typed);\n this.log.debug('[SharedWS] 🔔 render', event, `(target: ${renderTarget})`);\n }\n }\n\n // Browser Notification API\n if (useNativeNotification && typeof Notification !== 'undefined' && Notification.permission === 'granted') {\n const shouldNotify =\n nativeTarget === 'all' ||\n (nativeTarget === 'leader' && isLeader) ||\n (nativeTarget === 'active' && isVisible);\n\n // Native notifications make sense when tab is hidden\n if (shouldNotify && !isVisible) {\n const title = typeof config.title === 'function' ? config.title(typed) : config.title!;\n const body = typeof config.body === 'function' ? config.body(typed) : config.body;\n const tag = typeof config.tag === 'function' ? config.tag(typed) : config.tag;\n\n const notif = new Notification(title, { body, icon: config.icon, tag });\n\n if (config.onClick) {\n const handler = config.onClick;\n notif.onclick = () => {\n handler(typed);\n window.focus();\n };\n }\n\n this.log.debug('[SharedWS] 🔔 native', title, `(target: ${nativeTarget})`);\n }\n }\n }) as EventHandler);\n }\n\n disconnect(): void {\n this[Symbol.dispose]();\n }\n\n private createSocket(): SocketAdapter {\n const socketOptions = {\n protocols: this.options.protocols,\n reconnect: this.options.reconnect,\n reconnectMaxDelay: this.options.reconnectMaxDelay,\n reconnectMaxRetries: this.options.reconnectMaxRetries,\n heartbeatInterval: this.options.heartbeatInterval,\n sendBuffer: this.options.sendBuffer,\n pingPayload: this.proto.ping,\n };\n\n if (this.options.useWorker) {\n // WebSocket runs in a Web Worker — main thread stays free\n return new WorkerSocket(this.url, {\n ...socketOptions,\n workerUrl: this.options.workerUrl,\n });\n }\n\n // WebSocket runs in main thread (default)\n return new SharedSocket(this.url, {\n ...socketOptions,\n auth: this.options.auth,\n authToken: this.options.authToken,\n authParam: this.options.authParam,\n serialize: this.options.serialize,\n deserialize: this.options.deserialize,\n });\n }\n\n private handleBecomeLeader(): void {\n this.log.info('[SharedWS] 👑 became leader');\n this.socket = this.createSocket();\n\n this.socket.onMessage((raw: unknown) => {\n let data: unknown = raw;\n for (const mw of this.incomingMiddleware) {\n data = mw(data);\n if (data === null) {\n this.log.debug('[SharedWS] ✗ incoming dropped by middleware');\n return;\n }\n }\n\n const msg = data as Record<string, unknown> | null | undefined;\n const event = (msg?.[this.proto.eventField] as string) ?? this.proto.defaultEvent;\n let payload = msg?.[this.proto.dataField] ?? data;\n\n // Per-event deserializer transforms data after global deserialize\n const eventDeserializer = this.deserializers.get(event);\n if (eventDeserializer) {\n payload = eventDeserializer(payload);\n }\n\n this.log.debug('[SharedWS] ← recv', event, payload);\n this.bus.broadcast('ws:message', { event, data: payload });\n });\n\n this.socket.onStateChange((state: string) => {\n this.log.info('[SharedWS]', state === 'connected' ? '✓ connected' : state === 'reconnecting' ? '🔄 reconnecting' : `state: ${state}`);\n switch (state) {\n case 'connected':\n this.bus.broadcast('ws:lifecycle', { type: 'connect' });\n this.reAuthenticateOnReconnect();\n break;\n case 'closed':\n this.bus.broadcast('ws:lifecycle', { type: 'disconnect' });\n break;\n case 'reconnecting':\n this.bus.broadcast('ws:lifecycle', { type: 'reconnecting' });\n break;\n }\n });\n\n this.cleanups.push(\n this.bus.respond<{ event: string; data: unknown }, unknown>('ws:request', async (req) => {\n return new Promise((resolve) => {\n const unsub = this.socket!.onMessage((response: unknown) => {\n const res = response as Record<string, unknown> | undefined;\n if (res?.[this.proto.eventField] === req.event || res?.requestId) {\n unsub();\n resolve(res?.[this.proto.dataField] ?? response);\n }\n });\n this.socket!.send({ event: req.event, data: req.data });\n });\n }),\n );\n\n this.socket.connect();\n }\n\n private reAuthenticateOnReconnect(): void {\n if (!this._isAuthenticated || !this.socket) return;\n\n const token = this.syncStore.get('$auth:token') as string | undefined;\n if (token) {\n this.socket.send({\n [this.proto.eventField]: this.proto.authLogin,\n [this.proto.dataField]: { token },\n });\n this.log.debug('[SharedWS] re-authenticated after reconnect');\n }\n\n // Re-join auth channels\n for (const name of this.authChannels.keys()) {\n this.socket.send({\n [this.proto.eventField]: this.proto.channelJoin,\n [this.proto.dataField]: { channel: name },\n });\n }\n\n // Re-subscribe auth topics\n for (const topic of this.authTopics) {\n this.socket.send({\n [this.proto.eventField]: this.proto.topicSubscribe,\n [this.proto.dataField]: { topic },\n });\n }\n }\n\n private handleLoseLeadership(): void {\n if (this.socket) {\n this.socket[Symbol.dispose]();\n this.socket = null;\n }\n }\n\n [Symbol.dispose](): void {\n if (this.disposed) return;\n this.disposed = true;\n\n this.coordinator[Symbol.dispose]();\n\n if (this.socket) {\n this.socket[Symbol.dispose]();\n this.socket = null;\n }\n\n for (const unsub of this.cleanups) unsub();\n this.cleanups = [];\n this.subs[Symbol.dispose]();\n this.bus[Symbol.dispose]();\n this.syncStore.clear();\n this.authChannels.clear();\n this.authTopics.clear();\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
 
7
7
 
8
- var _chunkMP3K5IEIcjs = require('./chunk-MP3K5IEI.cjs');
8
+ var _chunkADGLL3J2cjs = require('./chunk-ADGLL3J2.cjs');
9
9
 
10
10
 
11
11
  var _chunkET3YHQ7Vcjs = require('./chunk-ET3YHQ7V.cjs');
@@ -21,7 +21,7 @@ async function withSocket(url, optionsOrCallback, maybeCallback) {
21
21
  options = optionsOrCallback;
22
22
  callback = maybeCallback;
23
23
  }
24
- const ws = new (0, _chunkMP3K5IEIcjs.SharedWebSocket)(url, options);
24
+ const ws = new (0, _chunkADGLL3J2cjs.SharedWebSocket)(url, options);
25
25
  const controller = new AbortController();
26
26
  if (_optionalChain([options, 'optionalAccess', _ => _.signal])) {
27
27
  if (options.signal.aborted) {
@@ -47,5 +47,5 @@ async function withSocket(url, optionsOrCallback, maybeCallback) {
47
47
 
48
48
 
49
49
 
50
- exports.MessageBus = _chunkMP3K5IEIcjs.MessageBus; exports.SharedSocket = _chunkMP3K5IEIcjs.SharedSocket; exports.SharedWebSocket = _chunkMP3K5IEIcjs.SharedWebSocket; exports.SubscriptionManager = _chunkMP3K5IEIcjs.SubscriptionManager; exports.TabCoordinator = _chunkMP3K5IEIcjs.TabCoordinator; exports.TabSync = _chunkET3YHQ7Vcjs.TabSync; exports.WorkerSocket = _chunkMP3K5IEIcjs.WorkerSocket; exports.withSocket = withSocket;
50
+ exports.MessageBus = _chunkADGLL3J2cjs.MessageBus; exports.SharedSocket = _chunkADGLL3J2cjs.SharedSocket; exports.SharedWebSocket = _chunkADGLL3J2cjs.SharedWebSocket; exports.SubscriptionManager = _chunkADGLL3J2cjs.SubscriptionManager; exports.TabCoordinator = _chunkADGLL3J2cjs.TabCoordinator; exports.TabSync = _chunkET3YHQ7Vcjs.TabSync; exports.WorkerSocket = _chunkADGLL3J2cjs.WorkerSocket; exports.withSocket = withSocket;
51
51
  //# sourceMappingURL=index.cjs.map
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  SubscriptionManager,
6
6
  TabCoordinator,
7
7
  WorkerSocket
8
- } from "./chunk-INJYCCW7.js";
8
+ } from "./chunk-FZIIMO67.js";
9
9
  import {
10
10
  TabSync
11
11
  } from "./chunk-RM27CYKT.js";
package/dist/react.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkMP3K5IEIcjs = require('./chunk-MP3K5IEI.cjs');
3
+ var _chunkADGLL3J2cjs = require('./chunk-ADGLL3J2.cjs');
4
4
  require('./chunk-PNQIHDJF.cjs');
5
5
 
6
6
  // src/adapters/react.ts
@@ -16,7 +16,7 @@ var _react = require('react');
16
16
  var SharedWSContext = _react.createContext.call(void 0, null);
17
17
  function SharedWebSocketProvider({ url, options, children }) {
18
18
  const [socket] = _react.useState.call(void 0, () => {
19
- const ws = new (0, _chunkMP3K5IEIcjs.SharedWebSocket)(url, options);
19
+ const ws = new (0, _chunkADGLL3J2cjs.SharedWebSocket)(url, options);
20
20
  ws.connect();
21
21
  return ws;
22
22
  });
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SharedWebSocket
3
- } from "./chunk-INJYCCW7.js";
3
+ } from "./chunk-FZIIMO67.js";
4
4
  import "./chunk-B2V5HX77.js";
5
5
 
6
6
  // src/adapters/react.ts
package/dist/types.d.ts CHANGED
@@ -47,6 +47,8 @@ export interface SharedWebSocketOptions<TEvents extends EventMap = EventMap> {
47
47
  protocols?: string[];
48
48
  reconnect?: boolean;
49
49
  reconnectMaxDelay?: number;
50
+ /** Max reconnect attempts before giving up (default: Infinity — retry forever). */
51
+ reconnectMaxRetries?: number;
50
52
  heartbeatInterval?: number;
51
53
  electionTimeout?: number;
52
54
  leaderHeartbeat?: number;
package/dist/vue.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkMP3K5IEIcjs = require('./chunk-MP3K5IEI.cjs');
3
+ var _chunkADGLL3J2cjs = require('./chunk-ADGLL3J2.cjs');
4
4
  require('./chunk-PNQIHDJF.cjs');
5
5
 
6
6
  // src/adapters/vue.ts
@@ -15,7 +15,7 @@ var SharedWebSocketKey = /* @__PURE__ */ Symbol("SharedWebSocket");
15
15
  function createSharedWebSocketPlugin(url, options) {
16
16
  return {
17
17
  install(app) {
18
- const socket = new (0, _chunkMP3K5IEIcjs.SharedWebSocket)(url, options);
18
+ const socket = new (0, _chunkADGLL3J2cjs.SharedWebSocket)(url, options);
19
19
  socket.connect();
20
20
  app.provide(SharedWebSocketKey, socket);
21
21
  const originalUnmount = app.unmount.bind(app);
package/dist/vue.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SharedWebSocket
3
- } from "./chunk-INJYCCW7.js";
3
+ } from "./chunk-FZIIMO67.js";
4
4
  import "./chunk-B2V5HX77.js";
5
5
 
6
6
  // src/adapters/vue.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gwakko/shared-websocket",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
4
4
  "description": "Share ONE WebSocket connection across browser tabs — leader election, BroadcastChannel sync, optional Web Worker",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -6,6 +6,8 @@ interface SharedSocketOptions {
6
6
  protocols?: string[];
7
7
  reconnect?: boolean;
8
8
  reconnectMaxDelay?: number;
9
+ /** Max reconnect attempts before giving up (default: Infinity). */
10
+ reconnectMaxRetries?: number;
9
11
  heartbeatInterval?: number;
10
12
  sendBuffer?: number;
11
13
  auth?: () => string | Promise<string>;
@@ -30,6 +32,8 @@ export class SharedSocket implements Disposable {
30
32
  private onMessageFns = new Set<EventHandler>();
31
33
  private onStateChangeFns = new Set<(state: SocketState) => void>();
32
34
 
35
+ private reconnectAttempts = 0;
36
+
33
37
  private readonly opts: Required<Omit<SharedSocketOptions, 'auth' | 'authToken' | 'authParam' | 'pingPayload' | 'serialize' | 'deserialize'>> & {
34
38
  auth?: () => string | Promise<string>;
35
39
  authToken?: string;
@@ -47,6 +51,7 @@ export class SharedSocket implements Disposable {
47
51
  protocols: options.protocols ?? [],
48
52
  reconnect: options.reconnect ?? true,
49
53
  reconnectMaxDelay: options.reconnectMaxDelay ?? 30_000,
54
+ reconnectMaxRetries: options.reconnectMaxRetries ?? Infinity,
50
55
  heartbeatInterval: options.heartbeatInterval ?? 30_000,
51
56
  sendBuffer: options.sendBuffer ?? 100,
52
57
  auth: options.auth,
@@ -75,6 +80,7 @@ export class SharedSocket implements Disposable {
75
80
  this.ws = new WebSocket(connectUrl, this.opts.protocols);
76
81
 
77
82
  this.ws.onopen = () => {
83
+ this.reconnectAttempts = 0;
78
84
  this.setState('connected');
79
85
  this.flushBuffer();
80
86
  this.startHeartbeat();
@@ -143,6 +149,13 @@ export class SharedSocket implements Disposable {
143
149
  }
144
150
 
145
151
  private reconnect(): void {
152
+ this.reconnectAttempts++;
153
+
154
+ if (this.reconnectAttempts > this.opts.reconnectMaxRetries) {
155
+ this.setState('closed');
156
+ return;
157
+ }
158
+
146
159
  this.setState('reconnecting');
147
160
  const gen = backoff(1000, this.opts.reconnectMaxDelay);
148
161
 
@@ -664,6 +664,7 @@ export class SharedWebSocket<TEvents extends EventMap = EventMap> implements Dis
664
664
  protocols: this.options.protocols,
665
665
  reconnect: this.options.reconnect,
666
666
  reconnectMaxDelay: this.options.reconnectMaxDelay,
667
+ reconnectMaxRetries: this.options.reconnectMaxRetries,
667
668
  heartbeatInterval: this.options.heartbeatInterval,
668
669
  sendBuffer: this.options.sendBuffer,
669
670
  pingPayload: this.proto.ping,
package/src/types.ts CHANGED
@@ -52,6 +52,8 @@ export interface SharedWebSocketOptions<TEvents extends EventMap = EventMap> {
52
52
  protocols?: string[];
53
53
  reconnect?: boolean;
54
54
  reconnectMaxDelay?: number;
55
+ /** Max reconnect attempts before giving up (default: Infinity — retry forever). */
56
+ reconnectMaxRetries?: number;
55
57
  heartbeatInterval?: number;
56
58
  electionTimeout?: number;
57
59
  leaderHeartbeat?: number;
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/id.ts","../src/MessageBus.ts","../src/TabCoordinator.ts","../src/utils/backoff.ts","../src/SharedSocket.ts","../src/WorkerSocket.ts","../src/SubscriptionManager.ts","../src/SharedWebSocket.ts"],"sourcesContent":["export function generateId(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n","import './utils/disposable';\nimport { generateId } from './utils/id';\nimport type { BusMessage, Unsubscribe } from './types';\n\ntype Listener = (msg: BusMessage) => void;\n\nexport class MessageBus implements Disposable {\n private channel: BroadcastChannel;\n private listeners = new Map<string, Set<Listener>>();\n private pendingRequests = new Map<string, { resolve: (v: unknown) => void; reject: (e: Error) => void; timer: ReturnType<typeof setTimeout> }>();\n\n constructor(\n channelName: string,\n private readonly tabId: string,\n ) {\n this.channel = new BroadcastChannel(channelName);\n this.channel.onmessage = (ev: MessageEvent<BusMessage>) => {\n this.handleMessage(ev.data);\n };\n }\n\n subscribe<T>(topic: string, fn: (data: T) => void): Unsubscribe {\n const wrapper: Listener = (msg) => {\n if (msg.source !== this.tabId) fn(msg.data as T);\n };\n this.addListener(topic, wrapper);\n return () => this.removeListener(topic, wrapper);\n }\n\n publish<T>(topic: string, data: T): void {\n this.postMessage({ topic, type: 'publish', data });\n }\n\n broadcast<T>(topic: string, data: T): void {\n const msg = this.createMessage(topic, 'broadcast', data);\n this.channel.postMessage(msg);\n // Also deliver to self\n this.handleMessage(msg);\n }\n\n async request<T, R>(topic: string, data: T, timeout = 5000): Promise<R> {\n const msg = this.createMessage(topic, 'request', data);\n return new Promise<R>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pendingRequests.delete(msg.id);\n reject(new Error(`MessageBus.request: timeout for topic \"${topic}\"`));\n }, timeout);\n this.pendingRequests.set(msg.id, { resolve: resolve as (v: unknown) => void, reject, timer });\n this.channel.postMessage(msg);\n });\n }\n\n respond<T, R>(topic: string, fn: (data: T) => R | Promise<R>): Unsubscribe {\n const wrapper: Listener = async (msg) => {\n if (msg.type !== 'request' || msg.source === this.tabId) return;\n const result = await fn(msg.data as T);\n this.postMessage({ topic, type: 'response', data: { requestId: msg.id, result } });\n };\n this.addListener(topic, wrapper);\n return () => this.removeListener(topic, wrapper);\n }\n\n private handleMessage(msg: BusMessage): void {\n // Handle response to pending request\n if (msg.type === 'response') {\n const payload = msg.data as { requestId: string; result: unknown };\n const pending = this.pendingRequests.get(payload.requestId);\n if (pending) {\n clearTimeout(pending.timer);\n this.pendingRequests.delete(payload.requestId);\n pending.resolve(payload.result);\n return;\n }\n }\n\n const listeners = this.listeners.get(msg.topic);\n if (listeners) {\n for (const fn of listeners) fn(msg);\n }\n }\n\n private postMessage(partial: Pick<BusMessage, 'topic' | 'type' | 'data'>): void {\n this.channel.postMessage(this.createMessage(partial.topic, partial.type, partial.data));\n }\n\n private createMessage(topic: string, type: BusMessage['type'], data: unknown): BusMessage {\n return { id: generateId(), source: this.tabId, topic, type, data, timestamp: Date.now() };\n }\n\n private addListener(topic: string, fn: Listener): void {\n let set = this.listeners.get(topic);\n if (!set) {\n set = new Set();\n this.listeners.set(topic, set);\n }\n set.add(fn);\n }\n\n private removeListener(topic: string, fn: Listener): void {\n this.listeners.get(topic)?.delete(fn);\n }\n\n [Symbol.dispose](): void {\n for (const pending of this.pendingRequests.values()) {\n clearTimeout(pending.timer);\n pending.reject(new Error('MessageBus disposed'));\n }\n this.pendingRequests.clear();\n this.listeners.clear();\n this.channel.close();\n }\n}\n","import './utils/disposable';\nimport { MessageBus } from './MessageBus';\nimport type { Unsubscribe } from './types';\n\ninterface CoordinatorOptions {\n electionTimeout?: number; // ms to wait for rejection (default 200)\n heartbeatInterval?: number; // ms between heartbeats (default 2000)\n leaderTimeout?: number; // ms without heartbeat to trigger election (default 5000)\n}\n\nexport class TabCoordinator implements Disposable {\n private _isLeader = false;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private leaderCheckTimer: ReturnType<typeof setInterval> | null = null;\n private lastHeartbeat = 0;\n private disposed = false;\n\n private onBecomeLeaderFns = new Set<() => void>();\n private onLoseLeadershipFns = new Set<() => void>();\n private cleanups: Unsubscribe[] = [];\n\n private readonly electionTimeout: number;\n private readonly heartbeatInterval: number;\n private readonly leaderTimeout: number;\n\n constructor(\n private readonly bus: MessageBus,\n private readonly tabId: string,\n options: CoordinatorOptions = {},\n ) {\n this.electionTimeout = options.electionTimeout ?? 200;\n this.heartbeatInterval = options.heartbeatInterval ?? 2000;\n this.leaderTimeout = options.leaderTimeout ?? 5000;\n\n // Listen for election requests — reject if we are leader\n this.cleanups.push(\n this.bus.subscribe<{ tabId: string }>('coord:election', () => {\n if (this._isLeader) {\n this.bus.publish('coord:reject', { tabId: this.tabId });\n }\n }),\n );\n\n // Listen for heartbeats\n this.cleanups.push(\n this.bus.subscribe<{ tabId: string }>('coord:heartbeat', () => {\n this.lastHeartbeat = Date.now();\n }),\n );\n\n // Listen for abdication\n this.cleanups.push(\n this.bus.subscribe('coord:abdicate', () => {\n if (!this._isLeader && !this.disposed) {\n this.elect();\n }\n }),\n );\n }\n\n get isLeader(): boolean {\n return this._isLeader;\n }\n\n async elect(): Promise<void> {\n if (this.disposed) return;\n\n return new Promise<void>((resolve) => {\n let rejected = false;\n\n const unsub = this.bus.subscribe('coord:reject', () => {\n rejected = true;\n unsub();\n // We are follower — start monitoring leader heartbeat\n this.startLeaderCheck();\n resolve();\n });\n\n this.bus.publish('coord:election', { tabId: this.tabId });\n\n setTimeout(() => {\n unsub();\n if (!rejected && !this.disposed) {\n this.becomeLeader();\n }\n resolve();\n }, this.electionTimeout);\n });\n }\n\n abdicate(): void {\n if (!this._isLeader) return;\n this._isLeader = false;\n this.stopHeartbeat();\n this.bus.publish('coord:abdicate', { tabId: this.tabId });\n for (const fn of this.onLoseLeadershipFns) fn();\n }\n\n onBecomeLeader(fn: () => void): Unsubscribe {\n this.onBecomeLeaderFns.add(fn);\n return () => this.onBecomeLeaderFns.delete(fn);\n }\n\n onLoseLeadership(fn: () => void): Unsubscribe {\n this.onLoseLeadershipFns.add(fn);\n return () => this.onLoseLeadershipFns.delete(fn);\n }\n\n private becomeLeader(): void {\n this._isLeader = true;\n this.stopLeaderCheck();\n this.startHeartbeat();\n for (const fn of this.onBecomeLeaderFns) fn();\n }\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n this.bus.publish('coord:heartbeat', { tabId: this.tabId });\n }, this.heartbeatInterval);\n // Send immediately\n this.bus.publish('coord:heartbeat', { tabId: this.tabId });\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private startLeaderCheck(): void {\n this.stopLeaderCheck();\n this.lastHeartbeat = Date.now();\n this.leaderCheckTimer = setInterval(() => {\n if (Date.now() - this.lastHeartbeat > this.leaderTimeout && !this.disposed) {\n this.stopLeaderCheck();\n this.elect();\n }\n }, 1000);\n }\n\n private stopLeaderCheck(): void {\n if (this.leaderCheckTimer) {\n clearInterval(this.leaderCheckTimer);\n this.leaderCheckTimer = null;\n }\n }\n\n [Symbol.dispose](): void {\n this.disposed = true;\n if (this._isLeader) {\n this.abdicate();\n }\n this.stopHeartbeat();\n this.stopLeaderCheck();\n for (const unsub of this.cleanups) unsub();\n this.cleanups = [];\n this.onBecomeLeaderFns.clear();\n this.onLoseLeadershipFns.clear();\n }\n}\n","/** Exponential backoff generator with jitter. */\nexport function* backoff(base = 1000, max = 30_000): Generator<number> {\n let delay = base;\n while (true) {\n const jitter = delay * 0.25 * (Math.random() * 2 - 1);\n yield Math.min(delay + jitter, max);\n delay = Math.min(delay * 2, max);\n }\n}\n","import './utils/disposable';\nimport { backoff } from './utils/backoff';\nimport type { SocketState, Unsubscribe, EventHandler } from './types';\n\ninterface SharedSocketOptions {\n protocols?: string[];\n reconnect?: boolean;\n reconnectMaxDelay?: number;\n heartbeatInterval?: number;\n sendBuffer?: number;\n auth?: () => string | Promise<string>;\n authToken?: string;\n authParam?: string;\n /** Heartbeat payload (default: { type: \"ping\" }). */\n pingPayload?: unknown;\n /** Custom serializer (default: JSON.stringify). */\n serialize?: (data: unknown) => string | ArrayBuffer | Blob;\n /** Custom deserializer (default: JSON.parse). */\n deserialize?: (raw: string | ArrayBuffer) => unknown;\n}\n\nexport class SharedSocket implements Disposable {\n private ws: WebSocket | null = null;\n private _state: SocketState = 'closed';\n private buffer: unknown[] = [];\n private disposed = false;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n private onMessageFns = new Set<EventHandler>();\n private onStateChangeFns = new Set<(state: SocketState) => void>();\n\n private readonly opts: Required<Omit<SharedSocketOptions, 'auth' | 'authToken' | 'authParam' | 'pingPayload' | 'serialize' | 'deserialize'>> & {\n auth?: () => string | Promise<string>;\n authToken?: string;\n authParam: string;\n pingPayload: unknown;\n serialize: (data: unknown) => string | ArrayBuffer | Blob;\n deserialize: (raw: string | ArrayBuffer) => unknown;\n };\n\n constructor(\n private url: string,\n options: SharedSocketOptions = {},\n ) {\n this.opts = {\n protocols: options.protocols ?? [],\n reconnect: options.reconnect ?? true,\n reconnectMaxDelay: options.reconnectMaxDelay ?? 30_000,\n heartbeatInterval: options.heartbeatInterval ?? 30_000,\n sendBuffer: options.sendBuffer ?? 100,\n auth: options.auth,\n authToken: options.authToken,\n authParam: options.authParam ?? 'token',\n pingPayload: options.pingPayload ?? { type: 'ping' },\n serialize: options.serialize ?? ((data: unknown) => JSON.stringify(data)),\n deserialize: options.deserialize ?? ((raw: string | ArrayBuffer) => {\n if (typeof raw === 'string') return JSON.parse(raw);\n // ArrayBuffer → decode as UTF-8 then parse\n return JSON.parse(new TextDecoder().decode(raw));\n }),\n };\n }\n\n get state(): SocketState {\n return this._state;\n }\n\n async connect(): Promise<void> {\n if (this.disposed) return;\n\n this.setState('connecting');\n\n const connectUrl = await this.buildUrl();\n this.ws = new WebSocket(connectUrl, this.opts.protocols);\n\n this.ws.onopen = () => {\n this.setState('connected');\n this.flushBuffer();\n this.startHeartbeat();\n };\n\n this.ws.onmessage = (ev: MessageEvent) => {\n let data: unknown;\n try {\n data = this.opts.deserialize(ev.data as string | ArrayBuffer);\n } catch {\n data = ev.data;\n }\n for (const fn of this.onMessageFns) fn(data);\n };\n\n this.ws.onclose = () => {\n this.stopHeartbeat();\n if (!this.disposed && this.opts.reconnect) {\n this.reconnect();\n } else {\n this.setState('closed');\n }\n };\n\n this.ws.onerror = () => {\n // onclose will fire after onerror\n };\n }\n\n disconnect(): void {\n this.disposed = true;\n this.stopHeartbeat();\n this.clearReconnect();\n\n if (this.ws) {\n this.ws.onclose = null;\n this.ws.onmessage = null;\n this.ws.onerror = null;\n if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {\n this.ws.close(1000, 'client disconnect');\n }\n this.ws = null;\n }\n\n this.setState('closed');\n }\n\n send(data: unknown): void {\n if (this._state === 'connected' && this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(this.opts.serialize(data));\n } else if (this._state === 'reconnecting' || this._state === 'connecting') {\n if (this.buffer.length < this.opts.sendBuffer) {\n this.buffer.push(data);\n }\n }\n }\n\n onMessage(fn: EventHandler): Unsubscribe {\n this.onMessageFns.add(fn);\n return () => this.onMessageFns.delete(fn);\n }\n\n onStateChange(fn: (state: SocketState) => void): Unsubscribe {\n this.onStateChangeFns.add(fn);\n return () => this.onStateChangeFns.delete(fn);\n }\n\n private reconnect(): void {\n this.setState('reconnecting');\n const gen = backoff(1000, this.opts.reconnectMaxDelay);\n\n const attempt = () => {\n if (this.disposed) return;\n const delay = gen.next().value;\n this.reconnectTimer = setTimeout(() => {\n if (!this.disposed) this.connect();\n }, delay);\n };\n\n attempt();\n }\n\n private flushBuffer(): void {\n const pending = this.buffer.splice(0);\n for (const item of pending) {\n this.send(item);\n }\n }\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(this.opts.serialize(this.opts.pingPayload));\n }\n }, this.opts.heartbeatInterval);\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private clearReconnect(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n\n private async buildUrl(): Promise<string> {\n // Resolve token: callback > static > none\n let token: string | undefined;\n if (this.opts.auth) {\n token = await this.opts.auth();\n } else if (this.opts.authToken) {\n token = this.opts.authToken;\n }\n\n if (!token) return this.url;\n\n // WebSocket URLs (ws://, wss://) are not fully supported by URL API.\n // Convert to http(s) for parsing, then back to ws(s).\n const httpUrl = this.url.replace(/^ws(s?):\\/\\//, 'http$1://');\n const parsed = new URL(httpUrl);\n parsed.searchParams.set(this.opts.authParam, token);\n\n return parsed.toString().replace(/^http(s?):\\/\\//, 'ws$1://');\n }\n\n private setState(state: SocketState): void {\n this._state = state;\n for (const fn of this.onStateChangeFns) fn(state);\n }\n\n [Symbol.dispose](): void {\n this.disconnect();\n this.onMessageFns.clear();\n this.onStateChangeFns.clear();\n this.buffer = [];\n }\n}\n","import './utils/disposable';\nimport type { SocketState, Unsubscribe, EventHandler } from './types';\n\n/**\n * WorkerSocket — WebSocket running inside a Web Worker.\n *\n * Same interface as SharedSocket, but WebSocket lives off main thread.\n * Benefits: heartbeat timers and JSON parsing don't block UI rendering.\n *\n * Use when:\n * - High message rate (50+ msgs/sec)\n * - Heavy JSON payloads\n * - UI does complex rendering that could block main thread\n *\n * Don't use when:\n * - Low message rate (simple chat, notifications)\n * - Bundle size matters (adds worker file)\n * - Debugging (Worker DevTools is less convenient)\n */\nexport class WorkerSocket implements Disposable {\n private worker: Worker | null = null;\n private _state: SocketState = 'closed';\n\n private onMessageFns = new Set<EventHandler>();\n private onStateChangeFns = new Set<(state: SocketState) => void>();\n\n constructor(\n private url: string,\n private options: {\n protocols?: string[];\n reconnect?: boolean;\n reconnectMaxDelay?: number;\n heartbeatInterval?: number;\n sendBuffer?: number;\n workerUrl?: string | URL;\n } = {},\n ) {}\n\n get state(): SocketState {\n return this._state;\n }\n\n connect(): void {\n // Create worker from inline blob if no workerUrl provided\n const workerUrl = this.options.workerUrl ?? this.createWorkerBlob();\n\n this.worker = new Worker(workerUrl, { type: 'module' });\n\n this.worker.onmessage = (ev: MessageEvent) => {\n const msg = ev.data;\n\n switch (msg.type) {\n case 'state':\n this._state = msg.state;\n for (const fn of this.onStateChangeFns) fn(msg.state);\n break;\n\n case 'message':\n for (const fn of this.onMessageFns) fn(msg.data);\n break;\n\n case 'open':\n // State already set via 'state' message\n break;\n\n case 'close':\n break;\n\n case 'error':\n console.error('WorkerSocket error:', msg.message);\n break;\n }\n };\n\n this.worker.postMessage({\n type: 'connect',\n url: this.url,\n protocols: this.options.protocols ?? [],\n reconnect: this.options.reconnect ?? true,\n reconnectMaxDelay: this.options.reconnectMaxDelay ?? 30_000,\n heartbeatInterval: this.options.heartbeatInterval ?? 30_000,\n bufferSize: this.options.sendBuffer ?? 100,\n });\n }\n\n send(data: unknown): void {\n this.worker?.postMessage({ type: 'send', data });\n }\n\n disconnect(): void {\n this.worker?.postMessage({ type: 'disconnect' });\n setTimeout(() => {\n this.worker?.terminate();\n this.worker = null;\n }, 100);\n this._state = 'closed';\n }\n\n onMessage(fn: EventHandler): Unsubscribe {\n this.onMessageFns.add(fn);\n return () => this.onMessageFns.delete(fn);\n }\n\n onStateChange(fn: (state: SocketState) => void): Unsubscribe {\n this.onStateChangeFns.add(fn);\n return () => this.onStateChangeFns.delete(fn);\n }\n\n private createWorkerBlob(): URL {\n // Inline the worker code as a blob URL\n // In production, use a bundler (Vite, webpack) to handle worker imports\n const code = `\n let ws = null, state = 'closed', buffer = [], disposed = false;\n let heartbeatTimer = null, reconnectTimer = null;\n let url = '', protocols = [], shouldReconnect = true;\n let maxDelay = 30000, hbInterval = 30000, maxBuf = 100, delay = 1000;\n\n function setState(s) { state = s; self.postMessage({ type: 'state', state: s }); }\n function connect() {\n if (disposed) return;\n setState('connecting');\n ws = new WebSocket(url, protocols);\n ws.onopen = () => { setState('connected'); delay = 1000; self.postMessage({ type: 'open' }); flush(); startHB(); };\n ws.onmessage = (e) => { let d; try { d = JSON.parse(e.data); } catch { d = e.data; } self.postMessage({ type: 'message', data: d }); };\n ws.onclose = (e) => { stopHB(); self.postMessage({ type: 'close', code: e.code, reason: e.reason }); if (!disposed && shouldReconnect && e.code !== 1000) reconnect(); else setState('closed'); };\n ws.onerror = () => { self.postMessage({ type: 'error', message: 'error' }); };\n }\n function send(d) { if (state === 'connected' && ws?.readyState === 1) ws.send(JSON.stringify(d)); else if (buffer.length < maxBuf) buffer.push(d); }\n function flush() { const p = buffer.splice(0); p.forEach(send); }\n function startHB() { stopHB(); heartbeatTimer = setInterval(() => { if (ws?.readyState === 1) ws.send('{\"type\":\"ping\"}'); }, hbInterval); }\n function stopHB() { if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; } }\n function reconnect() { setState('reconnecting'); const j = delay * 0.25 * (Math.random() * 2 - 1); reconnectTimer = setTimeout(() => { if (!disposed) connect(); }, Math.min(delay + j, maxDelay)); delay = Math.min(delay * 2, maxDelay); }\n self.onmessage = (e) => {\n const c = e.data;\n if (c.type === 'connect') { url = c.url; protocols = c.protocols || []; shouldReconnect = c.reconnect ?? true; maxDelay = c.reconnectMaxDelay || 30000; hbInterval = c.heartbeatInterval || 30000; maxBuf = c.bufferSize || 100; connect(); }\n if (c.type === 'send') send(c.data);\n if (c.type === 'disconnect') { disposed = true; stopHB(); if (reconnectTimer) clearTimeout(reconnectTimer); if (ws) { ws.onclose = null; if (ws.readyState < 2) ws.close(1000); ws = null; } buffer = []; setState('closed'); }\n };\n `;\n const blob = new Blob([code], { type: 'application/javascript' });\n return new URL(URL.createObjectURL(blob));\n }\n\n [Symbol.dispose](): void {\n this.disconnect();\n this.onMessageFns.clear();\n this.onStateChangeFns.clear();\n }\n}\n","import './utils/disposable';\nimport type { EventHandler, Unsubscribe } from './types';\n\nexport class SubscriptionManager implements Disposable {\n private handlers = new Map<string, Set<EventHandler>>();\n private lastMessages = new Map<string, unknown>();\n\n on(event: string, handler: EventHandler): Unsubscribe {\n let set = this.handlers.get(event);\n if (!set) {\n set = new Set();\n this.handlers.set(event, set);\n }\n set.add(handler);\n return () => set!.delete(handler);\n }\n\n once(event: string, handler: EventHandler): Unsubscribe {\n const wrapper: EventHandler = (data) => {\n unsub();\n handler(data);\n };\n const unsub = this.on(event, wrapper);\n return unsub;\n }\n\n off(event: string, handler?: EventHandler): void {\n if (handler) {\n this.handlers.get(event)?.delete(handler);\n } else {\n this.handlers.delete(event);\n }\n }\n\n emit(event: string, data: unknown): void {\n this.lastMessages.set(event, data);\n const set = this.handlers.get(event);\n if (set) {\n for (const fn of set) fn(data);\n }\n }\n\n getLastMessage(event: string): unknown | undefined {\n return this.lastMessages.get(event);\n }\n\n async *stream(event: string, signal?: AbortSignal): AsyncGenerator<unknown> {\n const queue: unknown[] = [];\n let resolve: (() => void) | null = null;\n let done = false;\n\n const unsub = this.on(event, (data) => {\n queue.push(data);\n resolve?.();\n });\n\n const onAbort = () => {\n done = true;\n resolve?.();\n };\n signal?.addEventListener('abort', onAbort);\n\n try {\n while (!done) {\n if (queue.length > 0) {\n yield queue.shift()!;\n } else {\n await new Promise<void>((r) => { resolve = r; });\n resolve = null;\n }\n }\n } finally {\n unsub();\n signal?.removeEventListener('abort', onAbort);\n }\n }\n\n offAll(): void {\n this.handlers.clear();\n this.lastMessages.clear();\n }\n\n [Symbol.dispose](): void {\n this.offAll();\n }\n}\n","import './utils/disposable';\nimport { generateId } from './utils/id';\nimport { MessageBus } from './MessageBus';\nimport { TabCoordinator } from './TabCoordinator';\nimport { SharedSocket } from './SharedSocket';\nimport { WorkerSocket } from './WorkerSocket';\nimport { SubscriptionManager } from './SubscriptionManager';\nimport type { SharedWebSocketOptions, TabRole, Unsubscribe, EventHandler, Channel, EventProtocol, EventMap, Logger, Middleware } from './types';\n\nconst DEFAULT_PROTOCOL: EventProtocol = {\n eventField: 'event',\n dataField: 'data',\n channelJoin: '$channel:join',\n channelLeave: '$channel:leave',\n ping: { type: 'ping' },\n defaultEvent: 'message',\n topicSubscribe: '$topic:subscribe',\n topicUnsubscribe: '$topic:unsubscribe',\n authLogin: '$auth:login',\n authLogout: '$auth:logout',\n authRevoked: '$auth:revoked',\n};\n\nconst NOOP_LOGGER: Logger = {\n debug() {},\n info() {},\n warn() {},\n error() {},\n};\n\n/** Common interface for both SharedSocket and WorkerSocket. */\ninterface SocketAdapter {\n readonly state: string;\n connect(): void;\n send(data: unknown): void;\n disconnect(): void;\n onMessage(fn: EventHandler): Unsubscribe;\n onStateChange(fn: (state: string) => void): Unsubscribe;\n [Symbol.dispose](): void;\n}\n\n/**\n * SharedWebSocket — shares ONE WebSocket connection across browser tabs.\n *\n * @typeParam TEvents - Event map for type-safe subscriptions.\n *\n * @example\n * // Typed events\n * type Events = {\n * 'chat.message': { text: string; userId: string };\n * 'order.created': { id: string; total: number };\n * };\n * const ws = new SharedWebSocket<Events>(url);\n * ws.on('chat.message', (msg) => msg.text); // ← msg: { text, userId }\n */\nexport class SharedWebSocket<TEvents extends EventMap = EventMap> implements Disposable {\n private bus: MessageBus;\n private coordinator: TabCoordinator;\n private socket: SocketAdapter | null = null;\n private subs = new SubscriptionManager();\n private syncStore = new Map<string, unknown>();\n private tabId: string;\n private cleanups: Unsubscribe[] = [];\n private disposed = false;\n private readonly proto: EventProtocol;\n private readonly log: Logger;\n private outgoingMiddleware: Middleware[] = [];\n private incomingMiddleware: Middleware[] = [];\n private serializers = new Map<string, (data: unknown) => unknown>();\n private deserializers = new Map<string, (data: unknown) => unknown>();\n private _isAuthenticated = false;\n private authChannels = new Map<string, Channel>();\n private authTopics = new Set<string>();\n\n constructor(\n private readonly url: string,\n private readonly options: SharedWebSocketOptions<TEvents> = {} as SharedWebSocketOptions<TEvents>,\n ) {\n this.proto = { ...DEFAULT_PROTOCOL, ...options.events };\n this.log = options.debug ? (options.logger ?? console) : NOOP_LOGGER;\n this.tabId = generateId();\n this.log.debug('[SharedWS] init', { tabId: this.tabId, url });\n this.bus = new MessageBus('shared-ws', this.tabId);\n this.coordinator = new TabCoordinator(this.bus, this.tabId, {\n electionTimeout: options.electionTimeout,\n heartbeatInterval: options.leaderHeartbeat,\n leaderTimeout: options.leaderTimeout,\n });\n\n // When ANY tab receives a WS message via bus → emit to local subscribers\n this.cleanups.push(\n this.bus.subscribe<{ event: string; data: unknown }>('ws:message', (msg) => {\n this.subs.emit(msg.event, msg.data);\n }),\n );\n\n // Leader listens for send requests from followers\n this.cleanups.push(\n this.bus.subscribe<{ event: string; data: unknown }>('ws:send', (msg) => {\n if (this.coordinator.isLeader && this.socket) {\n this.socket.send({ [this.proto.eventField]: msg.event, [this.proto.dataField]: msg.data });\n }\n }),\n );\n\n // Sync across tabs\n this.cleanups.push(\n this.bus.subscribe<{ key: string; value: unknown }>('ws:sync', (msg) => {\n this.syncStore.set(msg.key, msg.value);\n this.subs.emit(`sync:${msg.key}`, msg.value);\n }),\n );\n\n // Leader lifecycle\n this.coordinator.onBecomeLeader(() => {\n this.handleBecomeLeader();\n this.bus.broadcast('ws:lifecycle', { type: 'leader', isLeader: true });\n });\n this.coordinator.onLoseLeadership(() => {\n this.handleLoseLeadership();\n this.bus.broadcast('ws:lifecycle', { type: 'leader', isLeader: false });\n });\n\n // Lifecycle events from bus (all tabs receive)\n this.cleanups.push(\n this.bus.subscribe<{ type: string; isLeader?: boolean; error?: unknown; authenticated?: boolean }>('ws:lifecycle', (msg) => {\n switch (msg.type) {\n case 'connect':\n this.subs.emit('$lifecycle:connect', undefined);\n break;\n case 'disconnect':\n this.subs.emit('$lifecycle:disconnect', undefined);\n break;\n case 'reconnecting':\n this.subs.emit('$lifecycle:reconnecting', undefined);\n break;\n case 'leader':\n this.subs.emit('$lifecycle:leader', msg.isLeader);\n break;\n case 'error':\n this.subs.emit('$lifecycle:error', msg.error);\n break;\n case 'auth': {\n this._isAuthenticated = !!msg.authenticated;\n if (!msg.authenticated) {\n this.authChannels.clear();\n this.authTopics.clear();\n }\n this.subs.emit('$lifecycle:auth', msg.authenticated);\n break;\n }\n }\n }),\n );\n\n // Track tab visibility\n if (typeof document !== 'undefined') {\n const onVisibilityChange = () => {\n const active = !document.hidden;\n this.subs.emit('$lifecycle:active', active);\n this.log.debug('[SharedWS]', active ? '👁 tab active' : '👁 tab hidden');\n };\n document.addEventListener('visibilitychange', onVisibilityChange);\n this.cleanups.push(() => document.removeEventListener('visibilitychange', onVisibilityChange));\n }\n\n // Handle server-initiated auth revocation\n this.cleanups.push(\n this.subs.on(this.proto.authRevoked, () => {\n if (this.coordinator.isLeader) {\n for (const [, ch] of this.authChannels) ch.leave();\n for (const topic of this.authTopics) this.unsubscribe(topic);\n }\n this.authChannels.clear();\n this.authTopics.clear();\n this._isAuthenticated = false;\n this.syncStore.delete('$auth:token');\n this.subs.emit('$lifecycle:auth', false);\n this.log.warn('[SharedWS] auth revoked by server');\n }),\n );\n\n // Cleanup on tab close\n if (typeof window !== 'undefined') {\n const onBeforeUnload = () => this[Symbol.dispose]();\n window.addEventListener('beforeunload', onBeforeUnload);\n this.cleanups.push(() => window.removeEventListener('beforeunload', onBeforeUnload));\n }\n }\n\n get connected(): boolean {\n return this.socket?.state === 'connected' || !this.coordinator.isLeader;\n }\n\n get tabRole(): TabRole {\n return this.coordinator.isLeader ? 'leader' : 'follower';\n }\n\n /** Whether the user is authenticated via runtime auth. */\n get isAuthenticated(): boolean {\n return this._isAuthenticated;\n }\n\n /** Whether this tab is currently visible/focused. */\n get isActive(): boolean {\n return typeof document !== 'undefined' ? !document.hidden : true;\n }\n\n /** Start leader election and connect. */\n async connect(): Promise<void> {\n await this.coordinator.elect();\n }\n\n // ─── Lifecycle Hooks ─────────────────────────────────\n\n /** Called when WebSocket connection opens (broadcast to all tabs). */\n onConnect(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:connect', fn);\n }\n\n /** Called when WebSocket connection closes (broadcast to all tabs). */\n onDisconnect(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:disconnect', fn);\n }\n\n /** Called when WebSocket starts reconnecting (broadcast to all tabs). */\n onReconnecting(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:reconnecting', fn);\n }\n\n /** Called when this tab becomes leader or loses leadership. */\n onLeaderChange(fn: (isLeader: boolean) => void): Unsubscribe {\n return this.subs.on('$lifecycle:leader', fn as EventHandler);\n }\n\n /** Called on WebSocket or network error (broadcast to all tabs). */\n onError(fn: (error: unknown) => void): Unsubscribe {\n return this.subs.on('$lifecycle:error', fn as EventHandler);\n }\n\n /** Called when this tab becomes visible/focused. */\n onActive(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:active', ((isActive: unknown) => {\n if (isActive === true) fn();\n }) as EventHandler);\n }\n\n /** Called when this tab goes to background/hidden. */\n onInactive(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:active', ((isActive: unknown) => {\n if (isActive === false) fn();\n }) as EventHandler);\n }\n\n /** Called on any visibility change. */\n onVisibilityChange(fn: (isActive: boolean) => void): Unsubscribe {\n return this.subs.on('$lifecycle:active', fn as EventHandler);\n }\n\n // ─── Authentication ──────────────────────────────────\n\n /**\n * Authenticate on an existing connection. Sends auth event to server,\n * syncs auth state across all tabs. Use for login after guest connection.\n *\n * @example\n * const token = await loginApi(email, password);\n * ws.authenticate(token);\n *\n * @example\n * // React — via useSocketAuth hook\n * const { authenticate } = useSocketAuth();\n * authenticate(token);\n */\n authenticate(token: string): void {\n this._isAuthenticated = true;\n this.syncStore.set('$auth:token', token);\n this.bus.broadcast('ws:sync', { key: '$auth:token', value: token });\n this.send(this.proto.authLogin, { token });\n this.bus.broadcast('ws:lifecycle', { type: 'auth', authenticated: true });\n this.log.info('[SharedWS] authenticated');\n }\n\n /**\n * Deauthenticate — notifies server, auto-leaves all auth-required channels\n * and topics, syncs state across tabs. Connection stays open for public events.\n *\n * @example\n * ws.deauthenticate(); // connection stays open, auth subscriptions cleaned up\n */\n deauthenticate(): void {\n // Leave auth channels and unsubscribe auth topics\n for (const [, ch] of this.authChannels) ch.leave();\n this.authChannels.clear();\n for (const topic of this.authTopics) this.unsubscribe(topic);\n this.authTopics.clear();\n\n this._isAuthenticated = false;\n this.send(this.proto.authLogout, {});\n this.syncStore.delete('$auth:token');\n this.bus.broadcast('ws:sync', { key: '$auth:token', value: undefined });\n this.bus.broadcast('ws:lifecycle', { type: 'auth', authenticated: false });\n this.log.info('[SharedWS] deauthenticated');\n }\n\n /**\n * Called when auth state changes (authenticate, deauthenticate, or server revocation).\n *\n * @example\n * ws.onAuthChange((authenticated) => {\n * if (!authenticated) router.push('/login');\n * });\n */\n onAuthChange(fn: (authenticated: boolean) => void): Unsubscribe {\n return this.subs.on('$lifecycle:auth', fn as EventHandler);\n }\n\n // ─── Middleware ───────────────────────────────────────\n\n /**\n * Add middleware to transform messages before send or after receive.\n * Return null from middleware to drop the message.\n *\n * @example\n * // Add timestamp to every outgoing message\n * ws.use('outgoing', (msg) => ({ ...msg, timestamp: Date.now() }));\n *\n * @example\n * // Decrypt incoming messages\n * ws.use('incoming', (msg) => ({ ...msg, data: decrypt(msg.data) }));\n *\n * @example\n * // Drop messages from blocked users\n * ws.use('incoming', (msg) => blockedUsers.has(msg.userId) ? null : msg);\n */\n use(direction: 'outgoing' | 'incoming', fn: Middleware): this {\n if (direction === 'outgoing') {\n this.outgoingMiddleware.push(fn);\n } else {\n this.incomingMiddleware.push(fn);\n }\n return this;\n }\n\n // ─── Per-Event Serialization ─────────────────────────\n\n /**\n * Register a custom serializer for a specific event.\n * The data is transformed before outgoing middleware and global serialize.\n *\n * @example\n * // Binary for file uploads, JSON for everything else\n * ws.serializer('file.upload', (data) => new Blob([data as ArrayBuffer]));\n *\n * @example\n * // Protobuf for specific event\n * ws.serializer('trading.order', (data) => OrderProto.encode(data).finish());\n */\n serializer(event: string, fn: (data: unknown) => unknown): this {\n this.serializers.set(event, fn);\n return this;\n }\n\n /**\n * Register a custom deserializer for a specific event.\n * The data is transformed after global deserialize and before incoming middleware.\n *\n * @example\n * ws.deserializer('file.download', (data) => new Uint8Array(data as ArrayBuffer));\n *\n * @example\n * // Protobuf for specific event\n * ws.deserializer('trading.tick', (data) => TickProto.decode(data as Uint8Array));\n */\n deserializer(event: string, fn: (data: unknown) => unknown): this {\n this.deserializers.set(event, fn);\n return this;\n }\n\n // ─── Event Subscription ──────────────────────────────\n\n /** Subscribe to server events (works in ALL tabs). Type-safe with EventMap. */\n on<K extends string & keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): Unsubscribe;\n on(event: string, handler: EventHandler<unknown>): Unsubscribe;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n on(event: string, handler: (data: any) => void): Unsubscribe {\n return this.subs.on(event, handler);\n }\n\n once<K extends string & keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): Unsubscribe;\n once(event: string, handler: EventHandler<unknown>): Unsubscribe;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n once(event: string, handler: (data: any) => void): Unsubscribe {\n return this.subs.once(event, handler);\n }\n\n off(event: string, handler?: EventHandler): void {\n this.subs.off(event, handler);\n }\n\n /** Async generator for consuming events. Type-safe with EventMap. */\n stream<K extends string & keyof TEvents>(event: K, signal?: AbortSignal): AsyncGenerator<TEvents[K]>;\n stream(event: string, signal?: AbortSignal): AsyncGenerator<unknown>;\n stream(event: string, signal?: AbortSignal): AsyncGenerator<unknown> {\n return this.subs.stream(event, signal);\n }\n\n /** Send message to server (auto-routed through leader). Type-safe with EventMap. */\n send<K extends string & keyof TEvents>(event: K, data: TEvents[K]): void;\n send(event: string, data: unknown): void;\n send(event: string, data: unknown): void {\n // Per-event serializer transforms data before building payload\n const eventSerializer = this.serializers.get(event);\n const serializedData = eventSerializer ? eventSerializer(data) : data;\n\n let payload: unknown = { [this.proto.eventField]: event, [this.proto.dataField]: serializedData };\n\n for (const mw of this.outgoingMiddleware) {\n payload = mw(payload);\n if (payload === null) {\n this.log.debug('[SharedWS] ✗ outgoing dropped by middleware', event);\n return;\n }\n }\n\n this.log.debug('[SharedWS] → send', event, data);\n\n if (this.coordinator.isLeader && this.socket) {\n this.socket.send(payload);\n } else {\n this.bus.publish('ws:send', { event, data });\n }\n }\n\n /** Request/response through server via leader. */\n async request<T>(event: string, data: unknown, timeout = 5000): Promise<T> {\n return this.bus.request('ws:request', { event, data }, timeout);\n }\n\n /** Sync state across tabs (no server roundtrip). */\n sync<T>(key: string, value: T): void {\n this.syncStore.set(key, value);\n this.bus.broadcast('ws:sync', { key, value });\n }\n\n getSync<T>(key: string): T | undefined {\n return this.syncStore.get(key) as T | undefined;\n }\n\n onSync<T>(key: string, fn: (value: T) => void): Unsubscribe {\n return this.subs.on(`sync:${key}`, fn as EventHandler);\n }\n\n /**\n * Subscribe to a private/scoped channel. Returns a channel handle with\n * scoped on/send/stream methods. Sends join on subscribe, leave on unsubscribe.\n *\n * @example\n * const chat = ws.channel('chat:room_123');\n * chat.on('message', (msg) => render(msg));\n * chat.send('message', { text: 'Hello' });\n * chat.leave(); // sends leave + unsubscribes\n *\n * @example\n * // Private notifications for tenant\n * const notifications = ws.channel(`tenant:${tenantId}:notifications`);\n * notifications.on('alert', (alert) => showToast(alert));\n */\n channel(name: string, options?: { auth?: boolean }): Channel {\n // Notify server about channel subscription\n this.send(this.proto.channelJoin, { channel: name });\n\n const self = this;\n const unsubs: Unsubscribe[] = [];\n const isAuth = options?.auth ?? false;\n\n const ch: Channel = {\n name,\n on(event: string, handler: EventHandler): Unsubscribe {\n const unsub = self.subs.on(`${name}:${event}`, handler);\n unsubs.push(unsub);\n return unsub;\n },\n once(event: string, handler: EventHandler): Unsubscribe {\n const unsub = self.subs.once(`${name}:${event}`, handler);\n unsubs.push(unsub);\n return unsub;\n },\n send(event: string, data: unknown): void {\n self.send(`${name}:${event}`, data);\n },\n stream(event: string, signal?: AbortSignal): AsyncGenerator<unknown> {\n return self.subs.stream(`${name}:${event}`, signal);\n },\n leave(): void {\n self.send(self.proto.channelLeave, { channel: name });\n for (const unsub of unsubs) unsub();\n unsubs.length = 0;\n if (isAuth) self.authChannels.delete(name);\n },\n };\n\n if (isAuth) {\n this.authChannels.set(name, ch);\n }\n\n return ch;\n }\n\n // ─── Topics ──────────────────────────────────────────\n\n /**\n * Subscribe to a server-side topic. Server will start sending events for this topic.\n * Sends topicSubscribe event (default: \"$topic:subscribe\").\n *\n * @example\n * ws.subscribe('notifications:orders');\n * ws.subscribe('notifications:payments');\n * ws.subscribe(`user:${userId}:mentions`);\n */\n subscribe(topic: string, options?: { auth?: boolean }): void {\n this.send(this.proto.topicSubscribe, { topic });\n if (options?.auth) {\n this.authTopics.add(topic);\n }\n this.log.debug('[SharedWS] subscribe topic', topic);\n }\n\n /**\n * Unsubscribe from a server-side topic.\n * Sends topicUnsubscribe event (default: \"$topic:unsubscribe\").\n */\n unsubscribe(topic: string): void {\n this.send(this.proto.topicUnsubscribe, { topic });\n this.authTopics.delete(topic);\n this.log.debug('[SharedWS] unsubscribe topic', topic);\n }\n\n // ─── Push Notifications ─────────────────────────────\n\n /**\n * Subscribe to an event and show notifications.\n *\n * **target** controls which tab(s) display the notification:\n * - `'active'` — only the currently visible tab (default for render)\n * - `'leader'` — only the leader tab (default for browser Notification)\n * - `'all'` — every tab (for critical alerts)\n *\n * @example\n * // Custom render — sonner toast on active tab only\n * ws.push('notification', {\n * render: (n) => toast(n.title),\n * target: 'active', // default for render\n * });\n *\n * @example\n * // Critical alert — show in ALL tabs\n * ws.push('payment.failed', {\n * render: (n) => toast.error('Payment failed!'),\n * target: 'all',\n * });\n *\n * @example\n * // Browser Notification — only from leader\n * ws.push('order.created', {\n * title: (order) => `New Order #${order.id}`,\n * target: 'leader', // default for browser Notification\n * });\n *\n * @example\n * // Both render + native with different targets\n * ws.push('order.created', {\n * render: (order) => toast(`Order #${order.id}`), // active tab\n * title: (order) => `New Order #${order.id}`, // leader → native\n * });\n */\n push<T = unknown>(\n event: string,\n config: {\n /** Custom render function — you decide how to display. */\n render?: (data: T) => void;\n /** Title for browser Notification API. */\n title?: string | ((data: T) => string);\n /** Body for browser Notification API. */\n body?: string | ((data: T) => string);\n /** Icon URL for browser Notification. */\n icon?: string;\n /** Tag for browser Notification deduplication. */\n tag?: string | ((data: T) => string);\n /**\n * Which tab(s) show the notification:\n * - `'active'` — only the visible/focused tab (default for render)\n * - `'leader'` — only the leader tab (default for browser Notification)\n * - `'all'` — every tab (critical alerts)\n */\n target?: 'active' | 'leader' | 'all';\n /** Called when browser Notification is clicked. */\n onClick?: (data: T) => void;\n },\n ): Unsubscribe {\n const useNativeNotification = !!config.title;\n\n // Default target: 'active' for render, 'leader' for native\n const renderTarget = config.target ?? 'active';\n const nativeTarget = config.target ?? 'leader';\n\n if (useNativeNotification && typeof Notification !== 'undefined' && Notification.permission === 'default') {\n Notification.requestPermission();\n }\n\n return this.on(event, ((data: unknown) => {\n const typed = data as T;\n const isVisible = typeof document !== 'undefined' && !document.hidden;\n const isLeader = this.tabRole === 'leader';\n\n // Custom render\n if (config.render) {\n const shouldRender =\n renderTarget === 'all' ||\n (renderTarget === 'active' && isVisible) ||\n (renderTarget === 'leader' && isLeader);\n\n if (shouldRender) {\n config.render(typed);\n this.log.debug('[SharedWS] 🔔 render', event, `(target: ${renderTarget})`);\n }\n }\n\n // Browser Notification API\n if (useNativeNotification && typeof Notification !== 'undefined' && Notification.permission === 'granted') {\n const shouldNotify =\n nativeTarget === 'all' ||\n (nativeTarget === 'leader' && isLeader) ||\n (nativeTarget === 'active' && isVisible);\n\n // Native notifications make sense when tab is hidden\n if (shouldNotify && !isVisible) {\n const title = typeof config.title === 'function' ? config.title(typed) : config.title!;\n const body = typeof config.body === 'function' ? config.body(typed) : config.body;\n const tag = typeof config.tag === 'function' ? config.tag(typed) : config.tag;\n\n const notif = new Notification(title, { body, icon: config.icon, tag });\n\n if (config.onClick) {\n const handler = config.onClick;\n notif.onclick = () => {\n handler(typed);\n window.focus();\n };\n }\n\n this.log.debug('[SharedWS] 🔔 native', title, `(target: ${nativeTarget})`);\n }\n }\n }) as EventHandler);\n }\n\n disconnect(): void {\n this[Symbol.dispose]();\n }\n\n private createSocket(): SocketAdapter {\n const socketOptions = {\n protocols: this.options.protocols,\n reconnect: this.options.reconnect,\n reconnectMaxDelay: this.options.reconnectMaxDelay,\n heartbeatInterval: this.options.heartbeatInterval,\n sendBuffer: this.options.sendBuffer,\n pingPayload: this.proto.ping,\n };\n\n if (this.options.useWorker) {\n // WebSocket runs in a Web Worker — main thread stays free\n return new WorkerSocket(this.url, {\n ...socketOptions,\n workerUrl: this.options.workerUrl,\n });\n }\n\n // WebSocket runs in main thread (default)\n return new SharedSocket(this.url, {\n ...socketOptions,\n auth: this.options.auth,\n authToken: this.options.authToken,\n authParam: this.options.authParam,\n serialize: this.options.serialize,\n deserialize: this.options.deserialize,\n });\n }\n\n private handleBecomeLeader(): void {\n this.log.info('[SharedWS] 👑 became leader');\n this.socket = this.createSocket();\n\n this.socket.onMessage((raw: unknown) => {\n let data: unknown = raw;\n for (const mw of this.incomingMiddleware) {\n data = mw(data);\n if (data === null) {\n this.log.debug('[SharedWS] ✗ incoming dropped by middleware');\n return;\n }\n }\n\n const msg = data as Record<string, unknown> | null | undefined;\n const event = (msg?.[this.proto.eventField] as string) ?? this.proto.defaultEvent;\n let payload = msg?.[this.proto.dataField] ?? data;\n\n // Per-event deserializer transforms data after global deserialize\n const eventDeserializer = this.deserializers.get(event);\n if (eventDeserializer) {\n payload = eventDeserializer(payload);\n }\n\n this.log.debug('[SharedWS] ← recv', event, payload);\n this.bus.broadcast('ws:message', { event, data: payload });\n });\n\n this.socket.onStateChange((state: string) => {\n this.log.info('[SharedWS]', state === 'connected' ? '✓ connected' : state === 'reconnecting' ? '🔄 reconnecting' : `state: ${state}`);\n switch (state) {\n case 'connected':\n this.bus.broadcast('ws:lifecycle', { type: 'connect' });\n this.reAuthenticateOnReconnect();\n break;\n case 'closed':\n this.bus.broadcast('ws:lifecycle', { type: 'disconnect' });\n break;\n case 'reconnecting':\n this.bus.broadcast('ws:lifecycle', { type: 'reconnecting' });\n break;\n }\n });\n\n this.cleanups.push(\n this.bus.respond<{ event: string; data: unknown }, unknown>('ws:request', async (req) => {\n return new Promise((resolve) => {\n const unsub = this.socket!.onMessage((response: unknown) => {\n const res = response as Record<string, unknown> | undefined;\n if (res?.[this.proto.eventField] === req.event || res?.requestId) {\n unsub();\n resolve(res?.[this.proto.dataField] ?? response);\n }\n });\n this.socket!.send({ event: req.event, data: req.data });\n });\n }),\n );\n\n this.socket.connect();\n }\n\n private reAuthenticateOnReconnect(): void {\n if (!this._isAuthenticated || !this.socket) return;\n\n const token = this.syncStore.get('$auth:token') as string | undefined;\n if (token) {\n this.socket.send({\n [this.proto.eventField]: this.proto.authLogin,\n [this.proto.dataField]: { token },\n });\n this.log.debug('[SharedWS] re-authenticated after reconnect');\n }\n\n // Re-join auth channels\n for (const name of this.authChannels.keys()) {\n this.socket.send({\n [this.proto.eventField]: this.proto.channelJoin,\n [this.proto.dataField]: { channel: name },\n });\n }\n\n // Re-subscribe auth topics\n for (const topic of this.authTopics) {\n this.socket.send({\n [this.proto.eventField]: this.proto.topicSubscribe,\n [this.proto.dataField]: { topic },\n });\n }\n }\n\n private handleLoseLeadership(): void {\n if (this.socket) {\n this.socket[Symbol.dispose]();\n this.socket = null;\n }\n }\n\n [Symbol.dispose](): void {\n if (this.disposed) return;\n this.disposed = true;\n\n this.coordinator[Symbol.dispose]();\n\n if (this.socket) {\n this.socket[Symbol.dispose]();\n this.socket = null;\n }\n\n for (const unsub of this.cleanups) unsub();\n this.cleanups = [];\n this.subs[Symbol.dispose]();\n this.bus[Symbol.dispose]();\n this.syncStore.clear();\n this.authChannels.clear();\n this.authTopics.clear();\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/gwakko/Projects/shared-websocket/dist/chunk-MP3K5IEI.cjs","../src/utils/id.ts","../src/MessageBus.ts","../src/TabCoordinator.ts","../src/utils/backoff.ts","../src/SharedSocket.ts","../src/WorkerSocket.ts","../src/SubscriptionManager.ts","../src/SharedWebSocket.ts"],"names":[],"mappings":"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","file":"/Users/gwakko/Projects/shared-websocket/dist/chunk-MP3K5IEI.cjs","sourcesContent":[null,"export function generateId(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n","import './utils/disposable';\nimport { generateId } from './utils/id';\nimport type { BusMessage, Unsubscribe } from './types';\n\ntype Listener = (msg: BusMessage) => void;\n\nexport class MessageBus implements Disposable {\n private channel: BroadcastChannel;\n private listeners = new Map<string, Set<Listener>>();\n private pendingRequests = new Map<string, { resolve: (v: unknown) => void; reject: (e: Error) => void; timer: ReturnType<typeof setTimeout> }>();\n\n constructor(\n channelName: string,\n private readonly tabId: string,\n ) {\n this.channel = new BroadcastChannel(channelName);\n this.channel.onmessage = (ev: MessageEvent<BusMessage>) => {\n this.handleMessage(ev.data);\n };\n }\n\n subscribe<T>(topic: string, fn: (data: T) => void): Unsubscribe {\n const wrapper: Listener = (msg) => {\n if (msg.source !== this.tabId) fn(msg.data as T);\n };\n this.addListener(topic, wrapper);\n return () => this.removeListener(topic, wrapper);\n }\n\n publish<T>(topic: string, data: T): void {\n this.postMessage({ topic, type: 'publish', data });\n }\n\n broadcast<T>(topic: string, data: T): void {\n const msg = this.createMessage(topic, 'broadcast', data);\n this.channel.postMessage(msg);\n // Also deliver to self\n this.handleMessage(msg);\n }\n\n async request<T, R>(topic: string, data: T, timeout = 5000): Promise<R> {\n const msg = this.createMessage(topic, 'request', data);\n return new Promise<R>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pendingRequests.delete(msg.id);\n reject(new Error(`MessageBus.request: timeout for topic \"${topic}\"`));\n }, timeout);\n this.pendingRequests.set(msg.id, { resolve: resolve as (v: unknown) => void, reject, timer });\n this.channel.postMessage(msg);\n });\n }\n\n respond<T, R>(topic: string, fn: (data: T) => R | Promise<R>): Unsubscribe {\n const wrapper: Listener = async (msg) => {\n if (msg.type !== 'request' || msg.source === this.tabId) return;\n const result = await fn(msg.data as T);\n this.postMessage({ topic, type: 'response', data: { requestId: msg.id, result } });\n };\n this.addListener(topic, wrapper);\n return () => this.removeListener(topic, wrapper);\n }\n\n private handleMessage(msg: BusMessage): void {\n // Handle response to pending request\n if (msg.type === 'response') {\n const payload = msg.data as { requestId: string; result: unknown };\n const pending = this.pendingRequests.get(payload.requestId);\n if (pending) {\n clearTimeout(pending.timer);\n this.pendingRequests.delete(payload.requestId);\n pending.resolve(payload.result);\n return;\n }\n }\n\n const listeners = this.listeners.get(msg.topic);\n if (listeners) {\n for (const fn of listeners) fn(msg);\n }\n }\n\n private postMessage(partial: Pick<BusMessage, 'topic' | 'type' | 'data'>): void {\n this.channel.postMessage(this.createMessage(partial.topic, partial.type, partial.data));\n }\n\n private createMessage(topic: string, type: BusMessage['type'], data: unknown): BusMessage {\n return { id: generateId(), source: this.tabId, topic, type, data, timestamp: Date.now() };\n }\n\n private addListener(topic: string, fn: Listener): void {\n let set = this.listeners.get(topic);\n if (!set) {\n set = new Set();\n this.listeners.set(topic, set);\n }\n set.add(fn);\n }\n\n private removeListener(topic: string, fn: Listener): void {\n this.listeners.get(topic)?.delete(fn);\n }\n\n [Symbol.dispose](): void {\n for (const pending of this.pendingRequests.values()) {\n clearTimeout(pending.timer);\n pending.reject(new Error('MessageBus disposed'));\n }\n this.pendingRequests.clear();\n this.listeners.clear();\n this.channel.close();\n }\n}\n","import './utils/disposable';\nimport { MessageBus } from './MessageBus';\nimport type { Unsubscribe } from './types';\n\ninterface CoordinatorOptions {\n electionTimeout?: number; // ms to wait for rejection (default 200)\n heartbeatInterval?: number; // ms between heartbeats (default 2000)\n leaderTimeout?: number; // ms without heartbeat to trigger election (default 5000)\n}\n\nexport class TabCoordinator implements Disposable {\n private _isLeader = false;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private leaderCheckTimer: ReturnType<typeof setInterval> | null = null;\n private lastHeartbeat = 0;\n private disposed = false;\n\n private onBecomeLeaderFns = new Set<() => void>();\n private onLoseLeadershipFns = new Set<() => void>();\n private cleanups: Unsubscribe[] = [];\n\n private readonly electionTimeout: number;\n private readonly heartbeatInterval: number;\n private readonly leaderTimeout: number;\n\n constructor(\n private readonly bus: MessageBus,\n private readonly tabId: string,\n options: CoordinatorOptions = {},\n ) {\n this.electionTimeout = options.electionTimeout ?? 200;\n this.heartbeatInterval = options.heartbeatInterval ?? 2000;\n this.leaderTimeout = options.leaderTimeout ?? 5000;\n\n // Listen for election requests — reject if we are leader\n this.cleanups.push(\n this.bus.subscribe<{ tabId: string }>('coord:election', () => {\n if (this._isLeader) {\n this.bus.publish('coord:reject', { tabId: this.tabId });\n }\n }),\n );\n\n // Listen for heartbeats\n this.cleanups.push(\n this.bus.subscribe<{ tabId: string }>('coord:heartbeat', () => {\n this.lastHeartbeat = Date.now();\n }),\n );\n\n // Listen for abdication\n this.cleanups.push(\n this.bus.subscribe('coord:abdicate', () => {\n if (!this._isLeader && !this.disposed) {\n this.elect();\n }\n }),\n );\n }\n\n get isLeader(): boolean {\n return this._isLeader;\n }\n\n async elect(): Promise<void> {\n if (this.disposed) return;\n\n return new Promise<void>((resolve) => {\n let rejected = false;\n\n const unsub = this.bus.subscribe('coord:reject', () => {\n rejected = true;\n unsub();\n // We are follower — start monitoring leader heartbeat\n this.startLeaderCheck();\n resolve();\n });\n\n this.bus.publish('coord:election', { tabId: this.tabId });\n\n setTimeout(() => {\n unsub();\n if (!rejected && !this.disposed) {\n this.becomeLeader();\n }\n resolve();\n }, this.electionTimeout);\n });\n }\n\n abdicate(): void {\n if (!this._isLeader) return;\n this._isLeader = false;\n this.stopHeartbeat();\n this.bus.publish('coord:abdicate', { tabId: this.tabId });\n for (const fn of this.onLoseLeadershipFns) fn();\n }\n\n onBecomeLeader(fn: () => void): Unsubscribe {\n this.onBecomeLeaderFns.add(fn);\n return () => this.onBecomeLeaderFns.delete(fn);\n }\n\n onLoseLeadership(fn: () => void): Unsubscribe {\n this.onLoseLeadershipFns.add(fn);\n return () => this.onLoseLeadershipFns.delete(fn);\n }\n\n private becomeLeader(): void {\n this._isLeader = true;\n this.stopLeaderCheck();\n this.startHeartbeat();\n for (const fn of this.onBecomeLeaderFns) fn();\n }\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n this.bus.publish('coord:heartbeat', { tabId: this.tabId });\n }, this.heartbeatInterval);\n // Send immediately\n this.bus.publish('coord:heartbeat', { tabId: this.tabId });\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private startLeaderCheck(): void {\n this.stopLeaderCheck();\n this.lastHeartbeat = Date.now();\n this.leaderCheckTimer = setInterval(() => {\n if (Date.now() - this.lastHeartbeat > this.leaderTimeout && !this.disposed) {\n this.stopLeaderCheck();\n this.elect();\n }\n }, 1000);\n }\n\n private stopLeaderCheck(): void {\n if (this.leaderCheckTimer) {\n clearInterval(this.leaderCheckTimer);\n this.leaderCheckTimer = null;\n }\n }\n\n [Symbol.dispose](): void {\n this.disposed = true;\n if (this._isLeader) {\n this.abdicate();\n }\n this.stopHeartbeat();\n this.stopLeaderCheck();\n for (const unsub of this.cleanups) unsub();\n this.cleanups = [];\n this.onBecomeLeaderFns.clear();\n this.onLoseLeadershipFns.clear();\n }\n}\n","/** Exponential backoff generator with jitter. */\nexport function* backoff(base = 1000, max = 30_000): Generator<number> {\n let delay = base;\n while (true) {\n const jitter = delay * 0.25 * (Math.random() * 2 - 1);\n yield Math.min(delay + jitter, max);\n delay = Math.min(delay * 2, max);\n }\n}\n","import './utils/disposable';\nimport { backoff } from './utils/backoff';\nimport type { SocketState, Unsubscribe, EventHandler } from './types';\n\ninterface SharedSocketOptions {\n protocols?: string[];\n reconnect?: boolean;\n reconnectMaxDelay?: number;\n heartbeatInterval?: number;\n sendBuffer?: number;\n auth?: () => string | Promise<string>;\n authToken?: string;\n authParam?: string;\n /** Heartbeat payload (default: { type: \"ping\" }). */\n pingPayload?: unknown;\n /** Custom serializer (default: JSON.stringify). */\n serialize?: (data: unknown) => string | ArrayBuffer | Blob;\n /** Custom deserializer (default: JSON.parse). */\n deserialize?: (raw: string | ArrayBuffer) => unknown;\n}\n\nexport class SharedSocket implements Disposable {\n private ws: WebSocket | null = null;\n private _state: SocketState = 'closed';\n private buffer: unknown[] = [];\n private disposed = false;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n private onMessageFns = new Set<EventHandler>();\n private onStateChangeFns = new Set<(state: SocketState) => void>();\n\n private readonly opts: Required<Omit<SharedSocketOptions, 'auth' | 'authToken' | 'authParam' | 'pingPayload' | 'serialize' | 'deserialize'>> & {\n auth?: () => string | Promise<string>;\n authToken?: string;\n authParam: string;\n pingPayload: unknown;\n serialize: (data: unknown) => string | ArrayBuffer | Blob;\n deserialize: (raw: string | ArrayBuffer) => unknown;\n };\n\n constructor(\n private url: string,\n options: SharedSocketOptions = {},\n ) {\n this.opts = {\n protocols: options.protocols ?? [],\n reconnect: options.reconnect ?? true,\n reconnectMaxDelay: options.reconnectMaxDelay ?? 30_000,\n heartbeatInterval: options.heartbeatInterval ?? 30_000,\n sendBuffer: options.sendBuffer ?? 100,\n auth: options.auth,\n authToken: options.authToken,\n authParam: options.authParam ?? 'token',\n pingPayload: options.pingPayload ?? { type: 'ping' },\n serialize: options.serialize ?? ((data: unknown) => JSON.stringify(data)),\n deserialize: options.deserialize ?? ((raw: string | ArrayBuffer) => {\n if (typeof raw === 'string') return JSON.parse(raw);\n // ArrayBuffer → decode as UTF-8 then parse\n return JSON.parse(new TextDecoder().decode(raw));\n }),\n };\n }\n\n get state(): SocketState {\n return this._state;\n }\n\n async connect(): Promise<void> {\n if (this.disposed) return;\n\n this.setState('connecting');\n\n const connectUrl = await this.buildUrl();\n this.ws = new WebSocket(connectUrl, this.opts.protocols);\n\n this.ws.onopen = () => {\n this.setState('connected');\n this.flushBuffer();\n this.startHeartbeat();\n };\n\n this.ws.onmessage = (ev: MessageEvent) => {\n let data: unknown;\n try {\n data = this.opts.deserialize(ev.data as string | ArrayBuffer);\n } catch {\n data = ev.data;\n }\n for (const fn of this.onMessageFns) fn(data);\n };\n\n this.ws.onclose = () => {\n this.stopHeartbeat();\n if (!this.disposed && this.opts.reconnect) {\n this.reconnect();\n } else {\n this.setState('closed');\n }\n };\n\n this.ws.onerror = () => {\n // onclose will fire after onerror\n };\n }\n\n disconnect(): void {\n this.disposed = true;\n this.stopHeartbeat();\n this.clearReconnect();\n\n if (this.ws) {\n this.ws.onclose = null;\n this.ws.onmessage = null;\n this.ws.onerror = null;\n if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {\n this.ws.close(1000, 'client disconnect');\n }\n this.ws = null;\n }\n\n this.setState('closed');\n }\n\n send(data: unknown): void {\n if (this._state === 'connected' && this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(this.opts.serialize(data));\n } else if (this._state === 'reconnecting' || this._state === 'connecting') {\n if (this.buffer.length < this.opts.sendBuffer) {\n this.buffer.push(data);\n }\n }\n }\n\n onMessage(fn: EventHandler): Unsubscribe {\n this.onMessageFns.add(fn);\n return () => this.onMessageFns.delete(fn);\n }\n\n onStateChange(fn: (state: SocketState) => void): Unsubscribe {\n this.onStateChangeFns.add(fn);\n return () => this.onStateChangeFns.delete(fn);\n }\n\n private reconnect(): void {\n this.setState('reconnecting');\n const gen = backoff(1000, this.opts.reconnectMaxDelay);\n\n const attempt = () => {\n if (this.disposed) return;\n const delay = gen.next().value;\n this.reconnectTimer = setTimeout(() => {\n if (!this.disposed) this.connect();\n }, delay);\n };\n\n attempt();\n }\n\n private flushBuffer(): void {\n const pending = this.buffer.splice(0);\n for (const item of pending) {\n this.send(item);\n }\n }\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(this.opts.serialize(this.opts.pingPayload));\n }\n }, this.opts.heartbeatInterval);\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private clearReconnect(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n\n private async buildUrl(): Promise<string> {\n // Resolve token: callback > static > none\n let token: string | undefined;\n if (this.opts.auth) {\n token = await this.opts.auth();\n } else if (this.opts.authToken) {\n token = this.opts.authToken;\n }\n\n if (!token) return this.url;\n\n // WebSocket URLs (ws://, wss://) are not fully supported by URL API.\n // Convert to http(s) for parsing, then back to ws(s).\n const httpUrl = this.url.replace(/^ws(s?):\\/\\//, 'http$1://');\n const parsed = new URL(httpUrl);\n parsed.searchParams.set(this.opts.authParam, token);\n\n return parsed.toString().replace(/^http(s?):\\/\\//, 'ws$1://');\n }\n\n private setState(state: SocketState): void {\n this._state = state;\n for (const fn of this.onStateChangeFns) fn(state);\n }\n\n [Symbol.dispose](): void {\n this.disconnect();\n this.onMessageFns.clear();\n this.onStateChangeFns.clear();\n this.buffer = [];\n }\n}\n","import './utils/disposable';\nimport type { SocketState, Unsubscribe, EventHandler } from './types';\n\n/**\n * WorkerSocket — WebSocket running inside a Web Worker.\n *\n * Same interface as SharedSocket, but WebSocket lives off main thread.\n * Benefits: heartbeat timers and JSON parsing don't block UI rendering.\n *\n * Use when:\n * - High message rate (50+ msgs/sec)\n * - Heavy JSON payloads\n * - UI does complex rendering that could block main thread\n *\n * Don't use when:\n * - Low message rate (simple chat, notifications)\n * - Bundle size matters (adds worker file)\n * - Debugging (Worker DevTools is less convenient)\n */\nexport class WorkerSocket implements Disposable {\n private worker: Worker | null = null;\n private _state: SocketState = 'closed';\n\n private onMessageFns = new Set<EventHandler>();\n private onStateChangeFns = new Set<(state: SocketState) => void>();\n\n constructor(\n private url: string,\n private options: {\n protocols?: string[];\n reconnect?: boolean;\n reconnectMaxDelay?: number;\n heartbeatInterval?: number;\n sendBuffer?: number;\n workerUrl?: string | URL;\n } = {},\n ) {}\n\n get state(): SocketState {\n return this._state;\n }\n\n connect(): void {\n // Create worker from inline blob if no workerUrl provided\n const workerUrl = this.options.workerUrl ?? this.createWorkerBlob();\n\n this.worker = new Worker(workerUrl, { type: 'module' });\n\n this.worker.onmessage = (ev: MessageEvent) => {\n const msg = ev.data;\n\n switch (msg.type) {\n case 'state':\n this._state = msg.state;\n for (const fn of this.onStateChangeFns) fn(msg.state);\n break;\n\n case 'message':\n for (const fn of this.onMessageFns) fn(msg.data);\n break;\n\n case 'open':\n // State already set via 'state' message\n break;\n\n case 'close':\n break;\n\n case 'error':\n console.error('WorkerSocket error:', msg.message);\n break;\n }\n };\n\n this.worker.postMessage({\n type: 'connect',\n url: this.url,\n protocols: this.options.protocols ?? [],\n reconnect: this.options.reconnect ?? true,\n reconnectMaxDelay: this.options.reconnectMaxDelay ?? 30_000,\n heartbeatInterval: this.options.heartbeatInterval ?? 30_000,\n bufferSize: this.options.sendBuffer ?? 100,\n });\n }\n\n send(data: unknown): void {\n this.worker?.postMessage({ type: 'send', data });\n }\n\n disconnect(): void {\n this.worker?.postMessage({ type: 'disconnect' });\n setTimeout(() => {\n this.worker?.terminate();\n this.worker = null;\n }, 100);\n this._state = 'closed';\n }\n\n onMessage(fn: EventHandler): Unsubscribe {\n this.onMessageFns.add(fn);\n return () => this.onMessageFns.delete(fn);\n }\n\n onStateChange(fn: (state: SocketState) => void): Unsubscribe {\n this.onStateChangeFns.add(fn);\n return () => this.onStateChangeFns.delete(fn);\n }\n\n private createWorkerBlob(): URL {\n // Inline the worker code as a blob URL\n // In production, use a bundler (Vite, webpack) to handle worker imports\n const code = `\n let ws = null, state = 'closed', buffer = [], disposed = false;\n let heartbeatTimer = null, reconnectTimer = null;\n let url = '', protocols = [], shouldReconnect = true;\n let maxDelay = 30000, hbInterval = 30000, maxBuf = 100, delay = 1000;\n\n function setState(s) { state = s; self.postMessage({ type: 'state', state: s }); }\n function connect() {\n if (disposed) return;\n setState('connecting');\n ws = new WebSocket(url, protocols);\n ws.onopen = () => { setState('connected'); delay = 1000; self.postMessage({ type: 'open' }); flush(); startHB(); };\n ws.onmessage = (e) => { let d; try { d = JSON.parse(e.data); } catch { d = e.data; } self.postMessage({ type: 'message', data: d }); };\n ws.onclose = (e) => { stopHB(); self.postMessage({ type: 'close', code: e.code, reason: e.reason }); if (!disposed && shouldReconnect && e.code !== 1000) reconnect(); else setState('closed'); };\n ws.onerror = () => { self.postMessage({ type: 'error', message: 'error' }); };\n }\n function send(d) { if (state === 'connected' && ws?.readyState === 1) ws.send(JSON.stringify(d)); else if (buffer.length < maxBuf) buffer.push(d); }\n function flush() { const p = buffer.splice(0); p.forEach(send); }\n function startHB() { stopHB(); heartbeatTimer = setInterval(() => { if (ws?.readyState === 1) ws.send('{\"type\":\"ping\"}'); }, hbInterval); }\n function stopHB() { if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; } }\n function reconnect() { setState('reconnecting'); const j = delay * 0.25 * (Math.random() * 2 - 1); reconnectTimer = setTimeout(() => { if (!disposed) connect(); }, Math.min(delay + j, maxDelay)); delay = Math.min(delay * 2, maxDelay); }\n self.onmessage = (e) => {\n const c = e.data;\n if (c.type === 'connect') { url = c.url; protocols = c.protocols || []; shouldReconnect = c.reconnect ?? true; maxDelay = c.reconnectMaxDelay || 30000; hbInterval = c.heartbeatInterval || 30000; maxBuf = c.bufferSize || 100; connect(); }\n if (c.type === 'send') send(c.data);\n if (c.type === 'disconnect') { disposed = true; stopHB(); if (reconnectTimer) clearTimeout(reconnectTimer); if (ws) { ws.onclose = null; if (ws.readyState < 2) ws.close(1000); ws = null; } buffer = []; setState('closed'); }\n };\n `;\n const blob = new Blob([code], { type: 'application/javascript' });\n return new URL(URL.createObjectURL(blob));\n }\n\n [Symbol.dispose](): void {\n this.disconnect();\n this.onMessageFns.clear();\n this.onStateChangeFns.clear();\n }\n}\n","import './utils/disposable';\nimport type { EventHandler, Unsubscribe } from './types';\n\nexport class SubscriptionManager implements Disposable {\n private handlers = new Map<string, Set<EventHandler>>();\n private lastMessages = new Map<string, unknown>();\n\n on(event: string, handler: EventHandler): Unsubscribe {\n let set = this.handlers.get(event);\n if (!set) {\n set = new Set();\n this.handlers.set(event, set);\n }\n set.add(handler);\n return () => set!.delete(handler);\n }\n\n once(event: string, handler: EventHandler): Unsubscribe {\n const wrapper: EventHandler = (data) => {\n unsub();\n handler(data);\n };\n const unsub = this.on(event, wrapper);\n return unsub;\n }\n\n off(event: string, handler?: EventHandler): void {\n if (handler) {\n this.handlers.get(event)?.delete(handler);\n } else {\n this.handlers.delete(event);\n }\n }\n\n emit(event: string, data: unknown): void {\n this.lastMessages.set(event, data);\n const set = this.handlers.get(event);\n if (set) {\n for (const fn of set) fn(data);\n }\n }\n\n getLastMessage(event: string): unknown | undefined {\n return this.lastMessages.get(event);\n }\n\n async *stream(event: string, signal?: AbortSignal): AsyncGenerator<unknown> {\n const queue: unknown[] = [];\n let resolve: (() => void) | null = null;\n let done = false;\n\n const unsub = this.on(event, (data) => {\n queue.push(data);\n resolve?.();\n });\n\n const onAbort = () => {\n done = true;\n resolve?.();\n };\n signal?.addEventListener('abort', onAbort);\n\n try {\n while (!done) {\n if (queue.length > 0) {\n yield queue.shift()!;\n } else {\n await new Promise<void>((r) => { resolve = r; });\n resolve = null;\n }\n }\n } finally {\n unsub();\n signal?.removeEventListener('abort', onAbort);\n }\n }\n\n offAll(): void {\n this.handlers.clear();\n this.lastMessages.clear();\n }\n\n [Symbol.dispose](): void {\n this.offAll();\n }\n}\n","import './utils/disposable';\nimport { generateId } from './utils/id';\nimport { MessageBus } from './MessageBus';\nimport { TabCoordinator } from './TabCoordinator';\nimport { SharedSocket } from './SharedSocket';\nimport { WorkerSocket } from './WorkerSocket';\nimport { SubscriptionManager } from './SubscriptionManager';\nimport type { SharedWebSocketOptions, TabRole, Unsubscribe, EventHandler, Channel, EventProtocol, EventMap, Logger, Middleware } from './types';\n\nconst DEFAULT_PROTOCOL: EventProtocol = {\n eventField: 'event',\n dataField: 'data',\n channelJoin: '$channel:join',\n channelLeave: '$channel:leave',\n ping: { type: 'ping' },\n defaultEvent: 'message',\n topicSubscribe: '$topic:subscribe',\n topicUnsubscribe: '$topic:unsubscribe',\n authLogin: '$auth:login',\n authLogout: '$auth:logout',\n authRevoked: '$auth:revoked',\n};\n\nconst NOOP_LOGGER: Logger = {\n debug() {},\n info() {},\n warn() {},\n error() {},\n};\n\n/** Common interface for both SharedSocket and WorkerSocket. */\ninterface SocketAdapter {\n readonly state: string;\n connect(): void;\n send(data: unknown): void;\n disconnect(): void;\n onMessage(fn: EventHandler): Unsubscribe;\n onStateChange(fn: (state: string) => void): Unsubscribe;\n [Symbol.dispose](): void;\n}\n\n/**\n * SharedWebSocket — shares ONE WebSocket connection across browser tabs.\n *\n * @typeParam TEvents - Event map for type-safe subscriptions.\n *\n * @example\n * // Typed events\n * type Events = {\n * 'chat.message': { text: string; userId: string };\n * 'order.created': { id: string; total: number };\n * };\n * const ws = new SharedWebSocket<Events>(url);\n * ws.on('chat.message', (msg) => msg.text); // ← msg: { text, userId }\n */\nexport class SharedWebSocket<TEvents extends EventMap = EventMap> implements Disposable {\n private bus: MessageBus;\n private coordinator: TabCoordinator;\n private socket: SocketAdapter | null = null;\n private subs = new SubscriptionManager();\n private syncStore = new Map<string, unknown>();\n private tabId: string;\n private cleanups: Unsubscribe[] = [];\n private disposed = false;\n private readonly proto: EventProtocol;\n private readonly log: Logger;\n private outgoingMiddleware: Middleware[] = [];\n private incomingMiddleware: Middleware[] = [];\n private serializers = new Map<string, (data: unknown) => unknown>();\n private deserializers = new Map<string, (data: unknown) => unknown>();\n private _isAuthenticated = false;\n private authChannels = new Map<string, Channel>();\n private authTopics = new Set<string>();\n\n constructor(\n private readonly url: string,\n private readonly options: SharedWebSocketOptions<TEvents> = {} as SharedWebSocketOptions<TEvents>,\n ) {\n this.proto = { ...DEFAULT_PROTOCOL, ...options.events };\n this.log = options.debug ? (options.logger ?? console) : NOOP_LOGGER;\n this.tabId = generateId();\n this.log.debug('[SharedWS] init', { tabId: this.tabId, url });\n this.bus = new MessageBus('shared-ws', this.tabId);\n this.coordinator = new TabCoordinator(this.bus, this.tabId, {\n electionTimeout: options.electionTimeout,\n heartbeatInterval: options.leaderHeartbeat,\n leaderTimeout: options.leaderTimeout,\n });\n\n // When ANY tab receives a WS message via bus → emit to local subscribers\n this.cleanups.push(\n this.bus.subscribe<{ event: string; data: unknown }>('ws:message', (msg) => {\n this.subs.emit(msg.event, msg.data);\n }),\n );\n\n // Leader listens for send requests from followers\n this.cleanups.push(\n this.bus.subscribe<{ event: string; data: unknown }>('ws:send', (msg) => {\n if (this.coordinator.isLeader && this.socket) {\n this.socket.send({ [this.proto.eventField]: msg.event, [this.proto.dataField]: msg.data });\n }\n }),\n );\n\n // Sync across tabs\n this.cleanups.push(\n this.bus.subscribe<{ key: string; value: unknown }>('ws:sync', (msg) => {\n this.syncStore.set(msg.key, msg.value);\n this.subs.emit(`sync:${msg.key}`, msg.value);\n }),\n );\n\n // Leader lifecycle\n this.coordinator.onBecomeLeader(() => {\n this.handleBecomeLeader();\n this.bus.broadcast('ws:lifecycle', { type: 'leader', isLeader: true });\n });\n this.coordinator.onLoseLeadership(() => {\n this.handleLoseLeadership();\n this.bus.broadcast('ws:lifecycle', { type: 'leader', isLeader: false });\n });\n\n // Lifecycle events from bus (all tabs receive)\n this.cleanups.push(\n this.bus.subscribe<{ type: string; isLeader?: boolean; error?: unknown; authenticated?: boolean }>('ws:lifecycle', (msg) => {\n switch (msg.type) {\n case 'connect':\n this.subs.emit('$lifecycle:connect', undefined);\n break;\n case 'disconnect':\n this.subs.emit('$lifecycle:disconnect', undefined);\n break;\n case 'reconnecting':\n this.subs.emit('$lifecycle:reconnecting', undefined);\n break;\n case 'leader':\n this.subs.emit('$lifecycle:leader', msg.isLeader);\n break;\n case 'error':\n this.subs.emit('$lifecycle:error', msg.error);\n break;\n case 'auth': {\n this._isAuthenticated = !!msg.authenticated;\n if (!msg.authenticated) {\n this.authChannels.clear();\n this.authTopics.clear();\n }\n this.subs.emit('$lifecycle:auth', msg.authenticated);\n break;\n }\n }\n }),\n );\n\n // Track tab visibility\n if (typeof document !== 'undefined') {\n const onVisibilityChange = () => {\n const active = !document.hidden;\n this.subs.emit('$lifecycle:active', active);\n this.log.debug('[SharedWS]', active ? '👁 tab active' : '👁 tab hidden');\n };\n document.addEventListener('visibilitychange', onVisibilityChange);\n this.cleanups.push(() => document.removeEventListener('visibilitychange', onVisibilityChange));\n }\n\n // Handle server-initiated auth revocation\n this.cleanups.push(\n this.subs.on(this.proto.authRevoked, () => {\n if (this.coordinator.isLeader) {\n for (const [, ch] of this.authChannels) ch.leave();\n for (const topic of this.authTopics) this.unsubscribe(topic);\n }\n this.authChannels.clear();\n this.authTopics.clear();\n this._isAuthenticated = false;\n this.syncStore.delete('$auth:token');\n this.subs.emit('$lifecycle:auth', false);\n this.log.warn('[SharedWS] auth revoked by server');\n }),\n );\n\n // Cleanup on tab close\n if (typeof window !== 'undefined') {\n const onBeforeUnload = () => this[Symbol.dispose]();\n window.addEventListener('beforeunload', onBeforeUnload);\n this.cleanups.push(() => window.removeEventListener('beforeunload', onBeforeUnload));\n }\n }\n\n get connected(): boolean {\n return this.socket?.state === 'connected' || !this.coordinator.isLeader;\n }\n\n get tabRole(): TabRole {\n return this.coordinator.isLeader ? 'leader' : 'follower';\n }\n\n /** Whether the user is authenticated via runtime auth. */\n get isAuthenticated(): boolean {\n return this._isAuthenticated;\n }\n\n /** Whether this tab is currently visible/focused. */\n get isActive(): boolean {\n return typeof document !== 'undefined' ? !document.hidden : true;\n }\n\n /** Start leader election and connect. */\n async connect(): Promise<void> {\n await this.coordinator.elect();\n }\n\n // ─── Lifecycle Hooks ─────────────────────────────────\n\n /** Called when WebSocket connection opens (broadcast to all tabs). */\n onConnect(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:connect', fn);\n }\n\n /** Called when WebSocket connection closes (broadcast to all tabs). */\n onDisconnect(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:disconnect', fn);\n }\n\n /** Called when WebSocket starts reconnecting (broadcast to all tabs). */\n onReconnecting(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:reconnecting', fn);\n }\n\n /** Called when this tab becomes leader or loses leadership. */\n onLeaderChange(fn: (isLeader: boolean) => void): Unsubscribe {\n return this.subs.on('$lifecycle:leader', fn as EventHandler);\n }\n\n /** Called on WebSocket or network error (broadcast to all tabs). */\n onError(fn: (error: unknown) => void): Unsubscribe {\n return this.subs.on('$lifecycle:error', fn as EventHandler);\n }\n\n /** Called when this tab becomes visible/focused. */\n onActive(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:active', ((isActive: unknown) => {\n if (isActive === true) fn();\n }) as EventHandler);\n }\n\n /** Called when this tab goes to background/hidden. */\n onInactive(fn: () => void): Unsubscribe {\n return this.subs.on('$lifecycle:active', ((isActive: unknown) => {\n if (isActive === false) fn();\n }) as EventHandler);\n }\n\n /** Called on any visibility change. */\n onVisibilityChange(fn: (isActive: boolean) => void): Unsubscribe {\n return this.subs.on('$lifecycle:active', fn as EventHandler);\n }\n\n // ─── Authentication ──────────────────────────────────\n\n /**\n * Authenticate on an existing connection. Sends auth event to server,\n * syncs auth state across all tabs. Use for login after guest connection.\n *\n * @example\n * const token = await loginApi(email, password);\n * ws.authenticate(token);\n *\n * @example\n * // React — via useSocketAuth hook\n * const { authenticate } = useSocketAuth();\n * authenticate(token);\n */\n authenticate(token: string): void {\n this._isAuthenticated = true;\n this.syncStore.set('$auth:token', token);\n this.bus.broadcast('ws:sync', { key: '$auth:token', value: token });\n this.send(this.proto.authLogin, { token });\n this.bus.broadcast('ws:lifecycle', { type: 'auth', authenticated: true });\n this.log.info('[SharedWS] authenticated');\n }\n\n /**\n * Deauthenticate — notifies server, auto-leaves all auth-required channels\n * and topics, syncs state across tabs. Connection stays open for public events.\n *\n * @example\n * ws.deauthenticate(); // connection stays open, auth subscriptions cleaned up\n */\n deauthenticate(): void {\n // Leave auth channels and unsubscribe auth topics\n for (const [, ch] of this.authChannels) ch.leave();\n this.authChannels.clear();\n for (const topic of this.authTopics) this.unsubscribe(topic);\n this.authTopics.clear();\n\n this._isAuthenticated = false;\n this.send(this.proto.authLogout, {});\n this.syncStore.delete('$auth:token');\n this.bus.broadcast('ws:sync', { key: '$auth:token', value: undefined });\n this.bus.broadcast('ws:lifecycle', { type: 'auth', authenticated: false });\n this.log.info('[SharedWS] deauthenticated');\n }\n\n /**\n * Called when auth state changes (authenticate, deauthenticate, or server revocation).\n *\n * @example\n * ws.onAuthChange((authenticated) => {\n * if (!authenticated) router.push('/login');\n * });\n */\n onAuthChange(fn: (authenticated: boolean) => void): Unsubscribe {\n return this.subs.on('$lifecycle:auth', fn as EventHandler);\n }\n\n // ─── Middleware ───────────────────────────────────────\n\n /**\n * Add middleware to transform messages before send or after receive.\n * Return null from middleware to drop the message.\n *\n * @example\n * // Add timestamp to every outgoing message\n * ws.use('outgoing', (msg) => ({ ...msg, timestamp: Date.now() }));\n *\n * @example\n * // Decrypt incoming messages\n * ws.use('incoming', (msg) => ({ ...msg, data: decrypt(msg.data) }));\n *\n * @example\n * // Drop messages from blocked users\n * ws.use('incoming', (msg) => blockedUsers.has(msg.userId) ? null : msg);\n */\n use(direction: 'outgoing' | 'incoming', fn: Middleware): this {\n if (direction === 'outgoing') {\n this.outgoingMiddleware.push(fn);\n } else {\n this.incomingMiddleware.push(fn);\n }\n return this;\n }\n\n // ─── Per-Event Serialization ─────────────────────────\n\n /**\n * Register a custom serializer for a specific event.\n * The data is transformed before outgoing middleware and global serialize.\n *\n * @example\n * // Binary for file uploads, JSON for everything else\n * ws.serializer('file.upload', (data) => new Blob([data as ArrayBuffer]));\n *\n * @example\n * // Protobuf for specific event\n * ws.serializer('trading.order', (data) => OrderProto.encode(data).finish());\n */\n serializer(event: string, fn: (data: unknown) => unknown): this {\n this.serializers.set(event, fn);\n return this;\n }\n\n /**\n * Register a custom deserializer for a specific event.\n * The data is transformed after global deserialize and before incoming middleware.\n *\n * @example\n * ws.deserializer('file.download', (data) => new Uint8Array(data as ArrayBuffer));\n *\n * @example\n * // Protobuf for specific event\n * ws.deserializer('trading.tick', (data) => TickProto.decode(data as Uint8Array));\n */\n deserializer(event: string, fn: (data: unknown) => unknown): this {\n this.deserializers.set(event, fn);\n return this;\n }\n\n // ─── Event Subscription ──────────────────────────────\n\n /** Subscribe to server events (works in ALL tabs). Type-safe with EventMap. */\n on<K extends string & keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): Unsubscribe;\n on(event: string, handler: EventHandler<unknown>): Unsubscribe;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n on(event: string, handler: (data: any) => void): Unsubscribe {\n return this.subs.on(event, handler);\n }\n\n once<K extends string & keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): Unsubscribe;\n once(event: string, handler: EventHandler<unknown>): Unsubscribe;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n once(event: string, handler: (data: any) => void): Unsubscribe {\n return this.subs.once(event, handler);\n }\n\n off(event: string, handler?: EventHandler): void {\n this.subs.off(event, handler);\n }\n\n /** Async generator for consuming events. Type-safe with EventMap. */\n stream<K extends string & keyof TEvents>(event: K, signal?: AbortSignal): AsyncGenerator<TEvents[K]>;\n stream(event: string, signal?: AbortSignal): AsyncGenerator<unknown>;\n stream(event: string, signal?: AbortSignal): AsyncGenerator<unknown> {\n return this.subs.stream(event, signal);\n }\n\n /** Send message to server (auto-routed through leader). Type-safe with EventMap. */\n send<K extends string & keyof TEvents>(event: K, data: TEvents[K]): void;\n send(event: string, data: unknown): void;\n send(event: string, data: unknown): void {\n // Per-event serializer transforms data before building payload\n const eventSerializer = this.serializers.get(event);\n const serializedData = eventSerializer ? eventSerializer(data) : data;\n\n let payload: unknown = { [this.proto.eventField]: event, [this.proto.dataField]: serializedData };\n\n for (const mw of this.outgoingMiddleware) {\n payload = mw(payload);\n if (payload === null) {\n this.log.debug('[SharedWS] ✗ outgoing dropped by middleware', event);\n return;\n }\n }\n\n this.log.debug('[SharedWS] → send', event, data);\n\n if (this.coordinator.isLeader && this.socket) {\n this.socket.send(payload);\n } else {\n this.bus.publish('ws:send', { event, data });\n }\n }\n\n /** Request/response through server via leader. */\n async request<T>(event: string, data: unknown, timeout = 5000): Promise<T> {\n return this.bus.request('ws:request', { event, data }, timeout);\n }\n\n /** Sync state across tabs (no server roundtrip). */\n sync<T>(key: string, value: T): void {\n this.syncStore.set(key, value);\n this.bus.broadcast('ws:sync', { key, value });\n }\n\n getSync<T>(key: string): T | undefined {\n return this.syncStore.get(key) as T | undefined;\n }\n\n onSync<T>(key: string, fn: (value: T) => void): Unsubscribe {\n return this.subs.on(`sync:${key}`, fn as EventHandler);\n }\n\n /**\n * Subscribe to a private/scoped channel. Returns a channel handle with\n * scoped on/send/stream methods. Sends join on subscribe, leave on unsubscribe.\n *\n * @example\n * const chat = ws.channel('chat:room_123');\n * chat.on('message', (msg) => render(msg));\n * chat.send('message', { text: 'Hello' });\n * chat.leave(); // sends leave + unsubscribes\n *\n * @example\n * // Private notifications for tenant\n * const notifications = ws.channel(`tenant:${tenantId}:notifications`);\n * notifications.on('alert', (alert) => showToast(alert));\n */\n channel(name: string, options?: { auth?: boolean }): Channel {\n // Notify server about channel subscription\n this.send(this.proto.channelJoin, { channel: name });\n\n const self = this;\n const unsubs: Unsubscribe[] = [];\n const isAuth = options?.auth ?? false;\n\n const ch: Channel = {\n name,\n on(event: string, handler: EventHandler): Unsubscribe {\n const unsub = self.subs.on(`${name}:${event}`, handler);\n unsubs.push(unsub);\n return unsub;\n },\n once(event: string, handler: EventHandler): Unsubscribe {\n const unsub = self.subs.once(`${name}:${event}`, handler);\n unsubs.push(unsub);\n return unsub;\n },\n send(event: string, data: unknown): void {\n self.send(`${name}:${event}`, data);\n },\n stream(event: string, signal?: AbortSignal): AsyncGenerator<unknown> {\n return self.subs.stream(`${name}:${event}`, signal);\n },\n leave(): void {\n self.send(self.proto.channelLeave, { channel: name });\n for (const unsub of unsubs) unsub();\n unsubs.length = 0;\n if (isAuth) self.authChannels.delete(name);\n },\n };\n\n if (isAuth) {\n this.authChannels.set(name, ch);\n }\n\n return ch;\n }\n\n // ─── Topics ──────────────────────────────────────────\n\n /**\n * Subscribe to a server-side topic. Server will start sending events for this topic.\n * Sends topicSubscribe event (default: \"$topic:subscribe\").\n *\n * @example\n * ws.subscribe('notifications:orders');\n * ws.subscribe('notifications:payments');\n * ws.subscribe(`user:${userId}:mentions`);\n */\n subscribe(topic: string, options?: { auth?: boolean }): void {\n this.send(this.proto.topicSubscribe, { topic });\n if (options?.auth) {\n this.authTopics.add(topic);\n }\n this.log.debug('[SharedWS] subscribe topic', topic);\n }\n\n /**\n * Unsubscribe from a server-side topic.\n * Sends topicUnsubscribe event (default: \"$topic:unsubscribe\").\n */\n unsubscribe(topic: string): void {\n this.send(this.proto.topicUnsubscribe, { topic });\n this.authTopics.delete(topic);\n this.log.debug('[SharedWS] unsubscribe topic', topic);\n }\n\n // ─── Push Notifications ─────────────────────────────\n\n /**\n * Subscribe to an event and show notifications.\n *\n * **target** controls which tab(s) display the notification:\n * - `'active'` — only the currently visible tab (default for render)\n * - `'leader'` — only the leader tab (default for browser Notification)\n * - `'all'` — every tab (for critical alerts)\n *\n * @example\n * // Custom render — sonner toast on active tab only\n * ws.push('notification', {\n * render: (n) => toast(n.title),\n * target: 'active', // default for render\n * });\n *\n * @example\n * // Critical alert — show in ALL tabs\n * ws.push('payment.failed', {\n * render: (n) => toast.error('Payment failed!'),\n * target: 'all',\n * });\n *\n * @example\n * // Browser Notification — only from leader\n * ws.push('order.created', {\n * title: (order) => `New Order #${order.id}`,\n * target: 'leader', // default for browser Notification\n * });\n *\n * @example\n * // Both render + native with different targets\n * ws.push('order.created', {\n * render: (order) => toast(`Order #${order.id}`), // active tab\n * title: (order) => `New Order #${order.id}`, // leader → native\n * });\n */\n push<T = unknown>(\n event: string,\n config: {\n /** Custom render function — you decide how to display. */\n render?: (data: T) => void;\n /** Title for browser Notification API. */\n title?: string | ((data: T) => string);\n /** Body for browser Notification API. */\n body?: string | ((data: T) => string);\n /** Icon URL for browser Notification. */\n icon?: string;\n /** Tag for browser Notification deduplication. */\n tag?: string | ((data: T) => string);\n /**\n * Which tab(s) show the notification:\n * - `'active'` — only the visible/focused tab (default for render)\n * - `'leader'` — only the leader tab (default for browser Notification)\n * - `'all'` — every tab (critical alerts)\n */\n target?: 'active' | 'leader' | 'all';\n /** Called when browser Notification is clicked. */\n onClick?: (data: T) => void;\n },\n ): Unsubscribe {\n const useNativeNotification = !!config.title;\n\n // Default target: 'active' for render, 'leader' for native\n const renderTarget = config.target ?? 'active';\n const nativeTarget = config.target ?? 'leader';\n\n if (useNativeNotification && typeof Notification !== 'undefined' && Notification.permission === 'default') {\n Notification.requestPermission();\n }\n\n return this.on(event, ((data: unknown) => {\n const typed = data as T;\n const isVisible = typeof document !== 'undefined' && !document.hidden;\n const isLeader = this.tabRole === 'leader';\n\n // Custom render\n if (config.render) {\n const shouldRender =\n renderTarget === 'all' ||\n (renderTarget === 'active' && isVisible) ||\n (renderTarget === 'leader' && isLeader);\n\n if (shouldRender) {\n config.render(typed);\n this.log.debug('[SharedWS] 🔔 render', event, `(target: ${renderTarget})`);\n }\n }\n\n // Browser Notification API\n if (useNativeNotification && typeof Notification !== 'undefined' && Notification.permission === 'granted') {\n const shouldNotify =\n nativeTarget === 'all' ||\n (nativeTarget === 'leader' && isLeader) ||\n (nativeTarget === 'active' && isVisible);\n\n // Native notifications make sense when tab is hidden\n if (shouldNotify && !isVisible) {\n const title = typeof config.title === 'function' ? config.title(typed) : config.title!;\n const body = typeof config.body === 'function' ? config.body(typed) : config.body;\n const tag = typeof config.tag === 'function' ? config.tag(typed) : config.tag;\n\n const notif = new Notification(title, { body, icon: config.icon, tag });\n\n if (config.onClick) {\n const handler = config.onClick;\n notif.onclick = () => {\n handler(typed);\n window.focus();\n };\n }\n\n this.log.debug('[SharedWS] 🔔 native', title, `(target: ${nativeTarget})`);\n }\n }\n }) as EventHandler);\n }\n\n disconnect(): void {\n this[Symbol.dispose]();\n }\n\n private createSocket(): SocketAdapter {\n const socketOptions = {\n protocols: this.options.protocols,\n reconnect: this.options.reconnect,\n reconnectMaxDelay: this.options.reconnectMaxDelay,\n heartbeatInterval: this.options.heartbeatInterval,\n sendBuffer: this.options.sendBuffer,\n pingPayload: this.proto.ping,\n };\n\n if (this.options.useWorker) {\n // WebSocket runs in a Web Worker — main thread stays free\n return new WorkerSocket(this.url, {\n ...socketOptions,\n workerUrl: this.options.workerUrl,\n });\n }\n\n // WebSocket runs in main thread (default)\n return new SharedSocket(this.url, {\n ...socketOptions,\n auth: this.options.auth,\n authToken: this.options.authToken,\n authParam: this.options.authParam,\n serialize: this.options.serialize,\n deserialize: this.options.deserialize,\n });\n }\n\n private handleBecomeLeader(): void {\n this.log.info('[SharedWS] 👑 became leader');\n this.socket = this.createSocket();\n\n this.socket.onMessage((raw: unknown) => {\n let data: unknown = raw;\n for (const mw of this.incomingMiddleware) {\n data = mw(data);\n if (data === null) {\n this.log.debug('[SharedWS] ✗ incoming dropped by middleware');\n return;\n }\n }\n\n const msg = data as Record<string, unknown> | null | undefined;\n const event = (msg?.[this.proto.eventField] as string) ?? this.proto.defaultEvent;\n let payload = msg?.[this.proto.dataField] ?? data;\n\n // Per-event deserializer transforms data after global deserialize\n const eventDeserializer = this.deserializers.get(event);\n if (eventDeserializer) {\n payload = eventDeserializer(payload);\n }\n\n this.log.debug('[SharedWS] ← recv', event, payload);\n this.bus.broadcast('ws:message', { event, data: payload });\n });\n\n this.socket.onStateChange((state: string) => {\n this.log.info('[SharedWS]', state === 'connected' ? '✓ connected' : state === 'reconnecting' ? '🔄 reconnecting' : `state: ${state}`);\n switch (state) {\n case 'connected':\n this.bus.broadcast('ws:lifecycle', { type: 'connect' });\n this.reAuthenticateOnReconnect();\n break;\n case 'closed':\n this.bus.broadcast('ws:lifecycle', { type: 'disconnect' });\n break;\n case 'reconnecting':\n this.bus.broadcast('ws:lifecycle', { type: 'reconnecting' });\n break;\n }\n });\n\n this.cleanups.push(\n this.bus.respond<{ event: string; data: unknown }, unknown>('ws:request', async (req) => {\n return new Promise((resolve) => {\n const unsub = this.socket!.onMessage((response: unknown) => {\n const res = response as Record<string, unknown> | undefined;\n if (res?.[this.proto.eventField] === req.event || res?.requestId) {\n unsub();\n resolve(res?.[this.proto.dataField] ?? response);\n }\n });\n this.socket!.send({ event: req.event, data: req.data });\n });\n }),\n );\n\n this.socket.connect();\n }\n\n private reAuthenticateOnReconnect(): void {\n if (!this._isAuthenticated || !this.socket) return;\n\n const token = this.syncStore.get('$auth:token') as string | undefined;\n if (token) {\n this.socket.send({\n [this.proto.eventField]: this.proto.authLogin,\n [this.proto.dataField]: { token },\n });\n this.log.debug('[SharedWS] re-authenticated after reconnect');\n }\n\n // Re-join auth channels\n for (const name of this.authChannels.keys()) {\n this.socket.send({\n [this.proto.eventField]: this.proto.channelJoin,\n [this.proto.dataField]: { channel: name },\n });\n }\n\n // Re-subscribe auth topics\n for (const topic of this.authTopics) {\n this.socket.send({\n [this.proto.eventField]: this.proto.topicSubscribe,\n [this.proto.dataField]: { topic },\n });\n }\n }\n\n private handleLoseLeadership(): void {\n if (this.socket) {\n this.socket[Symbol.dispose]();\n this.socket = null;\n }\n }\n\n [Symbol.dispose](): void {\n if (this.disposed) return;\n this.disposed = true;\n\n this.coordinator[Symbol.dispose]();\n\n if (this.socket) {\n this.socket[Symbol.dispose]();\n this.socket = null;\n }\n\n for (const unsub of this.cleanups) unsub();\n this.cleanups = [];\n this.subs[Symbol.dispose]();\n this.bus[Symbol.dispose]();\n this.syncStore.clear();\n this.authChannels.clear();\n this.authTopics.clear();\n }\n}\n"]}