crossws 0.4.6 → 0.4.8

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.
@@ -1,6 +1,6 @@
1
- import { AdapterHookable, adapterUtils, getPeers } from "./adapter.mjs";
2
1
  import { import_websocket_server } from "./libs/ws.mjs";
3
- import { Message, Peer, toBufferLike } from "./peer.mjs";
2
+ import { AdapterHookable, adapterUtils, getPeers, toBufferLike } from "./adapter.mjs";
3
+ import { Message, Peer } from "./peer.mjs";
4
4
  import { WSError } from "./error.mjs";
5
5
  import { StubRequest } from "./_request.mjs";
6
6
  function fromNodeUpgradeHandler(handler) {
@@ -15,6 +15,7 @@ const nodeAdapter = (options = {}) => {
15
15
  if ("Deno" in globalThis || "Bun" in globalThis) throw new Error("[crossws] Using Node.js adapter in an incompatible environment.");
16
16
  const hooks = new AdapterHookable(options);
17
17
  const globalPeers = /* @__PURE__ */ new Map();
18
+ const baseUtils = adapterUtils(globalPeers, options);
18
19
  const wss = options.wss || new import_websocket_server.default({
19
20
  noServer: true,
20
21
  handleProtocols: () => false,
@@ -28,7 +29,8 @@ const nodeAdapter = (options = {}) => {
28
29
  request,
29
30
  peers,
30
31
  nodeReq,
31
- namespace: nodeReq._namespace
32
+ namespace: nodeReq._namespace,
33
+ sync: baseUtils.sync
32
34
  });
33
35
  peers.add(peer);
34
36
  hooks.callHook("open", peer);
@@ -41,8 +43,12 @@ const nodeAdapter = (options = {}) => {
41
43
  peers.delete(peer);
42
44
  hooks.callHook("error", peer, new WSError(error));
43
45
  });
46
+ const socket = ws._socket;
47
+ const onDrain = () => hooks.callHook("drain", peer);
48
+ socket?.on("drain", onDrain);
44
49
  ws.on("close", (code, reason) => {
45
50
  peers.delete(peer);
51
+ socket?.off("drain", onDrain);
46
52
  hooks.callHook("close", peer, {
47
53
  code,
48
54
  reason: reason?.toString()
@@ -54,7 +60,7 @@ const nodeAdapter = (options = {}) => {
54
60
  if (upgradeHeaders) for (const [key, value] of new Headers(upgradeHeaders)) outgoingHeaders.push(`${key}: ${value}`);
55
61
  });
56
62
  return {
57
- ...adapterUtils(globalPeers),
63
+ ...baseUtils,
58
64
  handleUpgrade: async (nodeReq, socket, head, webRequest) => {
59
65
  const request = webRequest || new NodeReqProxy(nodeReq);
60
66
  const { upgradeHeaders, endResponse, handled, context, namespace } = await hooks.upgrade(request);
@@ -89,11 +95,11 @@ var NodePeer = class extends Peer {
89
95
  binary: isBinary,
90
96
  ...options
91
97
  });
92
- return 0;
98
+ return this._internal.ws.bufferedAmount;
93
99
  }
94
- publish(topic, data, options) {
100
+ _publish(topic, data, options) {
95
101
  const dataBuff = toBufferLike(data);
96
- const isBinary = typeof data !== "string";
102
+ const isBinary = typeof dataBuff !== "string";
97
103
  const sendOptions = {
98
104
  compress: options?.compress,
99
105
  binary: isBinary,
@@ -1,28 +1,4 @@
1
- const kNodeInspect = /*#__PURE__*/ Symbol.for("nodejs.util.inspect.custom");
2
- function toBufferLike(val) {
3
- if (val === void 0 || val === null) return "";
4
- const type = typeof val;
5
- if (type === "string") return val;
6
- if (type === "number" || type === "boolean" || type === "bigint") return val.toString();
7
- if (type === "function" || type === "symbol") return "{}";
8
- if (val instanceof Uint8Array || val instanceof ArrayBuffer) return val;
9
- if (isPlainObject(val)) return JSON.stringify(val);
10
- return val;
11
- }
12
- function toString(val) {
13
- if (typeof val === "string") return val;
14
- const data = toBufferLike(val);
15
- if (typeof data === "string") return data;
16
- return `data:application/octet-stream;base64,${btoa(String.fromCharCode(...new Uint8Array(data)))}`;
17
- }
18
- function isPlainObject(value) {
19
- if (value === null || typeof value !== "object") return false;
20
- const prototype = Object.getPrototypeOf(value);
21
- if (prototype !== null && prototype !== Object.prototype && Object.getPrototypeOf(prototype) !== null) return false;
22
- if (Symbol.iterator in value) return false;
23
- if (Symbol.toStringTag in value) return Object.prototype.toString.call(value) === "[object Module]";
24
- return true;
25
- }
1
+ import { kNodeInspect, serializeMessage } from "./adapter.mjs";
26
2
  var Message = class {
27
3
  event;
28
4
  peer;
@@ -147,6 +123,34 @@ var Peer = class {
147
123
  get topics() {
148
124
  return this._topics;
149
125
  }
126
+ get bufferedAmount() {
127
+ return this._internal.ws?.bufferedAmount ?? 0;
128
+ }
129
+ waitForDrain(opts = {}) {
130
+ const threshold = opts.threshold ?? 0;
131
+ if (this.bufferedAmount <= threshold) return Promise.resolve();
132
+ const signal = opts.signal;
133
+ if (signal?.aborted) return Promise.reject(signal.reason);
134
+ return new Promise((resolve, reject) => {
135
+ const check = () => {
136
+ if (this.bufferedAmount <= threshold || (this.websocket.readyState ?? 1) > 1) {
137
+ cleanup();
138
+ resolve();
139
+ }
140
+ };
141
+ const onAbort = () => {
142
+ cleanup();
143
+ reject(signal.reason);
144
+ };
145
+ const timer = setInterval(check, opts.pollInterval ?? 100);
146
+ timer.unref?.();
147
+ const cleanup = () => {
148
+ clearInterval(timer);
149
+ signal?.removeEventListener("abort", onAbort);
150
+ };
151
+ signal?.addEventListener("abort", onAbort, { once: true });
152
+ });
153
+ }
150
154
  terminate() {
151
155
  this.close();
152
156
  }
@@ -156,6 +160,14 @@ var Peer = class {
156
160
  unsubscribe(topic) {
157
161
  this._topics.delete(topic);
158
162
  }
163
+ publish(topic, data, options) {
164
+ this._publish(topic, data, options);
165
+ this._internal.sync?.publish({
166
+ namespace: this.namespace,
167
+ topic,
168
+ data: serializeMessage(data)
169
+ });
170
+ }
159
171
  toString() {
160
172
  return this.id;
161
173
  }
@@ -183,4 +195,4 @@ function createWsProxy(ws, request) {
183
195
  return value;
184
196
  } });
185
197
  }
186
- export { Message, Peer, toBufferLike, toString };
198
+ export { Message, Peer };
@@ -1,11 +1,12 @@
1
- import { AdapterHookable, adapterUtils, getPeers } from "../_chunks/adapter.mjs";
2
- import { Message, Peer, toBufferLike } from "../_chunks/peer.mjs";
1
+ import { AdapterHookable, adapterUtils, getPeers, toBufferLike } from "../_chunks/adapter.mjs";
2
+ import { Message, Peer } from "../_chunks/peer.mjs";
3
3
  const bunAdapter = (options = {}) => {
4
4
  if (typeof Bun === "undefined") throw new Error("[crossws] Using Bun adapter in an incompatible environment.");
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
  async handleUpgrade(request, server) {
10
11
  const { upgradeHeaders, endResponse, context, namespace } = await hooks.upgrade(request);
11
12
  if (endResponse) return endResponse;
@@ -21,34 +22,39 @@ const bunAdapter = (options = {}) => {
21
22
  },
22
23
  websocket: {
23
24
  message: (ws, message) => {
24
- const peer = getPeer(ws, getPeers(globalPeers, ws.data.namespace));
25
+ const peer = getPeer(ws, getPeers(globalPeers, ws.data.namespace), baseUtils.sync);
25
26
  hooks.callHook("message", peer, new Message(message, peer));
26
27
  },
27
28
  open: (ws) => {
28
29
  const peers = getPeers(globalPeers, ws.data.namespace);
29
- const peer = getPeer(ws, peers);
30
+ const peer = getPeer(ws, peers, baseUtils.sync);
30
31
  peers.add(peer);
31
32
  hooks.callHook("open", peer);
32
33
  },
33
34
  close: (ws, code, reason) => {
34
35
  const peers = getPeers(globalPeers, ws.data.namespace);
35
- const peer = getPeer(ws, peers);
36
+ const peer = getPeer(ws, peers, baseUtils.sync);
36
37
  peers.delete(peer);
37
38
  hooks.callHook("close", peer, {
38
39
  code,
39
40
  reason
40
41
  });
42
+ },
43
+ drain: (ws) => {
44
+ const peer = getPeer(ws, getPeers(globalPeers, ws.data.namespace));
45
+ hooks.callHook("drain", peer);
41
46
  }
42
47
  }
43
48
  };
44
49
  };
45
- function getPeer(ws, peers) {
50
+ function getPeer(ws, peers, sync) {
46
51
  if (ws.data.peer) return ws.data.peer;
47
52
  const peer = new BunPeer({
48
53
  ws,
49
54
  request: ws.data.request,
50
55
  peers,
51
- namespace: ws.data.namespace
56
+ namespace: ws.data.namespace,
57
+ sync
52
58
  });
53
59
  ws.data.peer = peer;
54
60
  return peer;
@@ -60,10 +66,13 @@ var BunPeer = class extends Peer {
60
66
  get context() {
61
67
  return this._internal.ws.data.context;
62
68
  }
69
+ get bufferedAmount() {
70
+ return this._internal.ws.getBufferedAmount();
71
+ }
63
72
  send(data, options) {
64
73
  return this._internal.ws.send(toBufferLike(data), options?.compress);
65
74
  }
66
- publish(topic, data, options) {
75
+ _publish(topic, data, options) {
67
76
  return this._internal.ws.publish(topic, toBufferLike(data), options?.compress);
68
77
  }
69
78
  subscribe(topic) {
@@ -1,11 +1,12 @@
1
- import { AdapterHookable, adapterUtils, getPeers } from "../_chunks/adapter.mjs";
2
- import { Message, Peer, toBufferLike } from "../_chunks/peer.mjs";
1
+ import { AdapterHookable, adapterUtils, getPeers, toBufferLike } from "../_chunks/adapter.mjs";
2
+ import { Message, Peer } from "../_chunks/peer.mjs";
3
3
  import { WSError } from "../_chunks/error.mjs";
4
4
  const bunnyAdapter = (options = {}) => {
5
5
  const hooks = new AdapterHookable(options);
6
6
  const globalPeers = /* @__PURE__ */ new Map();
7
+ const baseUtils = adapterUtils(globalPeers, options);
7
8
  return {
8
- ...adapterUtils(globalPeers),
9
+ ...baseUtils,
9
10
  handleUpgrade: async (request) => {
10
11
  if (!request.upgradeWebSocket || typeof request.upgradeWebSocket !== "function") throw new Error("[crossws] Bunny adapter requires the request to have an upgradeWebSocket method.");
11
12
  const { endResponse, context, namespace, upgradeHeaders } = await hooks.upgrade(request);
@@ -23,7 +24,8 @@ const bunnyAdapter = (options = {}) => {
23
24
  namespace,
24
25
  remoteAddress,
25
26
  peers,
26
- context
27
+ context,
28
+ sync: baseUtils.sync
27
29
  });
28
30
  peers.add(peer);
29
31
  socket.addEventListener("open", () => {
@@ -54,7 +56,7 @@ var BunnyPeer = class extends Peer {
54
56
  send(data) {
55
57
  return this._internal.ws.send(toBufferLike(data));
56
58
  }
57
- publish(topic, data) {
59
+ _publish(topic, data) {
58
60
  const dataBuff = toBufferLike(data);
59
61
  for (const peer of this._internal.peers) if (peer !== this && peer._topics.has(topic)) peer._internal.ws.send(dataBuff);
60
62
  }
@@ -1,5 +1,5 @@
1
- import { AdapterHookable, adapterUtils, getPeers } from "../_chunks/adapter.mjs";
2
- import { Message, Peer, toBufferLike } from "../_chunks/peer.mjs";
1
+ import { AdapterHookable, adapterUtils, getPeers, toBufferLike } from "../_chunks/adapter.mjs";
2
+ import { Message, Peer } from "../_chunks/peer.mjs";
3
3
  import { WSError } from "../_chunks/error.mjs";
4
4
  import { StubRequest } from "../_chunks/_request.mjs";
5
5
  import { env } from "cloudflare:workers";
@@ -14,7 +14,8 @@ const cloudflareAdapter = (opts = {}) => {
14
14
  return binding.get(instanceId);
15
15
  }
16
16
  });
17
- const { publish: durablePublish, ...utils } = adapterUtils(globalPeers);
17
+ const baseUtils = adapterUtils(globalPeers, opts);
18
+ const { publish: durablePublish, ...utils } = baseUtils;
18
19
  return {
19
20
  ...utils,
20
21
  handleUpgrade: async (request, cfEnv, cfCtx) => {
@@ -65,7 +66,7 @@ const cloudflareAdapter = (opts = {}) => {
65
66
  const pair = new WebSocketPair();
66
67
  const client = pair[0];
67
68
  const server = pair[1];
68
- const peer = CloudflareDurablePeer._restore(obj, server, request, namespace);
69
+ const peer = CloudflareDurablePeer._restore(obj, server, request, namespace, baseUtils.sync);
69
70
  peers.add(peer);
70
71
  obj.ctx.acceptWebSocket(server);
71
72
  await hooks.callHook("open", peer);
@@ -76,11 +77,11 @@ const cloudflareAdapter = (opts = {}) => {
76
77
  });
77
78
  },
78
79
  handleDurableMessage: async (obj, ws, message) => {
79
- const peer = CloudflareDurablePeer._restore(obj, ws);
80
+ const peer = CloudflareDurablePeer._restore(obj, ws, void 0, void 0, baseUtils.sync);
80
81
  await hooks.callHook("message", peer, new Message(message, peer));
81
82
  },
82
83
  handleDurableClose: async (obj, ws, code, reason, wasClean) => {
83
- const peer = CloudflareDurablePeer._restore(obj, ws);
84
+ const peer = CloudflareDurablePeer._restore(obj, ws, void 0, void 0, baseUtils.sync);
84
85
  getPeers(globalPeers, peer.namespace).delete(peer);
85
86
  const details = {
86
87
  code,
@@ -106,7 +107,7 @@ const cloudflareAdapter = (opts = {}) => {
106
107
  };
107
108
  var CloudflareDurablePeer = class CloudflareDurablePeer extends Peer {
108
109
  get peers() {
109
- return new Set(this.#getwebsockets().map((ws) => CloudflareDurablePeer._restore(this._internal.durable, ws)));
110
+ return new Set(this.#getwebsockets().map((ws) => CloudflareDurablePeer._restore(this._internal.durable, ws, void 0, void 0, this._internal.sync)));
110
111
  }
111
112
  #getwebsockets() {
112
113
  return this._internal.durable.ctx.getWebSockets();
@@ -121,7 +122,7 @@ var CloudflareDurablePeer = class CloudflareDurablePeer extends Peer {
121
122
  state.t.add(topic);
122
123
  setAttachedState(this._internal.ws, state);
123
124
  }
124
- publish(topic, data) {
125
+ _publish(topic, data) {
125
126
  const websockets = this.#getwebsockets();
126
127
  if (websockets.length < 2) return;
127
128
  const dataBuff = toBufferLike(data);
@@ -133,7 +134,7 @@ var CloudflareDurablePeer = class CloudflareDurablePeer extends Peer {
133
134
  close(code, reason) {
134
135
  this._internal.ws.close(code, reason);
135
136
  }
136
- static _restore(durable, ws, request, namespace) {
137
+ static _restore(durable, ws, request, namespace, sync) {
137
138
  let peer = ws._crosswsPeer;
138
139
  if (peer) return peer;
139
140
  const state = ws.deserializeAttachment() || {};
@@ -141,7 +142,8 @@ var CloudflareDurablePeer = class CloudflareDurablePeer extends Peer {
141
142
  ws,
142
143
  request: request || new StubRequest(state.u || ""),
143
144
  namespace: namespace || state.n || "",
144
- durable
145
+ durable,
146
+ sync
145
147
  });
146
148
  if (state.i) peer._id = state.i;
147
149
  if (request?.url) state.u = request.url;
@@ -156,7 +158,7 @@ var CloudflareFallbackPeer = class extends Peer {
156
158
  this._internal.wsServer.send(toBufferLike(data));
157
159
  return 0;
158
160
  }
159
- publish(_topic, _message) {
161
+ _publish(_topic, _message) {
160
162
  console.warn("[crossws] [cloudflare] pub/sub support requires Durable Objects.");
161
163
  }
162
164
  close(code, reason) {
@@ -1,13 +1,16 @@
1
- import { AdapterHookable, adapterUtils, getPeers } from "../_chunks/adapter.mjs";
2
- import { Message, Peer, toBufferLike } from "../_chunks/peer.mjs";
1
+ import { AdapterHookable, adapterUtils, getPeers, toBufferLike } from "../_chunks/adapter.mjs";
2
+ import { Message, Peer } from "../_chunks/peer.mjs";
3
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,11 +1,12 @@
1
- import { AdapterHookable, adapterUtils, getPeers } from "../_chunks/adapter.mjs";
2
- import { Message, Peer, toString } 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,4 +1,4 @@
1
- import { Adapter, AdapterInstance, AdapterOptions, Peer, PeerContext } from "../_chunks/adapter.mjs";
1
+ import { Adapter, AdapterInstance, AdapterOptions, Peer, PeerContext, SyncDriver } from "../_chunks/adapter.mjs";
2
2
  import { WebSocket } from "../_chunks/web.mjs";
3
3
  import uws from "uWebSockets.js";
4
4
  declare const StubRequest: {
@@ -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 { AdapterHookable, adapterUtils, getPeers } from "../_chunks/adapter.mjs";
2
- import { Message, Peer, toBufferLike } from "../_chunks/peer.mjs";
1
+ import { AdapterHookable, adapterUtils, getPeers, toBufferLike } from "../_chunks/adapter.mjs";
2
+ import { Message, Peer } from "../_chunks/peer.mjs";
3
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);
@@ -136,8 +142,8 @@ var UWSReqProxy = class extends StubRequest {
136
142
  }
137
143
  };
138
144
  var UwsWebSocketProxy = class {
139
- _uws;
140
145
  readyState = 1;
146
+ _uws;
141
147
  constructor(_uws) {
142
148
  this._uws = _uws;
143
149
  }
package/dist/index.d.mts CHANGED
@@ -1,13 +1,26 @@
1
- import { Adapter, AdapterInstance, AdapterInternal, AdapterOptions, Hooks, Message, Peer, PeerContext, ResolveHooks, WSError, defineHooks, defineWebSocketAdapter } from "./_chunks/adapter.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
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
+ * A `ws+unix://<socketPath>:<pathname>` target is also supported out of the
8
+ * box for proxying to a Unix-socket upstream on Node, Bun, and Deno (no custom
9
+ * {@link WebSocket} constructor required) — crossws dials it through the
10
+ * matching per-runtime `crossws/websocket` client. See the guide for details.
11
+ *
7
12
  * Can be a static string/URL or a function that resolves the target dynamically
8
- * based on the incoming {@link Peer}.
13
+ * based on the incoming {@link Peer}. The resolver may be **async** (return a
14
+ * promise) — useful when the upstream address isn't known yet at connect time
15
+ * (e.g. a worker that's still booting or being hot-reloaded). Client frames
16
+ * sent in the meantime are buffered (bounded by {@link maxBufferSize}) and a
17
+ * non-zero {@link connectTimeout} also covers the resolution, so a resolver
18
+ * that never settles closes the peer with `1011` rather than hanging. With
19
+ * `connectTimeout: 0` (timeout disabled) a never-settling resolver leaves the
20
+ * peer open until {@link maxBufferSize} is hit (`1009`), so pair an unbounded
21
+ * timeout with your own resolver deadline.
9
22
  */
10
- target: string | URL | ((peer: Peer) => string | URL);
23
+ target: string | URL | ((peer: Peer) => string | URL | Promise<string | URL>);
11
24
  /**
12
25
  * Subprotocol(s) to offer the upstream during the handshake.
13
26
  *
@@ -81,6 +94,39 @@ interface WebSocketProxyOptions {
81
94
  * ```
82
95
  */
83
96
  headers?: HeadersInit | ((peer: Peer) => HeadersInit | undefined | void);
97
+ /**
98
+ * Extra options merged into the upstream `WebSocket` constructor's third
99
+ * argument, as a static object or a per-peer resolver.
100
+ *
101
+ * This is the escape hatch for runtime- or client-specific dialing options
102
+ * that the WHATWG `WebSocket` signature doesn't cover — e.g. Deno's unstable
103
+ * `client` (to dial a Unix socket or use a custom `Deno.HttpClient`), or the
104
+ * `ws`/`undici` `createConnection`/`dispatcher`/`agent` options.
105
+ *
106
+ * Merged with {@link headers}: keys returned here are spread first, then the
107
+ * resolved `headers` option is applied on top (so a dedicated `headers`
108
+ * option wins over a `headers` key returned here).
109
+ *
110
+ * > [!NOTE]
111
+ * > Only honored by `WebSocket` constructors that accept a third options
112
+ * > argument. The WHATWG global browser constructor ignores it; Deno and Bun
113
+ * > extend the signature with their own options.
114
+ *
115
+ * @example Proxy to a Unix socket on Deno
116
+ * ```ts
117
+ * createWebSocketProxy({
118
+ * // Deno's WebSocket rejects the `ws+unix:` scheme, so keep a plain
119
+ * // `ws://` target and redirect the transport via the `client` option.
120
+ * target: (peer) => `ws://localhost${new URL(peer.request.url).pathname}`,
121
+ * webSocketOptions: () => ({
122
+ * client: Deno.createHttpClient({
123
+ * proxy: { transport: "unix", path: "/run/worker.sock" },
124
+ * }),
125
+ * }),
126
+ * });
127
+ * ```
128
+ */
129
+ webSocketOptions?: Record<string, unknown> | ((peer: Peer) => Record<string, unknown> | undefined | void);
84
130
  }
85
131
  /**
86
132
  * Create a set of crossws hooks that proxy incoming WebSocket connections
@@ -94,4 +140,4 @@ interface WebSocketProxyOptions {
94
140
  * ```
95
141
  */
96
142
  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 };
143
+ 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 };