crossws 0.4.5 → 0.4.7

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 (49) hide show
  1. package/adapters/vercel.d.ts +2 -0
  2. package/dist/THIRD-PARTY-LICENSES.md +1 -1
  3. package/dist/_chunks/_request.mjs +2 -2
  4. package/dist/_chunks/_types.d.mts +8 -8
  5. package/dist/_chunks/adapter.d.mts +458 -4
  6. package/dist/_chunks/adapter.mjs +90 -13
  7. package/dist/_chunks/bun.d.mts +5 -3
  8. package/dist/_chunks/bunny.d.mts +2 -2
  9. package/dist/_chunks/cloudflare.d.mts +3 -3
  10. package/dist/_chunks/deno.d.mts +2 -2
  11. package/dist/_chunks/error.mjs +1 -1
  12. package/dist/_chunks/libs/ws.mjs +53 -4
  13. package/dist/_chunks/node.d.mts +2 -2
  14. package/dist/_chunks/node.mjs +17 -11
  15. package/dist/_chunks/peer.mjs +38 -26
  16. package/dist/_chunks/sse.d.mts +2 -2
  17. package/dist/_chunks/web.d.mts +1 -1
  18. package/dist/adapters/bun.d.mts +1 -1
  19. package/dist/adapters/bun.mjs +18 -9
  20. package/dist/adapters/bunny.d.mts +1 -1
  21. package/dist/adapters/bunny.mjs +8 -6
  22. package/dist/adapters/cloudflare.d.mts +1 -1
  23. package/dist/adapters/cloudflare.mjs +16 -13
  24. package/dist/adapters/deno.d.mts +1 -1
  25. package/dist/adapters/deno.mjs +22 -9
  26. package/dist/adapters/node.d.mts +2 -2
  27. package/dist/adapters/node.mjs +1 -1
  28. package/dist/adapters/sse.d.mts +1 -1
  29. package/dist/adapters/sse.mjs +7 -5
  30. package/dist/adapters/uws.d.mts +5 -4
  31. package/dist/adapters/uws.mjs +17 -10
  32. package/dist/adapters/vercel.d.mts +25 -0
  33. package/dist/adapters/vercel.mjs +48 -0
  34. package/dist/index.d.mts +30 -7
  35. package/dist/index.mjs +78 -35
  36. package/dist/server/bun.d.mts +1 -1
  37. package/dist/server/bunny.d.mts +1 -1
  38. package/dist/server/cloudflare.d.mts +1 -1
  39. package/dist/server/default.d.mts +1 -1
  40. package/dist/server/deno.d.mts +1 -1
  41. package/dist/server/node.d.mts +1 -1
  42. package/dist/server/node.mjs +1 -1
  43. package/dist/sync.d.mts +2 -0
  44. package/dist/sync.mjs +200 -0
  45. package/dist/websocket/node.mjs +1 -1
  46. package/dist/websocket/sse.d.mts +1 -1
  47. package/package.json +23 -35
  48. package/sync.d.ts +2 -0
  49. package/dist/_chunks/rolldown-runtime.mjs +0 -24
@@ -1,13 +1,16 @@
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 WSError } from "../_chunks/error.mjs";
1
+ import { AdapterHookable, adapterUtils, getPeers, toBufferLike } from "../_chunks/adapter.mjs";
2
+ import { Message, Peer } from "../_chunks/peer.mjs";
3
+ import { WSError } from "../_chunks/error.mjs";
4
4
  const denoAdapter = (options = {}) => {
5
5
  if (typeof Deno === "undefined") throw new Error("[crossws] Using Deno adapter in an incompatible environment.");
6
6
  const hooks = new AdapterHookable(options);
7
7
  const globalPeers = /* @__PURE__ */ new Map();
8
+ const baseUtils = adapterUtils(globalPeers, options);
8
9
  return {
9
- ...adapterUtils(globalPeers),
10
+ ...baseUtils,
10
11
  handleUpgrade: async (request, info) => {
12
+ const remoteAddress = info.remoteAddr?.hostname;
13
+ const requestSnapshot = snapshotRequest(request);
11
14
  const { upgradeHeaders, endResponse, context, namespace } = await hooks.upgrade(request);
12
15
  if (endResponse) return endResponse;
13
16
  const headers = upgradeHeaders instanceof Headers ? upgradeHeaders : new Headers(upgradeHeaders);
@@ -18,11 +21,12 @@ const denoAdapter = (options = {}) => {
18
21
  const peers = getPeers(globalPeers, namespace);
19
22
  const peer = new DenoPeer({
20
23
  ws: upgrade.socket,
21
- request,
24
+ request: requestSnapshot,
22
25
  peers,
23
- denoInfo: info,
26
+ remoteAddress,
24
27
  context,
25
- namespace
28
+ namespace,
29
+ sync: baseUtils.sync
26
30
  });
27
31
  peers.add(peer);
28
32
  upgrade.socket.addEventListener("open", () => {
@@ -43,14 +47,23 @@ const denoAdapter = (options = {}) => {
43
47
  }
44
48
  };
45
49
  };
50
+ function snapshotRequest(request) {
51
+ const url = request.url;
52
+ const headers = new Headers(request.headers);
53
+ return new Proxy(request, { get(target, prop, receiver) {
54
+ if (prop === "url") return url;
55
+ if (prop === "headers") return headers;
56
+ return Reflect.get(target, prop, receiver);
57
+ } });
58
+ }
46
59
  var DenoPeer = class extends Peer {
47
60
  get remoteAddress() {
48
- return this._internal.denoInfo.remoteAddr?.hostname;
61
+ return this._internal.remoteAddress;
49
62
  }
50
63
  send(data) {
51
64
  return this._internal.ws.send(toBufferLike(data));
52
65
  }
53
- publish(topic, data) {
66
+ _publish(topic, data) {
54
67
  const dataBuff = toBufferLike(data);
55
68
  for (const peer of this._internal.peers) if (peer !== this && peer._topics.has(topic)) peer._internal.ws.send(dataBuff);
56
69
  }
@@ -1,2 +1,2 @@
1
- import { a as fromNodeUpgradeHandler, i as NodeUpgradeHandler, n as NodeOptions, r as nodeAdapter, t as NodeAdapter } from "../_chunks/node.mjs";
2
- export { NodeAdapter, NodeOptions, NodeUpgradeHandler, nodeAdapter as default, fromNodeUpgradeHandler };
1
+ import { NodeAdapter, NodeOptions, NodeUpgradeHandler, fromNodeUpgradeHandler, nodeAdapter } from "../_chunks/node.mjs";
2
+ export { NodeAdapter, NodeOptions, type NodeUpgradeHandler, nodeAdapter as default, fromNodeUpgradeHandler };
@@ -1,2 +1,2 @@
1
- import { n as fromNodeUpgradeHandler, t as nodeAdapter } from "../_chunks/node.mjs";
1
+ import { fromNodeUpgradeHandler, nodeAdapter } from "../_chunks/node.mjs";
2
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,11 +1,12 @@
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";
1
+ import { AdapterHookable, adapterUtils, getPeers, toString } from "../_chunks/adapter.mjs";
2
+ import { Message, Peer } from "../_chunks/peer.mjs";
3
3
  const sseAdapter = (opts = {}) => {
4
4
  const hooks = new AdapterHookable(opts);
5
5
  const globalPeers = /* @__PURE__ */ new Map();
6
6
  const peersMap = opts.bidir ? /* @__PURE__ */ new Map() : void 0;
7
+ const baseUtils = adapterUtils(globalPeers, opts);
7
8
  return {
8
- ...adapterUtils(globalPeers),
9
+ ...baseUtils,
9
10
  fetch: async (request) => {
10
11
  const { upgradeHeaders, endResponse, context, namespace } = await hooks.upgrade(request);
11
12
  if (endResponse) return endResponse;
@@ -31,7 +32,8 @@ const sseAdapter = (opts = {}) => {
31
32
  hooks,
32
33
  ws,
33
34
  context,
34
- namespace
35
+ namespace,
36
+ sync: baseUtils.sync
35
37
  });
36
38
  peers.add(peer);
37
39
  if (opts.bidir) {
@@ -83,7 +85,7 @@ var SSEPeer = class extends Peer {
83
85
  this._sendEvent("message", toString(data));
84
86
  return 0;
85
87
  }
86
- publish(topic, data) {
88
+ _publish(topic, data) {
87
89
  const dataBuff = toString(data);
88
90
  for (const peer of this._internal.peers) if (peer !== this && peer._topics.has(topic)) peer._sendEvent("message", dataBuff);
89
91
  }
@@ -1,5 +1,5 @@
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, SyncDriver } from "../_chunks/adapter.mjs";
2
+ import { WebSocket } from "../_chunks/web.mjs";
3
3
  import uws from "uWebSockets.js";
4
4
  declare const StubRequest: {
5
5
  new (url: string, init?: RequestInit): Request;
@@ -29,6 +29,7 @@ declare class UWSPeer extends Peer<{
29
29
  uws: uws.WebSocket<UserData>;
30
30
  ws: UwsWebSocketProxy;
31
31
  uwsData: UserData;
32
+ sync?: SyncDriver;
32
33
  }> {
33
34
  get remoteAddress(): string | undefined;
34
35
  get context(): PeerContext;
@@ -37,7 +38,7 @@ declare class UWSPeer extends Peer<{
37
38
  }): number;
38
39
  subscribe(topic: string): void;
39
40
  unsubscribe(topic: string): void;
40
- publish(topic: string, message: string, options?: {
41
+ _publish(topic: string, message: string, options?: {
41
42
  compress?: boolean;
42
43
  }): number;
43
44
  close(code?: number, reason?: uws.RecognizedString): void;
@@ -47,8 +48,8 @@ declare class UWSReqProxy extends StubRequest {
47
48
  constructor(req: uws.HttpRequest);
48
49
  }
49
50
  declare class UwsWebSocketProxy implements Partial<WebSocket> {
50
- private _uws;
51
51
  readyState?: number;
52
+ private _uws;
52
53
  constructor(_uws: uws.WebSocket<UserData>);
53
54
  get bufferedAmount(): number;
54
55
  get protocol(): string;
@@ -1,16 +1,17 @@
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";
1
+ import { AdapterHookable, adapterUtils, getPeers, toBufferLike } from "../_chunks/adapter.mjs";
2
+ import { Message, Peer } from "../_chunks/peer.mjs";
3
+ import { StubRequest } from "../_chunks/_request.mjs";
4
4
  const uwsAdapter = (options = {}) => {
5
5
  const hooks = new AdapterHookable(options);
6
6
  const globalPeers = /* @__PURE__ */ new Map();
7
+ const baseUtils = adapterUtils(globalPeers, options, { nativePubSub: true });
7
8
  return {
8
- ...adapterUtils(globalPeers),
9
+ ...baseUtils,
9
10
  websocket: {
10
11
  ...options.uws,
11
12
  close(ws, code, message) {
12
13
  const peers = getPeers(globalPeers, ws.getUserData().namespace);
13
- const peer = getPeer(ws, peers);
14
+ const peer = getPeer(ws, peers, baseUtils.sync);
14
15
  peer._internal.ws.readyState = 2;
15
16
  peers.delete(peer);
16
17
  hooks.callHook("close", peer, {
@@ -20,12 +21,16 @@ const uwsAdapter = (options = {}) => {
20
21
  peer._internal.ws.readyState = 3;
21
22
  },
22
23
  message(ws, message, _isBinary) {
23
- const peer = getPeer(ws, getPeers(globalPeers, ws.getUserData().namespace));
24
+ const peer = getPeer(ws, getPeers(globalPeers, ws.getUserData().namespace), baseUtils.sync);
24
25
  hooks.callHook("message", peer, new Message(message, peer));
25
26
  },
27
+ drain(ws) {
28
+ const peer = getPeer(ws, getPeers(globalPeers, ws.getUserData().namespace));
29
+ hooks.callHook("drain", peer);
30
+ },
26
31
  open(ws) {
27
32
  const peers = getPeers(globalPeers, ws.getUserData().namespace);
28
- const peer = getPeer(ws, peers);
33
+ const peer = getPeer(ws, peers, baseUtils.sync);
29
34
  peers.add(peer);
30
35
  hooks.callHook("open", peer);
31
36
  },
@@ -70,7 +75,7 @@ const uwsAdapter = (options = {}) => {
70
75
  }
71
76
  };
72
77
  };
73
- function getPeer(uws, peers) {
78
+ function getPeer(uws, peers, sync) {
74
79
  const uwsData = uws.getUserData();
75
80
  if (uwsData.peer) return uwsData.peer;
76
81
  const peer = new UWSPeer({
@@ -79,7 +84,8 @@ function getPeer(uws, peers) {
79
84
  ws: new UwsWebSocketProxy(uws),
80
85
  request: uwsData.webReq,
81
86
  namespace: uwsData.namespace,
82
- uwsData
87
+ uwsData,
88
+ sync
83
89
  });
84
90
  uwsData.peer = peer;
85
91
  return peer;
@@ -106,7 +112,7 @@ var UWSPeer = class extends Peer {
106
112
  this._topics.delete(topic);
107
113
  this._internal.uws.unsubscribe(topic);
108
114
  }
109
- publish(topic, message, options) {
115
+ _publish(topic, message, options) {
110
116
  const data = toBufferLike(message);
111
117
  const isBinary = typeof data !== "string";
112
118
  this._internal.uws.publish(topic, data, isBinary, options?.compress);
@@ -137,6 +143,7 @@ var UWSReqProxy = class extends StubRequest {
137
143
  };
138
144
  var UwsWebSocketProxy = class {
139
145
  readyState = 1;
146
+ _uws;
140
147
  constructor(_uws) {
141
148
  this._uws = _uws;
142
149
  }
@@ -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,19 +1,42 @@
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
- import { n as WSOptions, t as ServerWithWSOptions } from "./_chunks/_types.mjs";
1
+ import { Adapter, AdapterInstance, AdapterInternal, AdapterOptions, Hooks, Message, Peer, PeerContext, ResolveHooks, SyncAdapter, SyncDriver, SyncErrorContext, SyncMessage, WSError, WaitForDrainOptions, defineHooks, defineWebSocketAdapter } from "./_chunks/adapter.mjs";
2
+ import { ServerWithWSOptions, WSOptions } from "./_chunks/_types.mjs";
3
3
  interface WebSocketProxyOptions {
4
4
  /**
5
5
  * Target WebSocket URL to proxy to (`ws://` or `wss://`).
6
6
  *
7
7
  * Can be a static string/URL or a function that resolves the target dynamically
8
- * based on the incoming {@link Peer}.
8
+ * based on the incoming {@link Peer}. The resolver may be **async** (return a
9
+ * promise) — useful when the upstream address isn't known yet at connect time
10
+ * (e.g. a worker that's still booting or being hot-reloaded). Client frames
11
+ * sent in the meantime are buffered (bounded by {@link maxBufferSize}) and a
12
+ * non-zero {@link connectTimeout} also covers the resolution, so a resolver
13
+ * that never settles closes the peer with `1011` rather than hanging. With
14
+ * `connectTimeout: 0` (timeout disabled) a never-settling resolver leaves the
15
+ * peer open until {@link maxBufferSize} is hit (`1009`), so pair an unbounded
16
+ * timeout with your own resolver deadline.
9
17
  */
10
- target: string | URL | ((peer: Peer) => string | URL);
18
+ target: string | URL | ((peer: Peer) => string | URL | Promise<string | URL>);
11
19
  /**
12
- * Forward the client's `sec-websocket-protocol` header to the upstream.
20
+ * Subprotocol(s) to offer the upstream during the handshake.
21
+ *
22
+ * - `true` (default) — forward the client's `sec-websocket-protocol` verbatim.
23
+ * - `false` — offer no subprotocol upstream.
24
+ * - `string` / `string[]` — offer a fixed subprotocol (or list) upstream,
25
+ * regardless of what the client requested.
26
+ * - `Record<string, string>` — rewrite map applied to the client's offered
27
+ * tokens: a token that matches a key is swapped for its value; tokens not
28
+ * in the map are forwarded verbatim.
29
+ * - function — resolve the upstream subprotocol(s) per {@link Peer}. Return a
30
+ * string, an array of strings, or `undefined` to offer none. Useful when the
31
+ * rewrite depends on more than the token value alone.
32
+ *
33
+ * Note: this controls only what is offered to the *upstream*. The subprotocol
34
+ * echoed back to the *client* remains the first token the client offered (per
35
+ * RFC 6455, the selected protocol must be one the client proposed).
13
36
  *
14
37
  * @default true
15
38
  */
16
- forwardProtocol?: boolean;
39
+ forwardProtocol?: boolean | string | string[] | Record<string, string> | ((peer: Peer) => string | string[] | undefined | void);
17
40
  /**
18
41
  * Maximum number of bytes buffered per peer while the upstream connection
19
42
  * is still opening. If exceeded, the peer is closed with code `1009`
@@ -79,4 +102,4 @@ interface WebSocketProxyOptions {
79
102
  * ```
80
103
  */
81
104
  declare function createWebSocketProxy(target: WebSocketProxyOptions["target"] | WebSocketProxyOptions): Partial<Hooks>;
82
- 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 };
105
+ export { type Adapter, type AdapterInstance, type AdapterInternal, type AdapterOptions, type Hooks, type Message, type Peer, type PeerContext, type ResolveHooks, type ServerWithWSOptions, type SyncAdapter, type SyncDriver, type SyncErrorContext, type SyncMessage, type WSError, type WSOptions, type WaitForDrainOptions, type WebSocketProxyOptions, createWebSocketProxy, defineHooks, defineWebSocketAdapter };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as defineHooks, n as defineWebSocketAdapter } from "./_chunks/adapter.mjs";
1
+ import { defineHooks, defineWebSocketAdapter } from "./_chunks/adapter.mjs";
2
2
  const DEFAULT_MAX_BUFFER_SIZE = 1024 * 1024;
3
3
  const DEFAULT_CONNECT_TIMEOUT = 1e4;
4
4
  const TOKEN_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
@@ -11,24 +11,13 @@ function createWebSocketProxy(target) {
11
11
  upgrade(request) {
12
12
  const reqProtocol = request.headers.get("sec-websocket-protocol");
13
13
  if (options.forwardProtocol === false || !reqProtocol) return;
14
- const accepted = reqProtocol.split(",")[0].trim();
15
- if (!TOKEN_RE.test(accepted)) return;
14
+ const accepted = _splitProtocolHeader(reqProtocol)[0];
15
+ if (!accepted || !TOKEN_RE.test(accepted)) return;
16
16
  return { headers: { "sec-websocket-protocol": accepted } };
17
17
  },
18
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
19
  const state = {
31
- ws,
20
+ ws: void 0,
32
21
  buffer: [],
33
22
  bufferSize: 0,
34
23
  open: false,
@@ -41,26 +30,20 @@ function createWebSocketProxy(target) {
41
30
  _cleanupState(upstreams, peer.id, state);
42
31
  _safeClose(peer, 1011, "Upstream connect timeout");
43
32
  }, 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;
33
+ let resolved;
34
+ try {
35
+ resolved = _resolveTarget(options.target, peer);
36
+ } catch {
56
37
  _cleanupState(upstreams, peer.id, state);
57
- _safeClose(peer, _remapIncomingCode(event.code), event.reason);
58
- });
59
- ws.addEventListener("error", () => {
38
+ _safeClose(peer, 1011, "Upstream setup failed");
39
+ return;
40
+ }
41
+ if (resolved instanceof Promise) resolved.then((url) => _dialUpstream(upstreams, peer, state, url, options, WebSocketCtor), () => {
60
42
  if (upstreams.get(peer.id) !== state) return;
61
43
  _cleanupState(upstreams, peer.id, state);
62
- _safeClose(peer, 1011, "Upstream error");
44
+ _safeClose(peer, 1011, "Upstream setup failed");
63
45
  });
46
+ else _dialUpstream(upstreams, peer, state, resolved, options, WebSocketCtor);
64
47
  },
65
48
  message(peer, message) {
66
49
  const state = upstreams.get(peer.id);
@@ -68,7 +51,7 @@ function createWebSocketProxy(target) {
68
51
  const raw = typeof message.rawData === "string" ? message.rawData : message.uint8Array();
69
52
  if (state.open) {
70
53
  try {
71
- state.ws.send(raw);
54
+ state.ws?.send(raw);
72
55
  } catch {}
73
56
  return;
74
57
  }
@@ -88,7 +71,7 @@ function createWebSocketProxy(target) {
88
71
  _clearTimeout(state);
89
72
  upstreams.delete(peer.id);
90
73
  try {
91
- state.ws.close(_normalizeOutgoingCode(details.code), _truncateReason(details.reason));
74
+ state.ws?.close(_normalizeOutgoingCode(details.code), _truncateReason(details.reason));
92
75
  } catch {}
93
76
  },
94
77
  error(peer) {
@@ -97,16 +80,56 @@ function createWebSocketProxy(target) {
97
80
  _clearTimeout(state);
98
81
  upstreams.delete(peer.id);
99
82
  try {
100
- state.ws.close(1011, "Peer error");
83
+ state.ws?.close(1011, "Peer error");
101
84
  } catch {}
102
85
  }
103
86
  };
104
87
  }
88
+ function _dialUpstream(upstreams, peer, state, url, options, WebSocketCtor) {
89
+ if (upstreams.get(peer.id) !== state) return;
90
+ let ws;
91
+ try {
92
+ const protocols = _resolveProtocols(peer, options.forwardProtocol);
93
+ const wsOptions = _resolveWsOptions(options.headers, peer);
94
+ ws = wsOptions ? new WebSocketCtor(url, protocols, wsOptions) : new WebSocketCtor(url, protocols);
95
+ ws.binaryType = "arraybuffer";
96
+ } catch {
97
+ _cleanupState(upstreams, peer.id, state);
98
+ _safeClose(peer, 1011, "Upstream setup failed");
99
+ return;
100
+ }
101
+ state.ws = ws;
102
+ ws.addEventListener("open", () => {
103
+ if (upstreams.get(peer.id) !== state) return;
104
+ _clearTimeout(state);
105
+ state.open = true;
106
+ try {
107
+ for (const data of state.buffer) ws.send(data);
108
+ } catch {} finally {
109
+ state.buffer.length = 0;
110
+ state.bufferSize = 0;
111
+ }
112
+ });
113
+ ws.addEventListener("message", (event) => {
114
+ if (upstreams.get(peer.id) !== state) return;
115
+ _safeSend(peer, event.data);
116
+ });
117
+ ws.addEventListener("close", (event) => {
118
+ if (upstreams.get(peer.id) !== state) return;
119
+ _cleanupState(upstreams, peer.id, state);
120
+ _safeClose(peer, _remapIncomingCode(event.code), event.reason);
121
+ });
122
+ ws.addEventListener("error", () => {
123
+ if (upstreams.get(peer.id) !== state) return;
124
+ _cleanupState(upstreams, peer.id, state);
125
+ _safeClose(peer, 1011, "Upstream error");
126
+ });
127
+ }
105
128
  function _cleanupState(upstreams, id, state) {
106
129
  _clearTimeout(state);
107
130
  upstreams.delete(id);
108
131
  try {
109
- state.ws.close();
132
+ state.ws?.close();
110
133
  } catch {}
111
134
  }
112
135
  function _clearTimeout(state) {
@@ -117,8 +140,12 @@ function _clearTimeout(state) {
117
140
  }
118
141
  function _resolveTarget(target, peer) {
119
142
  const raw = typeof target === "function" ? target(peer) : target;
143
+ if (_isThenable(raw)) return Promise.resolve(raw).then((value) => value instanceof URL ? value : new URL(value));
120
144
  return raw instanceof URL ? raw : new URL(raw);
121
145
  }
146
+ function _isThenable(value) {
147
+ return value != null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
148
+ }
122
149
  function _resolveWsOptions(headers, peer) {
123
150
  if (!headers) return;
124
151
  const resolved = typeof headers === "function" ? headers(peer) : headers;
@@ -127,10 +154,26 @@ function _resolveWsOptions(headers, peer) {
127
154
  }
128
155
  function _resolveProtocols(peer, forwardProtocol) {
129
156
  if (forwardProtocol === false) return;
157
+ if (typeof forwardProtocol === "function") return _normalizeProtocols(forwardProtocol(peer));
158
+ if (typeof forwardProtocol === "string" || Array.isArray(forwardProtocol)) return _normalizeProtocols(forwardProtocol);
130
159
  const header = peer.request?.headers.get("sec-websocket-protocol");
131
160
  if (!header) return;
161
+ const offered = _splitProtocolHeader(header);
162
+ if (forwardProtocol && typeof forwardProtocol === "object") {
163
+ const map = forwardProtocol;
164
+ return _normalizeProtocols(offered.map((p) => Object.prototype.hasOwnProperty.call(map, p) ? map[p] : p));
165
+ }
166
+ return _normalizeProtocols(offered);
167
+ }
168
+ function _splitProtocolHeader(header) {
132
169
  return header.split(",").map((p) => p.trim()).filter(Boolean);
133
170
  }
171
+ function _normalizeProtocols(value) {
172
+ if (value == null) return;
173
+ const list = (Array.isArray(value) ? value : [value]).filter((p) => p != null).map((p) => String(p).trim()).filter(Boolean);
174
+ const deduped = [...new Set(list)];
175
+ return deduped.length > 0 ? deduped : void 0;
176
+ }
134
177
  function _safeClose(peer, code, reason) {
135
178
  try {
136
179
  peer.close(code, _truncateReason(reason));
@@ -1,4 +1,4 @@
1
- import { n as WSOptions, t as ServerWithWSOptions } from "../_chunks/_types.mjs";
1
+ import { ServerWithWSOptions, WSOptions } from "../_chunks/_types.mjs";
2
2
  import { Server, ServerPlugin } from "srvx";
3
3
  declare function plugin(wsOpts: WSOptions): ServerPlugin;
4
4
  declare function serve(options: ServerWithWSOptions): Server;
@@ -1,4 +1,4 @@
1
- import { n as WSOptions, t as ServerWithWSOptions } from "../_chunks/_types.mjs";
1
+ import { ServerWithWSOptions, WSOptions } from "../_chunks/_types.mjs";
2
2
  import { Server, ServerPlugin } from "srvx";
3
3
  declare function plugin(wsOpts: WSOptions): ServerPlugin;
4
4
  declare function serve(options: ServerWithWSOptions): Server;
@@ -1,4 +1,4 @@
1
- import { n as WSOptions, t as ServerWithWSOptions } from "../_chunks/_types.mjs";
1
+ import { ServerWithWSOptions, WSOptions } from "../_chunks/_types.mjs";
2
2
  import { Server, ServerPlugin } from "srvx";
3
3
  declare function plugin(wsOpts: WSOptions): ServerPlugin;
4
4
  declare function serve(options: ServerWithWSOptions): Server;
@@ -1,4 +1,4 @@
1
- import { n as WSOptions, t as ServerWithWSOptions } from "../_chunks/_types.mjs";
1
+ import { ServerWithWSOptions, WSOptions } from "../_chunks/_types.mjs";
2
2
  import { Server, ServerPlugin } from "srvx";
3
3
  declare function plugin(wsOpts: WSOptions): ServerPlugin;
4
4
  declare function serve(options: ServerWithWSOptions): Server;
@@ -1,4 +1,4 @@
1
- import { n as WSOptions, t as ServerWithWSOptions } from "../_chunks/_types.mjs";
1
+ import { ServerWithWSOptions, WSOptions } from "../_chunks/_types.mjs";
2
2
  import { Server, ServerPlugin } from "srvx";
3
3
  declare function plugin(wsOpts: WSOptions): ServerPlugin;
4
4
  declare function serve(options: ServerWithWSOptions): Server;
@@ -1,4 +1,4 @@
1
- import { n as WSOptions, t as ServerWithWSOptions } from "../_chunks/_types.mjs";
1
+ import { ServerWithWSOptions, WSOptions } from "../_chunks/_types.mjs";
2
2
  import { Server, ServerPlugin } from "srvx";
3
3
  declare function plugin(wsOpts: WSOptions): ServerPlugin;
4
4
  declare function serve(options: ServerWithWSOptions): Server;
@@ -1,4 +1,4 @@
1
- import { t as nodeAdapter } from "../_chunks/node.mjs";
1
+ import { nodeAdapter } from "../_chunks/node.mjs";
2
2
  import { NodeRequest, serve as serve$1 } from "srvx/node";
3
3
  function plugin(wsOpts) {
4
4
  return (server) => {
@@ -0,0 +1,2 @@
1
+ import { PostgresClientLike, RedisClientLike, SyncAdapter, SyncDriver, SyncDriverName, SyncDriverOptions, SyncMessage, broadcastChannel, cluster, decodeEnvelope, encodeEnvelope, pgsql, redis, setupPrimaryCluster, syncDrivers } from "./_chunks/adapter.mjs";
2
+ export { type PostgresClientLike, type RedisClientLike, type SyncAdapter, type SyncDriver, SyncDriverName, SyncDriverOptions, type SyncMessage, broadcastChannel, cluster, decodeEnvelope, encodeEnvelope, pgsql, redis, setupPrimaryCluster, syncDrivers };