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.
- package/adapters/vercel.d.ts +2 -0
- package/dist/THIRD-PARTY-LICENSES.md +1 -1
- package/dist/_chunks/_request.mjs +2 -2
- package/dist/_chunks/_types.d.mts +8 -8
- package/dist/_chunks/adapter.d.mts +458 -4
- package/dist/_chunks/adapter.mjs +90 -13
- package/dist/_chunks/bun.d.mts +5 -3
- package/dist/_chunks/bunny.d.mts +2 -2
- package/dist/_chunks/cloudflare.d.mts +3 -3
- package/dist/_chunks/deno.d.mts +2 -2
- package/dist/_chunks/error.mjs +1 -1
- package/dist/_chunks/libs/ws.mjs +53 -4
- package/dist/_chunks/node.d.mts +2 -2
- package/dist/_chunks/node.mjs +17 -11
- package/dist/_chunks/peer.mjs +38 -26
- package/dist/_chunks/sse.d.mts +2 -2
- package/dist/_chunks/web.d.mts +1 -1
- package/dist/adapters/bun.d.mts +1 -1
- package/dist/adapters/bun.mjs +18 -9
- package/dist/adapters/bunny.d.mts +1 -1
- package/dist/adapters/bunny.mjs +8 -6
- package/dist/adapters/cloudflare.d.mts +1 -1
- package/dist/adapters/cloudflare.mjs +16 -13
- package/dist/adapters/deno.d.mts +1 -1
- package/dist/adapters/deno.mjs +22 -9
- package/dist/adapters/node.d.mts +2 -2
- package/dist/adapters/node.mjs +1 -1
- package/dist/adapters/sse.d.mts +1 -1
- package/dist/adapters/sse.mjs +7 -5
- package/dist/adapters/uws.d.mts +5 -4
- package/dist/adapters/uws.mjs +17 -10
- package/dist/adapters/vercel.d.mts +25 -0
- package/dist/adapters/vercel.mjs +48 -0
- package/dist/index.d.mts +30 -7
- package/dist/index.mjs +78 -35
- package/dist/server/bun.d.mts +1 -1
- package/dist/server/bunny.d.mts +1 -1
- package/dist/server/cloudflare.d.mts +1 -1
- package/dist/server/default.d.mts +1 -1
- package/dist/server/deno.d.mts +1 -1
- package/dist/server/node.d.mts +1 -1
- package/dist/server/node.mjs +1 -1
- package/dist/sync.d.mts +2 -0
- package/dist/sync.mjs +200 -0
- package/dist/websocket/node.mjs +1 -1
- package/dist/websocket/sse.d.mts +1 -1
- package/package.json +23 -35
- package/sync.d.ts +2 -0
- package/dist/_chunks/rolldown-runtime.mjs +0 -24
package/dist/adapters/deno.mjs
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
...
|
|
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
|
-
|
|
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.
|
|
61
|
+
return this._internal.remoteAddress;
|
|
49
62
|
}
|
|
50
63
|
send(data) {
|
|
51
64
|
return this._internal.ws.send(toBufferLike(data));
|
|
52
65
|
}
|
|
53
|
-
|
|
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
|
}
|
package/dist/adapters/node.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
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 };
|
package/dist/adapters/node.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fromNodeUpgradeHandler, nodeAdapter } from "../_chunks/node.mjs";
|
|
2
2
|
export { nodeAdapter as default, fromNodeUpgradeHandler };
|
package/dist/adapters/sse.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SSEAdapter, SSEOptions, sseAdapter } from "../_chunks/sse.mjs";
|
|
2
2
|
export { SSEAdapter, SSEOptions, sseAdapter as default };
|
package/dist/adapters/sse.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
}
|
package/dist/adapters/uws.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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
|
-
|
|
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;
|
package/dist/adapters/uws.mjs
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
...
|
|
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
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
-
*
|
|
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 {
|
|
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
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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,
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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));
|
package/dist/server/bun.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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;
|
package/dist/server/bunny.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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 {
|
|
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;
|
package/dist/server/deno.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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;
|
package/dist/server/node.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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;
|
package/dist/server/node.mjs
CHANGED
package/dist/sync.d.mts
ADDED
|
@@ -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 };
|