crossws 0.4.4 → 0.4.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.
Files changed (57) hide show
  1. package/adapters/bunny.d.ts +2 -0
  2. package/adapters/vercel.d.ts +2 -0
  3. package/dist/THIRD-PARTY-LICENSES.md +33 -0
  4. package/dist/_chunks/_request.mjs +1 -4
  5. package/dist/_chunks/_types.d.mts +9 -10
  6. package/dist/_chunks/adapter.d.mts +7 -15
  7. package/dist/_chunks/adapter.mjs +6 -7
  8. package/dist/_chunks/bun.d.mts +2 -5
  9. package/dist/_chunks/bunny.d.mts +22 -0
  10. package/dist/_chunks/cloudflare.d.mts +3 -6
  11. package/dist/_chunks/deno.d.mts +2 -5
  12. package/dist/_chunks/error.mjs +1 -4
  13. package/dist/_chunks/libs/ws.mjs +96 -1171
  14. package/dist/_chunks/node.d.mts +36 -7
  15. package/dist/_chunks/node.mjs +129 -0
  16. package/dist/_chunks/peer.mjs +2 -60
  17. package/dist/_chunks/rolldown-runtime.mjs +8 -16
  18. package/dist/_chunks/sse.d.mts +2 -5
  19. package/dist/_chunks/web.d.mts +1 -3
  20. package/dist/adapters/bun.d.mts +1 -1
  21. package/dist/adapters/bun.mjs +3 -8
  22. package/dist/adapters/bunny.d.mts +2 -0
  23. package/dist/adapters/bunny.mjs +68 -0
  24. package/dist/adapters/cloudflare.d.mts +1 -1
  25. package/dist/adapters/cloudflare.mjs +11 -15
  26. package/dist/adapters/deno.d.mts +1 -1
  27. package/dist/adapters/deno.mjs +4 -9
  28. package/dist/adapters/node.d.mts +2 -2
  29. package/dist/adapters/node.mjs +2 -125
  30. package/dist/adapters/sse.d.mts +1 -1
  31. package/dist/adapters/sse.mjs +3 -8
  32. package/dist/adapters/uws.d.mts +2 -7
  33. package/dist/adapters/uws.mjs +6 -10
  34. package/dist/adapters/vercel.d.mts +25 -0
  35. package/dist/adapters/vercel.mjs +48 -0
  36. package/dist/index.d.mts +97 -2
  37. package/dist/index.mjs +178 -3
  38. package/dist/server/bun.d.mts +1 -7
  39. package/dist/server/bun.mjs +3 -7
  40. package/dist/server/bunny.d.mts +5 -0
  41. package/dist/server/bunny.mjs +23 -0
  42. package/dist/server/cloudflare.d.mts +1 -7
  43. package/dist/server/cloudflare.mjs +3 -7
  44. package/dist/server/default.d.mts +1 -7
  45. package/dist/server/default.mjs +3 -7
  46. package/dist/server/deno.d.mts +1 -7
  47. package/dist/server/deno.mjs +3 -7
  48. package/dist/server/node.d.mts +1 -7
  49. package/dist/server/node.mjs +3 -9
  50. package/dist/websocket/native.d.mts +0 -2
  51. package/dist/websocket/native.mjs +1 -5
  52. package/dist/websocket/node.d.mts +0 -2
  53. package/dist/websocket/node.mjs +2 -8
  54. package/dist/websocket/sse.d.mts +1 -4
  55. package/dist/websocket/sse.mjs +1 -4
  56. package/package.json +45 -42
  57. package/server/bunny.d.ts +2 -0
@@ -1,125 +1,2 @@
1
- import "../_chunks/rolldown-runtime.mjs";
2
- import { i as AdapterHookable, r as getPeers, t as adapterUtils } from "../_chunks/adapter.mjs";
3
- import { n as import_websocket_server } from "../_chunks/libs/ws.mjs";
4
- import { n as Message, r as toBufferLike, t as Peer } from "../_chunks/peer.mjs";
5
- import { t as StubRequest } from "../_chunks/_request.mjs";
6
- import { t as WSError } from "../_chunks/error.mjs";
7
-
8
- //#region src/adapters/node.ts
9
- const nodeAdapter = (options = {}) => {
10
- if ("Deno" in globalThis || "Bun" in globalThis) throw new Error("[crossws] Using Node.js adapter in an incompatible environment.");
11
- const hooks = new AdapterHookable(options);
12
- const globalPeers = /* @__PURE__ */ new Map();
13
- const wss = options.wss || new import_websocket_server.default({
14
- noServer: true,
15
- handleProtocols: () => false,
16
- ...options.serverOptions
17
- });
18
- wss.on("connection", (ws, nodeReq) => {
19
- const request = new NodeReqProxy(nodeReq);
20
- const peers = getPeers(globalPeers, nodeReq._namespace);
21
- const peer = new NodePeer({
22
- ws,
23
- request,
24
- peers,
25
- nodeReq,
26
- namespace: nodeReq._namespace
27
- });
28
- peers.add(peer);
29
- hooks.callHook("open", peer);
30
- ws.on("message", (data) => {
31
- if (Array.isArray(data)) data = Buffer.concat(data);
32
- hooks.callHook("message", peer, new Message(data, peer));
33
- });
34
- ws.on("error", (error) => {
35
- peers.delete(peer);
36
- hooks.callHook("error", peer, new WSError(error));
37
- });
38
- ws.on("close", (code, reason) => {
39
- peers.delete(peer);
40
- hooks.callHook("close", peer, {
41
- code,
42
- reason: reason?.toString()
43
- });
44
- });
45
- });
46
- wss.on("headers", (outgoingHeaders, req) => {
47
- const upgradeHeaders = req._upgradeHeaders;
48
- if (upgradeHeaders) for (const [key, value] of new Headers(upgradeHeaders)) outgoingHeaders.push(`${key}: ${value}`);
49
- });
50
- return {
51
- ...adapterUtils(globalPeers),
52
- handleUpgrade: async (nodeReq, socket, head, webRequest) => {
53
- const request = webRequest || new NodeReqProxy(nodeReq);
54
- const { upgradeHeaders, endResponse, context, namespace } = await hooks.upgrade(request);
55
- if (endResponse) return sendResponse(socket, endResponse);
56
- nodeReq._request = request;
57
- nodeReq._upgradeHeaders = upgradeHeaders;
58
- nodeReq._context = context;
59
- nodeReq._namespace = namespace;
60
- wss.handleUpgrade(nodeReq, socket, head, (ws) => {
61
- wss.emit("connection", ws, nodeReq);
62
- });
63
- },
64
- closeAll: (code, data, force) => {
65
- for (const client of wss.clients) if (force) client.terminate();
66
- else client.close(code, data);
67
- }
68
- };
69
- };
70
- var node_default = nodeAdapter;
71
- var NodePeer = class extends Peer {
72
- get remoteAddress() {
73
- return this._internal.nodeReq.socket?.remoteAddress;
74
- }
75
- get context() {
76
- return this._internal.nodeReq._context;
77
- }
78
- send(data, options) {
79
- const dataBuff = toBufferLike(data);
80
- const isBinary = typeof dataBuff !== "string";
81
- this._internal.ws.send(dataBuff, {
82
- compress: options?.compress,
83
- binary: isBinary,
84
- ...options
85
- });
86
- return 0;
87
- }
88
- publish(topic, data, options) {
89
- const dataBuff = toBufferLike(data);
90
- const isBinary = typeof data !== "string";
91
- const sendOptions = {
92
- compress: options?.compress,
93
- binary: isBinary,
94
- ...options
95
- };
96
- for (const peer of this._internal.peers) if (peer !== this && peer._topics.has(topic)) peer._internal.ws.send(dataBuff, sendOptions);
97
- }
98
- close(code, data) {
99
- this._internal.ws.close(code, data);
100
- }
101
- terminate() {
102
- this._internal.ws.terminate();
103
- }
104
- };
105
- var NodeReqProxy = class extends StubRequest {
106
- constructor(req) {
107
- const host = req.headers["host"] || "localhost";
108
- const url = `${req.socket?.encrypted ?? req.headers["x-forwarded-proto"] === "https" ? "https" : "http"}://${host}${req.url}`;
109
- super(url, { headers: req.headers });
110
- }
111
- };
112
- async function sendResponse(socket, res) {
113
- const head = [`HTTP/1.1 ${res.status || 200} ${res.statusText || ""}`, ...[...res.headers.entries()].map(([key, value]) => `${encodeURIComponent(key)}: ${encodeURIComponent(value)}`)];
114
- socket.write(head.join("\r\n") + "\r\n\r\n");
115
- if (res.body) for await (const chunk of res.body) socket.write(chunk);
116
- return new Promise((resolve) => {
117
- socket.end(() => {
118
- socket.destroy();
119
- resolve();
120
- });
121
- });
122
- }
123
-
124
- //#endregion
125
- export { node_default as default };
1
+ import { fromNodeUpgradeHandler, nodeAdapter } from "../_chunks/node.mjs";
2
+ export { nodeAdapter as default, fromNodeUpgradeHandler };
@@ -1,2 +1,2 @@
1
- import { n as SSEOptions, r as sseAdapter, t as SSEAdapter } from "../_chunks/sse.mjs";
1
+ import { SSEAdapter, SSEOptions, sseAdapter } from "../_chunks/sse.mjs";
2
2
  export { SSEAdapter, SSEOptions, sseAdapter as default };
@@ -1,7 +1,5 @@
1
- import { i as AdapterHookable, r as getPeers, t as adapterUtils } from "../_chunks/adapter.mjs";
2
- import { i as toString, n as Message, t as Peer } from "../_chunks/peer.mjs";
3
-
4
- //#region src/adapters/sse.ts
1
+ import { AdapterHookable, adapterUtils, getPeers } from "../_chunks/adapter.mjs";
2
+ import { Message, Peer, toString } from "../_chunks/peer.mjs";
5
3
  const sseAdapter = (opts = {}) => {
6
4
  const hooks = new AdapterHookable(opts);
7
5
  const globalPeers = /* @__PURE__ */ new Map();
@@ -55,7 +53,6 @@ const sseAdapter = (opts = {}) => {
55
53
  }
56
54
  };
57
55
  };
58
- var sse_default = sseAdapter;
59
56
  var SSEPeer = class extends Peer {
60
57
  _sseStream;
61
58
  _sseStreamController;
@@ -97,6 +94,4 @@ var SSEPeer = class extends Peer {
97
94
  var SSEWebSocketStub = class {
98
95
  readyState;
99
96
  };
100
-
101
- //#endregion
102
- export { sse_default as default };
97
+ export { sseAdapter as default };
@@ -1,13 +1,9 @@
1
- import { d as PeerContext, n as AdapterInstance, r as AdapterOptions, t as Adapter, u as Peer } from "../_chunks/adapter.mjs";
2
- import { a as WebSocket } from "../_chunks/web.mjs";
1
+ import { Adapter, AdapterInstance, AdapterOptions, Peer, PeerContext } from "../_chunks/adapter.mjs";
2
+ import { WebSocket } from "../_chunks/web.mjs";
3
3
  import uws from "uWebSockets.js";
4
-
5
- //#region src/_request.d.ts
6
4
  declare const StubRequest: {
7
5
  new (url: string, init?: RequestInit): Request;
8
6
  };
9
- //#endregion
10
- //#region src/adapters/uws.d.ts
11
7
  type UserData = {
12
8
  peer?: UWSPeer;
13
9
  req: uws.HttpRequest;
@@ -58,5 +54,4 @@ declare class UwsWebSocketProxy implements Partial<WebSocket> {
58
54
  get protocol(): string;
59
55
  get extensions(): string;
60
56
  }
61
- //#endregion
62
57
  export { UWSAdapter, UWSOptions, uwsAdapter as default };
@@ -1,8 +1,6 @@
1
- import { i as AdapterHookable, r as getPeers, t as adapterUtils } from "../_chunks/adapter.mjs";
2
- import { n as Message, r as toBufferLike, t as Peer } from "../_chunks/peer.mjs";
3
- import { t as StubRequest } from "../_chunks/_request.mjs";
4
-
5
- //#region src/adapters/uws.ts
1
+ import { AdapterHookable, adapterUtils, getPeers } from "../_chunks/adapter.mjs";
2
+ import { Message, Peer, toBufferLike } from "../_chunks/peer.mjs";
3
+ import { StubRequest } from "../_chunks/_request.mjs";
6
4
  const uwsAdapter = (options = {}) => {
7
5
  const hooks = new AdapterHookable(options);
8
6
  const globalPeers = /* @__PURE__ */ new Map();
@@ -21,7 +19,7 @@ const uwsAdapter = (options = {}) => {
21
19
  });
22
20
  peer._internal.ws.readyState = 3;
23
21
  },
24
- message(ws, message, isBinary) {
22
+ message(ws, message, _isBinary) {
25
23
  const peer = getPeer(ws, getPeers(globalPeers, ws.getUserData().namespace));
26
24
  hooks.callHook("message", peer, new Message(message, peer));
27
25
  },
@@ -72,7 +70,6 @@ const uwsAdapter = (options = {}) => {
72
70
  }
73
71
  };
74
72
  };
75
- var uws_default = uwsAdapter;
76
73
  function getPeer(uws, peers) {
77
74
  const uwsData = uws.getUserData();
78
75
  if (uwsData.peer) return uwsData.peer;
@@ -139,6 +136,7 @@ var UWSReqProxy = class extends StubRequest {
139
136
  }
140
137
  };
141
138
  var UwsWebSocketProxy = class {
139
+ _uws;
142
140
  readyState = 1;
143
141
  constructor(_uws) {
144
142
  this._uws = _uws;
@@ -153,6 +151,4 @@ var UwsWebSocketProxy = class {
153
151
  return this._uws?.getUserData().extensions;
154
152
  }
155
153
  };
156
-
157
- //#endregion
158
- export { uws_default as default };
154
+ export { uwsAdapter as default };
@@ -0,0 +1,25 @@
1
+ import { Adapter } from "../_chunks/adapter.mjs";
2
+ import { NodeAdapter, NodeOptions } from "../_chunks/node.mjs";
3
+ import { IncomingMessage, ServerResponse } from "node:http";
4
+ interface VercelAdapter extends Omit<NodeAdapter, "handleUpgrade"> {
5
+ /**
6
+ * Handle a WebSocket upgrade from a Web `Request` (fetch-style handlers).
7
+ *
8
+ * Returns a `204` {@link Response} when the upgrade was handled, or
9
+ * `undefined` when the request is not a WebSocket upgrade or Vercel's upgrade
10
+ * context is unavailable.
11
+ */
12
+ handleWebUpgrade(request: Request): Promise<Response | undefined>;
13
+ /**
14
+ * Handle a WebSocket upgrade from a Node.js `IncomingMessage` (Node-style
15
+ * handlers).
16
+ *
17
+ * Returns `true` when the upgrade was handled (and ends `res` with `204`), or
18
+ * `false` when the request is not a WebSocket upgrade or Vercel's upgrade
19
+ * context is unavailable.
20
+ */
21
+ handleNodeUpgrade(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
22
+ }
23
+ interface VercelOptions extends NodeOptions {}
24
+ declare const vercelAdapter: Adapter<VercelAdapter, VercelOptions>;
25
+ export { VercelAdapter, VercelOptions, vercelAdapter as default };
@@ -0,0 +1,48 @@
1
+ import { nodeAdapter } from "../_chunks/node.mjs";
2
+ import { NodeRequest } from "srvx/node";
3
+ const VERCEL_REQUEST_CONTEXT_SYMBOL = Symbol.for("@vercel/request-context");
4
+ const vercelAdapter = (options = {}) => {
5
+ const wss = nodeAdapter(options);
6
+ async function handleWebUpgrade(request) {
7
+ if (!_isWsUpgrade(request.method, request.headers.get("upgrade") || void 0)) return;
8
+ const upgrade = _getVercelUpgrade();
9
+ if (!upgrade) return;
10
+ await wss.handleUpgrade(upgrade.req, upgrade.socket, upgrade.head, request);
11
+ return new Response(null, { status: 204 });
12
+ }
13
+ async function handleNodeUpgrade(req, res) {
14
+ if (!_isWsUpgrade(req.method, req.headers.upgrade)) return false;
15
+ const upgrade = _getVercelUpgrade();
16
+ if (!upgrade) return false;
17
+ await wss.handleUpgrade(upgrade.req, upgrade.socket, upgrade.head, new NodeRequest({
18
+ req,
19
+ res
20
+ }));
21
+ if (!res.headersSent && !res.writableEnded) {
22
+ res.statusCode = 204;
23
+ res.end();
24
+ }
25
+ return true;
26
+ }
27
+ const { handleUpgrade: _, ...rest } = wss;
28
+ return {
29
+ ...rest,
30
+ handleWebUpgrade,
31
+ handleNodeUpgrade
32
+ };
33
+ };
34
+ function _isWsUpgrade(method, upgradeHeader) {
35
+ return method === "GET" && upgradeHeader?.toLowerCase?.() === "websocket";
36
+ }
37
+ function _getVercelUpgrade() {
38
+ const upgrade = _getVercelRequestContext()?.upgradeWebSocket?.();
39
+ return upgrade?.req && upgrade?.socket && upgrade?.head ? upgrade : void 0;
40
+ }
41
+ function _getVercelRequestContext() {
42
+ const store = globalThis[VERCEL_REQUEST_CONTEXT_SYMBOL];
43
+ if (typeof store?.get !== "function") return;
44
+ const context = store.get();
45
+ if (!context || typeof context !== "object") return;
46
+ return context;
47
+ }
48
+ export { vercelAdapter as default };
package/dist/index.d.mts CHANGED
@@ -1,2 +1,97 @@
1
- import { a as Hooks, c as Message, d as PeerContext, f as WSError, i as defineWebSocketAdapter, l as AdapterInternal, n as AdapterInstance, o as ResolveHooks, r as AdapterOptions, s as defineHooks, t as Adapter, u as Peer } from "./_chunks/adapter.mjs";
2
- export { type Adapter, type AdapterInstance, type AdapterInternal, type AdapterOptions, type Hooks, type Message, type Peer, type PeerContext, type ResolveHooks, type WSError, defineHooks, defineWebSocketAdapter };
1
+ import { Adapter, AdapterInstance, AdapterInternal, AdapterOptions, Hooks, Message, Peer, PeerContext, ResolveHooks, WSError, defineHooks, defineWebSocketAdapter } from "./_chunks/adapter.mjs";
2
+ import { ServerWithWSOptions, WSOptions } from "./_chunks/_types.mjs";
3
+ interface WebSocketProxyOptions {
4
+ /**
5
+ * Target WebSocket URL to proxy to (`ws://` or `wss://`).
6
+ *
7
+ * Can be a static string/URL or a function that resolves the target dynamically
8
+ * based on the incoming {@link Peer}.
9
+ */
10
+ target: string | URL | ((peer: Peer) => string | URL);
11
+ /**
12
+ * Subprotocol(s) to offer the upstream during the handshake.
13
+ *
14
+ * - `true` (default) — forward the client's `sec-websocket-protocol` verbatim.
15
+ * - `false` — offer no subprotocol upstream.
16
+ * - `string` / `string[]` — offer a fixed subprotocol (or list) upstream,
17
+ * regardless of what the client requested.
18
+ * - `Record<string, string>` — rewrite map applied to the client's offered
19
+ * tokens: a token that matches a key is swapped for its value; tokens not
20
+ * in the map are forwarded verbatim.
21
+ * - function — resolve the upstream subprotocol(s) per {@link Peer}. Return a
22
+ * string, an array of strings, or `undefined` to offer none. Useful when the
23
+ * rewrite depends on more than the token value alone.
24
+ *
25
+ * Note: this controls only what is offered to the *upstream*. The subprotocol
26
+ * echoed back to the *client* remains the first token the client offered (per
27
+ * RFC 6455, the selected protocol must be one the client proposed).
28
+ *
29
+ * @default true
30
+ */
31
+ forwardProtocol?: boolean | string | string[] | Record<string, string> | ((peer: Peer) => string | string[] | undefined | void);
32
+ /**
33
+ * Maximum number of bytes buffered per peer while the upstream connection
34
+ * is still opening. If exceeded, the peer is closed with code `1009`
35
+ * (Message Too Big). Set to `0` to disable the limit.
36
+ *
37
+ * @default 1048576 (1 MiB)
38
+ */
39
+ maxBufferSize?: number;
40
+ /**
41
+ * Milliseconds to wait for the upstream WebSocket handshake to complete.
42
+ * If the upstream does not open within the timeout, the peer is closed
43
+ * with code `1011`. Set to `0` to disable the timeout.
44
+ *
45
+ * @default 10000
46
+ */
47
+ connectTimeout?: number;
48
+ /**
49
+ * Custom `WebSocket` constructor used to dial the upstream. Useful when
50
+ * the runtime does not expose a global `WebSocket` (Node.js < 22) or
51
+ * when you want to use a different client implementation (e.g. `ws`,
52
+ * `undici`, a mock for tests).
53
+ *
54
+ * @default globalThis.WebSocket
55
+ */
56
+ WebSocket?: typeof WebSocket;
57
+ /**
58
+ * Extra headers to send on the upstream handshake. Can be a static
59
+ * object or a resolver called per peer.
60
+ *
61
+ * Useful to forward identity from the incoming request (`cookie`,
62
+ * `authorization`, `origin`), or to inject a shared secret the
63
+ * upstream expects.
64
+ *
65
+ * > [!NOTE]
66
+ * > The WHATWG global `WebSocket` constructor does not accept custom
67
+ * > headers — this option is only honored by `WebSocket` constructors
68
+ * > that take a third options argument (e.g. `ws`, `undici`). Pass
69
+ * > one via the {@link WebSocket} option to use it.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * createWebSocketProxy({
74
+ * target: "wss://backend.example.com",
75
+ * WebSocket: WsFromNodeWs,
76
+ * headers: (peer) => ({
77
+ * cookie: peer.request.headers.get("cookie") ?? "",
78
+ * "x-forwarded-for": peer.remoteAddress ?? "",
79
+ * }),
80
+ * });
81
+ * ```
82
+ */
83
+ headers?: HeadersInit | ((peer: Peer) => HeadersInit | undefined | void);
84
+ }
85
+ /**
86
+ * Create a set of crossws hooks that proxy incoming WebSocket connections
87
+ * to an upstream `ws://` or `wss://` target.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * import { createWebSocketProxy } from "crossws";
92
+ *
93
+ * const hooks = createWebSocketProxy("wss://echo.websocket.org");
94
+ * ```
95
+ */
96
+ declare function createWebSocketProxy(target: WebSocketProxyOptions["target"] | WebSocketProxyOptions): Partial<Hooks>;
97
+ export { type Adapter, type AdapterInstance, type AdapterInternal, type AdapterOptions, type Hooks, type Message, type Peer, type PeerContext, type ResolveHooks, type ServerWithWSOptions, type WSError, type WSOptions, type WebSocketProxyOptions, createWebSocketProxy, defineHooks, defineWebSocketAdapter };
package/dist/index.mjs CHANGED
@@ -1,3 +1,178 @@
1
- import { a as defineHooks, n as defineWebSocketAdapter } from "./_chunks/adapter.mjs";
2
-
3
- export { defineHooks, defineWebSocketAdapter };
1
+ import { defineHooks, defineWebSocketAdapter } from "./_chunks/adapter.mjs";
2
+ const DEFAULT_MAX_BUFFER_SIZE = 1024 * 1024;
3
+ const DEFAULT_CONNECT_TIMEOUT = 1e4;
4
+ const TOKEN_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
5
+ function createWebSocketProxy(target) {
6
+ const options = typeof target === "string" || target instanceof URL || typeof target === "function" ? { target } : target;
7
+ const WebSocketCtor = options.WebSocket ?? globalThis.WebSocket;
8
+ if (typeof WebSocketCtor !== "function") throw new TypeError("createWebSocketProxy requires a `WebSocket` constructor. Pass one via the `WebSocket` option, or use a runtime that provides a global `WebSocket` (Node.js >= 22, Bun, Deno, Cloudflare Workers, browsers).");
9
+ const upstreams = /* @__PURE__ */ new Map();
10
+ return {
11
+ upgrade(request) {
12
+ const reqProtocol = request.headers.get("sec-websocket-protocol");
13
+ if (options.forwardProtocol === false || !reqProtocol) return;
14
+ const accepted = _splitProtocolHeader(reqProtocol)[0];
15
+ if (!accepted || !TOKEN_RE.test(accepted)) return;
16
+ return { headers: { "sec-websocket-protocol": accepted } };
17
+ },
18
+ open(peer) {
19
+ let ws;
20
+ try {
21
+ const url = _resolveTarget(options.target, peer);
22
+ const protocols = _resolveProtocols(peer, options.forwardProtocol);
23
+ const wsOptions = _resolveWsOptions(options.headers, peer);
24
+ ws = wsOptions ? new WebSocketCtor(url, protocols, wsOptions) : new WebSocketCtor(url, protocols);
25
+ ws.binaryType = "arraybuffer";
26
+ } catch {
27
+ _safeClose(peer, 1011, "Upstream setup failed");
28
+ return;
29
+ }
30
+ const state = {
31
+ ws,
32
+ buffer: [],
33
+ bufferSize: 0,
34
+ open: false,
35
+ timeout: void 0
36
+ };
37
+ upstreams.set(peer.id, state);
38
+ const timeoutMs = options.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;
39
+ if (timeoutMs > 0) state.timeout = setTimeout(() => {
40
+ if (upstreams.get(peer.id) !== state || state.open) return;
41
+ _cleanupState(upstreams, peer.id, state);
42
+ _safeClose(peer, 1011, "Upstream connect timeout");
43
+ }, timeoutMs);
44
+ ws.addEventListener("open", () => {
45
+ _clearTimeout(state);
46
+ state.open = true;
47
+ for (const data of state.buffer) ws.send(data);
48
+ state.buffer.length = 0;
49
+ state.bufferSize = 0;
50
+ });
51
+ ws.addEventListener("message", (event) => {
52
+ _safeSend(peer, event.data);
53
+ });
54
+ ws.addEventListener("close", (event) => {
55
+ if (upstreams.get(peer.id) !== state) return;
56
+ _cleanupState(upstreams, peer.id, state);
57
+ _safeClose(peer, _remapIncomingCode(event.code), event.reason);
58
+ });
59
+ ws.addEventListener("error", () => {
60
+ if (upstreams.get(peer.id) !== state) return;
61
+ _cleanupState(upstreams, peer.id, state);
62
+ _safeClose(peer, 1011, "Upstream error");
63
+ });
64
+ },
65
+ message(peer, message) {
66
+ const state = upstreams.get(peer.id);
67
+ if (!state) return;
68
+ const raw = typeof message.rawData === "string" ? message.rawData : message.uint8Array();
69
+ if (state.open) {
70
+ try {
71
+ state.ws.send(raw);
72
+ } catch {}
73
+ return;
74
+ }
75
+ const size = typeof raw === "string" ? raw.length * 3 : raw.byteLength;
76
+ const limit = options.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE;
77
+ if (limit > 0 && state.bufferSize + size > limit) {
78
+ _cleanupState(upstreams, peer.id, state);
79
+ _safeClose(peer, 1009, "Proxy buffer limit exceeded");
80
+ return;
81
+ }
82
+ state.buffer.push(typeof raw === "string" ? raw : Uint8Array.from(raw));
83
+ state.bufferSize += size;
84
+ },
85
+ close(peer, details) {
86
+ const state = upstreams.get(peer.id);
87
+ if (!state) return;
88
+ _clearTimeout(state);
89
+ upstreams.delete(peer.id);
90
+ try {
91
+ state.ws.close(_normalizeOutgoingCode(details.code), _truncateReason(details.reason));
92
+ } catch {}
93
+ },
94
+ error(peer) {
95
+ const state = upstreams.get(peer.id);
96
+ if (!state) return;
97
+ _clearTimeout(state);
98
+ upstreams.delete(peer.id);
99
+ try {
100
+ state.ws.close(1011, "Peer error");
101
+ } catch {}
102
+ }
103
+ };
104
+ }
105
+ function _cleanupState(upstreams, id, state) {
106
+ _clearTimeout(state);
107
+ upstreams.delete(id);
108
+ try {
109
+ state.ws.close();
110
+ } catch {}
111
+ }
112
+ function _clearTimeout(state) {
113
+ if (state.timeout !== void 0) {
114
+ clearTimeout(state.timeout);
115
+ state.timeout = void 0;
116
+ }
117
+ }
118
+ function _resolveTarget(target, peer) {
119
+ const raw = typeof target === "function" ? target(peer) : target;
120
+ return raw instanceof URL ? raw : new URL(raw);
121
+ }
122
+ function _resolveWsOptions(headers, peer) {
123
+ if (!headers) return;
124
+ const resolved = typeof headers === "function" ? headers(peer) : headers;
125
+ if (!resolved) return;
126
+ return { headers: resolved };
127
+ }
128
+ function _resolveProtocols(peer, forwardProtocol) {
129
+ if (forwardProtocol === false) return;
130
+ if (typeof forwardProtocol === "function") return _normalizeProtocols(forwardProtocol(peer));
131
+ if (typeof forwardProtocol === "string" || Array.isArray(forwardProtocol)) return _normalizeProtocols(forwardProtocol);
132
+ const header = peer.request?.headers.get("sec-websocket-protocol");
133
+ if (!header) return;
134
+ const offered = _splitProtocolHeader(header);
135
+ if (forwardProtocol && typeof forwardProtocol === "object") {
136
+ const map = forwardProtocol;
137
+ return _normalizeProtocols(offered.map((p) => Object.prototype.hasOwnProperty.call(map, p) ? map[p] : p));
138
+ }
139
+ return _normalizeProtocols(offered);
140
+ }
141
+ function _splitProtocolHeader(header) {
142
+ return header.split(",").map((p) => p.trim()).filter(Boolean);
143
+ }
144
+ function _normalizeProtocols(value) {
145
+ if (value == null) return;
146
+ const list = (Array.isArray(value) ? value : [value]).filter((p) => p != null).map((p) => String(p).trim()).filter(Boolean);
147
+ const deduped = [...new Set(list)];
148
+ return deduped.length > 0 ? deduped : void 0;
149
+ }
150
+ function _safeClose(peer, code, reason) {
151
+ try {
152
+ peer.close(code, _truncateReason(reason));
153
+ } catch {}
154
+ }
155
+ function _safeSend(peer, data) {
156
+ try {
157
+ peer.send(data);
158
+ } catch {}
159
+ }
160
+ function _truncateReason(reason) {
161
+ if (!reason) return reason;
162
+ const bytes = new TextEncoder().encode(reason);
163
+ if (bytes.length <= 123) return reason;
164
+ return new TextDecoder("utf-8", { fatal: false }).decode(bytes.subarray(0, 123));
165
+ }
166
+ function _remapIncomingCode(code) {
167
+ if (code === void 0) return void 0;
168
+ if (code === 1005) return 1e3;
169
+ if (code === 1006 || code === 1015) return 1011;
170
+ return code;
171
+ }
172
+ function _normalizeOutgoingCode(code) {
173
+ if (code === void 0) return void 0;
174
+ if (code === 1e3) return 1e3;
175
+ if (code >= 3e3 && code <= 4999) return code;
176
+ return 1e3;
177
+ }
178
+ export { createWebSocketProxy, defineHooks, defineWebSocketAdapter };
@@ -1,11 +1,5 @@
1
- import "../_chunks/bun.mjs";
2
- import "../_chunks/cloudflare.mjs";
3
- import "../_chunks/node.mjs";
4
- import { n as WSOptions, t as ServerWithWSOptions } from "../_chunks/_types.mjs";
1
+ import { ServerWithWSOptions, WSOptions } from "../_chunks/_types.mjs";
5
2
  import { Server, ServerPlugin } from "srvx";
6
-
7
- //#region src/server/bun.d.ts
8
3
  declare function plugin(wsOpts: WSOptions): ServerPlugin;
9
4
  declare function serve(options: ServerWithWSOptions): Server;
10
- //#endregion
11
5
  export { plugin, serve };
@@ -1,10 +1,8 @@
1
- import bun_default from "../adapters/bun.mjs";
1
+ import bunAdapter from "../adapters/bun.mjs";
2
2
  import { serve as serve$1 } from "srvx/bun";
3
-
4
- //#region src/server/bun.ts
5
3
  function plugin(wsOpts) {
6
4
  return (server) => {
7
- const ws = bun_default({
5
+ const ws = bunAdapter({
8
6
  hooks: wsOpts,
9
7
  resolve: wsOpts.resolve,
10
8
  ...wsOpts.options?.bun
@@ -25,6 +23,4 @@ function serve(options) {
25
23
  }
26
24
  return serve$1(options);
27
25
  }
28
-
29
- //#endregion
30
- export { plugin, serve };
26
+ export { plugin, serve };
@@ -0,0 +1,5 @@
1
+ import { ServerWithWSOptions, WSOptions } from "../_chunks/_types.mjs";
2
+ import { Server, ServerPlugin } from "srvx";
3
+ declare function plugin(wsOpts: WSOptions): ServerPlugin;
4
+ declare function serve(options: ServerWithWSOptions): Server;
5
+ export { plugin, serve };
@@ -0,0 +1,23 @@
1
+ import bunnyAdapter from "../adapters/bunny.mjs";
2
+ import { serve as serve$1 } from "srvx/bunny";
3
+ function plugin(wsOpts) {
4
+ return (server) => {
5
+ const ws = bunnyAdapter({
6
+ hooks: wsOpts,
7
+ resolve: wsOpts.resolve,
8
+ ...wsOpts.options?.bunny
9
+ });
10
+ server.options.middleware.unshift((req, next) => {
11
+ if (req.headers.get("upgrade")?.toLowerCase() === "websocket") return ws.handleUpgrade(req);
12
+ return next();
13
+ });
14
+ };
15
+ }
16
+ function serve(options) {
17
+ if (options.websocket) {
18
+ options.plugins ||= [];
19
+ options.plugins.push(plugin(options.websocket));
20
+ }
21
+ return serve$1(options);
22
+ }
23
+ export { plugin, serve };
@@ -1,11 +1,5 @@
1
- import "../_chunks/bun.mjs";
2
- import "../_chunks/cloudflare.mjs";
3
- import "../_chunks/node.mjs";
4
- import { n as WSOptions, t as ServerWithWSOptions } from "../_chunks/_types.mjs";
1
+ import { ServerWithWSOptions, WSOptions } from "../_chunks/_types.mjs";
5
2
  import { Server, ServerPlugin } from "srvx";
6
-
7
- //#region src/server/cloudflare.d.ts
8
3
  declare function plugin(wsOpts: WSOptions): ServerPlugin;
9
4
  declare function serve(options: ServerWithWSOptions): Server;
10
- //#endregion
11
5
  export { plugin, serve };