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/sync.mjs
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
function broadcastChannel(opts) {
|
|
2
|
+
return ({ id }) => {
|
|
3
|
+
const channel = new BroadcastChannel(opts.channel);
|
|
4
|
+
return {
|
|
5
|
+
subscribe(deliver) {
|
|
6
|
+
channel.addEventListener("message", (event) => {
|
|
7
|
+
const envelope = event.data;
|
|
8
|
+
const msg = envelope?.msg;
|
|
9
|
+
if (!envelope || envelope.id === id || typeof msg?.topic !== "string" || typeof msg.namespace !== "string" || !(typeof msg.data === "string" || msg.data instanceof Uint8Array)) return;
|
|
10
|
+
deliver(msg);
|
|
11
|
+
});
|
|
12
|
+
},
|
|
13
|
+
publish(msg) {
|
|
14
|
+
channel.postMessage({
|
|
15
|
+
id,
|
|
16
|
+
msg
|
|
17
|
+
});
|
|
18
|
+
},
|
|
19
|
+
close() {
|
|
20
|
+
channel.close();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function encodeEnvelope(id, msg) {
|
|
26
|
+
const binary = msg.data instanceof Uint8Array;
|
|
27
|
+
return JSON.stringify({
|
|
28
|
+
id,
|
|
29
|
+
msg: {
|
|
30
|
+
namespace: msg.namespace,
|
|
31
|
+
topic: msg.topic,
|
|
32
|
+
binary,
|
|
33
|
+
data: binary ? toBase64(msg.data) : msg.data
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function decodeEnvelope(raw) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(raw);
|
|
40
|
+
if (!parsed || typeof parsed.id !== "string" || !parsed.msg || typeof parsed.msg.topic !== "string" || typeof parsed.msg.namespace !== "string" || typeof parsed.msg.data !== "string") return;
|
|
41
|
+
return {
|
|
42
|
+
id: parsed.id,
|
|
43
|
+
msg: {
|
|
44
|
+
namespace: parsed.msg.namespace,
|
|
45
|
+
topic: parsed.msg.topic,
|
|
46
|
+
data: parsed.msg.binary ? fromBase64(parsed.msg.data) : parsed.msg.data
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
} catch {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function toBase64(data) {
|
|
54
|
+
let binary = "";
|
|
55
|
+
for (const byte of data) binary += String.fromCharCode(byte);
|
|
56
|
+
return btoa(binary);
|
|
57
|
+
}
|
|
58
|
+
function fromBase64(data) {
|
|
59
|
+
const binary = atob(data);
|
|
60
|
+
const out = new Uint8Array(binary.length);
|
|
61
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
function redis(opts) {
|
|
65
|
+
const channel = opts.channel;
|
|
66
|
+
const isNodeRedis = opts.connector === void 0 ? typeof opts.client.pSubscribe === "function" : opts.connector === "node-redis";
|
|
67
|
+
return ({ id }) => {
|
|
68
|
+
const subscriber = opts.client.duplicate();
|
|
69
|
+
let started = false;
|
|
70
|
+
let onMessage;
|
|
71
|
+
return {
|
|
72
|
+
async subscribe(deliver) {
|
|
73
|
+
started = true;
|
|
74
|
+
const handle = (raw) => {
|
|
75
|
+
const envelope = decodeEnvelope(raw);
|
|
76
|
+
if (!envelope || envelope.id === id) return;
|
|
77
|
+
deliver(envelope.msg);
|
|
78
|
+
};
|
|
79
|
+
if (isNodeRedis) {
|
|
80
|
+
await subscriber.connect?.();
|
|
81
|
+
await subscriber.subscribe(channel, (raw) => handle(raw));
|
|
82
|
+
} else {
|
|
83
|
+
onMessage = (ch, raw) => {
|
|
84
|
+
if (ch === channel) handle(raw);
|
|
85
|
+
};
|
|
86
|
+
subscriber.on?.("message", onMessage);
|
|
87
|
+
await subscriber.subscribe(channel);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
async publish(msg) {
|
|
91
|
+
await opts.client.publish(channel, encodeEnvelope(id, msg));
|
|
92
|
+
},
|
|
93
|
+
async close() {
|
|
94
|
+
if (!started) return;
|
|
95
|
+
if (onMessage) {
|
|
96
|
+
subscriber.off?.("message", onMessage);
|
|
97
|
+
onMessage = void 0;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
await subscriber.quit?.();
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error("[crossws] sync redis close failed:", error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function pgsql(opts) {
|
|
109
|
+
const channel = opts.channel;
|
|
110
|
+
if (new TextEncoder().encode(channel).length > 63) throw new Error(`[crossws] pgsql sync channel name must be at most 63 bytes (got ${channel.length} chars): ${channel}`);
|
|
111
|
+
const isPostgresJs = opts.connector === void 0 ? typeof opts.client.listen === "function" : opts.connector === "postgres.js";
|
|
112
|
+
if (!isPostgresJs && "idleCount" in opts.client && "totalCount" in opts.client && typeof opts.client.query === "function") throw new Error("[crossws] pgsql sync requires a dedicated `Client`, not a `Pool` (pool connections rotate, so LISTEN/NOTIFY can't deliver). Pass `new Client()` (node-postgres) or a `postgres()` instance (postgres.js).");
|
|
113
|
+
const quotedChannel = `"${channel.replace(/"/g, "\"\"")}"`;
|
|
114
|
+
return ({ id }) => {
|
|
115
|
+
let unlisten;
|
|
116
|
+
let onNotification;
|
|
117
|
+
return {
|
|
118
|
+
async subscribe(deliver) {
|
|
119
|
+
const handle = (raw) => {
|
|
120
|
+
const envelope = decodeEnvelope(raw);
|
|
121
|
+
if (!envelope || envelope.id === id) return;
|
|
122
|
+
deliver(envelope.msg);
|
|
123
|
+
};
|
|
124
|
+
if (isPostgresJs) unlisten = (await opts.client.listen(channel, (payload) => handle(payload)))?.unlisten;
|
|
125
|
+
else {
|
|
126
|
+
onNotification = (msg) => {
|
|
127
|
+
if (msg.channel === channel && msg.payload !== void 0) handle(msg.payload);
|
|
128
|
+
};
|
|
129
|
+
opts.client.on("notification", onNotification);
|
|
130
|
+
await opts.client.query(`LISTEN ${quotedChannel}`);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
async publish(msg) {
|
|
134
|
+
const payload = encodeEnvelope(id, msg);
|
|
135
|
+
if (isPostgresJs) await opts.client.notify(channel, payload);
|
|
136
|
+
else await opts.client.query("SELECT pg_notify($1, $2)", [channel, payload]);
|
|
137
|
+
},
|
|
138
|
+
async close() {
|
|
139
|
+
if (isPostgresJs) await unlisten?.();
|
|
140
|
+
else if (onNotification) {
|
|
141
|
+
opts.client.removeListener?.("notification", onNotification);
|
|
142
|
+
onNotification = void 0;
|
|
143
|
+
await opts.client.query?.(`UNLISTEN ${quotedChannel}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const CLUSTER_MESSAGE = "crossws:sync";
|
|
150
|
+
function isClusterEnvelope(message) {
|
|
151
|
+
return typeof message === "object" && message !== null && typeof message[CLUSTER_MESSAGE] === "string" && typeof message.env === "string";
|
|
152
|
+
}
|
|
153
|
+
let relayInstalled = false;
|
|
154
|
+
async function setupPrimaryCluster() {
|
|
155
|
+
const cluster = (await import("node:cluster")).default;
|
|
156
|
+
if (!cluster.isPrimary || relayInstalled) return;
|
|
157
|
+
relayInstalled = true;
|
|
158
|
+
cluster.on("message", (_worker, message) => {
|
|
159
|
+
if (!isClusterEnvelope(message)) return;
|
|
160
|
+
for (const id in cluster.workers) cluster.workers[id]?.send(message);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function cluster(opts) {
|
|
164
|
+
const channel = opts.channel;
|
|
165
|
+
return ({ id }) => {
|
|
166
|
+
const proc = globalThis.process;
|
|
167
|
+
let onMessage;
|
|
168
|
+
return {
|
|
169
|
+
subscribe(deliver) {
|
|
170
|
+
if (typeof proc?.send !== "function") throw new Error("[crossws] cluster sync must run in a worker forked by node:cluster (process.send is unavailable). Call setupPrimaryCluster() in the primary process and start your server in the workers.");
|
|
171
|
+
onMessage = (message) => {
|
|
172
|
+
if (!isClusterEnvelope(message) || message[CLUSTER_MESSAGE] !== channel) return;
|
|
173
|
+
const envelope = decodeEnvelope(message.env);
|
|
174
|
+
if (!envelope || envelope.id === id) return;
|
|
175
|
+
deliver(envelope.msg);
|
|
176
|
+
};
|
|
177
|
+
proc.on("message", onMessage);
|
|
178
|
+
},
|
|
179
|
+
publish(msg) {
|
|
180
|
+
proc?.send?.({
|
|
181
|
+
[CLUSTER_MESSAGE]: channel,
|
|
182
|
+
env: encodeEnvelope(id, msg)
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
close() {
|
|
186
|
+
if (onMessage) {
|
|
187
|
+
proc?.off?.("message", onMessage);
|
|
188
|
+
onMessage = void 0;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const syncDrivers = [
|
|
195
|
+
"broadcastChannel",
|
|
196
|
+
"redis",
|
|
197
|
+
"pgsql",
|
|
198
|
+
"cluster"
|
|
199
|
+
];
|
|
200
|
+
export { broadcastChannel, cluster, decodeEnvelope, encodeEnvelope, pgsql, redis, setupPrimaryCluster, syncDrivers };
|
package/dist/websocket/node.mjs
CHANGED
package/dist/websocket/sse.d.mts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crossws",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "Cross-platform WebSocket Servers for Node.js, Deno, Bun and Cloudflare Workers",
|
|
5
5
|
"homepage": "https://crossws.h3.dev",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,12 +19,14 @@
|
|
|
19
19
|
"types": "./dist/index.d.mts",
|
|
20
20
|
"exports": {
|
|
21
21
|
".": "./dist/index.mjs",
|
|
22
|
+
"./sync": "./dist/sync.mjs",
|
|
22
23
|
"./adapters/bun": "./dist/adapters/bun.mjs",
|
|
23
24
|
"./adapters/bunny": "./dist/adapters/bunny.mjs",
|
|
24
25
|
"./adapters/deno": "./dist/adapters/deno.mjs",
|
|
25
26
|
"./adapters/cloudflare": "./dist/adapters/cloudflare.mjs",
|
|
26
27
|
"./adapters/sse": "./dist/adapters/sse.mjs",
|
|
27
28
|
"./adapters/node": "./dist/adapters/node.mjs",
|
|
29
|
+
"./adapters/vercel": "./dist/adapters/vercel.mjs",
|
|
28
30
|
"./adapters/uws": "./dist/adapters/uws.mjs",
|
|
29
31
|
"./server/bun": "./dist/server/bun.mjs",
|
|
30
32
|
"./server/bunny": "./dist/server/bunny.mjs",
|
|
@@ -67,14 +69,14 @@
|
|
|
67
69
|
"typecheck": "tsgo --noEmit --skipLibCheck"
|
|
68
70
|
},
|
|
69
71
|
"devDependencies": {
|
|
70
|
-
"@cloudflare/workers-types": "^4.
|
|
71
|
-
"@types/bun": "^1.3.
|
|
72
|
-
"@types/deno": "^2.
|
|
73
|
-
"@types/node": "^
|
|
74
|
-
"@types/web": "^0.0.
|
|
72
|
+
"@cloudflare/workers-types": "^4.20260629.1",
|
|
73
|
+
"@types/bun": "^1.3.14",
|
|
74
|
+
"@types/deno": "^2.7.0",
|
|
75
|
+
"@types/node": "^26.0.1",
|
|
76
|
+
"@types/web": "^0.0.351",
|
|
75
77
|
"@types/ws": "^8.18.1",
|
|
76
|
-
"@typescript/native-preview": "
|
|
77
|
-
"@vitest/coverage-v8": "^4.1.
|
|
78
|
+
"@typescript/native-preview": "7.0.0-dev.20260629.1",
|
|
79
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
78
80
|
"automd": "^0.4.3",
|
|
79
81
|
"changelogen": "^0.6.2",
|
|
80
82
|
"consola": "^3.4.2",
|
|
@@ -82,20 +84,20 @@
|
|
|
82
84
|
"eventsource": "^4.1.0",
|
|
83
85
|
"execa": "^9.6.1",
|
|
84
86
|
"get-port-please": "^3.2.0",
|
|
85
|
-
"h3": "
|
|
86
|
-
"jiti": "^2.
|
|
87
|
-
"listhen": "^1.
|
|
88
|
-
"obuild": "^0.4.
|
|
89
|
-
"oxfmt": "^0.
|
|
90
|
-
"oxlint": "^1.
|
|
91
|
-
"srvx": "^0.11.
|
|
92
|
-
"typescript": "^6.0.
|
|
87
|
+
"h3": "2.0.1-rc.22",
|
|
88
|
+
"jiti": "^2.7.0",
|
|
89
|
+
"listhen": "^1.10.0",
|
|
90
|
+
"obuild": "^0.4.37",
|
|
91
|
+
"oxfmt": "^0.56.0",
|
|
92
|
+
"oxlint": "^1.71.0",
|
|
93
|
+
"srvx": "^0.11.17",
|
|
94
|
+
"typescript": "^6.0.3",
|
|
93
95
|
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.57.0",
|
|
94
96
|
"unbuild": "^3.6.1",
|
|
95
|
-
"undici": "^8.0
|
|
96
|
-
"vitest": "^4.1.
|
|
97
|
-
"wrangler": "^4.
|
|
98
|
-
"ws": "^8.
|
|
97
|
+
"undici": "^8.5.0",
|
|
98
|
+
"vitest": "^4.1.9",
|
|
99
|
+
"wrangler": "^4.105.0",
|
|
100
|
+
"ws": "^8.21.0"
|
|
99
101
|
},
|
|
100
102
|
"peerDependencies": {
|
|
101
103
|
"srvx": ">=0.11.5"
|
|
@@ -108,19 +110,5 @@
|
|
|
108
110
|
"resolutions": {
|
|
109
111
|
"crossws": "workspace:*"
|
|
110
112
|
},
|
|
111
|
-
"packageManager": "pnpm@
|
|
112
|
-
"pnpm": {
|
|
113
|
-
"ignoredBuiltDependencies": [
|
|
114
|
-
"@parcel/watcher",
|
|
115
|
-
"esbuild",
|
|
116
|
-
"sharp",
|
|
117
|
-
"workerd"
|
|
118
|
-
],
|
|
119
|
-
"onlyBuiltDependencies": [
|
|
120
|
-
"@parcel/watcher",
|
|
121
|
-
"esbuild",
|
|
122
|
-
"sharp",
|
|
123
|
-
"workerd"
|
|
124
|
-
]
|
|
125
|
-
}
|
|
113
|
+
"packageManager": "pnpm@11.7.0"
|
|
126
114
|
}
|
package/sync.d.ts
ADDED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
9
|
-
var __copyProps = (to, from, except, desc) => {
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
-
key = keys[i];
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
-
get: ((k) => from[k]).bind(null, key),
|
|
14
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
-
value: mod,
|
|
21
|
-
enumerable: true
|
|
22
|
-
}) : target, mod));
|
|
23
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
24
|
-
export { __require as n, __toESM as r, __commonJSMin as t };
|