next-ws 2.1.17 → 2.2.0
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/README.md +4 -4
- package/dist/cli.cjs +5 -4
- package/dist/server/index.cjs +58 -5
- package/dist/server/index.d.ts +56 -5
- package/package.json +14 -13
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<img alt='package version' src='https://img.shields.io/npm/v/next-ws?label=version'>
|
|
9
9
|
<img alt='total downloads' src='https://img.shields.io/npm/dt/next-ws'>
|
|
10
10
|
<br>
|
|
11
|
-
<a href='https://github.com/
|
|
12
|
-
<a href='https://github.com/
|
|
11
|
+
<a href='https://github.com/k0d13/next-ws'><img alt='next-ws repo stars' src='https://img.shields.io/github/stars/k0d13/next-ws?style=social'></a>
|
|
12
|
+
<a href='https://github.com/k0d13'><img alt='k0d13 followers' src='https://img.shields.io/github/followers/k0d13?style=social'></a>
|
|
13
13
|
<a href='https://discord.gg/B2rEQ9g2vf'><img src='https://discordapp.com/api/guilds/829836158007115806/widget.png?style=shield' alt='discord shield'/></a>
|
|
14
14
|
</div>
|
|
15
15
|
|
|
@@ -66,7 +66,7 @@ export function UPGRADE(
|
|
|
66
66
|
## 🌀 Examples
|
|
67
67
|
|
|
68
68
|
> [!TIP]
|
|
69
|
-
> For more detailed examples, refer the [`examples` directory](https://github.com/
|
|
69
|
+
> For more detailed examples, refer the [`examples` directory](https://github.com/k0d13/next-ws/tree/main/examples).
|
|
70
70
|
|
|
71
71
|
### Echo Server
|
|
72
72
|
|
|
@@ -96,4 +96,4 @@ You can now connect to your WebSocket server, send it a message and receive the
|
|
|
96
96
|
|
|
97
97
|
### Chat Room
|
|
98
98
|
|
|
99
|
-
See the [chat room example](https://github.com/
|
|
99
|
+
See the [chat room example](https://github.com/k0d13/next-ws/tree/main/examples/chat-room).
|
package/dist/cli.cjs
CHANGED
|
@@ -27,7 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
var import_minimist = __toESM(require("minimist"));
|
|
28
28
|
|
|
29
29
|
// package.json
|
|
30
|
-
var version = "2.
|
|
30
|
+
var version = "2.2.0";
|
|
31
31
|
|
|
32
32
|
// src/commands/helpers/define.ts
|
|
33
33
|
function defineCommandGroup(definition) {
|
|
@@ -728,7 +728,8 @@ var patchNextNodeServer = definePatchStep({
|
|
|
728
728
|
try { nextWs ??= require('next-ws/server') } catch {}
|
|
729
729
|
try { nextWs ??= require(require.resolve('next-ws/server', { paths: [process.cwd()] }) )} catch {}
|
|
730
730
|
try { nextWs ??= require('${resolveNextWsDirectory().replaceAll(import_node_path3.sep, "/").replaceAll("'", "\\'")}/dist/server/index.cjs') } catch {}
|
|
731
|
-
nextWs?.
|
|
731
|
+
const adapter = nextWs?.getAdapter?.();
|
|
732
|
+
nextWs?.setupWebSocketServer(this, adapter ? { adapter } : undefined);
|
|
732
733
|
`);
|
|
733
734
|
const block = import_jscodeshift.default.blockStatement(snippet.nodes()[0].program.body);
|
|
734
735
|
return (0, import_jscodeshift.default)(code).find(import_jscodeshift.default.ClassDeclaration, { id: { name: "NextNodeServer" } }).find(import_jscodeshift.default.MethodDefinition, { kind: "constructor" }).forEach(({ node }) => {
|
|
@@ -842,7 +843,7 @@ var patchCookies2 = definePatchStep({
|
|
|
842
843
|
});
|
|
843
844
|
var patch_2_default = definePatch({
|
|
844
845
|
name: "patch-2",
|
|
845
|
-
versions: ">=15.0.0 <=16.1.
|
|
846
|
+
versions: ">=15.0.0 <=16.1.7",
|
|
846
847
|
steps: [
|
|
847
848
|
patchNextNodeServer,
|
|
848
849
|
patchRouterServer,
|
|
@@ -916,7 +917,7 @@ var patch_default = defineCommand({
|
|
|
916
917
|
patch = patches_default[patches_default.length - 1];
|
|
917
918
|
info("Proceeding with the latest patch");
|
|
918
919
|
log(
|
|
919
|
-
"If you encounter any issues please report them at https://github.com/
|
|
920
|
+
"If you encounter any issues please report them at https://github.com/k0d13/next-ws/issues"
|
|
920
921
|
);
|
|
921
922
|
} else {
|
|
922
923
|
error("Aborted");
|
package/dist/server/index.cjs
CHANGED
|
@@ -30,8 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/server/index.ts
|
|
31
31
|
var server_exports = {};
|
|
32
32
|
__export(server_exports, {
|
|
33
|
+
getAdapter: () => getAdapter,
|
|
33
34
|
getHttpServer: () => getHttpServer,
|
|
34
35
|
getWebSocketServer: () => getWebSocketServer,
|
|
36
|
+
setAdapter: () => setAdapter,
|
|
35
37
|
setHttpServer: () => setHttpServer,
|
|
36
38
|
setWebSocketServer: () => setWebSocketServer,
|
|
37
39
|
setupWebSocketServer: () => setupWebSocketServer
|
|
@@ -76,11 +78,18 @@ var [getRequestStorage, setRequestStorage, useRequestStorage] = (
|
|
|
76
78
|
//
|
|
77
79
|
)
|
|
78
80
|
);
|
|
81
|
+
var [getAdapter, setAdapter, useAdapter] = (
|
|
82
|
+
//
|
|
83
|
+
useGlobal(
|
|
84
|
+
/* @__PURE__ */ Symbol.for("next-ws.adapter")
|
|
85
|
+
//
|
|
86
|
+
)
|
|
87
|
+
);
|
|
79
88
|
|
|
80
89
|
// src/server/setup.ts
|
|
81
90
|
var import_node_async_hooks = require("async_hooks");
|
|
82
91
|
var logger2 = __toESM(require("next/dist/build/output/log.js"));
|
|
83
|
-
var
|
|
92
|
+
var ws = __toESM(require("ws"));
|
|
84
93
|
|
|
85
94
|
// src/server/helpers/match.ts
|
|
86
95
|
function compileRoutePattern(routePattern) {
|
|
@@ -209,8 +218,8 @@ function createRequestStore(request) {
|
|
|
209
218
|
}
|
|
210
219
|
|
|
211
220
|
// src/server/setup.ts
|
|
212
|
-
var WebSocketServer2 =
|
|
213
|
-
function setupWebSocketServer(nextServer) {
|
|
221
|
+
var WebSocketServer2 = ws.WebSocketServer || ws.Server;
|
|
222
|
+
function setupWebSocketServer(nextServer, options) {
|
|
214
223
|
const httpServer = (
|
|
215
224
|
//
|
|
216
225
|
// @ts-expect-error - serverOptions is protected
|
|
@@ -226,10 +235,16 @@ function setupWebSocketServer(nextServer) {
|
|
|
226
235
|
//
|
|
227
236
|
useRequestStorage(() => new import_node_async_hooks.AsyncLocalStorage())
|
|
228
237
|
);
|
|
229
|
-
|
|
238
|
+
const adapter = options?.adapter;
|
|
239
|
+
if (adapter) {
|
|
240
|
+
logger2.ready("[next-ws] adapter configured for multi-instance support");
|
|
241
|
+
} else {
|
|
242
|
+
logger2.ready("[next-ws] has started the WebSocket server");
|
|
243
|
+
}
|
|
230
244
|
const kAttached = /* @__PURE__ */ Symbol.for("next-ws.http-server.attached");
|
|
231
245
|
if (Reflect.has(httpServer, kAttached)) return;
|
|
232
246
|
Reflect.set(httpServer, kAttached, true);
|
|
247
|
+
const routeClients = /* @__PURE__ */ new Map();
|
|
233
248
|
httpServer.on("upgrade", async (message, socket, head) => {
|
|
234
249
|
const request = toNextRequest(message);
|
|
235
250
|
const pathname = request.nextUrl.pathname;
|
|
@@ -252,10 +267,27 @@ function setupWebSocketServer(nextServer) {
|
|
|
252
267
|
}
|
|
253
268
|
if (handleSocket)
|
|
254
269
|
logger2.warnOnce(
|
|
255
|
-
"DeprecationWarning: [next-ws] SOCKET is deprecated, prefer UPGRADE instead, see https://github.com/
|
|
270
|
+
"DeprecationWarning: [next-ws] SOCKET is deprecated, prefer UPGRADE instead, see https://github.com/k0d13/next-ws#-usage"
|
|
256
271
|
);
|
|
257
272
|
wsServer.handleUpgrade(message, socket, head, async (client) => {
|
|
258
273
|
wsServer.emit("connection", client, message);
|
|
274
|
+
if (adapter) {
|
|
275
|
+
if (!routeClients.has(pathname)) {
|
|
276
|
+
routeClients.set(pathname, /* @__PURE__ */ new Set());
|
|
277
|
+
await adapter.onMessage(pathname, (message2) => {
|
|
278
|
+
const clients = routeClients.get(pathname);
|
|
279
|
+
if (!clients) return;
|
|
280
|
+
for (const localClient of clients) {
|
|
281
|
+
if (localClient.readyState === ws.WebSocket.OPEN) {
|
|
282
|
+
localClient.send(
|
|
283
|
+
typeof message2 === "string" ? message2 : JSON.stringify(message2)
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
routeClients.get(pathname)?.add(client);
|
|
290
|
+
}
|
|
259
291
|
try {
|
|
260
292
|
const context = { params: route.params };
|
|
261
293
|
if (handleUpgrade) {
|
|
@@ -272,6 +304,13 @@ function setupWebSocketServer(nextServer) {
|
|
|
272
304
|
if (typeof handleClose === "function")
|
|
273
305
|
client.once("close", () => handleClose());
|
|
274
306
|
}
|
|
307
|
+
if (adapter) {
|
|
308
|
+
client.on("message", (data) => {
|
|
309
|
+
adapter.broadcast(pathname, data).catch((err) => {
|
|
310
|
+
logger2.error("[next-ws] adapter broadcast failed:", err);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
}
|
|
275
314
|
} catch (cause) {
|
|
276
315
|
logger2.error(
|
|
277
316
|
`[next-ws] error in socket handler for '${pathname}'`,
|
|
@@ -282,14 +321,28 @@ function setupWebSocketServer(nextServer) {
|
|
|
282
321
|
} catch {
|
|
283
322
|
}
|
|
284
323
|
}
|
|
324
|
+
client.once("close", () => {
|
|
325
|
+
if (adapter) {
|
|
326
|
+
routeClients.get(pathname)?.delete(client);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
285
329
|
});
|
|
286
330
|
return;
|
|
287
331
|
});
|
|
332
|
+
if (adapter) {
|
|
333
|
+
httpServer.once("close", async () => {
|
|
334
|
+
await adapter.close().catch((err) => {
|
|
335
|
+
logger2.error("[next-ws] adapter cleanup failed:", err);
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
}
|
|
288
339
|
}
|
|
289
340
|
// Annotate the CommonJS export names for ESM import in node:
|
|
290
341
|
0 && (module.exports = {
|
|
342
|
+
getAdapter,
|
|
291
343
|
getHttpServer,
|
|
292
344
|
getWebSocketServer,
|
|
345
|
+
setAdapter,
|
|
293
346
|
setHttpServer,
|
|
294
347
|
setWebSocketServer,
|
|
295
348
|
setupWebSocketServer
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,9 +1,54 @@
|
|
|
1
1
|
import * as next_server from 'next/server';
|
|
2
2
|
import * as http from 'http';
|
|
3
3
|
import * as ws from 'ws';
|
|
4
|
-
import * as node_http from 'node:http';
|
|
5
4
|
import NextNodeServer from 'next/dist/server/next-server.js';
|
|
6
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Adapter interface for multi-instance WebSocket deployments.
|
|
8
|
+
* Enables message broadcasting across multiple server instances via pub/sub.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* class RedisAdapter implements Adapter {
|
|
13
|
+
* private pub = new Redis(process.env.REDIS_URL);
|
|
14
|
+
* private sub = new Redis(process.env.REDIS_URL);
|
|
15
|
+
*
|
|
16
|
+
* async broadcast(room: string, message: unknown): Promise<void> {
|
|
17
|
+
* await this.pub.publish(room, JSON.stringify(message));
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* async onMessage(room: string, handler: (message: unknown) => void): Promise<void> {
|
|
21
|
+
* this.sub.subscribe(room);
|
|
22
|
+
* this.sub.on('message', (channel, msg) => {
|
|
23
|
+
* if (channel === room) handler(JSON.parse(msg));
|
|
24
|
+
* });
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* async close(): Promise<void> {
|
|
28
|
+
* await Promise.all([this.pub.quit(), this.sub.quit()]);
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
interface Adapter {
|
|
34
|
+
/**
|
|
35
|
+
* Broadcast a message to all instances subscribed to the specified room.
|
|
36
|
+
* @param room Room identifier (typically the route pathname)
|
|
37
|
+
* @param message Message to broadcast (will be JSON stringified if needed)
|
|
38
|
+
*/
|
|
39
|
+
broadcast(room: string, message: unknown): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Subscribe to messages for a specific room.
|
|
42
|
+
* @param room Room identifier to subscribe to
|
|
43
|
+
* @param handler Callback invoked when messages arrive for this room
|
|
44
|
+
*/
|
|
45
|
+
onMessage(room: string, handler: (message: unknown) => void): void | Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Clean up adapter resources (close connections, unsubscribe, etc.)
|
|
48
|
+
*/
|
|
49
|
+
close(): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
|
|
7
52
|
/**
|
|
8
53
|
* WebSocket socket handler.
|
|
9
54
|
* @param client WebSocket client instance
|
|
@@ -41,15 +86,21 @@ type RouteContext<Path extends string> = {
|
|
|
41
86
|
params: Record<string, string | string[] | undefined> & RouteParams<Path> & RouteParams<Path>;
|
|
42
87
|
};
|
|
43
88
|
|
|
44
|
-
declare const getHttpServer: () =>
|
|
45
|
-
declare const setHttpServer: (value:
|
|
89
|
+
declare const getHttpServer: () => http.Server<typeof http.IncomingMessage, typeof http.ServerResponse> | undefined;
|
|
90
|
+
declare const setHttpServer: (value: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>) => boolean;
|
|
46
91
|
declare const getWebSocketServer: () => ws.WebSocketServer | undefined;
|
|
47
92
|
declare const setWebSocketServer: (value: ws.WebSocketServer) => boolean;
|
|
93
|
+
declare const getAdapter: () => Adapter | undefined;
|
|
94
|
+
declare const setAdapter: (value: Adapter | undefined) => boolean;
|
|
48
95
|
|
|
96
|
+
interface SetupOptions {
|
|
97
|
+
adapter?: Adapter;
|
|
98
|
+
}
|
|
49
99
|
/**
|
|
50
100
|
* Attach the WebSocket server to the HTTP server.
|
|
51
101
|
* @param nextServer Next.js Node server instance
|
|
102
|
+
* @param options Setup options including optional adapter
|
|
52
103
|
*/
|
|
53
|
-
declare function setupWebSocketServer(nextServer: NextNodeServer): void;
|
|
104
|
+
declare function setupWebSocketServer(nextServer: NextNodeServer, options?: SetupOptions): void;
|
|
54
105
|
|
|
55
|
-
export { type RouteContext, type SocketHandler, type UpgradeHandler, getHttpServer, getWebSocketServer, setHttpServer, setWebSocketServer, setupWebSocketServer };
|
|
106
|
+
export { type Adapter, type RouteContext, type SetupOptions, type SocketHandler, type UpgradeHandler, getAdapter, getHttpServer, getWebSocketServer, setAdapter, setHttpServer, setWebSocketServer, setupWebSocketServer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-ws",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Add support for WebSockets in the Next.js app directory",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
"server",
|
|
11
11
|
"client"
|
|
12
12
|
],
|
|
13
|
-
"homepage": "https://github.com/
|
|
13
|
+
"homepage": "https://github.com/k0d13/next-ws#readme",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
|
-
"url": "git+https://github.com/
|
|
16
|
+
"url": "git+https://github.com/k0d13/next-ws.git"
|
|
17
17
|
},
|
|
18
18
|
"bugs": {
|
|
19
|
-
"url": "https://github.com/
|
|
19
|
+
"url": "https://github.com/k0d13/next-ws/issues"
|
|
20
20
|
},
|
|
21
21
|
"bin": {
|
|
22
22
|
"next-ws": "./dist/cli.cjs"
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"jscodeshift": "^17.3.0",
|
|
40
40
|
"minimist": "^1.2.8",
|
|
41
|
-
"semver": "^7.7.
|
|
41
|
+
"semver": "^7.7.4"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"next": ">=13.5.1",
|
|
@@ -48,23 +48,24 @@
|
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@biomejs/biome": "^2.2.0",
|
|
50
50
|
"@changesets/changelog-git": "^0.2.1",
|
|
51
|
-
"@changesets/cli": "^2.
|
|
51
|
+
"@changesets/cli": "^2.30.0",
|
|
52
52
|
"@favware/npm-deprecate": "^2.0.0",
|
|
53
|
-
"@playwright/test": "^1.
|
|
53
|
+
"@playwright/test": "^1.58.2",
|
|
54
54
|
"@types/jscodeshift": "^17.3.0",
|
|
55
55
|
"@types/minimist": "^1.2.5",
|
|
56
|
-
"@types/node": "^24.
|
|
56
|
+
"@types/node": "^24.11.0",
|
|
57
57
|
"@types/react": "19.1.10",
|
|
58
|
-
"@types/semver": "^7.7.
|
|
58
|
+
"@types/semver": "^7.7.1",
|
|
59
59
|
"@types/ws": "^8.18.1",
|
|
60
|
-
"chalk": "^5.6.
|
|
60
|
+
"chalk": "^5.6.2",
|
|
61
61
|
"husky": "^9.1.7",
|
|
62
|
-
"
|
|
62
|
+
"ioredis": "^5.10.0",
|
|
63
|
+
"next": "16.1.7",
|
|
63
64
|
"pinst": "^3.0.0",
|
|
64
65
|
"react": "19.1.1",
|
|
65
66
|
"react-dom": "19.1.1",
|
|
66
|
-
"tsup": "^8.5.
|
|
67
|
-
"typescript": "^5.9.
|
|
67
|
+
"tsup": "^8.5.1",
|
|
68
|
+
"typescript": "^5.9.3"
|
|
68
69
|
},
|
|
69
70
|
"scripts": {
|
|
70
71
|
"check": "tsc --noEmit",
|