@hedystia/ws 2.3.1
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/.turbo/turbo-build.log +50 -0
- package/LICENSE +21 -0
- package/dist/_virtual/_rolldown/runtime.mjs +5 -0
- package/dist/client.cjs +148 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +120 -0
- package/dist/client.d.mts +120 -0
- package/dist/client.mjs +146 -0
- package/dist/client.mjs.map +1 -0
- package/dist/index.cjs +22 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +9 -0
- package/dist/index.mjs.map +1 -0
- package/dist/runtime.cjs +56 -0
- package/dist/runtime.cjs.map +1 -0
- package/dist/runtime.d.cts +47 -0
- package/dist/runtime.d.mts +47 -0
- package/dist/runtime.mjs +52 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/server.cjs +237 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +119 -0
- package/dist/server.d.mts +119 -0
- package/dist/server.mjs +236 -0
- package/dist/server.mjs.map +1 -0
- package/dist/types.d.cts +208 -0
- package/dist/types.d.mts +208 -0
- package/package.json +62 -0
- package/readme.md +102 -0
- package/src/client.ts +161 -0
- package/src/index.ts +19 -0
- package/src/runtime.ts +66 -0
- package/src/server.ts +313 -0
- package/src/types.ts +226 -0
- package/tsconfig.json +3 -0
- package/tsdown.config.ts +12 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ClientWebSocketOptions, ServerWebSocket, UpgradeOptions, UpgradeRequest, WSData, WSMessage, WebSocketHandlers, WebSocketServerOptions } from "./types.mjs";
|
|
2
|
+
import { WebSocketClient, createWebSocket, resolveWebSocket } from "./client.mjs";
|
|
3
|
+
import { Runtime, detectRuntime, isBrowser, isBun, isDeno, isNode } from "./runtime.mjs";
|
|
4
|
+
import { WebSocketServer } from "./server.mjs";
|
|
5
|
+
export { type ClientWebSocketOptions, type Runtime, type ServerWebSocket, type UpgradeOptions, type UpgradeRequest, type WSData, type WSMessage, WebSocketClient, type WebSocketHandlers, WebSocketServer, WebSocketServer as default, type WebSocketServerOptions, createWebSocket, detectRuntime, isBrowser, isBun, isDeno, isNode, resolveWebSocket };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { WebSocketClient, createWebSocket, resolveWebSocket } from "./client.mjs";
|
|
2
|
+
import { detectRuntime, isBrowser, isBun, isDeno, isNode } from "./runtime.mjs";
|
|
3
|
+
import { WebSocketServer } from "./server.mjs";
|
|
4
|
+
//#region src/index.ts
|
|
5
|
+
var src_default = WebSocketServer;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { WebSocketClient, WebSocketServer, createWebSocket, src_default as default, detectRuntime, isBrowser, isBun, isDeno, isNode, resolveWebSocket };
|
|
8
|
+
|
|
9
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["export type { ClientWebSocketOptions } from \"./client\";\nexport { createWebSocket, resolveWebSocket, WebSocketClient } from \"./client\";\nexport type { Runtime } from \"./runtime\";\nexport { detectRuntime, isBrowser, isBun, isDeno, isNode } from \"./runtime\";\nexport type {\n ServerWebSocket,\n UpgradeOptions,\n UpgradeRequest,\n WebSocketHandlers,\n WebSocketServerOptions,\n WSData,\n WSMessage,\n} from \"./server\";\n\nimport { WebSocketServer } from \"./server\";\n\nexport { WebSocketServer };\n\nexport default WebSocketServer;\n"],"mappings":";;;;AAkBA,IAAA,cAAe"}
|
package/dist/runtime.cjs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//#region src/runtime.ts
|
|
2
|
+
/**
|
|
3
|
+
* Detect the host runtime by probing well-known globals.
|
|
4
|
+
*
|
|
5
|
+
* @returns A {@link Runtime} discriminator for the active environment.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { detectRuntime } from "@hedystia/ws";
|
|
10
|
+
*
|
|
11
|
+
* if (detectRuntime() === "bun") {
|
|
12
|
+
* // ...use Bun-specific APIs
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
function detectRuntime() {
|
|
17
|
+
if (typeof globalThis === "undefined") return "unknown";
|
|
18
|
+
const g = globalThis;
|
|
19
|
+
if (g.Bun?.serve) return "bun";
|
|
20
|
+
if (g.Deno) return "deno";
|
|
21
|
+
if (g.process?.versions?.node) return "node";
|
|
22
|
+
if (typeof g.window !== "undefined" && typeof g.document !== "undefined") return "browser";
|
|
23
|
+
return "unknown";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Convenience predicate.
|
|
27
|
+
*
|
|
28
|
+
* @returns `true` when running on Bun.
|
|
29
|
+
*/
|
|
30
|
+
const isBun = () => detectRuntime() === "bun";
|
|
31
|
+
/**
|
|
32
|
+
* Convenience predicate.
|
|
33
|
+
*
|
|
34
|
+
* @returns `true` when running on Node.js.
|
|
35
|
+
*/
|
|
36
|
+
const isNode = () => detectRuntime() === "node";
|
|
37
|
+
/**
|
|
38
|
+
* Convenience predicate.
|
|
39
|
+
*
|
|
40
|
+
* @returns `true` when running on Deno.
|
|
41
|
+
*/
|
|
42
|
+
const isDeno = () => detectRuntime() === "deno";
|
|
43
|
+
/**
|
|
44
|
+
* Convenience predicate.
|
|
45
|
+
*
|
|
46
|
+
* @returns `true` when running inside a browser-like environment.
|
|
47
|
+
*/
|
|
48
|
+
const isBrowser = () => detectRuntime() === "browser";
|
|
49
|
+
//#endregion
|
|
50
|
+
exports.detectRuntime = detectRuntime;
|
|
51
|
+
exports.isBrowser = isBrowser;
|
|
52
|
+
exports.isBun = isBun;
|
|
53
|
+
exports.isDeno = isDeno;
|
|
54
|
+
exports.isNode = isNode;
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=runtime.cjs.map
|
|
@@ -0,0 +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;;;;;;;AAQT,MAAa,cAAuB,eAAe,KAAK;;;;;;AAOxD,MAAa,eAAwB,eAAe,KAAK;;;;;;AAOzD,MAAa,eAAwB,eAAe,KAAK;;;;;;AAOzD,MAAa,kBAA2B,eAAe,KAAK"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/runtime.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* String identifier of the JavaScript runtime hosting the current process.
|
|
4
|
+
*/
|
|
5
|
+
type Runtime = "bun" | "node" | "deno" | "browser" | "unknown";
|
|
6
|
+
/**
|
|
7
|
+
* Detect the host runtime by probing well-known globals.
|
|
8
|
+
*
|
|
9
|
+
* @returns A {@link Runtime} discriminator for the active environment.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { detectRuntime } from "@hedystia/ws";
|
|
14
|
+
*
|
|
15
|
+
* if (detectRuntime() === "bun") {
|
|
16
|
+
* // ...use Bun-specific APIs
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
declare function detectRuntime(): Runtime;
|
|
21
|
+
/**
|
|
22
|
+
* Convenience predicate.
|
|
23
|
+
*
|
|
24
|
+
* @returns `true` when running on Bun.
|
|
25
|
+
*/
|
|
26
|
+
declare const isBun: () => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Convenience predicate.
|
|
29
|
+
*
|
|
30
|
+
* @returns `true` when running on Node.js.
|
|
31
|
+
*/
|
|
32
|
+
declare const isNode: () => boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Convenience predicate.
|
|
35
|
+
*
|
|
36
|
+
* @returns `true` when running on Deno.
|
|
37
|
+
*/
|
|
38
|
+
declare const isDeno: () => boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Convenience predicate.
|
|
41
|
+
*
|
|
42
|
+
* @returns `true` when running inside a browser-like environment.
|
|
43
|
+
*/
|
|
44
|
+
declare const isBrowser: () => boolean;
|
|
45
|
+
//#endregion
|
|
46
|
+
export { Runtime, detectRuntime, isBrowser, isBun, isDeno, isNode };
|
|
47
|
+
//# sourceMappingURL=runtime.d.cts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/runtime.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* String identifier of the JavaScript runtime hosting the current process.
|
|
4
|
+
*/
|
|
5
|
+
type Runtime = "bun" | "node" | "deno" | "browser" | "unknown";
|
|
6
|
+
/**
|
|
7
|
+
* Detect the host runtime by probing well-known globals.
|
|
8
|
+
*
|
|
9
|
+
* @returns A {@link Runtime} discriminator for the active environment.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { detectRuntime } from "@hedystia/ws";
|
|
14
|
+
*
|
|
15
|
+
* if (detectRuntime() === "bun") {
|
|
16
|
+
* // ...use Bun-specific APIs
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
declare function detectRuntime(): Runtime;
|
|
21
|
+
/**
|
|
22
|
+
* Convenience predicate.
|
|
23
|
+
*
|
|
24
|
+
* @returns `true` when running on Bun.
|
|
25
|
+
*/
|
|
26
|
+
declare const isBun: () => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Convenience predicate.
|
|
29
|
+
*
|
|
30
|
+
* @returns `true` when running on Node.js.
|
|
31
|
+
*/
|
|
32
|
+
declare const isNode: () => boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Convenience predicate.
|
|
35
|
+
*
|
|
36
|
+
* @returns `true` when running on Deno.
|
|
37
|
+
*/
|
|
38
|
+
declare const isDeno: () => boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Convenience predicate.
|
|
41
|
+
*
|
|
42
|
+
* @returns `true` when running inside a browser-like environment.
|
|
43
|
+
*/
|
|
44
|
+
declare const isBrowser: () => boolean;
|
|
45
|
+
//#endregion
|
|
46
|
+
export { Runtime, detectRuntime, isBrowser, isBun, isDeno, isNode };
|
|
47
|
+
//# sourceMappingURL=runtime.d.mts.map
|
package/dist/runtime.mjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/runtime.ts
|
|
2
|
+
/**
|
|
3
|
+
* Detect the host runtime by probing well-known globals.
|
|
4
|
+
*
|
|
5
|
+
* @returns A {@link Runtime} discriminator for the active environment.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { detectRuntime } from "@hedystia/ws";
|
|
10
|
+
*
|
|
11
|
+
* if (detectRuntime() === "bun") {
|
|
12
|
+
* // ...use Bun-specific APIs
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
function detectRuntime() {
|
|
17
|
+
if (typeof globalThis === "undefined") return "unknown";
|
|
18
|
+
const g = globalThis;
|
|
19
|
+
if (g.Bun?.serve) return "bun";
|
|
20
|
+
if (g.Deno) return "deno";
|
|
21
|
+
if (g.process?.versions?.node) return "node";
|
|
22
|
+
if (typeof g.window !== "undefined" && typeof g.document !== "undefined") return "browser";
|
|
23
|
+
return "unknown";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Convenience predicate.
|
|
27
|
+
*
|
|
28
|
+
* @returns `true` when running on Bun.
|
|
29
|
+
*/
|
|
30
|
+
const isBun = () => detectRuntime() === "bun";
|
|
31
|
+
/**
|
|
32
|
+
* Convenience predicate.
|
|
33
|
+
*
|
|
34
|
+
* @returns `true` when running on Node.js.
|
|
35
|
+
*/
|
|
36
|
+
const isNode = () => detectRuntime() === "node";
|
|
37
|
+
/**
|
|
38
|
+
* Convenience predicate.
|
|
39
|
+
*
|
|
40
|
+
* @returns `true` when running on Deno.
|
|
41
|
+
*/
|
|
42
|
+
const isDeno = () => detectRuntime() === "deno";
|
|
43
|
+
/**
|
|
44
|
+
* Convenience predicate.
|
|
45
|
+
*
|
|
46
|
+
* @returns `true` when running inside a browser-like environment.
|
|
47
|
+
*/
|
|
48
|
+
const isBrowser = () => detectRuntime() === "browser";
|
|
49
|
+
//#endregion
|
|
50
|
+
export { detectRuntime, isBrowser, isBun, isDeno, isNode };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=runtime.mjs.map
|
|
@@ -0,0 +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;;;;;;;AAQT,MAAa,cAAuB,eAAe,KAAK;;;;;;AAOxD,MAAa,eAAwB,eAAe,KAAK;;;;;;AAOzD,MAAa,eAAwB,eAAe,KAAK;;;;;;AAOzD,MAAa,kBAA2B,eAAe,KAAK"}
|
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let ws = require("ws");
|
|
3
|
+
//#region src/server.ts
|
|
4
|
+
/**
|
|
5
|
+
* Runtime-agnostic WebSocket server.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* Internally backed by the [`ws`](https://github.com/websockets/ws) package
|
|
9
|
+
* which runs on Bun, Node.js and Deno (via `npm:` specifiers). The class
|
|
10
|
+
* does **not** create or own an HTTP server — callers feed it raw upgrade
|
|
11
|
+
* tuples coming from any HTTP runtime they prefer.
|
|
12
|
+
*
|
|
13
|
+
* It implements topic-based pub/sub on top of the per-connection
|
|
14
|
+
* `subscribe` / `unsubscribe` / `publish` API expected by Hedystia,
|
|
15
|
+
* matching the shape of `Bun.ServerWebSocket`.
|
|
16
|
+
*
|
|
17
|
+
* @typeParam Data - Shape of the user-attached `data` field
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { createServer } from "node:http";
|
|
22
|
+
* import { WebSocketServer } from "@hedystia/ws/server";
|
|
23
|
+
*
|
|
24
|
+
* const wss = new WebSocketServer({
|
|
25
|
+
* open: (ws) => ws.send("welcome"),
|
|
26
|
+
* message: (ws, msg) => ws.publish("room", msg),
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* const http = createServer((_req, res) => res.end("ok"));
|
|
30
|
+
* http.on("upgrade", (req, socket, head) => {
|
|
31
|
+
* wss.upgrade({ rawRequest: req, socket, head }, { data: { user: "anon" } });
|
|
32
|
+
* });
|
|
33
|
+
* http.listen(3000);
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
var WebSocketServer = class {
|
|
37
|
+
handlers;
|
|
38
|
+
wss;
|
|
39
|
+
topics = /* @__PURE__ */ new Map();
|
|
40
|
+
socketTopics = /* @__PURE__ */ new WeakMap();
|
|
41
|
+
allSockets = /* @__PURE__ */ new Set();
|
|
42
|
+
/**
|
|
43
|
+
* Build a new WebSocket server.
|
|
44
|
+
*
|
|
45
|
+
* @param handlers - Lifecycle handlers ({@link WebSocketHandlers})
|
|
46
|
+
* @param options - Optional behavioural overrides ({@link WebSocketServerOptions})
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const wss = new WebSocketServer(
|
|
51
|
+
* { message: (ws, msg) => ws.send(msg) },
|
|
52
|
+
* { maxPayload: 1024 * 1024 },
|
|
53
|
+
* );
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
constructor(handlers, options = {}) {
|
|
57
|
+
this.handlers = handlers;
|
|
58
|
+
this.wss = new ws.WebSocketServer({
|
|
59
|
+
noServer: true,
|
|
60
|
+
maxPayload: options.maxPayload,
|
|
61
|
+
perMessageDeflate: options.perMessageDeflate ?? false
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Upgrade a raw HTTP upgrade tuple to a WebSocket connection.
|
|
66
|
+
*
|
|
67
|
+
* @remarks
|
|
68
|
+
* The returned promise resolves to the connected {@link ServerWebSocket}
|
|
69
|
+
* once the handshake completes; rejection means the handshake failed.
|
|
70
|
+
*
|
|
71
|
+
* @param req - Upgrade tuple emitted by `node:http`'s `'upgrade'` event
|
|
72
|
+
* @param options - Optional initial `data` for the new connection
|
|
73
|
+
* @returns Promise that resolves with the established socket wrapper
|
|
74
|
+
*
|
|
75
|
+
* @throws {Error} When the underlying handshake throws synchronously
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* http.on("upgrade", async (req, socket, head) => {
|
|
80
|
+
* try {
|
|
81
|
+
* await wss.upgrade({ rawRequest: req, socket, head });
|
|
82
|
+
* } catch (err) {
|
|
83
|
+
* console.error("Upgrade failed", err);
|
|
84
|
+
* socket.destroy();
|
|
85
|
+
* }
|
|
86
|
+
* });
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
upgrade(req, options) {
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
try {
|
|
92
|
+
this.wss.handleUpgrade(req.rawRequest, req.socket, req.head, (socket) => {
|
|
93
|
+
const data = options?.data ?? {};
|
|
94
|
+
const wrapped = this.wrap(socket, data, req.rawRequest);
|
|
95
|
+
this.bind(socket, wrapped);
|
|
96
|
+
resolve(wrapped);
|
|
97
|
+
});
|
|
98
|
+
} catch (err) {
|
|
99
|
+
reject(err);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Publish a message to all sockets currently subscribed to `topic`.
|
|
105
|
+
*
|
|
106
|
+
* @param topic - Topic name
|
|
107
|
+
* @param message - Payload to broadcast
|
|
108
|
+
* @param _compress - Reserved for future use; ignored under the `ws` adapter
|
|
109
|
+
* @returns Number of sockets that received the message.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* wss.publish("room", JSON.stringify({ kind: "ping" }));
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
publish(topic, message, _compress) {
|
|
117
|
+
const set = this.topics.get(topic);
|
|
118
|
+
if (!set || set.size === 0) return 0;
|
|
119
|
+
const payload = toSendable(message);
|
|
120
|
+
let count = 0;
|
|
121
|
+
for (const socket of set) if (socket.readyState === 1) {
|
|
122
|
+
socket.send(payload);
|
|
123
|
+
count++;
|
|
124
|
+
}
|
|
125
|
+
return count;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Close the server and optionally terminate all live sockets.
|
|
129
|
+
*
|
|
130
|
+
* @param closeActiveConnections - When `true`, calls `socket.terminate()`
|
|
131
|
+
* on every live connection before shutting down.
|
|
132
|
+
*/
|
|
133
|
+
close(closeActiveConnections = false) {
|
|
134
|
+
if (closeActiveConnections) {
|
|
135
|
+
for (const socket of this.allSockets) try {
|
|
136
|
+
socket.terminate();
|
|
137
|
+
} catch {}
|
|
138
|
+
this.allSockets.clear();
|
|
139
|
+
}
|
|
140
|
+
this.wss.close();
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Attach the per-socket lifecycle listeners (`message`, `close`, `error`).
|
|
144
|
+
*
|
|
145
|
+
* @internal
|
|
146
|
+
*/
|
|
147
|
+
bind(socket, wrapped) {
|
|
148
|
+
this.allSockets.add(socket);
|
|
149
|
+
if (this.handlers.open) Promise.resolve(this.handlers.open(wrapped)).catch((err) => console.error("[ws] open handler error:", err));
|
|
150
|
+
socket.on("message", (raw, isBinary) => {
|
|
151
|
+
const message = isBinary ? raw instanceof ArrayBuffer ? new Uint8Array(raw) : Array.isArray(raw) ? Buffer.concat(raw) : raw : raw.toString();
|
|
152
|
+
Promise.resolve(this.handlers.message(wrapped, message)).catch((err) => console.error("[ws] message handler error:", err));
|
|
153
|
+
});
|
|
154
|
+
socket.on("close", (code, reason) => {
|
|
155
|
+
const owned = this.socketTopics.get(socket);
|
|
156
|
+
if (owned) {
|
|
157
|
+
for (const topic of owned) this.topics.get(topic)?.delete(socket);
|
|
158
|
+
this.socketTopics.delete(socket);
|
|
159
|
+
}
|
|
160
|
+
this.allSockets.delete(socket);
|
|
161
|
+
if (this.handlers.close) Promise.resolve(this.handlers.close(wrapped, code, reason?.toString() ?? "")).catch((err) => console.error("[ws] close handler error:", err));
|
|
162
|
+
});
|
|
163
|
+
socket.on("error", (err) => {
|
|
164
|
+
if (this.handlers.error) Promise.resolve(this.handlers.error(wrapped, err)).catch((e) => console.error("[ws] error handler error:", e));
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Build the {@link ServerWebSocket} wrapper exposed to user handlers.
|
|
169
|
+
*
|
|
170
|
+
* @internal
|
|
171
|
+
*/
|
|
172
|
+
wrap(socket, data, rawReq) {
|
|
173
|
+
const remoteAddress = rawReq?.socket?.remoteAddress || rawReq?.headers?.["x-forwarded-for"] || "";
|
|
174
|
+
const subscribe = (topic) => {
|
|
175
|
+
let set = this.topics.get(topic);
|
|
176
|
+
if (!set) {
|
|
177
|
+
set = /* @__PURE__ */ new Set();
|
|
178
|
+
this.topics.set(topic, set);
|
|
179
|
+
}
|
|
180
|
+
set.add(socket);
|
|
181
|
+
let owned = this.socketTopics.get(socket);
|
|
182
|
+
if (!owned) {
|
|
183
|
+
owned = /* @__PURE__ */ new Set();
|
|
184
|
+
this.socketTopics.set(socket, owned);
|
|
185
|
+
}
|
|
186
|
+
owned.add(topic);
|
|
187
|
+
};
|
|
188
|
+
const unsubscribe = (topic) => {
|
|
189
|
+
this.topics.get(topic)?.delete(socket);
|
|
190
|
+
this.socketTopics.get(socket)?.delete(topic);
|
|
191
|
+
};
|
|
192
|
+
const publishToPeers = (topic, message) => {
|
|
193
|
+
const set = this.topics.get(topic);
|
|
194
|
+
if (!set) return;
|
|
195
|
+
const payload = toSendable(message);
|
|
196
|
+
for (const peer of set) if (peer !== socket && peer.readyState === 1) peer.send(payload);
|
|
197
|
+
};
|
|
198
|
+
const wrapper = {
|
|
199
|
+
data,
|
|
200
|
+
get readyState() {
|
|
201
|
+
return socket.readyState;
|
|
202
|
+
},
|
|
203
|
+
remoteAddress,
|
|
204
|
+
send: (message, _compress) => {
|
|
205
|
+
const payload = toSendable(message);
|
|
206
|
+
socket.send(payload);
|
|
207
|
+
return typeof payload === "string" ? Buffer.byteLength(payload) : payload.byteLength;
|
|
208
|
+
},
|
|
209
|
+
close: (code, reason) => {
|
|
210
|
+
socket.close(code, reason);
|
|
211
|
+
},
|
|
212
|
+
subscribe,
|
|
213
|
+
unsubscribe,
|
|
214
|
+
publish: publishToPeers,
|
|
215
|
+
isSubscribed: (topic) => !!this.socketTopics.get(socket)?.has(topic),
|
|
216
|
+
cork: (cb) => cb(wrapper)
|
|
217
|
+
};
|
|
218
|
+
return wrapper;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Coerce a {@link WSMessage} into something the `ws` package can transmit.
|
|
223
|
+
*
|
|
224
|
+
* @param message - User-supplied payload
|
|
225
|
+
* @returns A `string`, `Buffer` or `Uint8Array` ready to be sent
|
|
226
|
+
*
|
|
227
|
+
* @internal
|
|
228
|
+
*/
|
|
229
|
+
function toSendable(message) {
|
|
230
|
+
if (typeof message === "string") return message;
|
|
231
|
+
if (message instanceof ArrayBuffer) return Buffer.from(message);
|
|
232
|
+
return message;
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
235
|
+
exports.WebSocketServer = WebSocketServer;
|
|
236
|
+
|
|
237
|
+
//# sourceMappingURL=server.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.cjs","names":["WSServer"],"sources":["../src/server.ts"],"sourcesContent":["import { type WebSocket as NodeWebSocket, WebSocketServer as WSServer } from \"ws\";\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 * Runtime-agnostic WebSocket server.\n *\n * @remarks\n * Internally backed by the [`ws`](https://github.com/websockets/ws) package\n * which runs on Bun, Node.js and Deno (via `npm:` specifiers). The class\n * does **not** create or own an HTTP server — callers feed it raw upgrade\n * tuples coming from any HTTP runtime they prefer.\n *\n * It implements topic-based pub/sub on top of the per-connection\n * `subscribe` / `unsubscribe` / `publish` API expected by Hedystia,\n * matching the shape of `Bun.ServerWebSocket`.\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 * });\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 wss: WSServer;\n private readonly topics = new Map<string, Set<NodeWebSocket>>();\n private readonly socketTopics = new WeakMap<NodeWebSocket, Set<string>>();\n private readonly allSockets = new Set<NodeWebSocket>();\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.wss = new WSServer({\n noServer: true,\n maxPayload: options.maxPayload,\n perMessageDeflate: (options.perMessageDeflate ?? false) as any,\n });\n }\n\n /**\n * Upgrade a raw HTTP upgrade tuple to a WebSocket connection.\n *\n * @remarks\n * The returned promise resolves to the connected {@link ServerWebSocket}\n * once the handshake completes; rejection means the handshake failed.\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 the underlying handshake throws synchronously\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 this.wss.handleUpgrade(req.rawRequest, req.socket, req.head as Buffer, (socket) => {\n const data = (options?.data ?? ({} as Data)) as Data;\n const wrapped = this.wrap(socket, data, req.rawRequest);\n this.bind(socket, wrapped);\n resolve(wrapped);\n });\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; ignored under the `ws` adapter\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 socket of set) {\n if (socket.readyState === 1) {\n socket.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`, calls `socket.terminate()`\n * on every live connection before shutting down.\n */\n close(closeActiveConnections = false): void {\n if (closeActiveConnections) {\n for (const socket of this.allSockets) {\n try {\n socket.terminate();\n } catch {\n /* ignore */\n }\n }\n this.allSockets.clear();\n }\n this.wss.close();\n }\n\n /**\n * Attach the per-socket lifecycle listeners (`message`, `close`, `error`).\n *\n * @internal\n */\n private bind(socket: NodeWebSocket, wrapped: ServerWebSocket<Data>): void {\n this.allSockets.add(socket);\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 socket.on(\"message\", (raw, isBinary) => {\n const message: WSMessage = isBinary\n ? raw instanceof ArrayBuffer\n ? new Uint8Array(raw)\n : Array.isArray(raw)\n ? Buffer.concat(raw)\n : (raw as Buffer)\n : raw.toString();\n Promise.resolve(this.handlers.message(wrapped, message)).catch((err) =>\n console.error(\"[ws] message handler error:\", err),\n );\n });\n\n socket.on(\"close\", (code, reason) => {\n const owned = this.socketTopics.get(socket);\n if (owned) {\n for (const topic of owned) {\n this.topics.get(topic)?.delete(socket);\n }\n this.socketTopics.delete(socket);\n }\n this.allSockets.delete(socket);\n if (this.handlers.close) {\n Promise.resolve(this.handlers.close(wrapped, code, reason?.toString() ?? \"\")).catch((err) =>\n console.error(\"[ws] close handler error:\", err),\n );\n }\n });\n\n socket.on(\"error\", (err) => {\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\n /**\n * Build the {@link ServerWebSocket} wrapper exposed to user handlers.\n *\n * @internal\n */\n private wrap(socket: NodeWebSocket, 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) => {\n let set = this.topics.get(topic);\n if (!set) {\n set = new Set();\n this.topics.set(topic, set);\n }\n set.add(socket);\n let owned = this.socketTopics.get(socket);\n if (!owned) {\n owned = new Set();\n this.socketTopics.set(socket, owned);\n }\n owned.add(topic);\n };\n\n const unsubscribe = (topic: string) => {\n this.topics.get(topic)?.delete(socket);\n this.socketTopics.get(socket)?.delete(topic);\n };\n\n const publishToPeers = (topic: string, message: WSMessage) => {\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 !== socket && peer.readyState === 1) {\n peer.send(payload);\n }\n }\n };\n\n const wrapper: ServerWebSocket<Data> = {\n data,\n get readyState() {\n return socket.readyState;\n },\n remoteAddress,\n send: (message, _compress) => {\n const payload = toSendable(message);\n socket.send(payload);\n return typeof payload === \"string\"\n ? Buffer.byteLength(payload)\n : (payload as Buffer | Uint8Array).byteLength;\n },\n close: (code, reason) => {\n socket.close(code, reason);\n },\n subscribe,\n unsubscribe,\n publish: publishToPeers,\n isSubscribed: (topic) => !!this.socketTopics.get(socket)?.has(topic),\n cork: (cb) => cb(wrapper),\n };\n\n return wrapper;\n }\n}\n\n/**\n * Coerce a {@link WSMessage} into something the `ws` package can transmit.\n *\n * @param message - User-supplied payload\n * @returns A `string`, `Buffer` or `Uint8Array` ready to be sent\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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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { ServerWebSocket, UpgradeOptions, UpgradeRequest, WSData, WSMessage, WebSocketHandlers, WebSocketServerOptions } from "./types.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/server.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Runtime-agnostic WebSocket server.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* Internally backed by the [`ws`](https://github.com/websockets/ws) package
|
|
9
|
+
* which runs on Bun, Node.js and Deno (via `npm:` specifiers). The class
|
|
10
|
+
* does **not** create or own an HTTP server — callers feed it raw upgrade
|
|
11
|
+
* tuples coming from any HTTP runtime they prefer.
|
|
12
|
+
*
|
|
13
|
+
* It implements topic-based pub/sub on top of the per-connection
|
|
14
|
+
* `subscribe` / `unsubscribe` / `publish` API expected by Hedystia,
|
|
15
|
+
* matching the shape of `Bun.ServerWebSocket`.
|
|
16
|
+
*
|
|
17
|
+
* @typeParam Data - Shape of the user-attached `data` field
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { createServer } from "node:http";
|
|
22
|
+
* import { WebSocketServer } from "@hedystia/ws/server";
|
|
23
|
+
*
|
|
24
|
+
* const wss = new WebSocketServer({
|
|
25
|
+
* open: (ws) => ws.send("welcome"),
|
|
26
|
+
* message: (ws, msg) => ws.publish("room", msg),
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* const http = createServer((_req, res) => res.end("ok"));
|
|
30
|
+
* http.on("upgrade", (req, socket, head) => {
|
|
31
|
+
* wss.upgrade({ rawRequest: req, socket, head }, { data: { user: "anon" } });
|
|
32
|
+
* });
|
|
33
|
+
* http.listen(3000);
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
declare class WebSocketServer<Data extends WSData = WSData> {
|
|
37
|
+
private readonly handlers;
|
|
38
|
+
private readonly wss;
|
|
39
|
+
private readonly topics;
|
|
40
|
+
private readonly socketTopics;
|
|
41
|
+
private readonly allSockets;
|
|
42
|
+
/**
|
|
43
|
+
* Build a new WebSocket server.
|
|
44
|
+
*
|
|
45
|
+
* @param handlers - Lifecycle handlers ({@link WebSocketHandlers})
|
|
46
|
+
* @param options - Optional behavioural overrides ({@link WebSocketServerOptions})
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const wss = new WebSocketServer(
|
|
51
|
+
* { message: (ws, msg) => ws.send(msg) },
|
|
52
|
+
* { maxPayload: 1024 * 1024 },
|
|
53
|
+
* );
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
constructor(handlers: WebSocketHandlers<Data>, options?: WebSocketServerOptions);
|
|
57
|
+
/**
|
|
58
|
+
* Upgrade a raw HTTP upgrade tuple to a WebSocket connection.
|
|
59
|
+
*
|
|
60
|
+
* @remarks
|
|
61
|
+
* The returned promise resolves to the connected {@link ServerWebSocket}
|
|
62
|
+
* once the handshake completes; rejection means the handshake failed.
|
|
63
|
+
*
|
|
64
|
+
* @param req - Upgrade tuple emitted by `node:http`'s `'upgrade'` event
|
|
65
|
+
* @param options - Optional initial `data` for the new connection
|
|
66
|
+
* @returns Promise that resolves with the established socket wrapper
|
|
67
|
+
*
|
|
68
|
+
* @throws {Error} When the underlying handshake throws synchronously
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* http.on("upgrade", async (req, socket, head) => {
|
|
73
|
+
* try {
|
|
74
|
+
* await wss.upgrade({ rawRequest: req, socket, head });
|
|
75
|
+
* } catch (err) {
|
|
76
|
+
* console.error("Upgrade failed", err);
|
|
77
|
+
* socket.destroy();
|
|
78
|
+
* }
|
|
79
|
+
* });
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
upgrade(req: UpgradeRequest, options?: UpgradeOptions<Data>): Promise<ServerWebSocket<Data>>;
|
|
83
|
+
/**
|
|
84
|
+
* Publish a message to all sockets currently subscribed to `topic`.
|
|
85
|
+
*
|
|
86
|
+
* @param topic - Topic name
|
|
87
|
+
* @param message - Payload to broadcast
|
|
88
|
+
* @param _compress - Reserved for future use; ignored under the `ws` adapter
|
|
89
|
+
* @returns Number of sockets that received the message.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* wss.publish("room", JSON.stringify({ kind: "ping" }));
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
publish(topic: string, message: WSMessage, _compress?: boolean): number;
|
|
97
|
+
/**
|
|
98
|
+
* Close the server and optionally terminate all live sockets.
|
|
99
|
+
*
|
|
100
|
+
* @param closeActiveConnections - When `true`, calls `socket.terminate()`
|
|
101
|
+
* on every live connection before shutting down.
|
|
102
|
+
*/
|
|
103
|
+
close(closeActiveConnections?: boolean): void;
|
|
104
|
+
/**
|
|
105
|
+
* Attach the per-socket lifecycle listeners (`message`, `close`, `error`).
|
|
106
|
+
*
|
|
107
|
+
* @internal
|
|
108
|
+
*/
|
|
109
|
+
private bind;
|
|
110
|
+
/**
|
|
111
|
+
* Build the {@link ServerWebSocket} wrapper exposed to user handlers.
|
|
112
|
+
*
|
|
113
|
+
* @internal
|
|
114
|
+
*/
|
|
115
|
+
private wrap;
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
export { type ServerWebSocket, type UpgradeOptions, type UpgradeRequest, type WSData, type WSMessage, type WebSocketHandlers, WebSocketServer, type WebSocketServerOptions };
|
|
119
|
+
//# sourceMappingURL=server.d.cts.map
|