react-websocket 3.6.0 → 4.18.0
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 +34 -5
- package/dist/ag-ui.cjs +421 -0
- package/dist/ag-ui.cjs.map +1 -0
- package/dist/ag-ui.d.cts +493 -0
- package/dist/ag-ui.d.ts +171 -0
- package/dist/ag-ui.js +418 -0
- package/dist/ag-ui.js.map +1 -0
- package/dist/core.cjs +727 -162
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +501 -62
- package/dist/core.d.ts +501 -62
- package/dist/core.js +723 -163
- package/dist/core.js.map +1 -1
- package/dist/devtools.cjs +186 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.cts +32 -0
- package/dist/devtools.d.ts +32 -0
- package/dist/devtools.js +185 -0
- package/dist/devtools.js.map +1 -0
- package/dist/index.cjs +214 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +705 -56
- package/dist/index.d.ts +421 -30
- package/dist/index.js +211 -21
- package/dist/index.js.map +1 -1
- package/dist/outbox.cjs +222 -0
- package/dist/outbox.cjs.map +1 -0
- package/dist/outbox.d.cts +127 -0
- package/dist/outbox.d.ts +127 -0
- package/dist/outbox.js +219 -0
- package/dist/outbox.js.map +1 -0
- package/dist/replay.cjs +324 -0
- package/dist/replay.cjs.map +1 -0
- package/dist/replay.d.cts +124 -0
- package/dist/replay.d.ts +124 -0
- package/dist/replay.js +322 -0
- package/dist/replay.js.map +1 -0
- package/dist/tabs.cjs +336 -0
- package/dist/tabs.cjs.map +1 -0
- package/dist/tabs.d.cts +81 -0
- package/dist/tabs.d.ts +81 -0
- package/dist/tabs.js +335 -0
- package/dist/tabs.js.map +1 -0
- package/dist/timeline.d.cts +1 -213
- package/dist/types-BEecFqIB.d.cts +268 -0
- package/dist/webtransport.cjs +243 -0
- package/dist/webtransport.cjs.map +1 -0
- package/dist/webtransport.d.cts +67 -0
- package/dist/webtransport.d.ts +67 -0
- package/dist/webtransport.js +242 -0
- package/dist/webtransport.js.map +1 -0
- package/package.json +139 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs.cjs","names":[],"sources":["../src/tabs.ts"],"sourcesContent":["// An entry with no imports: it only implements the `WebSocket` interface.\n\n/**\n * Recognize topic subscriptions, so tabs sharing a socket don't unsubscribe each other. With\n * `json`, only messages that parse are passed to these, and one that throws counts as\n * `undefined`, so heartbeat pings and other plain strings go through.\n */\nexport interface TabsTopics {\n /** The topic an outgoing message subscribes to, or `undefined`. */\n subscribe: (message: any) => string | null | undefined;\n /** The topic an outgoing message unsubscribes from, or `undefined`. */\n unsubscribe: (message: any) => string | null | undefined;\n /**\n * The topic of an incoming message, to deliver it only to the tabs that hold that topic.\n * Without it, every tab gets every message.\n */\n topic?: (message: any) => string | null | undefined;\n}\n\n/** Options for {@link createTabsWebSocket}. */\nexport interface TabsWebSocketOptions {\n /** The `WebSocket` the leading tab opens. Defaults to the global one. */\n WebSocket?: typeof WebSocket;\n /** Messages are JSON: the `topics` functions get them parsed. */\n json?: boolean;\n /** Share topic subscriptions between tabs. See {@link TabsTopics}. */\n topics?: TabsTopics;\n /**\n * How long the leading tab keeps the shared socket open after the last tab using it lets go,\n * in ms, so a reload doesn't close and reopen it.\n * @default 1000\n */\n linger?: number;\n}\n\n/** The class from {@link createTabsWebSocket}: a `WebSocket`, plus a way to stop sharing. */\nexport interface TabsWebSocket {\n new (url: string | URL, protocols?: string | string[]): WebSocket;\n readonly prototype: WebSocket;\n readonly CONNECTING: 0;\n readonly OPEN: 1;\n readonly CLOSING: 2;\n readonly CLOSED: 3;\n /**\n * Stop leading and listening in this tab: close the shared sockets it holds and let another\n * tab take over. For tests and hot module reloading; a closed tab does this by itself.\n */\n dispose(): void;\n}\n\nconst CONNECTING = 0;\nconst OPEN = 1;\nconst CLOSING = 2;\nconst CLOSED = 3;\n\n/** What tabs say to each other over the channel. */\ntype Signal =\n | { t: 'connect'; id: string }\n | { t: 'send'; id: string; data: unknown }\n | { t: 'disconnect'; id: string }\n | { t: 'open'; id: string; protocol: string }\n | { t: 'message'; data: unknown; to?: string[] }\n | { t: 'close'; code: number; reason: string; wasClean: boolean }\n | { t: 'leader' };\n\nfunction closeEvent(code: number, reason: string, wasClean: boolean): Event {\n const init = { code, reason, wasClean };\n // Node 22 has no CloseEvent.\n return typeof CloseEvent === 'function'\n ? new CloseEvent('close', init)\n : Object.assign(new Event('close'), init);\n}\n\nlet ids = 0;\n\n// What `read()` returns for a message that isn't JSON.\nconst UNREAD = Symbol();\n\n/**\n * Share one WebSocket between all the tabs of an app. Pass the returned class as the\n * `WebSocket` option: every tab keeps its own client, with its own reconnects, queue, topics and\n * hooks, but only one tab, the leader, opens a connection to the server. The others reach it\n * through a `BroadcastChannel`. Users with ten tabs open cost the server one connection, not ten.\n *\n * - The leader is elected with the Web Locks API. When its tab closes, another tab takes over,\n * and every tab's client reconnects through it.\n * - Each tab's sends go out on the shared socket. Every message from the server reaches every\n * tab, or, with `topics`, only the tabs that hold its topic.\n * - With `topics`, an unsubscribe goes to the server only when no other tab still holds the topic.\n *\n * Where `BroadcastChannel` or the Web Locks API is missing, each tab opens its own socket, as\n * without this.\n *\n * @example\n * ```ts\n * import { createTabsWebSocket } from 'react-websocket/tabs';\n *\n * export const live = createWebSocket(url, {\n * json: true,\n * WebSocket: createTabsWebSocket({\n * json: true,\n * topics: {\n * subscribe: message => (message.type === 'subscribe' ? message.topic : undefined),\n * unsubscribe: message => (message.type === 'unsubscribe' ? message.topic : undefined),\n * topic: message => message.topic\n * }\n * })\n * });\n * ```\n */\nexport function createTabsWebSocket(options: TabsWebSocketOptions = {}): TabsWebSocket {\n const linger = options.linger ?? 1000;\n // One per URL in this tab: it waits to lead, and leads while it holds the lock.\n const agents = new Map<string, { channel: BroadcastChannel; stop: () => void }>();\n\n const read = (data: unknown) => {\n if (!options.json || typeof data !== 'string') return data;\n try {\n return JSON.parse(data);\n } catch {\n return UNREAD;\n }\n };\n\n /** A topic `recognize` finds in `data`, if it parses and `recognize` doesn't throw. */\n const topicOf = (\n recognize: ((message: any) => string | null | undefined) | undefined,\n message: unknown\n ) => {\n if (!recognize || message === UNREAD) return undefined;\n try {\n return recognize(message) ?? undefined;\n } catch {\n return undefined;\n }\n };\n\n // The sockets this tab has on shared ones. A tab that goes away tells the leading tab,\n // which would otherwise keep their topics and the shared socket for them.\n const live = new Set<() => void>();\n let hiding = false;\n const onPageHide = () => {\n for (const leave of [...live]) leave();\n };\n\n function agent(key: string, url: string, protocols: string | string[] | undefined) {\n if (agents.has(key)) return;\n const channel = new BroadcastChannel(key);\n const post = (signal: Signal) => channel.postMessage(signal);\n let real: WebSocket | undefined;\n // The tabs' sockets on the shared one, and whether each has been told it's open.\n const clients = new Map<string, boolean>();\n // Who holds each topic, so an unsubscribe waits for the last one.\n const holders = new Map<string, Set<string>>();\n let lingerTimer: ReturnType<typeof setTimeout> | undefined;\n let release: (() => void) | undefined;\n\n function opened(id: string) {\n clients.set(id, true);\n post({ t: 'open', id, protocol: real!.protocol });\n }\n\n function connect() {\n if (real) return;\n const Impl = options.WebSocket ?? globalThis.WebSocket;\n let socket: WebSocket;\n try {\n socket = real = new Impl(url, protocols);\n } catch {\n clients.clear();\n return post({ t: 'close', code: 1006, reason: '', wasClean: false });\n }\n socket.binaryType = 'arraybuffer';\n socket.onopen = () => {\n for (const [id, told] of clients) if (!told) opened(id);\n };\n socket.onmessage = event => {\n const topic = topicOf(options.topics?.topic, read(event.data));\n if (topic === undefined) return post({ t: 'message', data: event.data });\n const to = [...(holders.get(topic) ?? [])];\n if (to.length) post({ t: 'message', data: event.data, to });\n };\n socket.onerror = () => {};\n socket.onclose = event => {\n if (real !== socket) return;\n real = undefined;\n clients.clear();\n holders.clear();\n post({ t: 'close', code: event.code, reason: event.reason, wasClean: event.wasClean });\n };\n }\n\n function send(id: string, data: unknown) {\n const { topics } = options;\n if (topics) {\n const message = read(data);\n const subscribed = topicOf(topics.subscribe, message);\n if (subscribed !== undefined) {\n if (!holders.has(subscribed)) holders.set(subscribed, new Set());\n holders.get(subscribed)!.add(id);\n }\n const unsubscribed = topicOf(topics.unsubscribe, message);\n if (unsubscribed !== undefined) {\n const set = holders.get(unsubscribed);\n set?.delete(id);\n if (set?.size) return; // another tab still holds it\n holders.delete(unsubscribed);\n }\n }\n if (real?.readyState === OPEN) real.send(data as Parameters<WebSocket['send']>[0]);\n }\n\n function handle(signal: Signal) {\n if (signal.t === 'connect') {\n clearTimeout(lingerTimer);\n clients.set(signal.id, false);\n connect();\n if (real?.readyState === OPEN) opened(signal.id);\n } else if (signal.t === 'send') {\n if (clients.get(signal.id)) send(signal.id, signal.data);\n } else if (signal.t === 'disconnect') {\n clients.delete(signal.id);\n for (const set of holders.values()) set.delete(signal.id);\n if (!clients.size) {\n clearTimeout(lingerTimer);\n lingerTimer = setTimeout(() => {\n if (clients.size || !real) return;\n // Forget it first: a tab connecting while it closes gets a new one.\n const socket = real;\n real = undefined;\n socket.close(1000);\n }, linger);\n }\n }\n }\n\n agents.set(key, {\n channel,\n stop() {\n clearTimeout(lingerTimer);\n channel.close();\n if (real) {\n real.onclose = null;\n real.close(1000);\n }\n release?.();\n }\n });\n void navigator.locks.request(key, () => {\n if (!agents.has(key)) return;\n channel.onmessage = event => handle(event.data as Signal);\n // Sockets that were using the tab that led before start over with this one.\n post({ t: 'leader' });\n // Lead until this tab goes away, or `dispose()`.\n return new Promise<void>(resolve => (release = resolve));\n });\n }\n\n const Fallback = options.WebSocket;\n\n class TabSocket extends EventTarget implements WebSocket {\n static readonly CONNECTING = CONNECTING;\n static readonly OPEN = OPEN;\n static readonly CLOSING = CLOSING;\n static readonly CLOSED = CLOSED;\n readonly CONNECTING = CONNECTING;\n readonly OPEN = OPEN;\n readonly CLOSING = CLOSING;\n readonly CLOSED = CLOSED;\n\n readonly url: string;\n readonly extensions = '';\n onopen: WebSocket['onopen'] = null;\n onmessage: WebSocket['onmessage'] = null;\n onerror: WebSocket['onerror'] = null;\n onclose: WebSocket['onclose'] = null;\n binaryType: BinaryType = 'blob';\n protocol = '';\n\n #id = `${Date.now().toString(36)}-${++ids}-${Math.random().toString(36).slice(2)}`;\n #state: WebSocket['readyState'] = CONNECTING;\n #channel: BroadcastChannel | undefined;\n // The page is going away, or into the back/forward cache: let go of the shared socket, and\n // close, so a page that comes back connects again.\n #leave = () => {\n if (this.#state >= CLOSING) return; // `close()` told the leader already\n this.#post({ t: 'disconnect', id: this.#id });\n this.#end(1001, '', false);\n };\n // Without `BroadcastChannel` or locks: a socket of this tab's own.\n #inner: WebSocket | undefined;\n\n static dispose() {\n for (const { stop } of agents.values()) stop();\n agents.clear();\n }\n\n constructor(url: string | URL, protocols?: string | string[]) {\n super();\n if (typeof window === 'undefined') {\n throw new Error('react-websocket/tabs: no WebSocket here (server render?)');\n }\n for (const type of ['open', 'message', 'error', 'close'] as const) {\n this.addEventListener(type, event =>\n (this[`on${type}`] as ((event: Event) => void) | null)?.call(this, event)\n );\n }\n this.url = String(url);\n if (typeof BroadcastChannel === 'undefined' || !globalThis.navigator?.locks) {\n const inner = (this.#inner = new (Fallback ?? globalThis.WebSocket)(url, protocols));\n // As from the leading tab: `#message` turns it into what `binaryType` asks for.\n inner.binaryType = 'arraybuffer';\n inner.onopen = () => this.#open(inner.protocol);\n inner.onmessage = event => this.#message(event.data);\n inner.onerror = () => this.dispatchEvent(new Event('error'));\n inner.onclose = event => this.#end(event.code, event.reason, event.wasClean);\n return;\n }\n const key = `react-websocket-tabs:${this.url}\\u0000${JSON.stringify(protocols ?? null)}`;\n agent(key, this.url, protocols);\n const channel = (this.#channel = new BroadcastChannel(key));\n channel.onmessage = event => this.#signal(event.data as Signal);\n this.#post({ t: 'connect', id: this.#id });\n live.add(this.#leave);\n if (!hiding) {\n hiding = true;\n addEventListener('pagehide', onPageHide);\n }\n }\n\n get readyState() {\n return this.#state;\n }\n\n get bufferedAmount() {\n return this.#inner?.bufferedAmount ?? 0;\n }\n\n send(data: Parameters<WebSocket['send']>[0]) {\n if (this.#state === CONNECTING) {\n throw new DOMException('WebSocket is still in CONNECTING state', 'InvalidStateError');\n }\n if (this.#state !== OPEN) return;\n if (this.#inner) this.#inner.send(data);\n else this.#post({ t: 'send', id: this.#id, data });\n }\n\n close(code = 1000, reason = '') {\n if (this.#state >= CLOSING) return;\n // First, so that a code the socket rejects throws before anything has changed.\n if (this.#inner) this.#inner.close(code, reason);\n this.#state = CLOSING;\n if (this.#inner) return;\n // Only this tab's socket closes; the shared one stays for the others.\n this.#post({ t: 'disconnect', id: this.#id });\n setTimeout(() => this.#end(code, reason, true), 0);\n }\n\n #post(signal: Signal) {\n this.#channel?.postMessage(signal);\n }\n\n #signal(signal: Signal) {\n if (signal.t === 'open') {\n if (signal.id === this.#id && this.#state === CONNECTING) this.#open(signal.protocol);\n } else if (signal.t === 'message') {\n if (!signal.to || signal.to.includes(this.#id)) this.#message(signal.data);\n } else if (signal.t === 'close') {\n if (this.#state !== CONNECTING) this.#end(signal.code, signal.reason, signal.wasClean);\n else {\n // The shared socket failed before this one opened.\n this.dispatchEvent(new Event('error'));\n this.#end(signal.code, signal.reason, false);\n }\n } else if (signal.t === 'leader') {\n // A new leading tab: start over with it, or ask it to connect us.\n if (this.#state === CONNECTING) this.#post({ t: 'connect', id: this.#id });\n else this.#end(1006, '', false);\n }\n }\n\n #open(protocol: string) {\n this.#state = OPEN;\n this.protocol = protocol;\n this.dispatchEvent(new Event('open'));\n }\n\n #message(data: unknown) {\n if (this.#state !== OPEN) return;\n if (data instanceof ArrayBuffer && this.binaryType === 'blob') data = new Blob([data]);\n this.dispatchEvent(new MessageEvent('message', { data }));\n }\n\n #end(code: number, reason: string, wasClean: boolean) {\n if (this.#state === CLOSED) return;\n this.#state = CLOSED;\n this.#channel?.close();\n live.delete(this.#leave);\n this.dispatchEvent(closeEvent(code, reason, wasClean));\n }\n }\n\n return TabSocket as unknown as TabsWebSocket;\n}\n"],"mappings":"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"}
|
package/dist/tabs.d.cts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
//#region src/tabs.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Recognize topic subscriptions, so tabs sharing a socket don't unsubscribe each other. With
|
|
4
|
+
* `json`, only messages that parse are passed to these, and one that throws counts as
|
|
5
|
+
* `undefined`, so heartbeat pings and other plain strings go through.
|
|
6
|
+
*/
|
|
7
|
+
export interface TabsTopics {
|
|
8
|
+
/** The topic an outgoing message subscribes to, or `undefined`. */
|
|
9
|
+
subscribe: (message: any) => string | null | undefined;
|
|
10
|
+
/** The topic an outgoing message unsubscribes from, or `undefined`. */
|
|
11
|
+
unsubscribe: (message: any) => string | null | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* The topic of an incoming message, to deliver it only to the tabs that hold that topic.
|
|
14
|
+
* Without it, every tab gets every message.
|
|
15
|
+
*/
|
|
16
|
+
topic?: (message: any) => string | null | undefined;
|
|
17
|
+
}
|
|
18
|
+
/** Options for {@link createTabsWebSocket}. */
|
|
19
|
+
export interface TabsWebSocketOptions {
|
|
20
|
+
/** The `WebSocket` the leading tab opens. Defaults to the global one. */
|
|
21
|
+
WebSocket?: typeof WebSocket;
|
|
22
|
+
/** Messages are JSON: the `topics` functions get them parsed. */
|
|
23
|
+
json?: boolean;
|
|
24
|
+
/** Share topic subscriptions between tabs. See {@link TabsTopics}. */
|
|
25
|
+
topics?: TabsTopics;
|
|
26
|
+
/**
|
|
27
|
+
* How long the leading tab keeps the shared socket open after the last tab using it lets go,
|
|
28
|
+
* in ms, so a reload doesn't close and reopen it.
|
|
29
|
+
* @default 1000
|
|
30
|
+
*/
|
|
31
|
+
linger?: number;
|
|
32
|
+
}
|
|
33
|
+
/** The class from {@link createTabsWebSocket}: a `WebSocket`, plus a way to stop sharing. */
|
|
34
|
+
export interface TabsWebSocket {
|
|
35
|
+
new (url: string | URL, protocols?: string | string[]): WebSocket;
|
|
36
|
+
readonly prototype: WebSocket;
|
|
37
|
+
readonly CONNECTING: 0;
|
|
38
|
+
readonly OPEN: 1;
|
|
39
|
+
readonly CLOSING: 2;
|
|
40
|
+
readonly CLOSED: 3;
|
|
41
|
+
/**
|
|
42
|
+
* Stop leading and listening in this tab: close the shared sockets it holds and let another
|
|
43
|
+
* tab take over. For tests and hot module reloading; a closed tab does this by itself.
|
|
44
|
+
*/
|
|
45
|
+
dispose(): void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Share one WebSocket between all the tabs of an app. Pass the returned class as the
|
|
49
|
+
* `WebSocket` option: every tab keeps its own client, with its own reconnects, queue, topics and
|
|
50
|
+
* hooks, but only one tab, the leader, opens a connection to the server. The others reach it
|
|
51
|
+
* through a `BroadcastChannel`. Users with ten tabs open cost the server one connection, not ten.
|
|
52
|
+
*
|
|
53
|
+
* - The leader is elected with the Web Locks API. When its tab closes, another tab takes over,
|
|
54
|
+
* and every tab's client reconnects through it.
|
|
55
|
+
* - Each tab's sends go out on the shared socket. Every message from the server reaches every
|
|
56
|
+
* tab, or, with `topics`, only the tabs that hold its topic.
|
|
57
|
+
* - With `topics`, an unsubscribe goes to the server only when no other tab still holds the topic.
|
|
58
|
+
*
|
|
59
|
+
* Where `BroadcastChannel` or the Web Locks API is missing, each tab opens its own socket, as
|
|
60
|
+
* without this.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { createTabsWebSocket } from 'react-websocket/tabs';
|
|
65
|
+
*
|
|
66
|
+
* export const live = createWebSocket(url, {
|
|
67
|
+
* json: true,
|
|
68
|
+
* WebSocket: createTabsWebSocket({
|
|
69
|
+
* json: true,
|
|
70
|
+
* topics: {
|
|
71
|
+
* subscribe: message => (message.type === 'subscribe' ? message.topic : undefined),
|
|
72
|
+
* unsubscribe: message => (message.type === 'unsubscribe' ? message.topic : undefined),
|
|
73
|
+
* topic: message => message.topic
|
|
74
|
+
* }
|
|
75
|
+
* })
|
|
76
|
+
* });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function createTabsWebSocket(options?: TabsWebSocketOptions): TabsWebSocket;
|
|
80
|
+
//#endregion
|
|
81
|
+
//# sourceMappingURL=tabs.d.cts.map
|
package/dist/tabs.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
//#region src/tabs.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Recognize topic subscriptions, so tabs sharing a socket don't unsubscribe each other. With
|
|
4
|
+
* `json`, only messages that parse are passed to these, and one that throws counts as
|
|
5
|
+
* `undefined`, so heartbeat pings and other plain strings go through.
|
|
6
|
+
*/
|
|
7
|
+
export interface TabsTopics {
|
|
8
|
+
/** The topic an outgoing message subscribes to, or `undefined`. */
|
|
9
|
+
subscribe: (message: any) => string | null | undefined;
|
|
10
|
+
/** The topic an outgoing message unsubscribes from, or `undefined`. */
|
|
11
|
+
unsubscribe: (message: any) => string | null | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* The topic of an incoming message, to deliver it only to the tabs that hold that topic.
|
|
14
|
+
* Without it, every tab gets every message.
|
|
15
|
+
*/
|
|
16
|
+
topic?: (message: any) => string | null | undefined;
|
|
17
|
+
}
|
|
18
|
+
/** Options for {@link createTabsWebSocket}. */
|
|
19
|
+
export interface TabsWebSocketOptions {
|
|
20
|
+
/** The `WebSocket` the leading tab opens. Defaults to the global one. */
|
|
21
|
+
WebSocket?: typeof WebSocket;
|
|
22
|
+
/** Messages are JSON: the `topics` functions get them parsed. */
|
|
23
|
+
json?: boolean;
|
|
24
|
+
/** Share topic subscriptions between tabs. See {@link TabsTopics}. */
|
|
25
|
+
topics?: TabsTopics;
|
|
26
|
+
/**
|
|
27
|
+
* How long the leading tab keeps the shared socket open after the last tab using it lets go,
|
|
28
|
+
* in ms, so a reload doesn't close and reopen it.
|
|
29
|
+
* @default 1000
|
|
30
|
+
*/
|
|
31
|
+
linger?: number;
|
|
32
|
+
}
|
|
33
|
+
/** The class from {@link createTabsWebSocket}: a `WebSocket`, plus a way to stop sharing. */
|
|
34
|
+
export interface TabsWebSocket {
|
|
35
|
+
new (url: string | URL, protocols?: string | string[]): WebSocket;
|
|
36
|
+
readonly prototype: WebSocket;
|
|
37
|
+
readonly CONNECTING: 0;
|
|
38
|
+
readonly OPEN: 1;
|
|
39
|
+
readonly CLOSING: 2;
|
|
40
|
+
readonly CLOSED: 3;
|
|
41
|
+
/**
|
|
42
|
+
* Stop leading and listening in this tab: close the shared sockets it holds and let another
|
|
43
|
+
* tab take over. For tests and hot module reloading; a closed tab does this by itself.
|
|
44
|
+
*/
|
|
45
|
+
dispose(): void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Share one WebSocket between all the tabs of an app. Pass the returned class as the
|
|
49
|
+
* `WebSocket` option: every tab keeps its own client, with its own reconnects, queue, topics and
|
|
50
|
+
* hooks, but only one tab, the leader, opens a connection to the server. The others reach it
|
|
51
|
+
* through a `BroadcastChannel`. Users with ten tabs open cost the server one connection, not ten.
|
|
52
|
+
*
|
|
53
|
+
* - The leader is elected with the Web Locks API. When its tab closes, another tab takes over,
|
|
54
|
+
* and every tab's client reconnects through it.
|
|
55
|
+
* - Each tab's sends go out on the shared socket. Every message from the server reaches every
|
|
56
|
+
* tab, or, with `topics`, only the tabs that hold its topic.
|
|
57
|
+
* - With `topics`, an unsubscribe goes to the server only when no other tab still holds the topic.
|
|
58
|
+
*
|
|
59
|
+
* Where `BroadcastChannel` or the Web Locks API is missing, each tab opens its own socket, as
|
|
60
|
+
* without this.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { createTabsWebSocket } from 'react-websocket/tabs';
|
|
65
|
+
*
|
|
66
|
+
* export const live = createWebSocket(url, {
|
|
67
|
+
* json: true,
|
|
68
|
+
* WebSocket: createTabsWebSocket({
|
|
69
|
+
* json: true,
|
|
70
|
+
* topics: {
|
|
71
|
+
* subscribe: message => (message.type === 'subscribe' ? message.topic : undefined),
|
|
72
|
+
* unsubscribe: message => (message.type === 'unsubscribe' ? message.topic : undefined),
|
|
73
|
+
* topic: message => message.topic
|
|
74
|
+
* }
|
|
75
|
+
* })
|
|
76
|
+
* });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function createTabsWebSocket(options?: TabsWebSocketOptions): TabsWebSocket;
|
|
80
|
+
//#endregion
|
|
81
|
+
//# sourceMappingURL=tabs.d.ts.map
|
package/dist/tabs.js
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
//#region src/tabs.ts
|
|
2
|
+
const CONNECTING = 0;
|
|
3
|
+
const OPEN = 1;
|
|
4
|
+
const CLOSING = 2;
|
|
5
|
+
const CLOSED = 3;
|
|
6
|
+
function closeEvent(code, reason, wasClean) {
|
|
7
|
+
const init = {
|
|
8
|
+
code,
|
|
9
|
+
reason,
|
|
10
|
+
wasClean
|
|
11
|
+
};
|
|
12
|
+
return typeof CloseEvent === "function" ? new CloseEvent("close", init) : Object.assign(new Event("close"), init);
|
|
13
|
+
}
|
|
14
|
+
let ids = 0;
|
|
15
|
+
const UNREAD = Symbol();
|
|
16
|
+
/**
|
|
17
|
+
* Share one WebSocket between all the tabs of an app. Pass the returned class as the
|
|
18
|
+
* `WebSocket` option: every tab keeps its own client, with its own reconnects, queue, topics and
|
|
19
|
+
* hooks, but only one tab, the leader, opens a connection to the server. The others reach it
|
|
20
|
+
* through a `BroadcastChannel`. Users with ten tabs open cost the server one connection, not ten.
|
|
21
|
+
*
|
|
22
|
+
* - The leader is elected with the Web Locks API. When its tab closes, another tab takes over,
|
|
23
|
+
* and every tab's client reconnects through it.
|
|
24
|
+
* - Each tab's sends go out on the shared socket. Every message from the server reaches every
|
|
25
|
+
* tab, or, with `topics`, only the tabs that hold its topic.
|
|
26
|
+
* - With `topics`, an unsubscribe goes to the server only when no other tab still holds the topic.
|
|
27
|
+
*
|
|
28
|
+
* Where `BroadcastChannel` or the Web Locks API is missing, each tab opens its own socket, as
|
|
29
|
+
* without this.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { createTabsWebSocket } from 'react-websocket/tabs';
|
|
34
|
+
*
|
|
35
|
+
* export const live = createWebSocket(url, {
|
|
36
|
+
* json: true,
|
|
37
|
+
* WebSocket: createTabsWebSocket({
|
|
38
|
+
* json: true,
|
|
39
|
+
* topics: {
|
|
40
|
+
* subscribe: message => (message.type === 'subscribe' ? message.topic : undefined),
|
|
41
|
+
* unsubscribe: message => (message.type === 'unsubscribe' ? message.topic : undefined),
|
|
42
|
+
* topic: message => message.topic
|
|
43
|
+
* }
|
|
44
|
+
* })
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
function createTabsWebSocket(options = {}) {
|
|
49
|
+
const linger = options.linger ?? 1e3;
|
|
50
|
+
const agents = /* @__PURE__ */ new Map();
|
|
51
|
+
const read = (data) => {
|
|
52
|
+
if (!options.json || typeof data !== "string") return data;
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(data);
|
|
55
|
+
} catch {
|
|
56
|
+
return UNREAD;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
/** A topic `recognize` finds in `data`, if it parses and `recognize` doesn't throw. */
|
|
60
|
+
const topicOf = (recognize, message) => {
|
|
61
|
+
if (!recognize || message === UNREAD) return void 0;
|
|
62
|
+
try {
|
|
63
|
+
return recognize(message) ?? void 0;
|
|
64
|
+
} catch {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const live = /* @__PURE__ */ new Set();
|
|
69
|
+
let hiding = false;
|
|
70
|
+
const onPageHide = () => {
|
|
71
|
+
for (const leave of [...live]) leave();
|
|
72
|
+
};
|
|
73
|
+
function agent(key, url, protocols) {
|
|
74
|
+
if (agents.has(key)) return;
|
|
75
|
+
const channel = new BroadcastChannel(key);
|
|
76
|
+
const post = (signal) => channel.postMessage(signal);
|
|
77
|
+
let real;
|
|
78
|
+
const clients = /* @__PURE__ */ new Map();
|
|
79
|
+
const holders = /* @__PURE__ */ new Map();
|
|
80
|
+
let lingerTimer;
|
|
81
|
+
let release;
|
|
82
|
+
function opened(id) {
|
|
83
|
+
clients.set(id, true);
|
|
84
|
+
post({
|
|
85
|
+
t: "open",
|
|
86
|
+
id,
|
|
87
|
+
protocol: real.protocol
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function connect() {
|
|
91
|
+
if (real) return;
|
|
92
|
+
const Impl = options.WebSocket ?? globalThis.WebSocket;
|
|
93
|
+
let socket;
|
|
94
|
+
try {
|
|
95
|
+
socket = real = new Impl(url, protocols);
|
|
96
|
+
} catch {
|
|
97
|
+
clients.clear();
|
|
98
|
+
return post({
|
|
99
|
+
t: "close",
|
|
100
|
+
code: 1006,
|
|
101
|
+
reason: "",
|
|
102
|
+
wasClean: false
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
socket.binaryType = "arraybuffer";
|
|
106
|
+
socket.onopen = () => {
|
|
107
|
+
for (const [id, told] of clients) if (!told) opened(id);
|
|
108
|
+
};
|
|
109
|
+
socket.onmessage = (event) => {
|
|
110
|
+
const topic = topicOf(options.topics?.topic, read(event.data));
|
|
111
|
+
if (topic === void 0) return post({
|
|
112
|
+
t: "message",
|
|
113
|
+
data: event.data
|
|
114
|
+
});
|
|
115
|
+
const to = [...holders.get(topic) ?? []];
|
|
116
|
+
if (to.length) post({
|
|
117
|
+
t: "message",
|
|
118
|
+
data: event.data,
|
|
119
|
+
to
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
socket.onerror = () => {};
|
|
123
|
+
socket.onclose = (event) => {
|
|
124
|
+
if (real !== socket) return;
|
|
125
|
+
real = void 0;
|
|
126
|
+
clients.clear();
|
|
127
|
+
holders.clear();
|
|
128
|
+
post({
|
|
129
|
+
t: "close",
|
|
130
|
+
code: event.code,
|
|
131
|
+
reason: event.reason,
|
|
132
|
+
wasClean: event.wasClean
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function send(id, data) {
|
|
137
|
+
const { topics } = options;
|
|
138
|
+
if (topics) {
|
|
139
|
+
const message = read(data);
|
|
140
|
+
const subscribed = topicOf(topics.subscribe, message);
|
|
141
|
+
if (subscribed !== void 0) {
|
|
142
|
+
if (!holders.has(subscribed)) holders.set(subscribed, /* @__PURE__ */ new Set());
|
|
143
|
+
holders.get(subscribed).add(id);
|
|
144
|
+
}
|
|
145
|
+
const unsubscribed = topicOf(topics.unsubscribe, message);
|
|
146
|
+
if (unsubscribed !== void 0) {
|
|
147
|
+
const set = holders.get(unsubscribed);
|
|
148
|
+
set?.delete(id);
|
|
149
|
+
if (set?.size) return;
|
|
150
|
+
holders.delete(unsubscribed);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (real?.readyState === OPEN) real.send(data);
|
|
154
|
+
}
|
|
155
|
+
function handle(signal) {
|
|
156
|
+
if (signal.t === "connect") {
|
|
157
|
+
clearTimeout(lingerTimer);
|
|
158
|
+
clients.set(signal.id, false);
|
|
159
|
+
connect();
|
|
160
|
+
if (real?.readyState === OPEN) opened(signal.id);
|
|
161
|
+
} else if (signal.t === "send") {
|
|
162
|
+
if (clients.get(signal.id)) send(signal.id, signal.data);
|
|
163
|
+
} else if (signal.t === "disconnect") {
|
|
164
|
+
clients.delete(signal.id);
|
|
165
|
+
for (const set of holders.values()) set.delete(signal.id);
|
|
166
|
+
if (!clients.size) {
|
|
167
|
+
clearTimeout(lingerTimer);
|
|
168
|
+
lingerTimer = setTimeout(() => {
|
|
169
|
+
if (clients.size || !real) return;
|
|
170
|
+
const socket = real;
|
|
171
|
+
real = void 0;
|
|
172
|
+
socket.close(1e3);
|
|
173
|
+
}, linger);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
agents.set(key, {
|
|
178
|
+
channel,
|
|
179
|
+
stop() {
|
|
180
|
+
clearTimeout(lingerTimer);
|
|
181
|
+
channel.close();
|
|
182
|
+
if (real) {
|
|
183
|
+
real.onclose = null;
|
|
184
|
+
real.close(1e3);
|
|
185
|
+
}
|
|
186
|
+
release?.();
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
navigator.locks.request(key, () => {
|
|
190
|
+
if (!agents.has(key)) return;
|
|
191
|
+
channel.onmessage = (event) => handle(event.data);
|
|
192
|
+
post({ t: "leader" });
|
|
193
|
+
return new Promise((resolve) => release = resolve);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const Fallback = options.WebSocket;
|
|
197
|
+
class TabSocket extends EventTarget {
|
|
198
|
+
static CONNECTING = CONNECTING;
|
|
199
|
+
static OPEN = OPEN;
|
|
200
|
+
static CLOSING = CLOSING;
|
|
201
|
+
static CLOSED = CLOSED;
|
|
202
|
+
CONNECTING = CONNECTING;
|
|
203
|
+
OPEN = OPEN;
|
|
204
|
+
CLOSING = CLOSING;
|
|
205
|
+
CLOSED = CLOSED;
|
|
206
|
+
url;
|
|
207
|
+
extensions = "";
|
|
208
|
+
onopen = null;
|
|
209
|
+
onmessage = null;
|
|
210
|
+
onerror = null;
|
|
211
|
+
onclose = null;
|
|
212
|
+
binaryType = "blob";
|
|
213
|
+
protocol = "";
|
|
214
|
+
#id = `${Date.now().toString(36)}-${++ids}-${Math.random().toString(36).slice(2)}`;
|
|
215
|
+
#state = CONNECTING;
|
|
216
|
+
#channel;
|
|
217
|
+
#leave = () => {
|
|
218
|
+
if (this.#state >= CLOSING) return;
|
|
219
|
+
this.#post({
|
|
220
|
+
t: "disconnect",
|
|
221
|
+
id: this.#id
|
|
222
|
+
});
|
|
223
|
+
this.#end(1001, "", false);
|
|
224
|
+
};
|
|
225
|
+
#inner;
|
|
226
|
+
static dispose() {
|
|
227
|
+
for (const { stop } of agents.values()) stop();
|
|
228
|
+
agents.clear();
|
|
229
|
+
}
|
|
230
|
+
constructor(url, protocols) {
|
|
231
|
+
super();
|
|
232
|
+
if (typeof window === "undefined") throw new Error("react-websocket/tabs: no WebSocket here (server render?)");
|
|
233
|
+
for (const type of [
|
|
234
|
+
"open",
|
|
235
|
+
"message",
|
|
236
|
+
"error",
|
|
237
|
+
"close"
|
|
238
|
+
]) this.addEventListener(type, (event) => this[`on${type}`]?.call(this, event));
|
|
239
|
+
this.url = String(url);
|
|
240
|
+
if (typeof BroadcastChannel === "undefined" || !globalThis.navigator?.locks) {
|
|
241
|
+
const inner = this.#inner = new (Fallback ?? globalThis.WebSocket)(url, protocols);
|
|
242
|
+
inner.binaryType = "arraybuffer";
|
|
243
|
+
inner.onopen = () => this.#open(inner.protocol);
|
|
244
|
+
inner.onmessage = (event) => this.#message(event.data);
|
|
245
|
+
inner.onerror = () => this.dispatchEvent(new Event("error"));
|
|
246
|
+
inner.onclose = (event) => this.#end(event.code, event.reason, event.wasClean);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const key = `react-websocket-tabs:${this.url}\u0000${JSON.stringify(protocols ?? null)}`;
|
|
250
|
+
agent(key, this.url, protocols);
|
|
251
|
+
const channel = this.#channel = new BroadcastChannel(key);
|
|
252
|
+
channel.onmessage = (event) => this.#signal(event.data);
|
|
253
|
+
this.#post({
|
|
254
|
+
t: "connect",
|
|
255
|
+
id: this.#id
|
|
256
|
+
});
|
|
257
|
+
live.add(this.#leave);
|
|
258
|
+
if (!hiding) {
|
|
259
|
+
hiding = true;
|
|
260
|
+
addEventListener("pagehide", onPageHide);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
get readyState() {
|
|
264
|
+
return this.#state;
|
|
265
|
+
}
|
|
266
|
+
get bufferedAmount() {
|
|
267
|
+
return this.#inner?.bufferedAmount ?? 0;
|
|
268
|
+
}
|
|
269
|
+
send(data) {
|
|
270
|
+
if (this.#state === CONNECTING) throw new DOMException("WebSocket is still in CONNECTING state", "InvalidStateError");
|
|
271
|
+
if (this.#state !== OPEN) return;
|
|
272
|
+
if (this.#inner) this.#inner.send(data);
|
|
273
|
+
else this.#post({
|
|
274
|
+
t: "send",
|
|
275
|
+
id: this.#id,
|
|
276
|
+
data
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
close(code = 1e3, reason = "") {
|
|
280
|
+
if (this.#state >= CLOSING) return;
|
|
281
|
+
if (this.#inner) this.#inner.close(code, reason);
|
|
282
|
+
this.#state = CLOSING;
|
|
283
|
+
if (this.#inner) return;
|
|
284
|
+
this.#post({
|
|
285
|
+
t: "disconnect",
|
|
286
|
+
id: this.#id
|
|
287
|
+
});
|
|
288
|
+
setTimeout(() => this.#end(code, reason, true), 0);
|
|
289
|
+
}
|
|
290
|
+
#post(signal) {
|
|
291
|
+
this.#channel?.postMessage(signal);
|
|
292
|
+
}
|
|
293
|
+
#signal(signal) {
|
|
294
|
+
if (signal.t === "open") {
|
|
295
|
+
if (signal.id === this.#id && this.#state === CONNECTING) this.#open(signal.protocol);
|
|
296
|
+
} else if (signal.t === "message") {
|
|
297
|
+
if (!signal.to || signal.to.includes(this.#id)) this.#message(signal.data);
|
|
298
|
+
} else if (signal.t === "close") {
|
|
299
|
+
if (this.#state !== CONNECTING) this.#end(signal.code, signal.reason, signal.wasClean);
|
|
300
|
+
else {
|
|
301
|
+
this.dispatchEvent(new Event("error"));
|
|
302
|
+
this.#end(signal.code, signal.reason, false);
|
|
303
|
+
}
|
|
304
|
+
} else if (signal.t === "leader") {
|
|
305
|
+
if (this.#state === CONNECTING) this.#post({
|
|
306
|
+
t: "connect",
|
|
307
|
+
id: this.#id
|
|
308
|
+
});
|
|
309
|
+
else this.#end(1006, "", false);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
#open(protocol) {
|
|
313
|
+
this.#state = OPEN;
|
|
314
|
+
this.protocol = protocol;
|
|
315
|
+
this.dispatchEvent(new Event("open"));
|
|
316
|
+
}
|
|
317
|
+
#message(data) {
|
|
318
|
+
if (this.#state !== OPEN) return;
|
|
319
|
+
if (data instanceof ArrayBuffer && this.binaryType === "blob") data = new Blob([data]);
|
|
320
|
+
this.dispatchEvent(new MessageEvent("message", { data }));
|
|
321
|
+
}
|
|
322
|
+
#end(code, reason, wasClean) {
|
|
323
|
+
if (this.#state === CLOSED) return;
|
|
324
|
+
this.#state = CLOSED;
|
|
325
|
+
this.#channel?.close();
|
|
326
|
+
live.delete(this.#leave);
|
|
327
|
+
this.dispatchEvent(closeEvent(code, reason, wasClean));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return TabSocket;
|
|
331
|
+
}
|
|
332
|
+
//#endregion
|
|
333
|
+
export { createTabsWebSocket };
|
|
334
|
+
|
|
335
|
+
//# sourceMappingURL=tabs.js.map
|
package/dist/tabs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs.js","names":[],"sources":["../src/tabs.ts"],"sourcesContent":["// An entry with no imports: it only implements the `WebSocket` interface.\n\n/**\n * Recognize topic subscriptions, so tabs sharing a socket don't unsubscribe each other. With\n * `json`, only messages that parse are passed to these, and one that throws counts as\n * `undefined`, so heartbeat pings and other plain strings go through.\n */\nexport interface TabsTopics {\n /** The topic an outgoing message subscribes to, or `undefined`. */\n subscribe: (message: any) => string | null | undefined;\n /** The topic an outgoing message unsubscribes from, or `undefined`. */\n unsubscribe: (message: any) => string | null | undefined;\n /**\n * The topic of an incoming message, to deliver it only to the tabs that hold that topic.\n * Without it, every tab gets every message.\n */\n topic?: (message: any) => string | null | undefined;\n}\n\n/** Options for {@link createTabsWebSocket}. */\nexport interface TabsWebSocketOptions {\n /** The `WebSocket` the leading tab opens. Defaults to the global one. */\n WebSocket?: typeof WebSocket;\n /** Messages are JSON: the `topics` functions get them parsed. */\n json?: boolean;\n /** Share topic subscriptions between tabs. See {@link TabsTopics}. */\n topics?: TabsTopics;\n /**\n * How long the leading tab keeps the shared socket open after the last tab using it lets go,\n * in ms, so a reload doesn't close and reopen it.\n * @default 1000\n */\n linger?: number;\n}\n\n/** The class from {@link createTabsWebSocket}: a `WebSocket`, plus a way to stop sharing. */\nexport interface TabsWebSocket {\n new (url: string | URL, protocols?: string | string[]): WebSocket;\n readonly prototype: WebSocket;\n readonly CONNECTING: 0;\n readonly OPEN: 1;\n readonly CLOSING: 2;\n readonly CLOSED: 3;\n /**\n * Stop leading and listening in this tab: close the shared sockets it holds and let another\n * tab take over. For tests and hot module reloading; a closed tab does this by itself.\n */\n dispose(): void;\n}\n\nconst CONNECTING = 0;\nconst OPEN = 1;\nconst CLOSING = 2;\nconst CLOSED = 3;\n\n/** What tabs say to each other over the channel. */\ntype Signal =\n | { t: 'connect'; id: string }\n | { t: 'send'; id: string; data: unknown }\n | { t: 'disconnect'; id: string }\n | { t: 'open'; id: string; protocol: string }\n | { t: 'message'; data: unknown; to?: string[] }\n | { t: 'close'; code: number; reason: string; wasClean: boolean }\n | { t: 'leader' };\n\nfunction closeEvent(code: number, reason: string, wasClean: boolean): Event {\n const init = { code, reason, wasClean };\n // Node 22 has no CloseEvent.\n return typeof CloseEvent === 'function'\n ? new CloseEvent('close', init)\n : Object.assign(new Event('close'), init);\n}\n\nlet ids = 0;\n\n// What `read()` returns for a message that isn't JSON.\nconst UNREAD = Symbol();\n\n/**\n * Share one WebSocket between all the tabs of an app. Pass the returned class as the\n * `WebSocket` option: every tab keeps its own client, with its own reconnects, queue, topics and\n * hooks, but only one tab, the leader, opens a connection to the server. The others reach it\n * through a `BroadcastChannel`. Users with ten tabs open cost the server one connection, not ten.\n *\n * - The leader is elected with the Web Locks API. When its tab closes, another tab takes over,\n * and every tab's client reconnects through it.\n * - Each tab's sends go out on the shared socket. Every message from the server reaches every\n * tab, or, with `topics`, only the tabs that hold its topic.\n * - With `topics`, an unsubscribe goes to the server only when no other tab still holds the topic.\n *\n * Where `BroadcastChannel` or the Web Locks API is missing, each tab opens its own socket, as\n * without this.\n *\n * @example\n * ```ts\n * import { createTabsWebSocket } from 'react-websocket/tabs';\n *\n * export const live = createWebSocket(url, {\n * json: true,\n * WebSocket: createTabsWebSocket({\n * json: true,\n * topics: {\n * subscribe: message => (message.type === 'subscribe' ? message.topic : undefined),\n * unsubscribe: message => (message.type === 'unsubscribe' ? message.topic : undefined),\n * topic: message => message.topic\n * }\n * })\n * });\n * ```\n */\nexport function createTabsWebSocket(options: TabsWebSocketOptions = {}): TabsWebSocket {\n const linger = options.linger ?? 1000;\n // One per URL in this tab: it waits to lead, and leads while it holds the lock.\n const agents = new Map<string, { channel: BroadcastChannel; stop: () => void }>();\n\n const read = (data: unknown) => {\n if (!options.json || typeof data !== 'string') return data;\n try {\n return JSON.parse(data);\n } catch {\n return UNREAD;\n }\n };\n\n /** A topic `recognize` finds in `data`, if it parses and `recognize` doesn't throw. */\n const topicOf = (\n recognize: ((message: any) => string | null | undefined) | undefined,\n message: unknown\n ) => {\n if (!recognize || message === UNREAD) return undefined;\n try {\n return recognize(message) ?? undefined;\n } catch {\n return undefined;\n }\n };\n\n // The sockets this tab has on shared ones. A tab that goes away tells the leading tab,\n // which would otherwise keep their topics and the shared socket for them.\n const live = new Set<() => void>();\n let hiding = false;\n const onPageHide = () => {\n for (const leave of [...live]) leave();\n };\n\n function agent(key: string, url: string, protocols: string | string[] | undefined) {\n if (agents.has(key)) return;\n const channel = new BroadcastChannel(key);\n const post = (signal: Signal) => channel.postMessage(signal);\n let real: WebSocket | undefined;\n // The tabs' sockets on the shared one, and whether each has been told it's open.\n const clients = new Map<string, boolean>();\n // Who holds each topic, so an unsubscribe waits for the last one.\n const holders = new Map<string, Set<string>>();\n let lingerTimer: ReturnType<typeof setTimeout> | undefined;\n let release: (() => void) | undefined;\n\n function opened(id: string) {\n clients.set(id, true);\n post({ t: 'open', id, protocol: real!.protocol });\n }\n\n function connect() {\n if (real) return;\n const Impl = options.WebSocket ?? globalThis.WebSocket;\n let socket: WebSocket;\n try {\n socket = real = new Impl(url, protocols);\n } catch {\n clients.clear();\n return post({ t: 'close', code: 1006, reason: '', wasClean: false });\n }\n socket.binaryType = 'arraybuffer';\n socket.onopen = () => {\n for (const [id, told] of clients) if (!told) opened(id);\n };\n socket.onmessage = event => {\n const topic = topicOf(options.topics?.topic, read(event.data));\n if (topic === undefined) return post({ t: 'message', data: event.data });\n const to = [...(holders.get(topic) ?? [])];\n if (to.length) post({ t: 'message', data: event.data, to });\n };\n socket.onerror = () => {};\n socket.onclose = event => {\n if (real !== socket) return;\n real = undefined;\n clients.clear();\n holders.clear();\n post({ t: 'close', code: event.code, reason: event.reason, wasClean: event.wasClean });\n };\n }\n\n function send(id: string, data: unknown) {\n const { topics } = options;\n if (topics) {\n const message = read(data);\n const subscribed = topicOf(topics.subscribe, message);\n if (subscribed !== undefined) {\n if (!holders.has(subscribed)) holders.set(subscribed, new Set());\n holders.get(subscribed)!.add(id);\n }\n const unsubscribed = topicOf(topics.unsubscribe, message);\n if (unsubscribed !== undefined) {\n const set = holders.get(unsubscribed);\n set?.delete(id);\n if (set?.size) return; // another tab still holds it\n holders.delete(unsubscribed);\n }\n }\n if (real?.readyState === OPEN) real.send(data as Parameters<WebSocket['send']>[0]);\n }\n\n function handle(signal: Signal) {\n if (signal.t === 'connect') {\n clearTimeout(lingerTimer);\n clients.set(signal.id, false);\n connect();\n if (real?.readyState === OPEN) opened(signal.id);\n } else if (signal.t === 'send') {\n if (clients.get(signal.id)) send(signal.id, signal.data);\n } else if (signal.t === 'disconnect') {\n clients.delete(signal.id);\n for (const set of holders.values()) set.delete(signal.id);\n if (!clients.size) {\n clearTimeout(lingerTimer);\n lingerTimer = setTimeout(() => {\n if (clients.size || !real) return;\n // Forget it first: a tab connecting while it closes gets a new one.\n const socket = real;\n real = undefined;\n socket.close(1000);\n }, linger);\n }\n }\n }\n\n agents.set(key, {\n channel,\n stop() {\n clearTimeout(lingerTimer);\n channel.close();\n if (real) {\n real.onclose = null;\n real.close(1000);\n }\n release?.();\n }\n });\n void navigator.locks.request(key, () => {\n if (!agents.has(key)) return;\n channel.onmessage = event => handle(event.data as Signal);\n // Sockets that were using the tab that led before start over with this one.\n post({ t: 'leader' });\n // Lead until this tab goes away, or `dispose()`.\n return new Promise<void>(resolve => (release = resolve));\n });\n }\n\n const Fallback = options.WebSocket;\n\n class TabSocket extends EventTarget implements WebSocket {\n static readonly CONNECTING = CONNECTING;\n static readonly OPEN = OPEN;\n static readonly CLOSING = CLOSING;\n static readonly CLOSED = CLOSED;\n readonly CONNECTING = CONNECTING;\n readonly OPEN = OPEN;\n readonly CLOSING = CLOSING;\n readonly CLOSED = CLOSED;\n\n readonly url: string;\n readonly extensions = '';\n onopen: WebSocket['onopen'] = null;\n onmessage: WebSocket['onmessage'] = null;\n onerror: WebSocket['onerror'] = null;\n onclose: WebSocket['onclose'] = null;\n binaryType: BinaryType = 'blob';\n protocol = '';\n\n #id = `${Date.now().toString(36)}-${++ids}-${Math.random().toString(36).slice(2)}`;\n #state: WebSocket['readyState'] = CONNECTING;\n #channel: BroadcastChannel | undefined;\n // The page is going away, or into the back/forward cache: let go of the shared socket, and\n // close, so a page that comes back connects again.\n #leave = () => {\n if (this.#state >= CLOSING) return; // `close()` told the leader already\n this.#post({ t: 'disconnect', id: this.#id });\n this.#end(1001, '', false);\n };\n // Without `BroadcastChannel` or locks: a socket of this tab's own.\n #inner: WebSocket | undefined;\n\n static dispose() {\n for (const { stop } of agents.values()) stop();\n agents.clear();\n }\n\n constructor(url: string | URL, protocols?: string | string[]) {\n super();\n if (typeof window === 'undefined') {\n throw new Error('react-websocket/tabs: no WebSocket here (server render?)');\n }\n for (const type of ['open', 'message', 'error', 'close'] as const) {\n this.addEventListener(type, event =>\n (this[`on${type}`] as ((event: Event) => void) | null)?.call(this, event)\n );\n }\n this.url = String(url);\n if (typeof BroadcastChannel === 'undefined' || !globalThis.navigator?.locks) {\n const inner = (this.#inner = new (Fallback ?? globalThis.WebSocket)(url, protocols));\n // As from the leading tab: `#message` turns it into what `binaryType` asks for.\n inner.binaryType = 'arraybuffer';\n inner.onopen = () => this.#open(inner.protocol);\n inner.onmessage = event => this.#message(event.data);\n inner.onerror = () => this.dispatchEvent(new Event('error'));\n inner.onclose = event => this.#end(event.code, event.reason, event.wasClean);\n return;\n }\n const key = `react-websocket-tabs:${this.url}\\u0000${JSON.stringify(protocols ?? null)}`;\n agent(key, this.url, protocols);\n const channel = (this.#channel = new BroadcastChannel(key));\n channel.onmessage = event => this.#signal(event.data as Signal);\n this.#post({ t: 'connect', id: this.#id });\n live.add(this.#leave);\n if (!hiding) {\n hiding = true;\n addEventListener('pagehide', onPageHide);\n }\n }\n\n get readyState() {\n return this.#state;\n }\n\n get bufferedAmount() {\n return this.#inner?.bufferedAmount ?? 0;\n }\n\n send(data: Parameters<WebSocket['send']>[0]) {\n if (this.#state === CONNECTING) {\n throw new DOMException('WebSocket is still in CONNECTING state', 'InvalidStateError');\n }\n if (this.#state !== OPEN) return;\n if (this.#inner) this.#inner.send(data);\n else this.#post({ t: 'send', id: this.#id, data });\n }\n\n close(code = 1000, reason = '') {\n if (this.#state >= CLOSING) return;\n // First, so that a code the socket rejects throws before anything has changed.\n if (this.#inner) this.#inner.close(code, reason);\n this.#state = CLOSING;\n if (this.#inner) return;\n // Only this tab's socket closes; the shared one stays for the others.\n this.#post({ t: 'disconnect', id: this.#id });\n setTimeout(() => this.#end(code, reason, true), 0);\n }\n\n #post(signal: Signal) {\n this.#channel?.postMessage(signal);\n }\n\n #signal(signal: Signal) {\n if (signal.t === 'open') {\n if (signal.id === this.#id && this.#state === CONNECTING) this.#open(signal.protocol);\n } else if (signal.t === 'message') {\n if (!signal.to || signal.to.includes(this.#id)) this.#message(signal.data);\n } else if (signal.t === 'close') {\n if (this.#state !== CONNECTING) this.#end(signal.code, signal.reason, signal.wasClean);\n else {\n // The shared socket failed before this one opened.\n this.dispatchEvent(new Event('error'));\n this.#end(signal.code, signal.reason, false);\n }\n } else if (signal.t === 'leader') {\n // A new leading tab: start over with it, or ask it to connect us.\n if (this.#state === CONNECTING) this.#post({ t: 'connect', id: this.#id });\n else this.#end(1006, '', false);\n }\n }\n\n #open(protocol: string) {\n this.#state = OPEN;\n this.protocol = protocol;\n this.dispatchEvent(new Event('open'));\n }\n\n #message(data: unknown) {\n if (this.#state !== OPEN) return;\n if (data instanceof ArrayBuffer && this.binaryType === 'blob') data = new Blob([data]);\n this.dispatchEvent(new MessageEvent('message', { data }));\n }\n\n #end(code: number, reason: string, wasClean: boolean) {\n if (this.#state === CLOSED) return;\n this.#state = CLOSED;\n this.#channel?.close();\n live.delete(this.#leave);\n this.dispatchEvent(closeEvent(code, reason, wasClean));\n }\n }\n\n return TabSocket as unknown as TabsWebSocket;\n}\n"],"mappings":"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"}
|