@hedystia/ws 2.3.5 → 2.3.6
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/dist/client.cjs.map +1 -1
- package/dist/client.mjs.map +1 -1
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.mjs.map +1 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/client.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.cjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import type { ClientWebSocketOptions } from \"./types\";\n\nexport type { ClientWebSocketOptions } from \"./types\";\n\n/**\n * Resolve the `WebSocket` constructor from the current runtime's global scope.\n *\n * @remarks\n * Every supported runtime exposes a WHATWG-compliant `WebSocket` natively:\n *\n * | Runtime | Available since |\n * |---------------|-----------------|\n * | Browsers | always |\n * | Bun | always |\n * | Deno | v1.4 |\n * | Node.js | v22 (stable) |\n *\n * If `globalThis.WebSocket` is not present (e.g. Node.js < 22 without a\n * polyfill), this function throws with a descriptive message instead of\n * silently falling back to a third-party package.\n *\n * @returns The global `WebSocket` constructor.\n * @throws {Error} When no native `WebSocket` is available in the current runtime.\n *\n * @example\n * ```ts\n * import { resolveWebSocket } from \"@hedystia/ws/client\";\n *\n * const WS = resolveWebSocket();\n * const socket = new WS(\"ws://localhost:3000\");\n * ```\n */\nexport function resolveWebSocket(): typeof WebSocket {\n if (typeof globalThis !== \"undefined\" && (globalThis as any).WebSocket) {\n return (globalThis as any).WebSocket as typeof WebSocket;\n }\n throw new Error(\n \"@hedystia/ws: no native WebSocket found in globalThis. \" +\n \"Ensure you are running Bun, Deno, a modern browser, or Node.js ≥ 22.\",\n );\n}\n\n/**\n * Create a `WebSocket` instance using the runtime's native `globalThis.WebSocket`.\n *\n * @remarks\n * Custom request headers are only honoured on runtimes that expose them via\n * the second constructor argument (e.g. Bun). On browsers and other\n * WHATWG-strict environments, `options.headers` is silently ignored —\n * matching standard WebSocket semantics.\n *\n * @param url - Absolute WebSocket URL (`ws://` or `wss://`).\n * @param options - Optional sub-protocols and headers, see {@link ClientWebSocketOptions}.\n * @returns A connected (or connecting) `WebSocket` instance.\n *\n * @throws {Error} When no native `WebSocket` is available in the current runtime.\n *\n * @example\n * ```ts\n * import { createWebSocket } from \"@hedystia/ws/client\";\n *\n * const ws = createWebSocket(\"ws://localhost:3000\", {\n * protocols: \"v1\",\n * headers: { authorization: \"Bearer ...\" },\n * });\n *\n * ws.onopen = () => ws.send(\"hi\");\n * ws.onmessage = (event) => console.log(event.data);\n * ```\n */\nexport function createWebSocket(url: string, options?: ClientWebSocketOptions): WebSocket {\n const Ctor = resolveWebSocket();\n\n if (options?.protocols) {\n return new Ctor(url, options.protocols as any);\n }\n return new Ctor(url);\n}\n\n/**\n * Lightweight runtime-agnostic wrapper that mirrors a small, predictable\n * subset of the WHATWG `WebSocket` interface.\n *\n * @remarks\n * Useful for higher-level code that wants to assign event handlers by\n * property (`socket.onmessage = …`) without referencing the global\n * `WebSocket` type directly. Delegates all operations to the native\n * `WebSocket` produced by {@link createWebSocket}.\n *\n * @example\n * ```ts\n * import { WebSocketClient } from \"@hedystia/ws/client\";\n *\n * const client = new WebSocketClient(\"ws://localhost:3000\");\n * client.onopen = () => client.send(\"hello\");\n * client.onmessage = (event) => console.log(event.data);\n * ```\n */\nexport class WebSocketClient {\n /**\n * Underlying WebSocket instance produced by {@link createWebSocket}.\n *\n * @readonly\n */\n readonly socket: WebSocket;\n\n /**\n * Create a new client and immediately initiate the connection.\n *\n * @param url - Absolute WebSocket URL (`ws://` or `wss://`).\n * @param options - Optional sub-protocols / headers; see {@link ClientWebSocketOptions}.\n */\n constructor(url: string, options?: ClientWebSocketOptions) {\n this.socket = createWebSocket(url, options);\n }\n\n /**\n * Current connection state, mirroring {@link WebSocket.readyState}.\n *\n * @returns `0` connecting · `1` open · `2` closing · `3` closed.\n */\n get readyState(): number {\n return this.socket.readyState;\n }\n\n /**\n * Send a payload to the server.\n *\n * @param data - WHATWG-compatible payload.\n */\n send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void {\n this.socket.send(data as any);\n }\n\n /**\n * Close the underlying socket.\n *\n * @param code - Close code (defaults to `1000`).\n * @param reason - Optional human-readable reason phrase.\n */\n close(code?: number, reason?: string): void {\n this.socket.close(code, reason);\n }\n\n /**\n * Assign the open-event listener.\n */\n set onopen(cb: ((ev: Event) => void) | null) {\n (this.socket as any).onopen = cb;\n }\n\n /**\n * Assign the message-event listener.\n */\n set onmessage(cb: ((ev: MessageEvent) => void) | null) {\n (this.socket as any).onmessage = cb;\n }\n\n /**\n * Assign the close-event listener.\n */\n set onclose(cb: ((ev: CloseEvent) => void) | null) {\n (this.socket as any).onclose = cb;\n }\n\n /**\n * Assign the error-event listener.\n */\n set onerror(cb: ((ev: Event) => void) | null) {\n (this.socket as any).onerror = cb;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,mBAAqC;CACnD,IAAI,OAAO,eAAe,eAAgB,WAAmB,WAC3D,OAAQ,WAAmB;CAE7B,MAAM,IAAI,MACR,
|
|
1
|
+
{"version":3,"file":"client.cjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import type { ClientWebSocketOptions } from \"./types\";\n\nexport type { ClientWebSocketOptions } from \"./types\";\n\n/**\n * Resolve the `WebSocket` constructor from the current runtime's global scope.\n *\n * @remarks\n * Every supported runtime exposes a WHATWG-compliant `WebSocket` natively:\n *\n * | Runtime | Available since |\n * |---------------|-----------------|\n * | Browsers | always |\n * | Bun | always |\n * | Deno | v1.4 |\n * | Node.js | v22 (stable) |\n *\n * If `globalThis.WebSocket` is not present (e.g. Node.js < 22 without a\n * polyfill), this function throws with a descriptive message instead of\n * silently falling back to a third-party package.\n *\n * @returns The global `WebSocket` constructor.\n * @throws {Error} When no native `WebSocket` is available in the current runtime.\n *\n * @example\n * ```ts\n * import { resolveWebSocket } from \"@hedystia/ws/client\";\n *\n * const WS = resolveWebSocket();\n * const socket = new WS(\"ws://localhost:3000\");\n * ```\n */\nexport function resolveWebSocket(): typeof WebSocket {\n if (typeof globalThis !== \"undefined\" && (globalThis as any).WebSocket) {\n return (globalThis as any).WebSocket as typeof WebSocket;\n }\n throw new Error(\n \"@hedystia/ws: no native WebSocket found in globalThis. \" +\n \"Ensure you are running Bun, Deno, a modern browser, or Node.js ≥ 22.\",\n );\n}\n\n/**\n * Create a `WebSocket` instance using the runtime's native `globalThis.WebSocket`.\n *\n * @remarks\n * Custom request headers are only honoured on runtimes that expose them via\n * the second constructor argument (e.g. Bun). On browsers and other\n * WHATWG-strict environments, `options.headers` is silently ignored —\n * matching standard WebSocket semantics.\n *\n * @param url - Absolute WebSocket URL (`ws://` or `wss://`).\n * @param options - Optional sub-protocols and headers, see {@link ClientWebSocketOptions}.\n * @returns A connected (or connecting) `WebSocket` instance.\n *\n * @throws {Error} When no native `WebSocket` is available in the current runtime.\n *\n * @example\n * ```ts\n * import { createWebSocket } from \"@hedystia/ws/client\";\n *\n * const ws = createWebSocket(\"ws://localhost:3000\", {\n * protocols: \"v1\",\n * headers: { authorization: \"Bearer ...\" },\n * });\n *\n * ws.onopen = () => ws.send(\"hi\");\n * ws.onmessage = (event) => console.log(event.data);\n * ```\n */\nexport function createWebSocket(url: string, options?: ClientWebSocketOptions): WebSocket {\n const Ctor = resolveWebSocket();\n\n if (options?.protocols) {\n return new Ctor(url, options.protocols as any);\n }\n return new Ctor(url);\n}\n\n/**\n * Lightweight runtime-agnostic wrapper that mirrors a small, predictable\n * subset of the WHATWG `WebSocket` interface.\n *\n * @remarks\n * Useful for higher-level code that wants to assign event handlers by\n * property (`socket.onmessage = …`) without referencing the global\n * `WebSocket` type directly. Delegates all operations to the native\n * `WebSocket` produced by {@link createWebSocket}.\n *\n * @example\n * ```ts\n * import { WebSocketClient } from \"@hedystia/ws/client\";\n *\n * const client = new WebSocketClient(\"ws://localhost:3000\");\n * client.onopen = () => client.send(\"hello\");\n * client.onmessage = (event) => console.log(event.data);\n * ```\n */\nexport class WebSocketClient {\n /**\n * Underlying WebSocket instance produced by {@link createWebSocket}.\n *\n * @readonly\n */\n readonly socket: WebSocket;\n\n /**\n * Create a new client and immediately initiate the connection.\n *\n * @param url - Absolute WebSocket URL (`ws://` or `wss://`).\n * @param options - Optional sub-protocols / headers; see {@link ClientWebSocketOptions}.\n */\n constructor(url: string, options?: ClientWebSocketOptions) {\n this.socket = createWebSocket(url, options);\n }\n\n /**\n * Current connection state, mirroring {@link WebSocket.readyState}.\n *\n * @returns `0` connecting · `1` open · `2` closing · `3` closed.\n */\n get readyState(): number {\n return this.socket.readyState;\n }\n\n /**\n * Send a payload to the server.\n *\n * @param data - WHATWG-compatible payload.\n */\n send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void {\n this.socket.send(data as any);\n }\n\n /**\n * Close the underlying socket.\n *\n * @param code - Close code (defaults to `1000`).\n * @param reason - Optional human-readable reason phrase.\n */\n close(code?: number, reason?: string): void {\n this.socket.close(code, reason);\n }\n\n /**\n * Assign the open-event listener.\n */\n set onopen(cb: ((ev: Event) => void) | null) {\n (this.socket as any).onopen = cb;\n }\n\n /**\n * Assign the message-event listener.\n */\n set onmessage(cb: ((ev: MessageEvent) => void) | null) {\n (this.socket as any).onmessage = cb;\n }\n\n /**\n * Assign the close-event listener.\n */\n set onclose(cb: ((ev: CloseEvent) => void) | null) {\n (this.socket as any).onclose = cb;\n }\n\n /**\n * Assign the error-event listener.\n */\n set onerror(cb: ((ev: Event) => void) | null) {\n (this.socket as any).onerror = cb;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,mBAAqC;CACnD,IAAI,OAAO,eAAe,eAAgB,WAAmB,WAC3D,OAAQ,WAAmB;CAE7B,MAAM,IAAI,MACR,6HAEF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,gBAAgB,KAAa,SAA6C;CACxF,MAAM,OAAO,iBAAiB;CAE9B,IAAI,SAAS,WACX,OAAO,IAAI,KAAK,KAAK,QAAQ,SAAgB;CAE/C,OAAO,IAAI,KAAK,GAAG;AACrB;;;;;;;;;;;;;;;;;;;;AAqBA,IAAa,kBAAb,MAA6B;;;;;;CAM3B;;;;;;;CAQA,YAAY,KAAa,SAAkC;EACzD,KAAK,SAAS,gBAAgB,KAAK,OAAO;CAC5C;;;;;;CAOA,IAAI,aAAqB;EACvB,OAAO,KAAK,OAAO;CACrB;;;;;;CAOA,KAAK,MAA+D;EAClE,KAAK,OAAO,KAAK,IAAW;CAC9B;;;;;;;CAQA,MAAM,MAAe,QAAuB;EAC1C,KAAK,OAAO,MAAM,MAAM,MAAM;CAChC;;;;CAKA,IAAI,OAAO,IAAkC;EAC3C,KAAM,OAAe,SAAS;CAChC;;;;CAKA,IAAI,UAAU,IAAyC;EACrD,KAAM,OAAe,YAAY;CACnC;;;;CAKA,IAAI,QAAQ,IAAuC;EACjD,KAAM,OAAe,UAAU;CACjC;;;;CAKA,IAAI,QAAQ,IAAkC;EAC5C,KAAM,OAAe,UAAU;CACjC;AACF"}
|
package/dist/client.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import type { ClientWebSocketOptions } from \"./types\";\n\nexport type { ClientWebSocketOptions } from \"./types\";\n\n/**\n * Resolve the `WebSocket` constructor from the current runtime's global scope.\n *\n * @remarks\n * Every supported runtime exposes a WHATWG-compliant `WebSocket` natively:\n *\n * | Runtime | Available since |\n * |---------------|-----------------|\n * | Browsers | always |\n * | Bun | always |\n * | Deno | v1.4 |\n * | Node.js | v22 (stable) |\n *\n * If `globalThis.WebSocket` is not present (e.g. Node.js < 22 without a\n * polyfill), this function throws with a descriptive message instead of\n * silently falling back to a third-party package.\n *\n * @returns The global `WebSocket` constructor.\n * @throws {Error} When no native `WebSocket` is available in the current runtime.\n *\n * @example\n * ```ts\n * import { resolveWebSocket } from \"@hedystia/ws/client\";\n *\n * const WS = resolveWebSocket();\n * const socket = new WS(\"ws://localhost:3000\");\n * ```\n */\nexport function resolveWebSocket(): typeof WebSocket {\n if (typeof globalThis !== \"undefined\" && (globalThis as any).WebSocket) {\n return (globalThis as any).WebSocket as typeof WebSocket;\n }\n throw new Error(\n \"@hedystia/ws: no native WebSocket found in globalThis. \" +\n \"Ensure you are running Bun, Deno, a modern browser, or Node.js ≥ 22.\",\n );\n}\n\n/**\n * Create a `WebSocket` instance using the runtime's native `globalThis.WebSocket`.\n *\n * @remarks\n * Custom request headers are only honoured on runtimes that expose them via\n * the second constructor argument (e.g. Bun). On browsers and other\n * WHATWG-strict environments, `options.headers` is silently ignored —\n * matching standard WebSocket semantics.\n *\n * @param url - Absolute WebSocket URL (`ws://` or `wss://`).\n * @param options - Optional sub-protocols and headers, see {@link ClientWebSocketOptions}.\n * @returns A connected (or connecting) `WebSocket` instance.\n *\n * @throws {Error} When no native `WebSocket` is available in the current runtime.\n *\n * @example\n * ```ts\n * import { createWebSocket } from \"@hedystia/ws/client\";\n *\n * const ws = createWebSocket(\"ws://localhost:3000\", {\n * protocols: \"v1\",\n * headers: { authorization: \"Bearer ...\" },\n * });\n *\n * ws.onopen = () => ws.send(\"hi\");\n * ws.onmessage = (event) => console.log(event.data);\n * ```\n */\nexport function createWebSocket(url: string, options?: ClientWebSocketOptions): WebSocket {\n const Ctor = resolveWebSocket();\n\n if (options?.protocols) {\n return new Ctor(url, options.protocols as any);\n }\n return new Ctor(url);\n}\n\n/**\n * Lightweight runtime-agnostic wrapper that mirrors a small, predictable\n * subset of the WHATWG `WebSocket` interface.\n *\n * @remarks\n * Useful for higher-level code that wants to assign event handlers by\n * property (`socket.onmessage = …`) without referencing the global\n * `WebSocket` type directly. Delegates all operations to the native\n * `WebSocket` produced by {@link createWebSocket}.\n *\n * @example\n * ```ts\n * import { WebSocketClient } from \"@hedystia/ws/client\";\n *\n * const client = new WebSocketClient(\"ws://localhost:3000\");\n * client.onopen = () => client.send(\"hello\");\n * client.onmessage = (event) => console.log(event.data);\n * ```\n */\nexport class WebSocketClient {\n /**\n * Underlying WebSocket instance produced by {@link createWebSocket}.\n *\n * @readonly\n */\n readonly socket: WebSocket;\n\n /**\n * Create a new client and immediately initiate the connection.\n *\n * @param url - Absolute WebSocket URL (`ws://` or `wss://`).\n * @param options - Optional sub-protocols / headers; see {@link ClientWebSocketOptions}.\n */\n constructor(url: string, options?: ClientWebSocketOptions) {\n this.socket = createWebSocket(url, options);\n }\n\n /**\n * Current connection state, mirroring {@link WebSocket.readyState}.\n *\n * @returns `0` connecting · `1` open · `2` closing · `3` closed.\n */\n get readyState(): number {\n return this.socket.readyState;\n }\n\n /**\n * Send a payload to the server.\n *\n * @param data - WHATWG-compatible payload.\n */\n send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void {\n this.socket.send(data as any);\n }\n\n /**\n * Close the underlying socket.\n *\n * @param code - Close code (defaults to `1000`).\n * @param reason - Optional human-readable reason phrase.\n */\n close(code?: number, reason?: string): void {\n this.socket.close(code, reason);\n }\n\n /**\n * Assign the open-event listener.\n */\n set onopen(cb: ((ev: Event) => void) | null) {\n (this.socket as any).onopen = cb;\n }\n\n /**\n * Assign the message-event listener.\n */\n set onmessage(cb: ((ev: MessageEvent) => void) | null) {\n (this.socket as any).onmessage = cb;\n }\n\n /**\n * Assign the close-event listener.\n */\n set onclose(cb: ((ev: CloseEvent) => void) | null) {\n (this.socket as any).onclose = cb;\n }\n\n /**\n * Assign the error-event listener.\n */\n set onerror(cb: ((ev: Event) => void) | null) {\n (this.socket as any).onerror = cb;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,mBAAqC;CACnD,IAAI,OAAO,eAAe,eAAgB,WAAmB,WAC3D,OAAQ,WAAmB;CAE7B,MAAM,IAAI,MACR,
|
|
1
|
+
{"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import type { ClientWebSocketOptions } from \"./types\";\n\nexport type { ClientWebSocketOptions } from \"./types\";\n\n/**\n * Resolve the `WebSocket` constructor from the current runtime's global scope.\n *\n * @remarks\n * Every supported runtime exposes a WHATWG-compliant `WebSocket` natively:\n *\n * | Runtime | Available since |\n * |---------------|-----------------|\n * | Browsers | always |\n * | Bun | always |\n * | Deno | v1.4 |\n * | Node.js | v22 (stable) |\n *\n * If `globalThis.WebSocket` is not present (e.g. Node.js < 22 without a\n * polyfill), this function throws with a descriptive message instead of\n * silently falling back to a third-party package.\n *\n * @returns The global `WebSocket` constructor.\n * @throws {Error} When no native `WebSocket` is available in the current runtime.\n *\n * @example\n * ```ts\n * import { resolveWebSocket } from \"@hedystia/ws/client\";\n *\n * const WS = resolveWebSocket();\n * const socket = new WS(\"ws://localhost:3000\");\n * ```\n */\nexport function resolveWebSocket(): typeof WebSocket {\n if (typeof globalThis !== \"undefined\" && (globalThis as any).WebSocket) {\n return (globalThis as any).WebSocket as typeof WebSocket;\n }\n throw new Error(\n \"@hedystia/ws: no native WebSocket found in globalThis. \" +\n \"Ensure you are running Bun, Deno, a modern browser, or Node.js ≥ 22.\",\n );\n}\n\n/**\n * Create a `WebSocket` instance using the runtime's native `globalThis.WebSocket`.\n *\n * @remarks\n * Custom request headers are only honoured on runtimes that expose them via\n * the second constructor argument (e.g. Bun). On browsers and other\n * WHATWG-strict environments, `options.headers` is silently ignored —\n * matching standard WebSocket semantics.\n *\n * @param url - Absolute WebSocket URL (`ws://` or `wss://`).\n * @param options - Optional sub-protocols and headers, see {@link ClientWebSocketOptions}.\n * @returns A connected (or connecting) `WebSocket` instance.\n *\n * @throws {Error} When no native `WebSocket` is available in the current runtime.\n *\n * @example\n * ```ts\n * import { createWebSocket } from \"@hedystia/ws/client\";\n *\n * const ws = createWebSocket(\"ws://localhost:3000\", {\n * protocols: \"v1\",\n * headers: { authorization: \"Bearer ...\" },\n * });\n *\n * ws.onopen = () => ws.send(\"hi\");\n * ws.onmessage = (event) => console.log(event.data);\n * ```\n */\nexport function createWebSocket(url: string, options?: ClientWebSocketOptions): WebSocket {\n const Ctor = resolveWebSocket();\n\n if (options?.protocols) {\n return new Ctor(url, options.protocols as any);\n }\n return new Ctor(url);\n}\n\n/**\n * Lightweight runtime-agnostic wrapper that mirrors a small, predictable\n * subset of the WHATWG `WebSocket` interface.\n *\n * @remarks\n * Useful for higher-level code that wants to assign event handlers by\n * property (`socket.onmessage = …`) without referencing the global\n * `WebSocket` type directly. Delegates all operations to the native\n * `WebSocket` produced by {@link createWebSocket}.\n *\n * @example\n * ```ts\n * import { WebSocketClient } from \"@hedystia/ws/client\";\n *\n * const client = new WebSocketClient(\"ws://localhost:3000\");\n * client.onopen = () => client.send(\"hello\");\n * client.onmessage = (event) => console.log(event.data);\n * ```\n */\nexport class WebSocketClient {\n /**\n * Underlying WebSocket instance produced by {@link createWebSocket}.\n *\n * @readonly\n */\n readonly socket: WebSocket;\n\n /**\n * Create a new client and immediately initiate the connection.\n *\n * @param url - Absolute WebSocket URL (`ws://` or `wss://`).\n * @param options - Optional sub-protocols / headers; see {@link ClientWebSocketOptions}.\n */\n constructor(url: string, options?: ClientWebSocketOptions) {\n this.socket = createWebSocket(url, options);\n }\n\n /**\n * Current connection state, mirroring {@link WebSocket.readyState}.\n *\n * @returns `0` connecting · `1` open · `2` closing · `3` closed.\n */\n get readyState(): number {\n return this.socket.readyState;\n }\n\n /**\n * Send a payload to the server.\n *\n * @param data - WHATWG-compatible payload.\n */\n send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void {\n this.socket.send(data as any);\n }\n\n /**\n * Close the underlying socket.\n *\n * @param code - Close code (defaults to `1000`).\n * @param reason - Optional human-readable reason phrase.\n */\n close(code?: number, reason?: string): void {\n this.socket.close(code, reason);\n }\n\n /**\n * Assign the open-event listener.\n */\n set onopen(cb: ((ev: Event) => void) | null) {\n (this.socket as any).onopen = cb;\n }\n\n /**\n * Assign the message-event listener.\n */\n set onmessage(cb: ((ev: MessageEvent) => void) | null) {\n (this.socket as any).onmessage = cb;\n }\n\n /**\n * Assign the close-event listener.\n */\n set onclose(cb: ((ev: CloseEvent) => void) | null) {\n (this.socket as any).onclose = cb;\n }\n\n /**\n * Assign the error-event listener.\n */\n set onerror(cb: ((ev: Event) => void) | null) {\n (this.socket as any).onerror = cb;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,mBAAqC;CACnD,IAAI,OAAO,eAAe,eAAgB,WAAmB,WAC3D,OAAQ,WAAmB;CAE7B,MAAM,IAAI,MACR,6HAEF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,gBAAgB,KAAa,SAA6C;CACxF,MAAM,OAAO,iBAAiB;CAE9B,IAAI,SAAS,WACX,OAAO,IAAI,KAAK,KAAK,QAAQ,SAAgB;CAE/C,OAAO,IAAI,KAAK,GAAG;AACrB;;;;;;;;;;;;;;;;;;;;AAqBA,IAAa,kBAAb,MAA6B;;;;;;CAM3B;;;;;;;CAQA,YAAY,KAAa,SAAkC;EACzD,KAAK,SAAS,gBAAgB,KAAK,OAAO;CAC5C;;;;;;CAOA,IAAI,aAAqB;EACvB,OAAO,KAAK,OAAO;CACrB;;;;;;CAOA,KAAK,MAA+D;EAClE,KAAK,OAAO,KAAK,IAAW;CAC9B;;;;;;;CAQA,MAAM,MAAe,QAAuB;EAC1C,KAAK,OAAO,MAAM,MAAM,MAAM;CAChC;;;;CAKA,IAAI,OAAO,IAAkC;EAC3C,KAAM,OAAe,SAAS;CAChC;;;;CAKA,IAAI,UAAU,IAAyC;EACrD,KAAM,OAAe,YAAY;CACnC;;;;CAKA,IAAI,QAAQ,IAAuC;EACjD,KAAM,OAAe,UAAU;CACjC;;;;CAKA,IAAI,QAAQ,IAAkC;EAC5C,KAAM,OAAe,UAAU;CACjC;AACF"}
|
package/dist/runtime.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.cjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * String identifier of the JavaScript runtime hosting the current process.\n */\nexport type Runtime = \"bun\" | \"node\" | \"deno\" | \"browser\" | \"unknown\";\n\n/**\n * Detect the host runtime by probing well-known globals.\n *\n * @returns A {@link Runtime} discriminator for the active environment.\n *\n * @example\n * ```ts\n * import { detectRuntime } from \"@hedystia/ws\";\n *\n * if (detectRuntime() === \"bun\") {\n * // ...use Bun-specific APIs\n * }\n * ```\n */\nexport function detectRuntime(): Runtime {\n if (typeof globalThis === \"undefined\") {\n return \"unknown\";\n }\n const g = globalThis as any;\n if (g.Bun?.serve) {\n return \"bun\";\n }\n if (g.Deno) {\n return \"deno\";\n }\n if (g.process?.versions?.node) {\n return \"node\";\n }\n if (typeof g.window !== \"undefined\" && typeof g.document !== \"undefined\") {\n return \"browser\";\n }\n return \"unknown\";\n}\n\n/**\n * Convenience predicate.\n *\n * @returns `true` when running on Bun.\n */\nexport const isBun = (): boolean => detectRuntime() === \"bun\";\n\n/**\n * Convenience predicate.\n *\n * @returns `true` when running on Node.js.\n */\nexport const isNode = (): boolean => detectRuntime() === \"node\";\n\n/**\n * Convenience predicate.\n *\n * @returns `true` when running on Deno.\n */\nexport const isDeno = (): boolean => detectRuntime() === \"deno\";\n\n/**\n * Convenience predicate.\n *\n * @returns `true` when running inside a browser-like environment.\n */\nexport const isBrowser = (): boolean => detectRuntime() === \"browser\";\n"],"mappings":";;;;;;;;;;;;;;;AAmBA,SAAgB,gBAAyB;CACvC,IAAI,OAAO,eAAe,aACxB,OAAO;CAET,MAAM,IAAI;CACV,IAAI,EAAE,KAAK,OACT,OAAO;CAET,IAAI,EAAE,MACJ,OAAO;CAET,IAAI,EAAE,SAAS,UAAU,MACvB,OAAO;CAET,IAAI,OAAO,EAAE,WAAW,eAAe,OAAO,EAAE,aAAa,aAC3D,OAAO;CAET,OAAO
|
|
1
|
+
{"version":3,"file":"runtime.cjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * String identifier of the JavaScript runtime hosting the current process.\n */\nexport type Runtime = \"bun\" | \"node\" | \"deno\" | \"browser\" | \"unknown\";\n\n/**\n * Detect the host runtime by probing well-known globals.\n *\n * @returns A {@link Runtime} discriminator for the active environment.\n *\n * @example\n * ```ts\n * import { detectRuntime } from \"@hedystia/ws\";\n *\n * if (detectRuntime() === \"bun\") {\n * // ...use Bun-specific APIs\n * }\n * ```\n */\nexport function detectRuntime(): Runtime {\n if (typeof globalThis === \"undefined\") {\n return \"unknown\";\n }\n const g = globalThis as any;\n if (g.Bun?.serve) {\n return \"bun\";\n }\n if (g.Deno) {\n return \"deno\";\n }\n if (g.process?.versions?.node) {\n return \"node\";\n }\n if (typeof g.window !== \"undefined\" && typeof g.document !== \"undefined\") {\n return \"browser\";\n }\n return \"unknown\";\n}\n\n/**\n * Convenience predicate.\n *\n * @returns `true` when running on Bun.\n */\nexport const isBun = (): boolean => detectRuntime() === \"bun\";\n\n/**\n * Convenience predicate.\n *\n * @returns `true` when running on Node.js.\n */\nexport const isNode = (): boolean => detectRuntime() === \"node\";\n\n/**\n * Convenience predicate.\n *\n * @returns `true` when running on Deno.\n */\nexport const isDeno = (): boolean => detectRuntime() === \"deno\";\n\n/**\n * Convenience predicate.\n *\n * @returns `true` when running inside a browser-like environment.\n */\nexport const isBrowser = (): boolean => detectRuntime() === \"browser\";\n"],"mappings":";;;;;;;;;;;;;;;AAmBA,SAAgB,gBAAyB;CACvC,IAAI,OAAO,eAAe,aACxB,OAAO;CAET,MAAM,IAAI;CACV,IAAI,EAAE,KAAK,OACT,OAAO;CAET,IAAI,EAAE,MACJ,OAAO;CAET,IAAI,EAAE,SAAS,UAAU,MACvB,OAAO;CAET,IAAI,OAAO,EAAE,WAAW,eAAe,OAAO,EAAE,aAAa,aAC3D,OAAO;CAET,OAAO;AACT;;;;;;AAOA,MAAa,cAAuB,cAAc,MAAM;;;;;;AAOxD,MAAa,eAAwB,cAAc,MAAM;;;;;;AAOzD,MAAa,eAAwB,cAAc,MAAM;;;;;;AAOzD,MAAa,kBAA2B,cAAc,MAAM"}
|
package/dist/runtime.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.mjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * String identifier of the JavaScript runtime hosting the current process.\n */\nexport type Runtime = \"bun\" | \"node\" | \"deno\" | \"browser\" | \"unknown\";\n\n/**\n * Detect the host runtime by probing well-known globals.\n *\n * @returns A {@link Runtime} discriminator for the active environment.\n *\n * @example\n * ```ts\n * import { detectRuntime } from \"@hedystia/ws\";\n *\n * if (detectRuntime() === \"bun\") {\n * // ...use Bun-specific APIs\n * }\n * ```\n */\nexport function detectRuntime(): Runtime {\n if (typeof globalThis === \"undefined\") {\n return \"unknown\";\n }\n const g = globalThis as any;\n if (g.Bun?.serve) {\n return \"bun\";\n }\n if (g.Deno) {\n return \"deno\";\n }\n if (g.process?.versions?.node) {\n return \"node\";\n }\n if (typeof g.window !== \"undefined\" && typeof g.document !== \"undefined\") {\n return \"browser\";\n }\n return \"unknown\";\n}\n\n/**\n * Convenience predicate.\n *\n * @returns `true` when running on Bun.\n */\nexport const isBun = (): boolean => detectRuntime() === \"bun\";\n\n/**\n * Convenience predicate.\n *\n * @returns `true` when running on Node.js.\n */\nexport const isNode = (): boolean => detectRuntime() === \"node\";\n\n/**\n * Convenience predicate.\n *\n * @returns `true` when running on Deno.\n */\nexport const isDeno = (): boolean => detectRuntime() === \"deno\";\n\n/**\n * Convenience predicate.\n *\n * @returns `true` when running inside a browser-like environment.\n */\nexport const isBrowser = (): boolean => detectRuntime() === \"browser\";\n"],"mappings":";;;;;;;;;;;;;;;AAmBA,SAAgB,gBAAyB;CACvC,IAAI,OAAO,eAAe,aACxB,OAAO;CAET,MAAM,IAAI;CACV,IAAI,EAAE,KAAK,OACT,OAAO;CAET,IAAI,EAAE,MACJ,OAAO;CAET,IAAI,EAAE,SAAS,UAAU,MACvB,OAAO;CAET,IAAI,OAAO,EAAE,WAAW,eAAe,OAAO,EAAE,aAAa,aAC3D,OAAO;CAET,OAAO
|
|
1
|
+
{"version":3,"file":"runtime.mjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * String identifier of the JavaScript runtime hosting the current process.\n */\nexport type Runtime = \"bun\" | \"node\" | \"deno\" | \"browser\" | \"unknown\";\n\n/**\n * Detect the host runtime by probing well-known globals.\n *\n * @returns A {@link Runtime} discriminator for the active environment.\n *\n * @example\n * ```ts\n * import { detectRuntime } from \"@hedystia/ws\";\n *\n * if (detectRuntime() === \"bun\") {\n * // ...use Bun-specific APIs\n * }\n * ```\n */\nexport function detectRuntime(): Runtime {\n if (typeof globalThis === \"undefined\") {\n return \"unknown\";\n }\n const g = globalThis as any;\n if (g.Bun?.serve) {\n return \"bun\";\n }\n if (g.Deno) {\n return \"deno\";\n }\n if (g.process?.versions?.node) {\n return \"node\";\n }\n if (typeof g.window !== \"undefined\" && typeof g.document !== \"undefined\") {\n return \"browser\";\n }\n return \"unknown\";\n}\n\n/**\n * Convenience predicate.\n *\n * @returns `true` when running on Bun.\n */\nexport const isBun = (): boolean => detectRuntime() === \"bun\";\n\n/**\n * Convenience predicate.\n *\n * @returns `true` when running on Node.js.\n */\nexport const isNode = (): boolean => detectRuntime() === \"node\";\n\n/**\n * Convenience predicate.\n *\n * @returns `true` when running on Deno.\n */\nexport const isDeno = (): boolean => detectRuntime() === \"deno\";\n\n/**\n * Convenience predicate.\n *\n * @returns `true` when running inside a browser-like environment.\n */\nexport const isBrowser = (): boolean => detectRuntime() === \"browser\";\n"],"mappings":";;;;;;;;;;;;;;;AAmBA,SAAgB,gBAAyB;CACvC,IAAI,OAAO,eAAe,aACxB,OAAO;CAET,MAAM,IAAI;CACV,IAAI,EAAE,KAAK,OACT,OAAO;CAET,IAAI,EAAE,MACJ,OAAO;CAET,IAAI,EAAE,SAAS,UAAU,MACvB,OAAO;CAET,IAAI,OAAO,EAAE,WAAW,eAAe,OAAO,EAAE,aAAa,aAC3D,OAAO;CAET,OAAO;AACT;;;;;;AAOA,MAAa,cAAuB,cAAc,MAAM;;;;;;AAOxD,MAAa,eAAwB,cAAc,MAAM;;;;;;AAOzD,MAAa,eAAwB,cAAc,MAAM;;;;;;AAOzD,MAAa,kBAA2B,cAAc,MAAM"}
|
package/dist/server.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.cjs","names":[],"sources":["../src/server.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { Duplex } from \"node:stream\";\n\nimport type {\n ServerWebSocket,\n UpgradeOptions,\n UpgradeRequest,\n WebSocketHandlers,\n WebSocketServerOptions,\n WSData,\n WSMessage,\n} from \"./types\";\n\nexport type {\n ServerWebSocket,\n UpgradeOptions,\n UpgradeRequest,\n WebSocketHandlers,\n WebSocketServerOptions,\n WSData,\n WSMessage,\n} from \"./types\";\n\n/**\n * WebSocket magic GUID defined in RFC 6455 section 4.2.2.\n *\n * @internal\n */\nconst WS_GUID = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\n\nconst OP_CONTINUATION = 0x0;\nconst OP_TEXT = 0x1;\nconst OP_BINARY = 0x2;\nconst OP_CLOSE = 0x8;\nconst OP_PING = 0x9;\nconst OP_PONG = 0xa;\n\n/**\n * Compute the `Sec-WebSocket-Accept` response header per RFC 6455 §4.2.2.\n *\n * @param clientKey - Value of the `Sec-WebSocket-Key` request header\n * @returns Base-64 encoded SHA-1 digest\n *\n * @internal\n */\nfunction computeAccept(clientKey: string): string {\n return createHash(\"sha1\")\n .update(clientKey + WS_GUID)\n .digest(\"base64\");\n}\n\n/**\n * Write a single unmasked (server-to-client) WebSocket data frame to a raw\n * duplex socket per RFC 6455 §5.2.\n *\n * @param socket - Underlying duplex transport\n * @param opcode - WebSocket opcode (text, binary, close, ping, pong …)\n * @param payload - Payload bytes to encapsulate\n *\n * @internal\n */\nfunction writeFrame(socket: Duplex, opcode: number, payload: Buffer): void {\n const len = payload.length;\n let header: Buffer;\n\n if (len < 126) {\n header = Buffer.allocUnsafe(2);\n header[0] = 0x80 | opcode;\n header[1] = len;\n } else if (len < 0x10000) {\n header = Buffer.allocUnsafe(4);\n header[0] = 0x80 | opcode;\n header[1] = 126;\n header.writeUInt16BE(len, 2);\n } else {\n header = Buffer.allocUnsafe(10);\n header[0] = 0x80 | opcode;\n header[1] = 127;\n header.writeUInt32BE(0, 2);\n header.writeUInt32BE(len >>> 0, 6);\n }\n\n if (payload.length === 0) {\n socket.write(header);\n } else {\n socket.write(Buffer.concat([header, payload]));\n }\n}\n\n/**\n * Incremental streaming RFC 6455 frame parser for masked client→server frames.\n * Handles fragmentation, ping/pong, and close frames internally.\n *\n * @internal\n */\nclass FrameParser {\n private buf: Buffer<ArrayBufferLike> = Buffer.alloc(0);\n private fragments: Buffer[] = [];\n private fragmentOpcode = 0;\n private readonly maxPayload: number;\n\n /** Callback fired when a complete data frame is assembled. */\n onMessage?: (data: Buffer | string, isBinary: boolean) => void;\n /** Callback fired when a close frame is received. */\n onClose?: (code: number, reason: string) => void;\n /** Callback fired when a ping frame is received. */\n onPing?: (data: Buffer) => void;\n /** Callback fired on parse errors or protocol violations. */\n onError?: (err: Error) => void;\n\n /**\n * @param maxPayload - Maximum allowed frame payload in bytes (default 100 MiB)\n */\n constructor(maxPayload = 100 * 1024 * 1024) {\n this.maxPayload = maxPayload;\n }\n\n /**\n * Feed a new chunk of raw socket data into the parser.\n *\n * @param chunk - Incoming bytes from the transport\n */\n push(chunk: Buffer): void {\n this.buf = this.buf.length === 0 ? chunk : Buffer.concat([this.buf, chunk]);\n this.drain();\n }\n\n /**\n * Consume as many complete frames as possible from the internal buffer.\n *\n * @internal\n */\n private drain(): void {\n for (;;) {\n if (this.buf.length < 2) {\n return;\n }\n\n const b0 = this.buf[0]!;\n const b1 = this.buf[1]!;\n const fin = (b0 & 0x80) !== 0;\n const rsv = b0 & 0x70;\n const opcode = b0 & 0x0f;\n const masked = (b1 & 0x80) !== 0;\n let payloadLen = b1 & 0x7f;\n let offset = 2;\n\n if (rsv !== 0) {\n this.onError?.(new Error(\"WebSocket: RSV bits must be 0 (no extensions negotiated)\"));\n return;\n }\n\n if (payloadLen === 126) {\n if (this.buf.length < 4) {\n return;\n }\n payloadLen = this.buf.readUInt16BE(2);\n offset = 4;\n } else if (payloadLen === 127) {\n if (this.buf.length < 10) {\n return;\n }\n const hi = this.buf.readUInt32BE(2);\n const lo = this.buf.readUInt32BE(6);\n payloadLen = hi * 0x1_0000_0000 + lo;\n offset = 10;\n }\n\n if (payloadLen > this.maxPayload) {\n this.onError?.(\n new Error(\n `WebSocket: payload length ${payloadLen} exceeds maxPayload ${this.maxPayload}`,\n ),\n );\n return;\n }\n\n const maskLen = masked ? 4 : 0;\n const frameEnd = offset + maskLen + payloadLen;\n if (this.buf.length < frameEnd) {\n return;\n }\n\n let payload: Buffer;\n if (masked) {\n const mask = this.buf.subarray(offset, offset + 4);\n payload = Buffer.allocUnsafe(payloadLen);\n for (let i = 0; i < payloadLen; i++) {\n payload[i] = this.buf[offset + 4 + i]! ^ mask[i & 3]!;\n }\n } else {\n payload = Buffer.from(this.buf.subarray(offset, frameEnd));\n }\n\n this.buf = this.buf.subarray(frameEnd);\n this.handleFrame(fin, opcode, payload);\n }\n }\n\n /**\n * Route a parsed frame to the appropriate callback based on opcode.\n *\n * @param fin - Whether this is the final fragment\n * @param opcode - WebSocket frame opcode\n * @param payload - Unmasked payload bytes\n *\n * @internal\n */\n private handleFrame(fin: boolean, opcode: number, payload: Buffer): void {\n switch (opcode) {\n case OP_PING:\n this.onPing?.(payload);\n return;\n\n case OP_PONG:\n return;\n\n case OP_CLOSE: {\n const code = payload.length >= 2 ? payload.readUInt16BE(0) : 1000;\n const reason = payload.length > 2 ? payload.subarray(2).toString(\"utf8\") : \"\";\n this.onClose?.(code, reason);\n return;\n }\n\n case OP_CONTINUATION:\n case OP_TEXT:\n case OP_BINARY:\n if (opcode !== OP_CONTINUATION) {\n this.fragmentOpcode = opcode;\n }\n this.fragments.push(payload);\n if (fin) {\n const full = Buffer.concat(this.fragments);\n this.fragments = [];\n const isBinary = this.fragmentOpcode === OP_BINARY;\n this.onMessage?.(isBinary ? full : full.toString(\"utf8\"), isBinary);\n }\n return;\n\n default:\n this.onError?.(new Error(`WebSocket: unknown opcode 0x${opcode.toString(16)}`));\n }\n }\n}\n\n/**\n * Thin wrapper around a raw `Duplex` that exposes a minimal frame-aware\n * WebSocket interface used internally by {@link WebSocketServer}.\n *\n * @internal\n */\nclass NativeSocket {\n /** Underlying duplex transport stream. */\n readonly duplex: Duplex;\n /** Incremental frame parser attached to this transport. */\n readonly parser: FrameParser;\n\n /** WHATWG-compatible ready-state: `1` open · `2` closing · `3` closed. */\n readyState: 1 | 2 | 3 = 1;\n\n /**\n * @param duplex - Raw duplex transport\n * @param maxPayload - Maximum allowed payload in bytes\n */\n constructor(duplex: Duplex, maxPayload?: number) {\n this.duplex = duplex;\n this.parser = new FrameParser(maxPayload);\n }\n\n /**\n * Send a data frame to the peer.\n *\n * @param data - Payload to transmit\n */\n send(data: string | Buffer | Uint8Array): void {\n if (this.readyState !== 1) {\n return;\n }\n const isBuf = Buffer.isBuffer(data);\n const buf =\n typeof data === \"string\"\n ? Buffer.from(data, \"utf8\")\n : isBuf\n ? data\n : Buffer.from(data as Uint8Array);\n writeFrame(this.duplex, typeof data === \"string\" ? OP_TEXT : OP_BINARY, buf);\n }\n\n /**\n * Initiate the close handshake.\n *\n * @param code - Close status code (default `1000`)\n * @param reason - Optional UTF-8 reason phrase\n */\n close(code = 1000, reason = \"\"): void {\n if (this.readyState !== 1) {\n return;\n }\n this.readyState = 2;\n const reasonBuf = Buffer.from(reason, \"utf8\");\n const payload = Buffer.allocUnsafe(2 + reasonBuf.length);\n payload.writeUInt16BE(code, 0);\n reasonBuf.copy(payload, 2);\n writeFrame(this.duplex, OP_CLOSE, payload);\n }\n\n /**\n * Hard-terminate the underlying transport without a close handshake.\n */\n terminate(): void {\n this.readyState = 3;\n this.duplex.destroy();\n }\n}\n\n/**\n * Runtime-agnostic WebSocket server built entirely on Node.js built-ins\n * (`node:crypto`, `node:stream`) — no third-party dependencies.\n *\n * @remarks\n * The class does **not** create or own an HTTP server. Callers feed it raw\n * upgrade tuples coming from any HTTP runtime (Node.js `http`, Bun, Deno,\n * Hono, Fastify's upgrade hook, etc.).\n *\n * Topic-based pub/sub is implemented in user-space, matching the shape of\n * `Bun.ServerWebSocket` so the same handler code runs on every runtime.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n *\n * @example\n * ```ts\n * import { createServer } from \"node:http\";\n * import { WebSocketServer } from \"@hedystia/ws/server\";\n *\n * const wss = new WebSocketServer({\n * open: (ws) => ws.send(\"welcome\"),\n * message: (ws, msg) => ws.publish(\"room\", msg),\n * close: (ws, code) => console.log(\"closed\", code),\n * });\n *\n * const http = createServer((_req, res) => res.end(\"ok\"));\n * http.on(\"upgrade\", (req, socket, head) => {\n * wss.upgrade({ rawRequest: req, socket, head }, { data: { user: \"anon\" } });\n * });\n * http.listen(3000);\n * ```\n */\nexport class WebSocketServer<Data extends WSData = WSData> {\n private readonly handlers: WebSocketHandlers<Data>;\n private readonly maxPayload: number | undefined;\n private readonly resolveData: ((req: any) => Record<string, any>) | undefined;\n private readonly topics = new Map<string, Set<any>>();\n private readonly socketTopics = new WeakMap<object, Set<string>>();\n private readonly allSockets = new Set<any>();\n\n /**\n * Build a new WebSocket server.\n *\n * @param handlers - Lifecycle handlers ({@link WebSocketHandlers})\n * @param options - Optional behavioural overrides ({@link WebSocketServerOptions})\n *\n * @example\n * ```ts\n * const wss = new WebSocketServer(\n * { message: (ws, msg) => ws.send(msg) },\n * { maxPayload: 1024 * 1024 },\n * );\n * ```\n */\n constructor(handlers: WebSocketHandlers<Data>, options: WebSocketServerOptions = {}) {\n this.handlers = handlers;\n this.maxPayload = options.maxPayload;\n this.resolveData = options.resolveData;\n }\n\n /**\n * Upgrade a raw HTTP upgrade tuple to a WebSocket connection.\n *\n * @remarks\n * Performs the RFC 6455 handshake synchronously on the duplex socket,\n * then wires up frame parsing and lifecycle handlers. The returned\n * promise resolves to the {@link ServerWebSocket} wrapper immediately\n * after the handshake bytes are written.\n *\n * @param req - Upgrade tuple emitted by `node:http`'s `'upgrade'` event\n * @param options - Optional initial `data` for the new connection\n * @returns Promise that resolves with the established socket wrapper\n *\n * @throws {Error} When `Sec-WebSocket-Key` is absent from the request headers\n *\n * @example\n * ```ts\n * http.on(\"upgrade\", async (req, socket, head) => {\n * try {\n * await wss.upgrade({ rawRequest: req, socket, head });\n * } catch (err) {\n * console.error(\"Upgrade failed\", err);\n * socket.destroy();\n * }\n * });\n * ```\n */\n upgrade(req: UpgradeRequest, options?: UpgradeOptions<Data>): Promise<ServerWebSocket<Data>> {\n return new Promise((resolve, reject) => {\n try {\n const rawReq = req.rawRequest;\n const duplex = req.socket as Duplex;\n\n const clientKey = rawReq.headers?.[\"sec-websocket-key\"] as string | undefined;\n if (!clientKey) {\n duplex.destroy();\n return reject(new Error(\"WebSocket upgrade: missing Sec-WebSocket-Key header\"));\n }\n\n const accept = computeAccept(clientKey);\n const rawProtocol = rawReq.headers?.[\"sec-websocket-protocol\"] as string | undefined;\n const protocol = rawProtocol?.split(\",\")[0]?.trim();\n\n let response =\n \"HTTP/1.1 101 Switching Protocols\\r\\n\" +\n \"Upgrade: websocket\\r\\n\" +\n \"Connection: Upgrade\\r\\n\" +\n `Sec-WebSocket-Accept: ${accept}\\r\\n`;\n\n if (protocol) {\n response += `Sec-WebSocket-Protocol: ${protocol}\\r\\n`;\n }\n response += \"\\r\\n\";\n duplex.write(response);\n\n const native = new NativeSocket(duplex, this.maxPayload);\n const data = (options?.data ?? {}) as Data;\n const wrapped = this.wrap(native, data, rawReq);\n this.bind(native, wrapped, req.head);\n\n resolve(wrapped);\n } catch (err) {\n reject(err);\n }\n });\n }\n\n /**\n * Publish a message to all sockets currently subscribed to `topic`.\n *\n * @param topic - Topic name\n * @param message - Payload to broadcast\n * @param _compress - Reserved for future use\n * @returns Number of sockets that received the message\n *\n * @example\n * ```ts\n * wss.publish(\"room\", JSON.stringify({ kind: \"ping\" }));\n * ```\n */\n publish(topic: string, message: WSMessage, _compress?: boolean): number {\n const set = this.topics.get(topic);\n if (!set || set.size === 0) {\n return 0;\n }\n const payload = toSendable(message);\n let count = 0;\n for (const native of set) {\n if (native.readyState === 1) {\n native.send(payload);\n count++;\n }\n }\n return count;\n }\n\n /**\n * Close the server and optionally terminate all live sockets.\n *\n * @param closeActiveConnections - When `true`, immediately terminates\n * every live connection before clearing internal state\n */\n close(closeActiveConnections = false): void {\n if (closeActiveConnections) {\n for (const native of this.allSockets) {\n try {\n if (typeof (native as any).terminate === \"function\") {\n (native as any).terminate();\n } else if (typeof (native as any).close === \"function\") {\n (native as any).close(1001, \"Server shutdown\");\n }\n } catch {\n /* ignore */\n }\n }\n this.allSockets.clear();\n }\n this.topics.clear();\n }\n\n /**\n * Wire up the duplex transport listeners (`data`, `close`, `error`) and\n * invoke the user's `open` handler.\n *\n * @param native - Wrapped transport socket\n * @param wrapped - Public {@link ServerWebSocket} wrapper\n * @param head - Buffered bytes captured by the HTTP parser (re-fed into the parser)\n *\n * @internal\n */\n private bind(\n native: NativeSocket,\n wrapped: ServerWebSocket<Data>,\n head: Buffer | Uint8Array,\n ): void {\n this.allSockets.add(native);\n\n if (head && head.length > 0) {\n native.parser.push(Buffer.isBuffer(head) ? head : Buffer.from(head));\n }\n\n native.parser.onMessage = (raw, isBinary) => {\n const message: WSMessage = isBinary\n ? raw instanceof Buffer\n ? raw\n : Buffer.from(raw as Uint8Array)\n : (raw as string);\n Promise.resolve(this.handlers.message(wrapped, message)).catch((err) =>\n console.error(\"[ws] message handler error:\", err),\n );\n };\n\n native.parser.onClose = (code, reason) => {\n if (native.readyState === 1) {\n native.close(code, reason);\n }\n this.cleanup(native);\n if (this.handlers.close) {\n Promise.resolve(this.handlers.close(wrapped, code, reason)).catch((err) =>\n console.error(\"[ws] close handler error:\", err),\n );\n }\n };\n\n native.parser.onPing = (data) => {\n if (native.readyState === 1) {\n writeFrame(native.duplex, OP_PONG, data);\n }\n };\n\n native.parser.onError = (err) => {\n native.terminate();\n this.cleanup(native);\n if (this.handlers.error) {\n Promise.resolve(this.handlers.error(wrapped, err)).catch((e) =>\n console.error(\"[ws] error handler error:\", e),\n );\n }\n };\n\n native.duplex.on(\"data\", (chunk: Buffer) => {\n try {\n native.parser.push(chunk);\n } catch (err) {\n console.error(\"[ws] frame parsing error:\", err);\n native.terminate();\n this.cleanup(native);\n }\n });\n\n native.duplex.on(\"close\", () => {\n if (native.readyState !== 3) {\n native.readyState = 3;\n this.cleanup(native);\n if (this.handlers.close) {\n Promise.resolve(this.handlers.close(wrapped, 1006, \"\")).catch((err) =>\n console.error(\"[ws] close handler error:\", err),\n );\n }\n }\n });\n\n native.duplex.on(\"error\", (err: Error) => {\n native.readyState = 3;\n this.cleanup(native);\n if (this.handlers.error) {\n Promise.resolve(this.handlers.error(wrapped, err)).catch((e) =>\n console.error(\"[ws] error handler error:\", e),\n );\n }\n });\n\n if (this.handlers.open) {\n Promise.resolve(this.handlers.open(wrapped)).catch((err) =>\n console.error(\"[ws] open handler error:\", err),\n );\n }\n }\n\n /**\n * Remove a socket from every topic it had joined and from the global\n * tracking set.\n *\n * @param native - Socket being cleaned up\n *\n * @internal\n */\n private cleanup(native: any): void {\n const owned = this.socketTopics.get(native);\n if (owned) {\n for (const topic of owned) {\n this.topics.get(topic)?.delete(native);\n }\n this.socketTopics.delete(native);\n }\n this.allSockets.delete(native);\n }\n\n /**\n * Build the {@link ServerWebSocket} wrapper exposed to user handlers,\n * embedding topic-based pub/sub backed by the server's internal maps.\n *\n * @param native - Wrapped transport socket\n * @param data - User-supplied `data` payload attached on upgrade\n * @param rawReq - Raw incoming message used to extract the remote address\n * @returns The fully-featured public wrapper\n *\n * @internal\n */\n private wrap(native: NativeSocket, data: Data, rawReq: any): ServerWebSocket<Data> {\n const remoteAddress: string =\n (rawReq?.socket?.remoteAddress as string) ||\n (rawReq?.headers?.[\"x-forwarded-for\"] as string) ||\n \"\";\n\n const subscribe = (topic: string): void => {\n let set = this.topics.get(topic);\n if (!set) {\n set = new Set();\n this.topics.set(topic, set);\n }\n set.add(native);\n let owned = this.socketTopics.get(native);\n if (!owned) {\n owned = new Set();\n this.socketTopics.set(native, owned);\n }\n owned.add(topic);\n };\n\n const unsubscribe = (topic: string): void => {\n this.topics.get(topic)?.delete(native);\n this.socketTopics.get(native)?.delete(topic);\n };\n\n const publishToPeers = (topic: string, message: WSMessage): void => {\n const set = this.topics.get(topic);\n if (!set) {\n return;\n }\n const payload = toSendable(message);\n for (const peer of set) {\n if (peer !== native && peer.readyState === 1) {\n peer.send(payload);\n }\n }\n };\n\n const wrapper: ServerWebSocket<Data> = {\n data,\n get readyState() {\n return native.readyState;\n },\n remoteAddress,\n send: (message, _compress) => {\n const payload = toSendable(message);\n native.send(payload);\n return typeof payload === \"string\"\n ? Buffer.byteLength(payload as string)\n : (payload as Buffer | Uint8Array).byteLength;\n },\n close: (code, reason) => {\n native.close(code, reason);\n },\n subscribe,\n unsubscribe,\n publish: publishToPeers,\n isSubscribed: (topic) => !!this.socketTopics.get(native)?.has(topic),\n cork: (cb) => cb(wrapper),\n };\n\n return wrapper;\n }\n}\n\n/**\n * Return value of {@link serve}.\n */\nexport interface ServeInfo {\n /** Port the HTTP server is listening on. */\n port: number;\n /** Hostname the HTTP server bound to. */\n hostname: string;\n /** Full URL of the listening server (uses `http://` scheme). */\n url: URL;\n /**\n * Publish a message to all sockets subscribed to `topic`.\n *\n * @param topic - Topic name\n * @param message - Payload to broadcast\n * @param compress - Whether to compress (honoured on Bun, ignored on Node)\n * @returns Number of sockets that received the message\n */\n publish: (topic: string, message: WSMessage, compress?: boolean) => number;\n /**\n * Stop the server and optionally close active connections.\n *\n * @param closeActiveConnections - When `true`, terminates all live sockets\n */\n stop: (closeActiveConnections?: boolean) => void | Promise<void>;\n}\n\n/**\n * Start a standalone WebSocket server on the given port.\n *\n * @remarks\n * Auto-detects the runtime and uses the native implementation:\n * - **Bun:** delegates to `Bun.serve()` with native WebSocket support\n * - **Node/Deno:** creates a `node:http` server with the built-in\n * {@link WebSocketServer} upgrade handler\n *\n * @typeParam Data - Shape of the user-attached `data` field\n *\n * @param handlers - Lifecycle handlers ({@link WebSocketHandlers})\n * @param options - Server options including `port` and `hostname`\n * @returns A promise resolving to {@link ServeInfo}\n *\n * @example\n * ```ts\n * import { serve } from \"@hedystia/ws\";\n *\n * const server = await serve({\n * open: (ws) => ws.subscribe(\"global\"),\n * message: (ws, msg) => ws.publish(\"global\", msg),\n * });\n *\n * console.log(`Listening on ${server.url}`);\n * ```\n */\nexport async function serve<Data extends WSData = WSData>(\n handlers: WebSocketHandlers<Data>,\n options?: WebSocketServerOptions & { port?: number; hostname?: string },\n): Promise<ServeInfo> {\n const { detectRuntime } = await import(\"./runtime\");\n const runtime = detectRuntime();\n\n if (runtime === \"bun\") {\n return serveBun(handlers, options);\n }\n return serveNode(handlers, options);\n}\n\n/**\n * Start a WebSocket server using Bun's native `Bun.serve()`.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n * @param handlers - Lifecycle handlers\n * @param options - Server options including `port` and `hostname`\n * @returns A promise resolving to {@link ServeInfo}\n *\n * @internal\n */\nasync function serveBun<Data extends WSData = WSData>(\n handlers: WebSocketHandlers<Data>,\n options?: WebSocketServerOptions & { port?: number; hostname?: string },\n): Promise<ServeInfo> {\n const topics = new Map<string, Set<ServerWebSocket<Data>>>();\n const socketTopics = new WeakMap<ServerWebSocket<Data>, Set<string>>();\n const allSockets = new Set<ServerWebSocket<Data>>();\n const nativeToWrapped = new WeakMap<any, ServerWebSocket<Data>>();\n\n function cleanup(ws: ServerWebSocket<Data>): void {\n const owned = socketTopics.get(ws);\n if (owned) {\n for (const topic of owned) {\n topics.get(topic)?.delete(ws);\n }\n socketTopics.delete(ws);\n }\n allSockets.delete(ws);\n }\n\n const server = (globalThis as any).Bun.serve({\n port: options?.port ?? 0,\n hostname: options?.hostname ?? \"0.0.0.0\",\n fetch: (req: Request) => {\n if (req.headers.get(\"upgrade\") === \"websocket\") {\n const data = (options?.resolveData ? options.resolveData(req) : {}) as Data;\n const ok = server.upgrade(req, { data });\n return ok ? undefined : new Response(\"upgrade failed\", { status: 400 });\n }\n return new Response(\"Not found\", { status: 404 });\n },\n websocket: {\n open: (ws: any) => {\n const data = ws.data ?? ({} as Data);\n const wrapped: ServerWebSocket<Data> = createBunWrapper(ws, data, topics, socketTopics);\n nativeToWrapped.set(ws, wrapped);\n allSockets.add(wrapped);\n if (handlers.open) {\n Promise.resolve(handlers.open(wrapped)).catch((err) =>\n console.error(\"[ws] open handler error:\", err),\n );\n }\n },\n message: (ws: any, msg: any) => {\n const wrapped = nativeToWrapped.get(ws);\n if (wrapped) {\n Promise.resolve(handlers.message(wrapped, msg)).catch((err) =>\n console.error(\"[ws] message handler error:\", err),\n );\n }\n },\n close: (ws: any, code: number, reason: string) => {\n const wrapped = nativeToWrapped.get(ws);\n if (wrapped) {\n cleanup(wrapped);\n if (handlers.close) {\n Promise.resolve(handlers.close(wrapped, code, reason)).catch((err) =>\n console.error(\"[ws] close handler error:\", err),\n );\n }\n }\n },\n drain: handlers.drain\n ? (ws: any) => {\n const wrapped = nativeToWrapped.get(ws);\n if (wrapped && handlers.drain) {\n Promise.resolve(handlers.drain(wrapped)).catch((err) =>\n console.error(\"[ws] drain handler error:\", err),\n );\n }\n }\n : undefined,\n },\n });\n\n return {\n port: server.port,\n hostname: server.hostname,\n url: new URL(`http://${server.hostname}:${server.port}/`),\n publish: (topic, message, _compress) => {\n const set = topics.get(topic);\n if (!set || set.size === 0) {\n return 0;\n }\n const payload = toSendable(message);\n let count = 0;\n for (const ws of set) {\n if (ws.readyState === 1) {\n ws.send(payload);\n count++;\n }\n }\n return count;\n },\n stop: (closeActiveConnections) => {\n if (closeActiveConnections) {\n for (const ws of allSockets) {\n try {\n ws.close(1001, \"Server shutdown\");\n } catch {\n /* ignore */\n }\n }\n allSockets.clear();\n }\n server.stop(closeActiveConnections);\n },\n };\n}\n\n/**\n * Wrap a native Bun WebSocket into a {@link ServerWebSocket} compatible with\n * the public handler interface.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n * @param ws - Raw Bun WebSocket\n * @param data - User-supplied data payload\n * @param topics - Global topic → socket set map\n * @param socketTopics - Reverse lookup for per-socket topic membership\n * @returns A {@link ServerWebSocket} wrapper\n *\n * @internal\n */\nfunction createBunWrapper<Data extends WSData = WSData>(\n ws: any,\n data: Data,\n topics: Map<string, Set<ServerWebSocket<Data>>>,\n socketTopics: WeakMap<ServerWebSocket<Data>, Set<string>>,\n): ServerWebSocket<Data> {\n const wrapper: ServerWebSocket<Data> = {\n data,\n get readyState() {\n return ws.readyState;\n },\n get remoteAddress() {\n return ws.remoteAddress || \"\";\n },\n send: (message, _compress) => {\n ws.send(message);\n return typeof message === \"string\"\n ? Buffer.byteLength(message)\n : (message as ArrayBuffer | Uint8Array).byteLength;\n },\n close: (code, reason) => ws.close(code, reason),\n subscribe: (topic: string) => {\n let set = topics.get(topic);\n if (!set) {\n set = new Set();\n topics.set(topic, set);\n }\n set.add(wrapper);\n let owned = socketTopics.get(wrapper);\n if (!owned) {\n owned = new Set();\n socketTopics.set(wrapper, owned);\n }\n owned.add(topic);\n },\n unsubscribe: (topic: string) => {\n topics.get(topic)?.delete(wrapper);\n socketTopics.get(wrapper)?.delete(topic);\n },\n publish: (topic: string, message: WSMessage, _compress?: boolean) => {\n const set = topics.get(topic);\n if (!set) {\n return;\n }\n const payload = toSendable(message);\n for (const peer of set) {\n if (peer !== wrapper && peer.readyState === 1) {\n peer.send(payload);\n }\n }\n },\n isSubscribed: (topic: string) =>\n !!(socketTopics.get(wrapper) as Set<string> | undefined)?.has(topic),\n cork: (cb: (ws: ServerWebSocket<Data>) => void) => cb(wrapper),\n };\n return wrapper;\n}\n\n/**\n * Start a WebSocket server using Node.js `node:http` + the built-in\n * {@link WebSocketServer} upgrade handler.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n * @param handlers - Lifecycle handlers\n * @param options - Server options including `port` and `hostname`\n * @returns A promise resolving to {@link ServeInfo}\n *\n * @internal\n */\nasync function serveNode<Data extends WSData = WSData>(\n handlers: WebSocketHandlers<Data>,\n options?: WebSocketServerOptions & { port?: number; hostname?: string },\n): Promise<ServeInfo> {\n const { createServer: createHttpServer } = await import(\"node:http\");\n const wss = new WebSocketServer<Data>(handlers, options);\n\n const httpServer = createHttpServer((_req: any, res: any) => {\n res.writeHead(404);\n res.end(\"Not found\");\n });\n\n httpServer.on(\"upgrade\", (req: any, socket: any, head: any) => {\n const data = options?.resolveData ? options.resolveData(req) : undefined;\n wss\n .upgrade({ rawRequest: req, socket, head }, data ? { data: data as Data } : undefined)\n .catch(() => socket.destroy());\n });\n\n await new Promise<void>((resolve) =>\n httpServer.listen(options?.port ?? 0, options?.hostname ?? \"0.0.0.0\", resolve),\n );\n\n const addr = httpServer.address() as any;\n const port = addr?.port ?? 0;\n\n return {\n port,\n hostname: options?.hostname ?? \"0.0.0.0\",\n url: new URL(`http://${options?.hostname ?? \"0.0.0.0\"}:${port}/`),\n publish: (topic, message, _compress) => wss.publish(topic, message, _compress),\n stop: (closeActiveConnections) => {\n wss.close(closeActiveConnections);\n httpServer.close();\n },\n };\n}\n\n/**\n * Coerce a {@link WSMessage} into a form that {@link NativeSocket.send} accepts.\n *\n * @param message - User-supplied payload\n * @returns A `string`, `Buffer` or `Uint8Array` ready to be framed\n *\n * @internal\n */\nfunction toSendable(message: WSMessage): string | Buffer | Uint8Array {\n if (typeof message === \"string\") {\n return message;\n }\n if (message instanceof ArrayBuffer) {\n return Buffer.from(message);\n }\n return message;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.cjs","names":[],"sources":["../src/server.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { Duplex } from \"node:stream\";\n\nimport type {\n ServerWebSocket,\n UpgradeOptions,\n UpgradeRequest,\n WebSocketHandlers,\n WebSocketServerOptions,\n WSData,\n WSMessage,\n} from \"./types\";\n\nexport type {\n ServerWebSocket,\n UpgradeOptions,\n UpgradeRequest,\n WebSocketHandlers,\n WebSocketServerOptions,\n WSData,\n WSMessage,\n} from \"./types\";\n\n/**\n * WebSocket magic GUID defined in RFC 6455 section 4.2.2.\n *\n * @internal\n */\nconst WS_GUID = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\n\nconst OP_CONTINUATION = 0x0;\nconst OP_TEXT = 0x1;\nconst OP_BINARY = 0x2;\nconst OP_CLOSE = 0x8;\nconst OP_PING = 0x9;\nconst OP_PONG = 0xa;\n\n/**\n * Compute the `Sec-WebSocket-Accept` response header per RFC 6455 §4.2.2.\n *\n * @param clientKey - Value of the `Sec-WebSocket-Key` request header\n * @returns Base-64 encoded SHA-1 digest\n *\n * @internal\n */\nfunction computeAccept(clientKey: string): string {\n return createHash(\"sha1\")\n .update(clientKey + WS_GUID)\n .digest(\"base64\");\n}\n\n/**\n * Write a single unmasked (server-to-client) WebSocket data frame to a raw\n * duplex socket per RFC 6455 §5.2.\n *\n * @param socket - Underlying duplex transport\n * @param opcode - WebSocket opcode (text, binary, close, ping, pong …)\n * @param payload - Payload bytes to encapsulate\n *\n * @internal\n */\nfunction writeFrame(socket: Duplex, opcode: number, payload: Buffer): void {\n const len = payload.length;\n let header: Buffer;\n\n if (len < 126) {\n header = Buffer.allocUnsafe(2);\n header[0] = 0x80 | opcode;\n header[1] = len;\n } else if (len < 0x10000) {\n header = Buffer.allocUnsafe(4);\n header[0] = 0x80 | opcode;\n header[1] = 126;\n header.writeUInt16BE(len, 2);\n } else {\n header = Buffer.allocUnsafe(10);\n header[0] = 0x80 | opcode;\n header[1] = 127;\n header.writeUInt32BE(0, 2);\n header.writeUInt32BE(len >>> 0, 6);\n }\n\n if (payload.length === 0) {\n socket.write(header);\n } else {\n socket.write(Buffer.concat([header, payload]));\n }\n}\n\n/**\n * Incremental streaming RFC 6455 frame parser for masked client→server frames.\n * Handles fragmentation, ping/pong, and close frames internally.\n *\n * @internal\n */\nclass FrameParser {\n private buf: Buffer<ArrayBufferLike> = Buffer.alloc(0);\n private fragments: Buffer[] = [];\n private fragmentOpcode = 0;\n private readonly maxPayload: number;\n\n /** Callback fired when a complete data frame is assembled. */\n onMessage?: (data: Buffer | string, isBinary: boolean) => void;\n /** Callback fired when a close frame is received. */\n onClose?: (code: number, reason: string) => void;\n /** Callback fired when a ping frame is received. */\n onPing?: (data: Buffer) => void;\n /** Callback fired on parse errors or protocol violations. */\n onError?: (err: Error) => void;\n\n /**\n * @param maxPayload - Maximum allowed frame payload in bytes (default 100 MiB)\n */\n constructor(maxPayload = 100 * 1024 * 1024) {\n this.maxPayload = maxPayload;\n }\n\n /**\n * Feed a new chunk of raw socket data into the parser.\n *\n * @param chunk - Incoming bytes from the transport\n */\n push(chunk: Buffer): void {\n this.buf = this.buf.length === 0 ? chunk : Buffer.concat([this.buf, chunk]);\n this.drain();\n }\n\n /**\n * Consume as many complete frames as possible from the internal buffer.\n *\n * @internal\n */\n private drain(): void {\n for (;;) {\n if (this.buf.length < 2) {\n return;\n }\n\n const b0 = this.buf[0]!;\n const b1 = this.buf[1]!;\n const fin = (b0 & 0x80) !== 0;\n const rsv = b0 & 0x70;\n const opcode = b0 & 0x0f;\n const masked = (b1 & 0x80) !== 0;\n let payloadLen = b1 & 0x7f;\n let offset = 2;\n\n if (rsv !== 0) {\n this.onError?.(new Error(\"WebSocket: RSV bits must be 0 (no extensions negotiated)\"));\n return;\n }\n\n if (payloadLen === 126) {\n if (this.buf.length < 4) {\n return;\n }\n payloadLen = this.buf.readUInt16BE(2);\n offset = 4;\n } else if (payloadLen === 127) {\n if (this.buf.length < 10) {\n return;\n }\n const hi = this.buf.readUInt32BE(2);\n const lo = this.buf.readUInt32BE(6);\n payloadLen = hi * 0x1_0000_0000 + lo;\n offset = 10;\n }\n\n if (payloadLen > this.maxPayload) {\n this.onError?.(\n new Error(\n `WebSocket: payload length ${payloadLen} exceeds maxPayload ${this.maxPayload}`,\n ),\n );\n return;\n }\n\n const maskLen = masked ? 4 : 0;\n const frameEnd = offset + maskLen + payloadLen;\n if (this.buf.length < frameEnd) {\n return;\n }\n\n let payload: Buffer;\n if (masked) {\n const mask = this.buf.subarray(offset, offset + 4);\n payload = Buffer.allocUnsafe(payloadLen);\n for (let i = 0; i < payloadLen; i++) {\n payload[i] = this.buf[offset + 4 + i]! ^ mask[i & 3]!;\n }\n } else {\n payload = Buffer.from(this.buf.subarray(offset, frameEnd));\n }\n\n this.buf = this.buf.subarray(frameEnd);\n this.handleFrame(fin, opcode, payload);\n }\n }\n\n /**\n * Route a parsed frame to the appropriate callback based on opcode.\n *\n * @param fin - Whether this is the final fragment\n * @param opcode - WebSocket frame opcode\n * @param payload - Unmasked payload bytes\n *\n * @internal\n */\n private handleFrame(fin: boolean, opcode: number, payload: Buffer): void {\n switch (opcode) {\n case OP_PING:\n this.onPing?.(payload);\n return;\n\n case OP_PONG:\n return;\n\n case OP_CLOSE: {\n const code = payload.length >= 2 ? payload.readUInt16BE(0) : 1000;\n const reason = payload.length > 2 ? payload.subarray(2).toString(\"utf8\") : \"\";\n this.onClose?.(code, reason);\n return;\n }\n\n case OP_CONTINUATION:\n case OP_TEXT:\n case OP_BINARY:\n if (opcode !== OP_CONTINUATION) {\n this.fragmentOpcode = opcode;\n }\n this.fragments.push(payload);\n if (fin) {\n const full = Buffer.concat(this.fragments);\n this.fragments = [];\n const isBinary = this.fragmentOpcode === OP_BINARY;\n this.onMessage?.(isBinary ? full : full.toString(\"utf8\"), isBinary);\n }\n return;\n\n default:\n this.onError?.(new Error(`WebSocket: unknown opcode 0x${opcode.toString(16)}`));\n }\n }\n}\n\n/**\n * Thin wrapper around a raw `Duplex` that exposes a minimal frame-aware\n * WebSocket interface used internally by {@link WebSocketServer}.\n *\n * @internal\n */\nclass NativeSocket {\n /** Underlying duplex transport stream. */\n readonly duplex: Duplex;\n /** Incremental frame parser attached to this transport. */\n readonly parser: FrameParser;\n\n /** WHATWG-compatible ready-state: `1` open · `2` closing · `3` closed. */\n readyState: 1 | 2 | 3 = 1;\n\n /**\n * @param duplex - Raw duplex transport\n * @param maxPayload - Maximum allowed payload in bytes\n */\n constructor(duplex: Duplex, maxPayload?: number) {\n this.duplex = duplex;\n this.parser = new FrameParser(maxPayload);\n }\n\n /**\n * Send a data frame to the peer.\n *\n * @param data - Payload to transmit\n */\n send(data: string | Buffer | Uint8Array): void {\n if (this.readyState !== 1) {\n return;\n }\n const isBuf = Buffer.isBuffer(data);\n const buf =\n typeof data === \"string\"\n ? Buffer.from(data, \"utf8\")\n : isBuf\n ? data\n : Buffer.from(data as Uint8Array);\n writeFrame(this.duplex, typeof data === \"string\" ? OP_TEXT : OP_BINARY, buf);\n }\n\n /**\n * Initiate the close handshake.\n *\n * @param code - Close status code (default `1000`)\n * @param reason - Optional UTF-8 reason phrase\n */\n close(code = 1000, reason = \"\"): void {\n if (this.readyState !== 1) {\n return;\n }\n this.readyState = 2;\n const reasonBuf = Buffer.from(reason, \"utf8\");\n const payload = Buffer.allocUnsafe(2 + reasonBuf.length);\n payload.writeUInt16BE(code, 0);\n reasonBuf.copy(payload, 2);\n writeFrame(this.duplex, OP_CLOSE, payload);\n }\n\n /**\n * Hard-terminate the underlying transport without a close handshake.\n */\n terminate(): void {\n this.readyState = 3;\n this.duplex.destroy();\n }\n}\n\n/**\n * Runtime-agnostic WebSocket server built entirely on Node.js built-ins\n * (`node:crypto`, `node:stream`) — no third-party dependencies.\n *\n * @remarks\n * The class does **not** create or own an HTTP server. Callers feed it raw\n * upgrade tuples coming from any HTTP runtime (Node.js `http`, Bun, Deno,\n * Hono, Fastify's upgrade hook, etc.).\n *\n * Topic-based pub/sub is implemented in user-space, matching the shape of\n * `Bun.ServerWebSocket` so the same handler code runs on every runtime.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n *\n * @example\n * ```ts\n * import { createServer } from \"node:http\";\n * import { WebSocketServer } from \"@hedystia/ws/server\";\n *\n * const wss = new WebSocketServer({\n * open: (ws) => ws.send(\"welcome\"),\n * message: (ws, msg) => ws.publish(\"room\", msg),\n * close: (ws, code) => console.log(\"closed\", code),\n * });\n *\n * const http = createServer((_req, res) => res.end(\"ok\"));\n * http.on(\"upgrade\", (req, socket, head) => {\n * wss.upgrade({ rawRequest: req, socket, head }, { data: { user: \"anon\" } });\n * });\n * http.listen(3000);\n * ```\n */\nexport class WebSocketServer<Data extends WSData = WSData> {\n private readonly handlers: WebSocketHandlers<Data>;\n private readonly maxPayload: number | undefined;\n private readonly resolveData: ((req: any) => Record<string, any>) | undefined;\n private readonly topics = new Map<string, Set<any>>();\n private readonly socketTopics = new WeakMap<object, Set<string>>();\n private readonly allSockets = new Set<any>();\n\n /**\n * Build a new WebSocket server.\n *\n * @param handlers - Lifecycle handlers ({@link WebSocketHandlers})\n * @param options - Optional behavioural overrides ({@link WebSocketServerOptions})\n *\n * @example\n * ```ts\n * const wss = new WebSocketServer(\n * { message: (ws, msg) => ws.send(msg) },\n * { maxPayload: 1024 * 1024 },\n * );\n * ```\n */\n constructor(handlers: WebSocketHandlers<Data>, options: WebSocketServerOptions = {}) {\n this.handlers = handlers;\n this.maxPayload = options.maxPayload;\n this.resolveData = options.resolveData;\n }\n\n /**\n * Upgrade a raw HTTP upgrade tuple to a WebSocket connection.\n *\n * @remarks\n * Performs the RFC 6455 handshake synchronously on the duplex socket,\n * then wires up frame parsing and lifecycle handlers. The returned\n * promise resolves to the {@link ServerWebSocket} wrapper immediately\n * after the handshake bytes are written.\n *\n * @param req - Upgrade tuple emitted by `node:http`'s `'upgrade'` event\n * @param options - Optional initial `data` for the new connection\n * @returns Promise that resolves with the established socket wrapper\n *\n * @throws {Error} When `Sec-WebSocket-Key` is absent from the request headers\n *\n * @example\n * ```ts\n * http.on(\"upgrade\", async (req, socket, head) => {\n * try {\n * await wss.upgrade({ rawRequest: req, socket, head });\n * } catch (err) {\n * console.error(\"Upgrade failed\", err);\n * socket.destroy();\n * }\n * });\n * ```\n */\n upgrade(req: UpgradeRequest, options?: UpgradeOptions<Data>): Promise<ServerWebSocket<Data>> {\n return new Promise((resolve, reject) => {\n try {\n const rawReq = req.rawRequest;\n const duplex = req.socket as Duplex;\n\n const clientKey = rawReq.headers?.[\"sec-websocket-key\"] as string | undefined;\n if (!clientKey) {\n duplex.destroy();\n return reject(new Error(\"WebSocket upgrade: missing Sec-WebSocket-Key header\"));\n }\n\n const accept = computeAccept(clientKey);\n const rawProtocol = rawReq.headers?.[\"sec-websocket-protocol\"] as string | undefined;\n const protocol = rawProtocol?.split(\",\")[0]?.trim();\n\n let response =\n \"HTTP/1.1 101 Switching Protocols\\r\\n\" +\n \"Upgrade: websocket\\r\\n\" +\n \"Connection: Upgrade\\r\\n\" +\n `Sec-WebSocket-Accept: ${accept}\\r\\n`;\n\n if (protocol) {\n response += `Sec-WebSocket-Protocol: ${protocol}\\r\\n`;\n }\n response += \"\\r\\n\";\n duplex.write(response);\n\n const native = new NativeSocket(duplex, this.maxPayload);\n const data = (options?.data ?? {}) as Data;\n const wrapped = this.wrap(native, data, rawReq);\n this.bind(native, wrapped, req.head);\n\n resolve(wrapped);\n } catch (err) {\n reject(err);\n }\n });\n }\n\n /**\n * Publish a message to all sockets currently subscribed to `topic`.\n *\n * @param topic - Topic name\n * @param message - Payload to broadcast\n * @param _compress - Reserved for future use\n * @returns Number of sockets that received the message\n *\n * @example\n * ```ts\n * wss.publish(\"room\", JSON.stringify({ kind: \"ping\" }));\n * ```\n */\n publish(topic: string, message: WSMessage, _compress?: boolean): number {\n const set = this.topics.get(topic);\n if (!set || set.size === 0) {\n return 0;\n }\n const payload = toSendable(message);\n let count = 0;\n for (const native of set) {\n if (native.readyState === 1) {\n native.send(payload);\n count++;\n }\n }\n return count;\n }\n\n /**\n * Close the server and optionally terminate all live sockets.\n *\n * @param closeActiveConnections - When `true`, immediately terminates\n * every live connection before clearing internal state\n */\n close(closeActiveConnections = false): void {\n if (closeActiveConnections) {\n for (const native of this.allSockets) {\n try {\n if (typeof (native as any).terminate === \"function\") {\n (native as any).terminate();\n } else if (typeof (native as any).close === \"function\") {\n (native as any).close(1001, \"Server shutdown\");\n }\n } catch {\n /* ignore */\n }\n }\n this.allSockets.clear();\n }\n this.topics.clear();\n }\n\n /**\n * Wire up the duplex transport listeners (`data`, `close`, `error`) and\n * invoke the user's `open` handler.\n *\n * @param native - Wrapped transport socket\n * @param wrapped - Public {@link ServerWebSocket} wrapper\n * @param head - Buffered bytes captured by the HTTP parser (re-fed into the parser)\n *\n * @internal\n */\n private bind(\n native: NativeSocket,\n wrapped: ServerWebSocket<Data>,\n head: Buffer | Uint8Array,\n ): void {\n this.allSockets.add(native);\n\n if (head && head.length > 0) {\n native.parser.push(Buffer.isBuffer(head) ? head : Buffer.from(head));\n }\n\n native.parser.onMessage = (raw, isBinary) => {\n const message: WSMessage = isBinary\n ? raw instanceof Buffer\n ? raw\n : Buffer.from(raw as Uint8Array)\n : (raw as string);\n Promise.resolve(this.handlers.message(wrapped, message)).catch((err) =>\n console.error(\"[ws] message handler error:\", err),\n );\n };\n\n native.parser.onClose = (code, reason) => {\n if (native.readyState === 1) {\n native.close(code, reason);\n }\n this.cleanup(native);\n if (this.handlers.close) {\n Promise.resolve(this.handlers.close(wrapped, code, reason)).catch((err) =>\n console.error(\"[ws] close handler error:\", err),\n );\n }\n };\n\n native.parser.onPing = (data) => {\n if (native.readyState === 1) {\n writeFrame(native.duplex, OP_PONG, data);\n }\n };\n\n native.parser.onError = (err) => {\n native.terminate();\n this.cleanup(native);\n if (this.handlers.error) {\n Promise.resolve(this.handlers.error(wrapped, err)).catch((e) =>\n console.error(\"[ws] error handler error:\", e),\n );\n }\n };\n\n native.duplex.on(\"data\", (chunk: Buffer) => {\n try {\n native.parser.push(chunk);\n } catch (err) {\n console.error(\"[ws] frame parsing error:\", err);\n native.terminate();\n this.cleanup(native);\n }\n });\n\n native.duplex.on(\"close\", () => {\n if (native.readyState !== 3) {\n native.readyState = 3;\n this.cleanup(native);\n if (this.handlers.close) {\n Promise.resolve(this.handlers.close(wrapped, 1006, \"\")).catch((err) =>\n console.error(\"[ws] close handler error:\", err),\n );\n }\n }\n });\n\n native.duplex.on(\"error\", (err: Error) => {\n native.readyState = 3;\n this.cleanup(native);\n if (this.handlers.error) {\n Promise.resolve(this.handlers.error(wrapped, err)).catch((e) =>\n console.error(\"[ws] error handler error:\", e),\n );\n }\n });\n\n if (this.handlers.open) {\n Promise.resolve(this.handlers.open(wrapped)).catch((err) =>\n console.error(\"[ws] open handler error:\", err),\n );\n }\n }\n\n /**\n * Remove a socket from every topic it had joined and from the global\n * tracking set.\n *\n * @param native - Socket being cleaned up\n *\n * @internal\n */\n private cleanup(native: any): void {\n const owned = this.socketTopics.get(native);\n if (owned) {\n for (const topic of owned) {\n this.topics.get(topic)?.delete(native);\n }\n this.socketTopics.delete(native);\n }\n this.allSockets.delete(native);\n }\n\n /**\n * Build the {@link ServerWebSocket} wrapper exposed to user handlers,\n * embedding topic-based pub/sub backed by the server's internal maps.\n *\n * @param native - Wrapped transport socket\n * @param data - User-supplied `data` payload attached on upgrade\n * @param rawReq - Raw incoming message used to extract the remote address\n * @returns The fully-featured public wrapper\n *\n * @internal\n */\n private wrap(native: NativeSocket, data: Data, rawReq: any): ServerWebSocket<Data> {\n const remoteAddress: string =\n (rawReq?.socket?.remoteAddress as string) ||\n (rawReq?.headers?.[\"x-forwarded-for\"] as string) ||\n \"\";\n\n const subscribe = (topic: string): void => {\n let set = this.topics.get(topic);\n if (!set) {\n set = new Set();\n this.topics.set(topic, set);\n }\n set.add(native);\n let owned = this.socketTopics.get(native);\n if (!owned) {\n owned = new Set();\n this.socketTopics.set(native, owned);\n }\n owned.add(topic);\n };\n\n const unsubscribe = (topic: string): void => {\n this.topics.get(topic)?.delete(native);\n this.socketTopics.get(native)?.delete(topic);\n };\n\n const publishToPeers = (topic: string, message: WSMessage): void => {\n const set = this.topics.get(topic);\n if (!set) {\n return;\n }\n const payload = toSendable(message);\n for (const peer of set) {\n if (peer !== native && peer.readyState === 1) {\n peer.send(payload);\n }\n }\n };\n\n const wrapper: ServerWebSocket<Data> = {\n data,\n get readyState() {\n return native.readyState;\n },\n remoteAddress,\n send: (message, _compress) => {\n const payload = toSendable(message);\n native.send(payload);\n return typeof payload === \"string\"\n ? Buffer.byteLength(payload as string)\n : (payload as Buffer | Uint8Array).byteLength;\n },\n close: (code, reason) => {\n native.close(code, reason);\n },\n subscribe,\n unsubscribe,\n publish: publishToPeers,\n isSubscribed: (topic) => !!this.socketTopics.get(native)?.has(topic),\n cork: (cb) => cb(wrapper),\n };\n\n return wrapper;\n }\n}\n\n/**\n * Return value of {@link serve}.\n */\nexport interface ServeInfo {\n /** Port the HTTP server is listening on. */\n port: number;\n /** Hostname the HTTP server bound to. */\n hostname: string;\n /** Full URL of the listening server (uses `http://` scheme). */\n url: URL;\n /**\n * Publish a message to all sockets subscribed to `topic`.\n *\n * @param topic - Topic name\n * @param message - Payload to broadcast\n * @param compress - Whether to compress (honoured on Bun, ignored on Node)\n * @returns Number of sockets that received the message\n */\n publish: (topic: string, message: WSMessage, compress?: boolean) => number;\n /**\n * Stop the server and optionally close active connections.\n *\n * @param closeActiveConnections - When `true`, terminates all live sockets\n */\n stop: (closeActiveConnections?: boolean) => void | Promise<void>;\n}\n\n/**\n * Start a standalone WebSocket server on the given port.\n *\n * @remarks\n * Auto-detects the runtime and uses the native implementation:\n * - **Bun:** delegates to `Bun.serve()` with native WebSocket support\n * - **Node/Deno:** creates a `node:http` server with the built-in\n * {@link WebSocketServer} upgrade handler\n *\n * @typeParam Data - Shape of the user-attached `data` field\n *\n * @param handlers - Lifecycle handlers ({@link WebSocketHandlers})\n * @param options - Server options including `port` and `hostname`\n * @returns A promise resolving to {@link ServeInfo}\n *\n * @example\n * ```ts\n * import { serve } from \"@hedystia/ws\";\n *\n * const server = await serve({\n * open: (ws) => ws.subscribe(\"global\"),\n * message: (ws, msg) => ws.publish(\"global\", msg),\n * });\n *\n * console.log(`Listening on ${server.url}`);\n * ```\n */\nexport async function serve<Data extends WSData = WSData>(\n handlers: WebSocketHandlers<Data>,\n options?: WebSocketServerOptions & { port?: number; hostname?: string },\n): Promise<ServeInfo> {\n const { detectRuntime } = await import(\"./runtime\");\n const runtime = detectRuntime();\n\n if (runtime === \"bun\") {\n return serveBun(handlers, options);\n }\n return serveNode(handlers, options);\n}\n\n/**\n * Start a WebSocket server using Bun's native `Bun.serve()`.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n * @param handlers - Lifecycle handlers\n * @param options - Server options including `port` and `hostname`\n * @returns A promise resolving to {@link ServeInfo}\n *\n * @internal\n */\nasync function serveBun<Data extends WSData = WSData>(\n handlers: WebSocketHandlers<Data>,\n options?: WebSocketServerOptions & { port?: number; hostname?: string },\n): Promise<ServeInfo> {\n const topics = new Map<string, Set<ServerWebSocket<Data>>>();\n const socketTopics = new WeakMap<ServerWebSocket<Data>, Set<string>>();\n const allSockets = new Set<ServerWebSocket<Data>>();\n const nativeToWrapped = new WeakMap<any, ServerWebSocket<Data>>();\n\n function cleanup(ws: ServerWebSocket<Data>): void {\n const owned = socketTopics.get(ws);\n if (owned) {\n for (const topic of owned) {\n topics.get(topic)?.delete(ws);\n }\n socketTopics.delete(ws);\n }\n allSockets.delete(ws);\n }\n\n const server = (globalThis as any).Bun.serve({\n port: options?.port ?? 0,\n hostname: options?.hostname ?? \"0.0.0.0\",\n fetch: (req: Request) => {\n if (req.headers.get(\"upgrade\") === \"websocket\") {\n const data = (options?.resolveData ? options.resolveData(req) : {}) as Data;\n const ok = server.upgrade(req, { data });\n return ok ? undefined : new Response(\"upgrade failed\", { status: 400 });\n }\n return new Response(\"Not found\", { status: 404 });\n },\n websocket: {\n open: (ws: any) => {\n const data = ws.data ?? ({} as Data);\n const wrapped: ServerWebSocket<Data> = createBunWrapper(ws, data, topics, socketTopics);\n nativeToWrapped.set(ws, wrapped);\n allSockets.add(wrapped);\n if (handlers.open) {\n Promise.resolve(handlers.open(wrapped)).catch((err) =>\n console.error(\"[ws] open handler error:\", err),\n );\n }\n },\n message: (ws: any, msg: any) => {\n const wrapped = nativeToWrapped.get(ws);\n if (wrapped) {\n Promise.resolve(handlers.message(wrapped, msg)).catch((err) =>\n console.error(\"[ws] message handler error:\", err),\n );\n }\n },\n close: (ws: any, code: number, reason: string) => {\n const wrapped = nativeToWrapped.get(ws);\n if (wrapped) {\n cleanup(wrapped);\n if (handlers.close) {\n Promise.resolve(handlers.close(wrapped, code, reason)).catch((err) =>\n console.error(\"[ws] close handler error:\", err),\n );\n }\n }\n },\n drain: handlers.drain\n ? (ws: any) => {\n const wrapped = nativeToWrapped.get(ws);\n if (wrapped && handlers.drain) {\n Promise.resolve(handlers.drain(wrapped)).catch((err) =>\n console.error(\"[ws] drain handler error:\", err),\n );\n }\n }\n : undefined,\n },\n });\n\n return {\n port: server.port,\n hostname: server.hostname,\n url: new URL(`http://${server.hostname}:${server.port}/`),\n publish: (topic, message, _compress) => {\n const set = topics.get(topic);\n if (!set || set.size === 0) {\n return 0;\n }\n const payload = toSendable(message);\n let count = 0;\n for (const ws of set) {\n if (ws.readyState === 1) {\n ws.send(payload);\n count++;\n }\n }\n return count;\n },\n stop: (closeActiveConnections) => {\n if (closeActiveConnections) {\n for (const ws of allSockets) {\n try {\n ws.close(1001, \"Server shutdown\");\n } catch {\n /* ignore */\n }\n }\n allSockets.clear();\n }\n server.stop(closeActiveConnections);\n },\n };\n}\n\n/**\n * Wrap a native Bun WebSocket into a {@link ServerWebSocket} compatible with\n * the public handler interface.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n * @param ws - Raw Bun WebSocket\n * @param data - User-supplied data payload\n * @param topics - Global topic → socket set map\n * @param socketTopics - Reverse lookup for per-socket topic membership\n * @returns A {@link ServerWebSocket} wrapper\n *\n * @internal\n */\nfunction createBunWrapper<Data extends WSData = WSData>(\n ws: any,\n data: Data,\n topics: Map<string, Set<ServerWebSocket<Data>>>,\n socketTopics: WeakMap<ServerWebSocket<Data>, Set<string>>,\n): ServerWebSocket<Data> {\n const wrapper: ServerWebSocket<Data> = {\n data,\n get readyState() {\n return ws.readyState;\n },\n get remoteAddress() {\n return ws.remoteAddress || \"\";\n },\n send: (message, _compress) => {\n ws.send(message);\n return typeof message === \"string\"\n ? Buffer.byteLength(message)\n : (message as ArrayBuffer | Uint8Array).byteLength;\n },\n close: (code, reason) => ws.close(code, reason),\n subscribe: (topic: string) => {\n let set = topics.get(topic);\n if (!set) {\n set = new Set();\n topics.set(topic, set);\n }\n set.add(wrapper);\n let owned = socketTopics.get(wrapper);\n if (!owned) {\n owned = new Set();\n socketTopics.set(wrapper, owned);\n }\n owned.add(topic);\n },\n unsubscribe: (topic: string) => {\n topics.get(topic)?.delete(wrapper);\n socketTopics.get(wrapper)?.delete(topic);\n },\n publish: (topic: string, message: WSMessage, _compress?: boolean) => {\n const set = topics.get(topic);\n if (!set) {\n return;\n }\n const payload = toSendable(message);\n for (const peer of set) {\n if (peer !== wrapper && peer.readyState === 1) {\n peer.send(payload);\n }\n }\n },\n isSubscribed: (topic: string) =>\n !!(socketTopics.get(wrapper) as Set<string> | undefined)?.has(topic),\n cork: (cb: (ws: ServerWebSocket<Data>) => void) => cb(wrapper),\n };\n return wrapper;\n}\n\n/**\n * Start a WebSocket server using Node.js `node:http` + the built-in\n * {@link WebSocketServer} upgrade handler.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n * @param handlers - Lifecycle handlers\n * @param options - Server options including `port` and `hostname`\n * @returns A promise resolving to {@link ServeInfo}\n *\n * @internal\n */\nasync function serveNode<Data extends WSData = WSData>(\n handlers: WebSocketHandlers<Data>,\n options?: WebSocketServerOptions & { port?: number; hostname?: string },\n): Promise<ServeInfo> {\n const { createServer: createHttpServer } = await import(\"node:http\");\n const wss = new WebSocketServer<Data>(handlers, options);\n\n const httpServer = createHttpServer((_req: any, res: any) => {\n res.writeHead(404);\n res.end(\"Not found\");\n });\n\n httpServer.on(\"upgrade\", (req: any, socket: any, head: any) => {\n const data = options?.resolveData ? options.resolveData(req) : undefined;\n wss\n .upgrade({ rawRequest: req, socket, head }, data ? { data: data as Data } : undefined)\n .catch(() => socket.destroy());\n });\n\n await new Promise<void>((resolve) =>\n httpServer.listen(options?.port ?? 0, options?.hostname ?? \"0.0.0.0\", resolve),\n );\n\n const addr = httpServer.address() as any;\n const port = addr?.port ?? 0;\n\n return {\n port,\n hostname: options?.hostname ?? \"0.0.0.0\",\n url: new URL(`http://${options?.hostname ?? \"0.0.0.0\"}:${port}/`),\n publish: (topic, message, _compress) => wss.publish(topic, message, _compress),\n stop: (closeActiveConnections) => {\n wss.close(closeActiveConnections);\n httpServer.close();\n },\n };\n}\n\n/**\n * Coerce a {@link WSMessage} into a form that {@link NativeSocket.send} accepts.\n *\n * @param message - User-supplied payload\n * @returns A `string`, `Buffer` or `Uint8Array` ready to be framed\n *\n * @internal\n */\nfunction toSendable(message: WSMessage): string | Buffer | Uint8Array {\n if (typeof message === \"string\") {\n return message;\n }\n if (message instanceof ArrayBuffer) {\n return Buffer.from(message);\n }\n return message;\n}\n"],"mappings":"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"}
|
package/dist/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.mjs","names":[],"sources":["../src/server.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { Duplex } from \"node:stream\";\n\nimport type {\n ServerWebSocket,\n UpgradeOptions,\n UpgradeRequest,\n WebSocketHandlers,\n WebSocketServerOptions,\n WSData,\n WSMessage,\n} from \"./types\";\n\nexport type {\n ServerWebSocket,\n UpgradeOptions,\n UpgradeRequest,\n WebSocketHandlers,\n WebSocketServerOptions,\n WSData,\n WSMessage,\n} from \"./types\";\n\n/**\n * WebSocket magic GUID defined in RFC 6455 section 4.2.2.\n *\n * @internal\n */\nconst WS_GUID = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\n\nconst OP_CONTINUATION = 0x0;\nconst OP_TEXT = 0x1;\nconst OP_BINARY = 0x2;\nconst OP_CLOSE = 0x8;\nconst OP_PING = 0x9;\nconst OP_PONG = 0xa;\n\n/**\n * Compute the `Sec-WebSocket-Accept` response header per RFC 6455 §4.2.2.\n *\n * @param clientKey - Value of the `Sec-WebSocket-Key` request header\n * @returns Base-64 encoded SHA-1 digest\n *\n * @internal\n */\nfunction computeAccept(clientKey: string): string {\n return createHash(\"sha1\")\n .update(clientKey + WS_GUID)\n .digest(\"base64\");\n}\n\n/**\n * Write a single unmasked (server-to-client) WebSocket data frame to a raw\n * duplex socket per RFC 6455 §5.2.\n *\n * @param socket - Underlying duplex transport\n * @param opcode - WebSocket opcode (text, binary, close, ping, pong …)\n * @param payload - Payload bytes to encapsulate\n *\n * @internal\n */\nfunction writeFrame(socket: Duplex, opcode: number, payload: Buffer): void {\n const len = payload.length;\n let header: Buffer;\n\n if (len < 126) {\n header = Buffer.allocUnsafe(2);\n header[0] = 0x80 | opcode;\n header[1] = len;\n } else if (len < 0x10000) {\n header = Buffer.allocUnsafe(4);\n header[0] = 0x80 | opcode;\n header[1] = 126;\n header.writeUInt16BE(len, 2);\n } else {\n header = Buffer.allocUnsafe(10);\n header[0] = 0x80 | opcode;\n header[1] = 127;\n header.writeUInt32BE(0, 2);\n header.writeUInt32BE(len >>> 0, 6);\n }\n\n if (payload.length === 0) {\n socket.write(header);\n } else {\n socket.write(Buffer.concat([header, payload]));\n }\n}\n\n/**\n * Incremental streaming RFC 6455 frame parser for masked client→server frames.\n * Handles fragmentation, ping/pong, and close frames internally.\n *\n * @internal\n */\nclass FrameParser {\n private buf: Buffer<ArrayBufferLike> = Buffer.alloc(0);\n private fragments: Buffer[] = [];\n private fragmentOpcode = 0;\n private readonly maxPayload: number;\n\n /** Callback fired when a complete data frame is assembled. */\n onMessage?: (data: Buffer | string, isBinary: boolean) => void;\n /** Callback fired when a close frame is received. */\n onClose?: (code: number, reason: string) => void;\n /** Callback fired when a ping frame is received. */\n onPing?: (data: Buffer) => void;\n /** Callback fired on parse errors or protocol violations. */\n onError?: (err: Error) => void;\n\n /**\n * @param maxPayload - Maximum allowed frame payload in bytes (default 100 MiB)\n */\n constructor(maxPayload = 100 * 1024 * 1024) {\n this.maxPayload = maxPayload;\n }\n\n /**\n * Feed a new chunk of raw socket data into the parser.\n *\n * @param chunk - Incoming bytes from the transport\n */\n push(chunk: Buffer): void {\n this.buf = this.buf.length === 0 ? chunk : Buffer.concat([this.buf, chunk]);\n this.drain();\n }\n\n /**\n * Consume as many complete frames as possible from the internal buffer.\n *\n * @internal\n */\n private drain(): void {\n for (;;) {\n if (this.buf.length < 2) {\n return;\n }\n\n const b0 = this.buf[0]!;\n const b1 = this.buf[1]!;\n const fin = (b0 & 0x80) !== 0;\n const rsv = b0 & 0x70;\n const opcode = b0 & 0x0f;\n const masked = (b1 & 0x80) !== 0;\n let payloadLen = b1 & 0x7f;\n let offset = 2;\n\n if (rsv !== 0) {\n this.onError?.(new Error(\"WebSocket: RSV bits must be 0 (no extensions negotiated)\"));\n return;\n }\n\n if (payloadLen === 126) {\n if (this.buf.length < 4) {\n return;\n }\n payloadLen = this.buf.readUInt16BE(2);\n offset = 4;\n } else if (payloadLen === 127) {\n if (this.buf.length < 10) {\n return;\n }\n const hi = this.buf.readUInt32BE(2);\n const lo = this.buf.readUInt32BE(6);\n payloadLen = hi * 0x1_0000_0000 + lo;\n offset = 10;\n }\n\n if (payloadLen > this.maxPayload) {\n this.onError?.(\n new Error(\n `WebSocket: payload length ${payloadLen} exceeds maxPayload ${this.maxPayload}`,\n ),\n );\n return;\n }\n\n const maskLen = masked ? 4 : 0;\n const frameEnd = offset + maskLen + payloadLen;\n if (this.buf.length < frameEnd) {\n return;\n }\n\n let payload: Buffer;\n if (masked) {\n const mask = this.buf.subarray(offset, offset + 4);\n payload = Buffer.allocUnsafe(payloadLen);\n for (let i = 0; i < payloadLen; i++) {\n payload[i] = this.buf[offset + 4 + i]! ^ mask[i & 3]!;\n }\n } else {\n payload = Buffer.from(this.buf.subarray(offset, frameEnd));\n }\n\n this.buf = this.buf.subarray(frameEnd);\n this.handleFrame(fin, opcode, payload);\n }\n }\n\n /**\n * Route a parsed frame to the appropriate callback based on opcode.\n *\n * @param fin - Whether this is the final fragment\n * @param opcode - WebSocket frame opcode\n * @param payload - Unmasked payload bytes\n *\n * @internal\n */\n private handleFrame(fin: boolean, opcode: number, payload: Buffer): void {\n switch (opcode) {\n case OP_PING:\n this.onPing?.(payload);\n return;\n\n case OP_PONG:\n return;\n\n case OP_CLOSE: {\n const code = payload.length >= 2 ? payload.readUInt16BE(0) : 1000;\n const reason = payload.length > 2 ? payload.subarray(2).toString(\"utf8\") : \"\";\n this.onClose?.(code, reason);\n return;\n }\n\n case OP_CONTINUATION:\n case OP_TEXT:\n case OP_BINARY:\n if (opcode !== OP_CONTINUATION) {\n this.fragmentOpcode = opcode;\n }\n this.fragments.push(payload);\n if (fin) {\n const full = Buffer.concat(this.fragments);\n this.fragments = [];\n const isBinary = this.fragmentOpcode === OP_BINARY;\n this.onMessage?.(isBinary ? full : full.toString(\"utf8\"), isBinary);\n }\n return;\n\n default:\n this.onError?.(new Error(`WebSocket: unknown opcode 0x${opcode.toString(16)}`));\n }\n }\n}\n\n/**\n * Thin wrapper around a raw `Duplex` that exposes a minimal frame-aware\n * WebSocket interface used internally by {@link WebSocketServer}.\n *\n * @internal\n */\nclass NativeSocket {\n /** Underlying duplex transport stream. */\n readonly duplex: Duplex;\n /** Incremental frame parser attached to this transport. */\n readonly parser: FrameParser;\n\n /** WHATWG-compatible ready-state: `1` open · `2` closing · `3` closed. */\n readyState: 1 | 2 | 3 = 1;\n\n /**\n * @param duplex - Raw duplex transport\n * @param maxPayload - Maximum allowed payload in bytes\n */\n constructor(duplex: Duplex, maxPayload?: number) {\n this.duplex = duplex;\n this.parser = new FrameParser(maxPayload);\n }\n\n /**\n * Send a data frame to the peer.\n *\n * @param data - Payload to transmit\n */\n send(data: string | Buffer | Uint8Array): void {\n if (this.readyState !== 1) {\n return;\n }\n const isBuf = Buffer.isBuffer(data);\n const buf =\n typeof data === \"string\"\n ? Buffer.from(data, \"utf8\")\n : isBuf\n ? data\n : Buffer.from(data as Uint8Array);\n writeFrame(this.duplex, typeof data === \"string\" ? OP_TEXT : OP_BINARY, buf);\n }\n\n /**\n * Initiate the close handshake.\n *\n * @param code - Close status code (default `1000`)\n * @param reason - Optional UTF-8 reason phrase\n */\n close(code = 1000, reason = \"\"): void {\n if (this.readyState !== 1) {\n return;\n }\n this.readyState = 2;\n const reasonBuf = Buffer.from(reason, \"utf8\");\n const payload = Buffer.allocUnsafe(2 + reasonBuf.length);\n payload.writeUInt16BE(code, 0);\n reasonBuf.copy(payload, 2);\n writeFrame(this.duplex, OP_CLOSE, payload);\n }\n\n /**\n * Hard-terminate the underlying transport without a close handshake.\n */\n terminate(): void {\n this.readyState = 3;\n this.duplex.destroy();\n }\n}\n\n/**\n * Runtime-agnostic WebSocket server built entirely on Node.js built-ins\n * (`node:crypto`, `node:stream`) — no third-party dependencies.\n *\n * @remarks\n * The class does **not** create or own an HTTP server. Callers feed it raw\n * upgrade tuples coming from any HTTP runtime (Node.js `http`, Bun, Deno,\n * Hono, Fastify's upgrade hook, etc.).\n *\n * Topic-based pub/sub is implemented in user-space, matching the shape of\n * `Bun.ServerWebSocket` so the same handler code runs on every runtime.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n *\n * @example\n * ```ts\n * import { createServer } from \"node:http\";\n * import { WebSocketServer } from \"@hedystia/ws/server\";\n *\n * const wss = new WebSocketServer({\n * open: (ws) => ws.send(\"welcome\"),\n * message: (ws, msg) => ws.publish(\"room\", msg),\n * close: (ws, code) => console.log(\"closed\", code),\n * });\n *\n * const http = createServer((_req, res) => res.end(\"ok\"));\n * http.on(\"upgrade\", (req, socket, head) => {\n * wss.upgrade({ rawRequest: req, socket, head }, { data: { user: \"anon\" } });\n * });\n * http.listen(3000);\n * ```\n */\nexport class WebSocketServer<Data extends WSData = WSData> {\n private readonly handlers: WebSocketHandlers<Data>;\n private readonly maxPayload: number | undefined;\n private readonly resolveData: ((req: any) => Record<string, any>) | undefined;\n private readonly topics = new Map<string, Set<any>>();\n private readonly socketTopics = new WeakMap<object, Set<string>>();\n private readonly allSockets = new Set<any>();\n\n /**\n * Build a new WebSocket server.\n *\n * @param handlers - Lifecycle handlers ({@link WebSocketHandlers})\n * @param options - Optional behavioural overrides ({@link WebSocketServerOptions})\n *\n * @example\n * ```ts\n * const wss = new WebSocketServer(\n * { message: (ws, msg) => ws.send(msg) },\n * { maxPayload: 1024 * 1024 },\n * );\n * ```\n */\n constructor(handlers: WebSocketHandlers<Data>, options: WebSocketServerOptions = {}) {\n this.handlers = handlers;\n this.maxPayload = options.maxPayload;\n this.resolveData = options.resolveData;\n }\n\n /**\n * Upgrade a raw HTTP upgrade tuple to a WebSocket connection.\n *\n * @remarks\n * Performs the RFC 6455 handshake synchronously on the duplex socket,\n * then wires up frame parsing and lifecycle handlers. The returned\n * promise resolves to the {@link ServerWebSocket} wrapper immediately\n * after the handshake bytes are written.\n *\n * @param req - Upgrade tuple emitted by `node:http`'s `'upgrade'` event\n * @param options - Optional initial `data` for the new connection\n * @returns Promise that resolves with the established socket wrapper\n *\n * @throws {Error} When `Sec-WebSocket-Key` is absent from the request headers\n *\n * @example\n * ```ts\n * http.on(\"upgrade\", async (req, socket, head) => {\n * try {\n * await wss.upgrade({ rawRequest: req, socket, head });\n * } catch (err) {\n * console.error(\"Upgrade failed\", err);\n * socket.destroy();\n * }\n * });\n * ```\n */\n upgrade(req: UpgradeRequest, options?: UpgradeOptions<Data>): Promise<ServerWebSocket<Data>> {\n return new Promise((resolve, reject) => {\n try {\n const rawReq = req.rawRequest;\n const duplex = req.socket as Duplex;\n\n const clientKey = rawReq.headers?.[\"sec-websocket-key\"] as string | undefined;\n if (!clientKey) {\n duplex.destroy();\n return reject(new Error(\"WebSocket upgrade: missing Sec-WebSocket-Key header\"));\n }\n\n const accept = computeAccept(clientKey);\n const rawProtocol = rawReq.headers?.[\"sec-websocket-protocol\"] as string | undefined;\n const protocol = rawProtocol?.split(\",\")[0]?.trim();\n\n let response =\n \"HTTP/1.1 101 Switching Protocols\\r\\n\" +\n \"Upgrade: websocket\\r\\n\" +\n \"Connection: Upgrade\\r\\n\" +\n `Sec-WebSocket-Accept: ${accept}\\r\\n`;\n\n if (protocol) {\n response += `Sec-WebSocket-Protocol: ${protocol}\\r\\n`;\n }\n response += \"\\r\\n\";\n duplex.write(response);\n\n const native = new NativeSocket(duplex, this.maxPayload);\n const data = (options?.data ?? {}) as Data;\n const wrapped = this.wrap(native, data, rawReq);\n this.bind(native, wrapped, req.head);\n\n resolve(wrapped);\n } catch (err) {\n reject(err);\n }\n });\n }\n\n /**\n * Publish a message to all sockets currently subscribed to `topic`.\n *\n * @param topic - Topic name\n * @param message - Payload to broadcast\n * @param _compress - Reserved for future use\n * @returns Number of sockets that received the message\n *\n * @example\n * ```ts\n * wss.publish(\"room\", JSON.stringify({ kind: \"ping\" }));\n * ```\n */\n publish(topic: string, message: WSMessage, _compress?: boolean): number {\n const set = this.topics.get(topic);\n if (!set || set.size === 0) {\n return 0;\n }\n const payload = toSendable(message);\n let count = 0;\n for (const native of set) {\n if (native.readyState === 1) {\n native.send(payload);\n count++;\n }\n }\n return count;\n }\n\n /**\n * Close the server and optionally terminate all live sockets.\n *\n * @param closeActiveConnections - When `true`, immediately terminates\n * every live connection before clearing internal state\n */\n close(closeActiveConnections = false): void {\n if (closeActiveConnections) {\n for (const native of this.allSockets) {\n try {\n if (typeof (native as any).terminate === \"function\") {\n (native as any).terminate();\n } else if (typeof (native as any).close === \"function\") {\n (native as any).close(1001, \"Server shutdown\");\n }\n } catch {\n /* ignore */\n }\n }\n this.allSockets.clear();\n }\n this.topics.clear();\n }\n\n /**\n * Wire up the duplex transport listeners (`data`, `close`, `error`) and\n * invoke the user's `open` handler.\n *\n * @param native - Wrapped transport socket\n * @param wrapped - Public {@link ServerWebSocket} wrapper\n * @param head - Buffered bytes captured by the HTTP parser (re-fed into the parser)\n *\n * @internal\n */\n private bind(\n native: NativeSocket,\n wrapped: ServerWebSocket<Data>,\n head: Buffer | Uint8Array,\n ): void {\n this.allSockets.add(native);\n\n if (head && head.length > 0) {\n native.parser.push(Buffer.isBuffer(head) ? head : Buffer.from(head));\n }\n\n native.parser.onMessage = (raw, isBinary) => {\n const message: WSMessage = isBinary\n ? raw instanceof Buffer\n ? raw\n : Buffer.from(raw as Uint8Array)\n : (raw as string);\n Promise.resolve(this.handlers.message(wrapped, message)).catch((err) =>\n console.error(\"[ws] message handler error:\", err),\n );\n };\n\n native.parser.onClose = (code, reason) => {\n if (native.readyState === 1) {\n native.close(code, reason);\n }\n this.cleanup(native);\n if (this.handlers.close) {\n Promise.resolve(this.handlers.close(wrapped, code, reason)).catch((err) =>\n console.error(\"[ws] close handler error:\", err),\n );\n }\n };\n\n native.parser.onPing = (data) => {\n if (native.readyState === 1) {\n writeFrame(native.duplex, OP_PONG, data);\n }\n };\n\n native.parser.onError = (err) => {\n native.terminate();\n this.cleanup(native);\n if (this.handlers.error) {\n Promise.resolve(this.handlers.error(wrapped, err)).catch((e) =>\n console.error(\"[ws] error handler error:\", e),\n );\n }\n };\n\n native.duplex.on(\"data\", (chunk: Buffer) => {\n try {\n native.parser.push(chunk);\n } catch (err) {\n console.error(\"[ws] frame parsing error:\", err);\n native.terminate();\n this.cleanup(native);\n }\n });\n\n native.duplex.on(\"close\", () => {\n if (native.readyState !== 3) {\n native.readyState = 3;\n this.cleanup(native);\n if (this.handlers.close) {\n Promise.resolve(this.handlers.close(wrapped, 1006, \"\")).catch((err) =>\n console.error(\"[ws] close handler error:\", err),\n );\n }\n }\n });\n\n native.duplex.on(\"error\", (err: Error) => {\n native.readyState = 3;\n this.cleanup(native);\n if (this.handlers.error) {\n Promise.resolve(this.handlers.error(wrapped, err)).catch((e) =>\n console.error(\"[ws] error handler error:\", e),\n );\n }\n });\n\n if (this.handlers.open) {\n Promise.resolve(this.handlers.open(wrapped)).catch((err) =>\n console.error(\"[ws] open handler error:\", err),\n );\n }\n }\n\n /**\n * Remove a socket from every topic it had joined and from the global\n * tracking set.\n *\n * @param native - Socket being cleaned up\n *\n * @internal\n */\n private cleanup(native: any): void {\n const owned = this.socketTopics.get(native);\n if (owned) {\n for (const topic of owned) {\n this.topics.get(topic)?.delete(native);\n }\n this.socketTopics.delete(native);\n }\n this.allSockets.delete(native);\n }\n\n /**\n * Build the {@link ServerWebSocket} wrapper exposed to user handlers,\n * embedding topic-based pub/sub backed by the server's internal maps.\n *\n * @param native - Wrapped transport socket\n * @param data - User-supplied `data` payload attached on upgrade\n * @param rawReq - Raw incoming message used to extract the remote address\n * @returns The fully-featured public wrapper\n *\n * @internal\n */\n private wrap(native: NativeSocket, data: Data, rawReq: any): ServerWebSocket<Data> {\n const remoteAddress: string =\n (rawReq?.socket?.remoteAddress as string) ||\n (rawReq?.headers?.[\"x-forwarded-for\"] as string) ||\n \"\";\n\n const subscribe = (topic: string): void => {\n let set = this.topics.get(topic);\n if (!set) {\n set = new Set();\n this.topics.set(topic, set);\n }\n set.add(native);\n let owned = this.socketTopics.get(native);\n if (!owned) {\n owned = new Set();\n this.socketTopics.set(native, owned);\n }\n owned.add(topic);\n };\n\n const unsubscribe = (topic: string): void => {\n this.topics.get(topic)?.delete(native);\n this.socketTopics.get(native)?.delete(topic);\n };\n\n const publishToPeers = (topic: string, message: WSMessage): void => {\n const set = this.topics.get(topic);\n if (!set) {\n return;\n }\n const payload = toSendable(message);\n for (const peer of set) {\n if (peer !== native && peer.readyState === 1) {\n peer.send(payload);\n }\n }\n };\n\n const wrapper: ServerWebSocket<Data> = {\n data,\n get readyState() {\n return native.readyState;\n },\n remoteAddress,\n send: (message, _compress) => {\n const payload = toSendable(message);\n native.send(payload);\n return typeof payload === \"string\"\n ? Buffer.byteLength(payload as string)\n : (payload as Buffer | Uint8Array).byteLength;\n },\n close: (code, reason) => {\n native.close(code, reason);\n },\n subscribe,\n unsubscribe,\n publish: publishToPeers,\n isSubscribed: (topic) => !!this.socketTopics.get(native)?.has(topic),\n cork: (cb) => cb(wrapper),\n };\n\n return wrapper;\n }\n}\n\n/**\n * Return value of {@link serve}.\n */\nexport interface ServeInfo {\n /** Port the HTTP server is listening on. */\n port: number;\n /** Hostname the HTTP server bound to. */\n hostname: string;\n /** Full URL of the listening server (uses `http://` scheme). */\n url: URL;\n /**\n * Publish a message to all sockets subscribed to `topic`.\n *\n * @param topic - Topic name\n * @param message - Payload to broadcast\n * @param compress - Whether to compress (honoured on Bun, ignored on Node)\n * @returns Number of sockets that received the message\n */\n publish: (topic: string, message: WSMessage, compress?: boolean) => number;\n /**\n * Stop the server and optionally close active connections.\n *\n * @param closeActiveConnections - When `true`, terminates all live sockets\n */\n stop: (closeActiveConnections?: boolean) => void | Promise<void>;\n}\n\n/**\n * Start a standalone WebSocket server on the given port.\n *\n * @remarks\n * Auto-detects the runtime and uses the native implementation:\n * - **Bun:** delegates to `Bun.serve()` with native WebSocket support\n * - **Node/Deno:** creates a `node:http` server with the built-in\n * {@link WebSocketServer} upgrade handler\n *\n * @typeParam Data - Shape of the user-attached `data` field\n *\n * @param handlers - Lifecycle handlers ({@link WebSocketHandlers})\n * @param options - Server options including `port` and `hostname`\n * @returns A promise resolving to {@link ServeInfo}\n *\n * @example\n * ```ts\n * import { serve } from \"@hedystia/ws\";\n *\n * const server = await serve({\n * open: (ws) => ws.subscribe(\"global\"),\n * message: (ws, msg) => ws.publish(\"global\", msg),\n * });\n *\n * console.log(`Listening on ${server.url}`);\n * ```\n */\nexport async function serve<Data extends WSData = WSData>(\n handlers: WebSocketHandlers<Data>,\n options?: WebSocketServerOptions & { port?: number; hostname?: string },\n): Promise<ServeInfo> {\n const { detectRuntime } = await import(\"./runtime\");\n const runtime = detectRuntime();\n\n if (runtime === \"bun\") {\n return serveBun(handlers, options);\n }\n return serveNode(handlers, options);\n}\n\n/**\n * Start a WebSocket server using Bun's native `Bun.serve()`.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n * @param handlers - Lifecycle handlers\n * @param options - Server options including `port` and `hostname`\n * @returns A promise resolving to {@link ServeInfo}\n *\n * @internal\n */\nasync function serveBun<Data extends WSData = WSData>(\n handlers: WebSocketHandlers<Data>,\n options?: WebSocketServerOptions & { port?: number; hostname?: string },\n): Promise<ServeInfo> {\n const topics = new Map<string, Set<ServerWebSocket<Data>>>();\n const socketTopics = new WeakMap<ServerWebSocket<Data>, Set<string>>();\n const allSockets = new Set<ServerWebSocket<Data>>();\n const nativeToWrapped = new WeakMap<any, ServerWebSocket<Data>>();\n\n function cleanup(ws: ServerWebSocket<Data>): void {\n const owned = socketTopics.get(ws);\n if (owned) {\n for (const topic of owned) {\n topics.get(topic)?.delete(ws);\n }\n socketTopics.delete(ws);\n }\n allSockets.delete(ws);\n }\n\n const server = (globalThis as any).Bun.serve({\n port: options?.port ?? 0,\n hostname: options?.hostname ?? \"0.0.0.0\",\n fetch: (req: Request) => {\n if (req.headers.get(\"upgrade\") === \"websocket\") {\n const data = (options?.resolveData ? options.resolveData(req) : {}) as Data;\n const ok = server.upgrade(req, { data });\n return ok ? undefined : new Response(\"upgrade failed\", { status: 400 });\n }\n return new Response(\"Not found\", { status: 404 });\n },\n websocket: {\n open: (ws: any) => {\n const data = ws.data ?? ({} as Data);\n const wrapped: ServerWebSocket<Data> = createBunWrapper(ws, data, topics, socketTopics);\n nativeToWrapped.set(ws, wrapped);\n allSockets.add(wrapped);\n if (handlers.open) {\n Promise.resolve(handlers.open(wrapped)).catch((err) =>\n console.error(\"[ws] open handler error:\", err),\n );\n }\n },\n message: (ws: any, msg: any) => {\n const wrapped = nativeToWrapped.get(ws);\n if (wrapped) {\n Promise.resolve(handlers.message(wrapped, msg)).catch((err) =>\n console.error(\"[ws] message handler error:\", err),\n );\n }\n },\n close: (ws: any, code: number, reason: string) => {\n const wrapped = nativeToWrapped.get(ws);\n if (wrapped) {\n cleanup(wrapped);\n if (handlers.close) {\n Promise.resolve(handlers.close(wrapped, code, reason)).catch((err) =>\n console.error(\"[ws] close handler error:\", err),\n );\n }\n }\n },\n drain: handlers.drain\n ? (ws: any) => {\n const wrapped = nativeToWrapped.get(ws);\n if (wrapped && handlers.drain) {\n Promise.resolve(handlers.drain(wrapped)).catch((err) =>\n console.error(\"[ws] drain handler error:\", err),\n );\n }\n }\n : undefined,\n },\n });\n\n return {\n port: server.port,\n hostname: server.hostname,\n url: new URL(`http://${server.hostname}:${server.port}/`),\n publish: (topic, message, _compress) => {\n const set = topics.get(topic);\n if (!set || set.size === 0) {\n return 0;\n }\n const payload = toSendable(message);\n let count = 0;\n for (const ws of set) {\n if (ws.readyState === 1) {\n ws.send(payload);\n count++;\n }\n }\n return count;\n },\n stop: (closeActiveConnections) => {\n if (closeActiveConnections) {\n for (const ws of allSockets) {\n try {\n ws.close(1001, \"Server shutdown\");\n } catch {\n /* ignore */\n }\n }\n allSockets.clear();\n }\n server.stop(closeActiveConnections);\n },\n };\n}\n\n/**\n * Wrap a native Bun WebSocket into a {@link ServerWebSocket} compatible with\n * the public handler interface.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n * @param ws - Raw Bun WebSocket\n * @param data - User-supplied data payload\n * @param topics - Global topic → socket set map\n * @param socketTopics - Reverse lookup for per-socket topic membership\n * @returns A {@link ServerWebSocket} wrapper\n *\n * @internal\n */\nfunction createBunWrapper<Data extends WSData = WSData>(\n ws: any,\n data: Data,\n topics: Map<string, Set<ServerWebSocket<Data>>>,\n socketTopics: WeakMap<ServerWebSocket<Data>, Set<string>>,\n): ServerWebSocket<Data> {\n const wrapper: ServerWebSocket<Data> = {\n data,\n get readyState() {\n return ws.readyState;\n },\n get remoteAddress() {\n return ws.remoteAddress || \"\";\n },\n send: (message, _compress) => {\n ws.send(message);\n return typeof message === \"string\"\n ? Buffer.byteLength(message)\n : (message as ArrayBuffer | Uint8Array).byteLength;\n },\n close: (code, reason) => ws.close(code, reason),\n subscribe: (topic: string) => {\n let set = topics.get(topic);\n if (!set) {\n set = new Set();\n topics.set(topic, set);\n }\n set.add(wrapper);\n let owned = socketTopics.get(wrapper);\n if (!owned) {\n owned = new Set();\n socketTopics.set(wrapper, owned);\n }\n owned.add(topic);\n },\n unsubscribe: (topic: string) => {\n topics.get(topic)?.delete(wrapper);\n socketTopics.get(wrapper)?.delete(topic);\n },\n publish: (topic: string, message: WSMessage, _compress?: boolean) => {\n const set = topics.get(topic);\n if (!set) {\n return;\n }\n const payload = toSendable(message);\n for (const peer of set) {\n if (peer !== wrapper && peer.readyState === 1) {\n peer.send(payload);\n }\n }\n },\n isSubscribed: (topic: string) =>\n !!(socketTopics.get(wrapper) as Set<string> | undefined)?.has(topic),\n cork: (cb: (ws: ServerWebSocket<Data>) => void) => cb(wrapper),\n };\n return wrapper;\n}\n\n/**\n * Start a WebSocket server using Node.js `node:http` + the built-in\n * {@link WebSocketServer} upgrade handler.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n * @param handlers - Lifecycle handlers\n * @param options - Server options including `port` and `hostname`\n * @returns A promise resolving to {@link ServeInfo}\n *\n * @internal\n */\nasync function serveNode<Data extends WSData = WSData>(\n handlers: WebSocketHandlers<Data>,\n options?: WebSocketServerOptions & { port?: number; hostname?: string },\n): Promise<ServeInfo> {\n const { createServer: createHttpServer } = await import(\"node:http\");\n const wss = new WebSocketServer<Data>(handlers, options);\n\n const httpServer = createHttpServer((_req: any, res: any) => {\n res.writeHead(404);\n res.end(\"Not found\");\n });\n\n httpServer.on(\"upgrade\", (req: any, socket: any, head: any) => {\n const data = options?.resolveData ? options.resolveData(req) : undefined;\n wss\n .upgrade({ rawRequest: req, socket, head }, data ? { data: data as Data } : undefined)\n .catch(() => socket.destroy());\n });\n\n await new Promise<void>((resolve) =>\n httpServer.listen(options?.port ?? 0, options?.hostname ?? \"0.0.0.0\", resolve),\n );\n\n const addr = httpServer.address() as any;\n const port = addr?.port ?? 0;\n\n return {\n port,\n hostname: options?.hostname ?? \"0.0.0.0\",\n url: new URL(`http://${options?.hostname ?? \"0.0.0.0\"}:${port}/`),\n publish: (topic, message, _compress) => wss.publish(topic, message, _compress),\n stop: (closeActiveConnections) => {\n wss.close(closeActiveConnections);\n httpServer.close();\n },\n };\n}\n\n/**\n * Coerce a {@link WSMessage} into a form that {@link NativeSocket.send} accepts.\n *\n * @param message - User-supplied payload\n * @returns A `string`, `Buffer` or `Uint8Array` ready to be framed\n *\n * @internal\n */\nfunction toSendable(message: WSMessage): string | Buffer | Uint8Array {\n if (typeof message === \"string\") {\n return message;\n }\n if (message instanceof ArrayBuffer) {\n return Buffer.from(message);\n }\n return message;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.mjs","names":[],"sources":["../src/server.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { Duplex } from \"node:stream\";\n\nimport type {\n ServerWebSocket,\n UpgradeOptions,\n UpgradeRequest,\n WebSocketHandlers,\n WebSocketServerOptions,\n WSData,\n WSMessage,\n} from \"./types\";\n\nexport type {\n ServerWebSocket,\n UpgradeOptions,\n UpgradeRequest,\n WebSocketHandlers,\n WebSocketServerOptions,\n WSData,\n WSMessage,\n} from \"./types\";\n\n/**\n * WebSocket magic GUID defined in RFC 6455 section 4.2.2.\n *\n * @internal\n */\nconst WS_GUID = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\n\nconst OP_CONTINUATION = 0x0;\nconst OP_TEXT = 0x1;\nconst OP_BINARY = 0x2;\nconst OP_CLOSE = 0x8;\nconst OP_PING = 0x9;\nconst OP_PONG = 0xa;\n\n/**\n * Compute the `Sec-WebSocket-Accept` response header per RFC 6455 §4.2.2.\n *\n * @param clientKey - Value of the `Sec-WebSocket-Key` request header\n * @returns Base-64 encoded SHA-1 digest\n *\n * @internal\n */\nfunction computeAccept(clientKey: string): string {\n return createHash(\"sha1\")\n .update(clientKey + WS_GUID)\n .digest(\"base64\");\n}\n\n/**\n * Write a single unmasked (server-to-client) WebSocket data frame to a raw\n * duplex socket per RFC 6455 §5.2.\n *\n * @param socket - Underlying duplex transport\n * @param opcode - WebSocket opcode (text, binary, close, ping, pong …)\n * @param payload - Payload bytes to encapsulate\n *\n * @internal\n */\nfunction writeFrame(socket: Duplex, opcode: number, payload: Buffer): void {\n const len = payload.length;\n let header: Buffer;\n\n if (len < 126) {\n header = Buffer.allocUnsafe(2);\n header[0] = 0x80 | opcode;\n header[1] = len;\n } else if (len < 0x10000) {\n header = Buffer.allocUnsafe(4);\n header[0] = 0x80 | opcode;\n header[1] = 126;\n header.writeUInt16BE(len, 2);\n } else {\n header = Buffer.allocUnsafe(10);\n header[0] = 0x80 | opcode;\n header[1] = 127;\n header.writeUInt32BE(0, 2);\n header.writeUInt32BE(len >>> 0, 6);\n }\n\n if (payload.length === 0) {\n socket.write(header);\n } else {\n socket.write(Buffer.concat([header, payload]));\n }\n}\n\n/**\n * Incremental streaming RFC 6455 frame parser for masked client→server frames.\n * Handles fragmentation, ping/pong, and close frames internally.\n *\n * @internal\n */\nclass FrameParser {\n private buf: Buffer<ArrayBufferLike> = Buffer.alloc(0);\n private fragments: Buffer[] = [];\n private fragmentOpcode = 0;\n private readonly maxPayload: number;\n\n /** Callback fired when a complete data frame is assembled. */\n onMessage?: (data: Buffer | string, isBinary: boolean) => void;\n /** Callback fired when a close frame is received. */\n onClose?: (code: number, reason: string) => void;\n /** Callback fired when a ping frame is received. */\n onPing?: (data: Buffer) => void;\n /** Callback fired on parse errors or protocol violations. */\n onError?: (err: Error) => void;\n\n /**\n * @param maxPayload - Maximum allowed frame payload in bytes (default 100 MiB)\n */\n constructor(maxPayload = 100 * 1024 * 1024) {\n this.maxPayload = maxPayload;\n }\n\n /**\n * Feed a new chunk of raw socket data into the parser.\n *\n * @param chunk - Incoming bytes from the transport\n */\n push(chunk: Buffer): void {\n this.buf = this.buf.length === 0 ? chunk : Buffer.concat([this.buf, chunk]);\n this.drain();\n }\n\n /**\n * Consume as many complete frames as possible from the internal buffer.\n *\n * @internal\n */\n private drain(): void {\n for (;;) {\n if (this.buf.length < 2) {\n return;\n }\n\n const b0 = this.buf[0]!;\n const b1 = this.buf[1]!;\n const fin = (b0 & 0x80) !== 0;\n const rsv = b0 & 0x70;\n const opcode = b0 & 0x0f;\n const masked = (b1 & 0x80) !== 0;\n let payloadLen = b1 & 0x7f;\n let offset = 2;\n\n if (rsv !== 0) {\n this.onError?.(new Error(\"WebSocket: RSV bits must be 0 (no extensions negotiated)\"));\n return;\n }\n\n if (payloadLen === 126) {\n if (this.buf.length < 4) {\n return;\n }\n payloadLen = this.buf.readUInt16BE(2);\n offset = 4;\n } else if (payloadLen === 127) {\n if (this.buf.length < 10) {\n return;\n }\n const hi = this.buf.readUInt32BE(2);\n const lo = this.buf.readUInt32BE(6);\n payloadLen = hi * 0x1_0000_0000 + lo;\n offset = 10;\n }\n\n if (payloadLen > this.maxPayload) {\n this.onError?.(\n new Error(\n `WebSocket: payload length ${payloadLen} exceeds maxPayload ${this.maxPayload}`,\n ),\n );\n return;\n }\n\n const maskLen = masked ? 4 : 0;\n const frameEnd = offset + maskLen + payloadLen;\n if (this.buf.length < frameEnd) {\n return;\n }\n\n let payload: Buffer;\n if (masked) {\n const mask = this.buf.subarray(offset, offset + 4);\n payload = Buffer.allocUnsafe(payloadLen);\n for (let i = 0; i < payloadLen; i++) {\n payload[i] = this.buf[offset + 4 + i]! ^ mask[i & 3]!;\n }\n } else {\n payload = Buffer.from(this.buf.subarray(offset, frameEnd));\n }\n\n this.buf = this.buf.subarray(frameEnd);\n this.handleFrame(fin, opcode, payload);\n }\n }\n\n /**\n * Route a parsed frame to the appropriate callback based on opcode.\n *\n * @param fin - Whether this is the final fragment\n * @param opcode - WebSocket frame opcode\n * @param payload - Unmasked payload bytes\n *\n * @internal\n */\n private handleFrame(fin: boolean, opcode: number, payload: Buffer): void {\n switch (opcode) {\n case OP_PING:\n this.onPing?.(payload);\n return;\n\n case OP_PONG:\n return;\n\n case OP_CLOSE: {\n const code = payload.length >= 2 ? payload.readUInt16BE(0) : 1000;\n const reason = payload.length > 2 ? payload.subarray(2).toString(\"utf8\") : \"\";\n this.onClose?.(code, reason);\n return;\n }\n\n case OP_CONTINUATION:\n case OP_TEXT:\n case OP_BINARY:\n if (opcode !== OP_CONTINUATION) {\n this.fragmentOpcode = opcode;\n }\n this.fragments.push(payload);\n if (fin) {\n const full = Buffer.concat(this.fragments);\n this.fragments = [];\n const isBinary = this.fragmentOpcode === OP_BINARY;\n this.onMessage?.(isBinary ? full : full.toString(\"utf8\"), isBinary);\n }\n return;\n\n default:\n this.onError?.(new Error(`WebSocket: unknown opcode 0x${opcode.toString(16)}`));\n }\n }\n}\n\n/**\n * Thin wrapper around a raw `Duplex` that exposes a minimal frame-aware\n * WebSocket interface used internally by {@link WebSocketServer}.\n *\n * @internal\n */\nclass NativeSocket {\n /** Underlying duplex transport stream. */\n readonly duplex: Duplex;\n /** Incremental frame parser attached to this transport. */\n readonly parser: FrameParser;\n\n /** WHATWG-compatible ready-state: `1` open · `2` closing · `3` closed. */\n readyState: 1 | 2 | 3 = 1;\n\n /**\n * @param duplex - Raw duplex transport\n * @param maxPayload - Maximum allowed payload in bytes\n */\n constructor(duplex: Duplex, maxPayload?: number) {\n this.duplex = duplex;\n this.parser = new FrameParser(maxPayload);\n }\n\n /**\n * Send a data frame to the peer.\n *\n * @param data - Payload to transmit\n */\n send(data: string | Buffer | Uint8Array): void {\n if (this.readyState !== 1) {\n return;\n }\n const isBuf = Buffer.isBuffer(data);\n const buf =\n typeof data === \"string\"\n ? Buffer.from(data, \"utf8\")\n : isBuf\n ? data\n : Buffer.from(data as Uint8Array);\n writeFrame(this.duplex, typeof data === \"string\" ? OP_TEXT : OP_BINARY, buf);\n }\n\n /**\n * Initiate the close handshake.\n *\n * @param code - Close status code (default `1000`)\n * @param reason - Optional UTF-8 reason phrase\n */\n close(code = 1000, reason = \"\"): void {\n if (this.readyState !== 1) {\n return;\n }\n this.readyState = 2;\n const reasonBuf = Buffer.from(reason, \"utf8\");\n const payload = Buffer.allocUnsafe(2 + reasonBuf.length);\n payload.writeUInt16BE(code, 0);\n reasonBuf.copy(payload, 2);\n writeFrame(this.duplex, OP_CLOSE, payload);\n }\n\n /**\n * Hard-terminate the underlying transport without a close handshake.\n */\n terminate(): void {\n this.readyState = 3;\n this.duplex.destroy();\n }\n}\n\n/**\n * Runtime-agnostic WebSocket server built entirely on Node.js built-ins\n * (`node:crypto`, `node:stream`) — no third-party dependencies.\n *\n * @remarks\n * The class does **not** create or own an HTTP server. Callers feed it raw\n * upgrade tuples coming from any HTTP runtime (Node.js `http`, Bun, Deno,\n * Hono, Fastify's upgrade hook, etc.).\n *\n * Topic-based pub/sub is implemented in user-space, matching the shape of\n * `Bun.ServerWebSocket` so the same handler code runs on every runtime.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n *\n * @example\n * ```ts\n * import { createServer } from \"node:http\";\n * import { WebSocketServer } from \"@hedystia/ws/server\";\n *\n * const wss = new WebSocketServer({\n * open: (ws) => ws.send(\"welcome\"),\n * message: (ws, msg) => ws.publish(\"room\", msg),\n * close: (ws, code) => console.log(\"closed\", code),\n * });\n *\n * const http = createServer((_req, res) => res.end(\"ok\"));\n * http.on(\"upgrade\", (req, socket, head) => {\n * wss.upgrade({ rawRequest: req, socket, head }, { data: { user: \"anon\" } });\n * });\n * http.listen(3000);\n * ```\n */\nexport class WebSocketServer<Data extends WSData = WSData> {\n private readonly handlers: WebSocketHandlers<Data>;\n private readonly maxPayload: number | undefined;\n private readonly resolveData: ((req: any) => Record<string, any>) | undefined;\n private readonly topics = new Map<string, Set<any>>();\n private readonly socketTopics = new WeakMap<object, Set<string>>();\n private readonly allSockets = new Set<any>();\n\n /**\n * Build a new WebSocket server.\n *\n * @param handlers - Lifecycle handlers ({@link WebSocketHandlers})\n * @param options - Optional behavioural overrides ({@link WebSocketServerOptions})\n *\n * @example\n * ```ts\n * const wss = new WebSocketServer(\n * { message: (ws, msg) => ws.send(msg) },\n * { maxPayload: 1024 * 1024 },\n * );\n * ```\n */\n constructor(handlers: WebSocketHandlers<Data>, options: WebSocketServerOptions = {}) {\n this.handlers = handlers;\n this.maxPayload = options.maxPayload;\n this.resolveData = options.resolveData;\n }\n\n /**\n * Upgrade a raw HTTP upgrade tuple to a WebSocket connection.\n *\n * @remarks\n * Performs the RFC 6455 handshake synchronously on the duplex socket,\n * then wires up frame parsing and lifecycle handlers. The returned\n * promise resolves to the {@link ServerWebSocket} wrapper immediately\n * after the handshake bytes are written.\n *\n * @param req - Upgrade tuple emitted by `node:http`'s `'upgrade'` event\n * @param options - Optional initial `data` for the new connection\n * @returns Promise that resolves with the established socket wrapper\n *\n * @throws {Error} When `Sec-WebSocket-Key` is absent from the request headers\n *\n * @example\n * ```ts\n * http.on(\"upgrade\", async (req, socket, head) => {\n * try {\n * await wss.upgrade({ rawRequest: req, socket, head });\n * } catch (err) {\n * console.error(\"Upgrade failed\", err);\n * socket.destroy();\n * }\n * });\n * ```\n */\n upgrade(req: UpgradeRequest, options?: UpgradeOptions<Data>): Promise<ServerWebSocket<Data>> {\n return new Promise((resolve, reject) => {\n try {\n const rawReq = req.rawRequest;\n const duplex = req.socket as Duplex;\n\n const clientKey = rawReq.headers?.[\"sec-websocket-key\"] as string | undefined;\n if (!clientKey) {\n duplex.destroy();\n return reject(new Error(\"WebSocket upgrade: missing Sec-WebSocket-Key header\"));\n }\n\n const accept = computeAccept(clientKey);\n const rawProtocol = rawReq.headers?.[\"sec-websocket-protocol\"] as string | undefined;\n const protocol = rawProtocol?.split(\",\")[0]?.trim();\n\n let response =\n \"HTTP/1.1 101 Switching Protocols\\r\\n\" +\n \"Upgrade: websocket\\r\\n\" +\n \"Connection: Upgrade\\r\\n\" +\n `Sec-WebSocket-Accept: ${accept}\\r\\n`;\n\n if (protocol) {\n response += `Sec-WebSocket-Protocol: ${protocol}\\r\\n`;\n }\n response += \"\\r\\n\";\n duplex.write(response);\n\n const native = new NativeSocket(duplex, this.maxPayload);\n const data = (options?.data ?? {}) as Data;\n const wrapped = this.wrap(native, data, rawReq);\n this.bind(native, wrapped, req.head);\n\n resolve(wrapped);\n } catch (err) {\n reject(err);\n }\n });\n }\n\n /**\n * Publish a message to all sockets currently subscribed to `topic`.\n *\n * @param topic - Topic name\n * @param message - Payload to broadcast\n * @param _compress - Reserved for future use\n * @returns Number of sockets that received the message\n *\n * @example\n * ```ts\n * wss.publish(\"room\", JSON.stringify({ kind: \"ping\" }));\n * ```\n */\n publish(topic: string, message: WSMessage, _compress?: boolean): number {\n const set = this.topics.get(topic);\n if (!set || set.size === 0) {\n return 0;\n }\n const payload = toSendable(message);\n let count = 0;\n for (const native of set) {\n if (native.readyState === 1) {\n native.send(payload);\n count++;\n }\n }\n return count;\n }\n\n /**\n * Close the server and optionally terminate all live sockets.\n *\n * @param closeActiveConnections - When `true`, immediately terminates\n * every live connection before clearing internal state\n */\n close(closeActiveConnections = false): void {\n if (closeActiveConnections) {\n for (const native of this.allSockets) {\n try {\n if (typeof (native as any).terminate === \"function\") {\n (native as any).terminate();\n } else if (typeof (native as any).close === \"function\") {\n (native as any).close(1001, \"Server shutdown\");\n }\n } catch {\n /* ignore */\n }\n }\n this.allSockets.clear();\n }\n this.topics.clear();\n }\n\n /**\n * Wire up the duplex transport listeners (`data`, `close`, `error`) and\n * invoke the user's `open` handler.\n *\n * @param native - Wrapped transport socket\n * @param wrapped - Public {@link ServerWebSocket} wrapper\n * @param head - Buffered bytes captured by the HTTP parser (re-fed into the parser)\n *\n * @internal\n */\n private bind(\n native: NativeSocket,\n wrapped: ServerWebSocket<Data>,\n head: Buffer | Uint8Array,\n ): void {\n this.allSockets.add(native);\n\n if (head && head.length > 0) {\n native.parser.push(Buffer.isBuffer(head) ? head : Buffer.from(head));\n }\n\n native.parser.onMessage = (raw, isBinary) => {\n const message: WSMessage = isBinary\n ? raw instanceof Buffer\n ? raw\n : Buffer.from(raw as Uint8Array)\n : (raw as string);\n Promise.resolve(this.handlers.message(wrapped, message)).catch((err) =>\n console.error(\"[ws] message handler error:\", err),\n );\n };\n\n native.parser.onClose = (code, reason) => {\n if (native.readyState === 1) {\n native.close(code, reason);\n }\n this.cleanup(native);\n if (this.handlers.close) {\n Promise.resolve(this.handlers.close(wrapped, code, reason)).catch((err) =>\n console.error(\"[ws] close handler error:\", err),\n );\n }\n };\n\n native.parser.onPing = (data) => {\n if (native.readyState === 1) {\n writeFrame(native.duplex, OP_PONG, data);\n }\n };\n\n native.parser.onError = (err) => {\n native.terminate();\n this.cleanup(native);\n if (this.handlers.error) {\n Promise.resolve(this.handlers.error(wrapped, err)).catch((e) =>\n console.error(\"[ws] error handler error:\", e),\n );\n }\n };\n\n native.duplex.on(\"data\", (chunk: Buffer) => {\n try {\n native.parser.push(chunk);\n } catch (err) {\n console.error(\"[ws] frame parsing error:\", err);\n native.terminate();\n this.cleanup(native);\n }\n });\n\n native.duplex.on(\"close\", () => {\n if (native.readyState !== 3) {\n native.readyState = 3;\n this.cleanup(native);\n if (this.handlers.close) {\n Promise.resolve(this.handlers.close(wrapped, 1006, \"\")).catch((err) =>\n console.error(\"[ws] close handler error:\", err),\n );\n }\n }\n });\n\n native.duplex.on(\"error\", (err: Error) => {\n native.readyState = 3;\n this.cleanup(native);\n if (this.handlers.error) {\n Promise.resolve(this.handlers.error(wrapped, err)).catch((e) =>\n console.error(\"[ws] error handler error:\", e),\n );\n }\n });\n\n if (this.handlers.open) {\n Promise.resolve(this.handlers.open(wrapped)).catch((err) =>\n console.error(\"[ws] open handler error:\", err),\n );\n }\n }\n\n /**\n * Remove a socket from every topic it had joined and from the global\n * tracking set.\n *\n * @param native - Socket being cleaned up\n *\n * @internal\n */\n private cleanup(native: any): void {\n const owned = this.socketTopics.get(native);\n if (owned) {\n for (const topic of owned) {\n this.topics.get(topic)?.delete(native);\n }\n this.socketTopics.delete(native);\n }\n this.allSockets.delete(native);\n }\n\n /**\n * Build the {@link ServerWebSocket} wrapper exposed to user handlers,\n * embedding topic-based pub/sub backed by the server's internal maps.\n *\n * @param native - Wrapped transport socket\n * @param data - User-supplied `data` payload attached on upgrade\n * @param rawReq - Raw incoming message used to extract the remote address\n * @returns The fully-featured public wrapper\n *\n * @internal\n */\n private wrap(native: NativeSocket, data: Data, rawReq: any): ServerWebSocket<Data> {\n const remoteAddress: string =\n (rawReq?.socket?.remoteAddress as string) ||\n (rawReq?.headers?.[\"x-forwarded-for\"] as string) ||\n \"\";\n\n const subscribe = (topic: string): void => {\n let set = this.topics.get(topic);\n if (!set) {\n set = new Set();\n this.topics.set(topic, set);\n }\n set.add(native);\n let owned = this.socketTopics.get(native);\n if (!owned) {\n owned = new Set();\n this.socketTopics.set(native, owned);\n }\n owned.add(topic);\n };\n\n const unsubscribe = (topic: string): void => {\n this.topics.get(topic)?.delete(native);\n this.socketTopics.get(native)?.delete(topic);\n };\n\n const publishToPeers = (topic: string, message: WSMessage): void => {\n const set = this.topics.get(topic);\n if (!set) {\n return;\n }\n const payload = toSendable(message);\n for (const peer of set) {\n if (peer !== native && peer.readyState === 1) {\n peer.send(payload);\n }\n }\n };\n\n const wrapper: ServerWebSocket<Data> = {\n data,\n get readyState() {\n return native.readyState;\n },\n remoteAddress,\n send: (message, _compress) => {\n const payload = toSendable(message);\n native.send(payload);\n return typeof payload === \"string\"\n ? Buffer.byteLength(payload as string)\n : (payload as Buffer | Uint8Array).byteLength;\n },\n close: (code, reason) => {\n native.close(code, reason);\n },\n subscribe,\n unsubscribe,\n publish: publishToPeers,\n isSubscribed: (topic) => !!this.socketTopics.get(native)?.has(topic),\n cork: (cb) => cb(wrapper),\n };\n\n return wrapper;\n }\n}\n\n/**\n * Return value of {@link serve}.\n */\nexport interface ServeInfo {\n /** Port the HTTP server is listening on. */\n port: number;\n /** Hostname the HTTP server bound to. */\n hostname: string;\n /** Full URL of the listening server (uses `http://` scheme). */\n url: URL;\n /**\n * Publish a message to all sockets subscribed to `topic`.\n *\n * @param topic - Topic name\n * @param message - Payload to broadcast\n * @param compress - Whether to compress (honoured on Bun, ignored on Node)\n * @returns Number of sockets that received the message\n */\n publish: (topic: string, message: WSMessage, compress?: boolean) => number;\n /**\n * Stop the server and optionally close active connections.\n *\n * @param closeActiveConnections - When `true`, terminates all live sockets\n */\n stop: (closeActiveConnections?: boolean) => void | Promise<void>;\n}\n\n/**\n * Start a standalone WebSocket server on the given port.\n *\n * @remarks\n * Auto-detects the runtime and uses the native implementation:\n * - **Bun:** delegates to `Bun.serve()` with native WebSocket support\n * - **Node/Deno:** creates a `node:http` server with the built-in\n * {@link WebSocketServer} upgrade handler\n *\n * @typeParam Data - Shape of the user-attached `data` field\n *\n * @param handlers - Lifecycle handlers ({@link WebSocketHandlers})\n * @param options - Server options including `port` and `hostname`\n * @returns A promise resolving to {@link ServeInfo}\n *\n * @example\n * ```ts\n * import { serve } from \"@hedystia/ws\";\n *\n * const server = await serve({\n * open: (ws) => ws.subscribe(\"global\"),\n * message: (ws, msg) => ws.publish(\"global\", msg),\n * });\n *\n * console.log(`Listening on ${server.url}`);\n * ```\n */\nexport async function serve<Data extends WSData = WSData>(\n handlers: WebSocketHandlers<Data>,\n options?: WebSocketServerOptions & { port?: number; hostname?: string },\n): Promise<ServeInfo> {\n const { detectRuntime } = await import(\"./runtime\");\n const runtime = detectRuntime();\n\n if (runtime === \"bun\") {\n return serveBun(handlers, options);\n }\n return serveNode(handlers, options);\n}\n\n/**\n * Start a WebSocket server using Bun's native `Bun.serve()`.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n * @param handlers - Lifecycle handlers\n * @param options - Server options including `port` and `hostname`\n * @returns A promise resolving to {@link ServeInfo}\n *\n * @internal\n */\nasync function serveBun<Data extends WSData = WSData>(\n handlers: WebSocketHandlers<Data>,\n options?: WebSocketServerOptions & { port?: number; hostname?: string },\n): Promise<ServeInfo> {\n const topics = new Map<string, Set<ServerWebSocket<Data>>>();\n const socketTopics = new WeakMap<ServerWebSocket<Data>, Set<string>>();\n const allSockets = new Set<ServerWebSocket<Data>>();\n const nativeToWrapped = new WeakMap<any, ServerWebSocket<Data>>();\n\n function cleanup(ws: ServerWebSocket<Data>): void {\n const owned = socketTopics.get(ws);\n if (owned) {\n for (const topic of owned) {\n topics.get(topic)?.delete(ws);\n }\n socketTopics.delete(ws);\n }\n allSockets.delete(ws);\n }\n\n const server = (globalThis as any).Bun.serve({\n port: options?.port ?? 0,\n hostname: options?.hostname ?? \"0.0.0.0\",\n fetch: (req: Request) => {\n if (req.headers.get(\"upgrade\") === \"websocket\") {\n const data = (options?.resolveData ? options.resolveData(req) : {}) as Data;\n const ok = server.upgrade(req, { data });\n return ok ? undefined : new Response(\"upgrade failed\", { status: 400 });\n }\n return new Response(\"Not found\", { status: 404 });\n },\n websocket: {\n open: (ws: any) => {\n const data = ws.data ?? ({} as Data);\n const wrapped: ServerWebSocket<Data> = createBunWrapper(ws, data, topics, socketTopics);\n nativeToWrapped.set(ws, wrapped);\n allSockets.add(wrapped);\n if (handlers.open) {\n Promise.resolve(handlers.open(wrapped)).catch((err) =>\n console.error(\"[ws] open handler error:\", err),\n );\n }\n },\n message: (ws: any, msg: any) => {\n const wrapped = nativeToWrapped.get(ws);\n if (wrapped) {\n Promise.resolve(handlers.message(wrapped, msg)).catch((err) =>\n console.error(\"[ws] message handler error:\", err),\n );\n }\n },\n close: (ws: any, code: number, reason: string) => {\n const wrapped = nativeToWrapped.get(ws);\n if (wrapped) {\n cleanup(wrapped);\n if (handlers.close) {\n Promise.resolve(handlers.close(wrapped, code, reason)).catch((err) =>\n console.error(\"[ws] close handler error:\", err),\n );\n }\n }\n },\n drain: handlers.drain\n ? (ws: any) => {\n const wrapped = nativeToWrapped.get(ws);\n if (wrapped && handlers.drain) {\n Promise.resolve(handlers.drain(wrapped)).catch((err) =>\n console.error(\"[ws] drain handler error:\", err),\n );\n }\n }\n : undefined,\n },\n });\n\n return {\n port: server.port,\n hostname: server.hostname,\n url: new URL(`http://${server.hostname}:${server.port}/`),\n publish: (topic, message, _compress) => {\n const set = topics.get(topic);\n if (!set || set.size === 0) {\n return 0;\n }\n const payload = toSendable(message);\n let count = 0;\n for (const ws of set) {\n if (ws.readyState === 1) {\n ws.send(payload);\n count++;\n }\n }\n return count;\n },\n stop: (closeActiveConnections) => {\n if (closeActiveConnections) {\n for (const ws of allSockets) {\n try {\n ws.close(1001, \"Server shutdown\");\n } catch {\n /* ignore */\n }\n }\n allSockets.clear();\n }\n server.stop(closeActiveConnections);\n },\n };\n}\n\n/**\n * Wrap a native Bun WebSocket into a {@link ServerWebSocket} compatible with\n * the public handler interface.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n * @param ws - Raw Bun WebSocket\n * @param data - User-supplied data payload\n * @param topics - Global topic → socket set map\n * @param socketTopics - Reverse lookup for per-socket topic membership\n * @returns A {@link ServerWebSocket} wrapper\n *\n * @internal\n */\nfunction createBunWrapper<Data extends WSData = WSData>(\n ws: any,\n data: Data,\n topics: Map<string, Set<ServerWebSocket<Data>>>,\n socketTopics: WeakMap<ServerWebSocket<Data>, Set<string>>,\n): ServerWebSocket<Data> {\n const wrapper: ServerWebSocket<Data> = {\n data,\n get readyState() {\n return ws.readyState;\n },\n get remoteAddress() {\n return ws.remoteAddress || \"\";\n },\n send: (message, _compress) => {\n ws.send(message);\n return typeof message === \"string\"\n ? Buffer.byteLength(message)\n : (message as ArrayBuffer | Uint8Array).byteLength;\n },\n close: (code, reason) => ws.close(code, reason),\n subscribe: (topic: string) => {\n let set = topics.get(topic);\n if (!set) {\n set = new Set();\n topics.set(topic, set);\n }\n set.add(wrapper);\n let owned = socketTopics.get(wrapper);\n if (!owned) {\n owned = new Set();\n socketTopics.set(wrapper, owned);\n }\n owned.add(topic);\n },\n unsubscribe: (topic: string) => {\n topics.get(topic)?.delete(wrapper);\n socketTopics.get(wrapper)?.delete(topic);\n },\n publish: (topic: string, message: WSMessage, _compress?: boolean) => {\n const set = topics.get(topic);\n if (!set) {\n return;\n }\n const payload = toSendable(message);\n for (const peer of set) {\n if (peer !== wrapper && peer.readyState === 1) {\n peer.send(payload);\n }\n }\n },\n isSubscribed: (topic: string) =>\n !!(socketTopics.get(wrapper) as Set<string> | undefined)?.has(topic),\n cork: (cb: (ws: ServerWebSocket<Data>) => void) => cb(wrapper),\n };\n return wrapper;\n}\n\n/**\n * Start a WebSocket server using Node.js `node:http` + the built-in\n * {@link WebSocketServer} upgrade handler.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n * @param handlers - Lifecycle handlers\n * @param options - Server options including `port` and `hostname`\n * @returns A promise resolving to {@link ServeInfo}\n *\n * @internal\n */\nasync function serveNode<Data extends WSData = WSData>(\n handlers: WebSocketHandlers<Data>,\n options?: WebSocketServerOptions & { port?: number; hostname?: string },\n): Promise<ServeInfo> {\n const { createServer: createHttpServer } = await import(\"node:http\");\n const wss = new WebSocketServer<Data>(handlers, options);\n\n const httpServer = createHttpServer((_req: any, res: any) => {\n res.writeHead(404);\n res.end(\"Not found\");\n });\n\n httpServer.on(\"upgrade\", (req: any, socket: any, head: any) => {\n const data = options?.resolveData ? options.resolveData(req) : undefined;\n wss\n .upgrade({ rawRequest: req, socket, head }, data ? { data: data as Data } : undefined)\n .catch(() => socket.destroy());\n });\n\n await new Promise<void>((resolve) =>\n httpServer.listen(options?.port ?? 0, options?.hostname ?? \"0.0.0.0\", resolve),\n );\n\n const addr = httpServer.address() as any;\n const port = addr?.port ?? 0;\n\n return {\n port,\n hostname: options?.hostname ?? \"0.0.0.0\",\n url: new URL(`http://${options?.hostname ?? \"0.0.0.0\"}:${port}/`),\n publish: (topic, message, _compress) => wss.publish(topic, message, _compress),\n stop: (closeActiveConnections) => {\n wss.close(closeActiveConnections);\n httpServer.close();\n },\n };\n}\n\n/**\n * Coerce a {@link WSMessage} into a form that {@link NativeSocket.send} accepts.\n *\n * @param message - User-supplied payload\n * @returns A `string`, `Buffer` or `Uint8Array` ready to be framed\n *\n * @internal\n */\nfunction toSendable(message: WSMessage): string | Buffer | Uint8Array {\n if (typeof message === \"string\") {\n return message;\n }\n if (message instanceof ArrayBuffer) {\n return Buffer.from(message);\n }\n return message;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED