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 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/apteryxxyz/next-ws'><img alt='next-ws repo stars' src='https://img.shields.io/github/stars/apteryxxyz/next-ws?style=social'></a>
12
- <a href='https://github.com/apteryxxyz'><img alt='apteryxxyz followers' src='https://img.shields.io/github/followers/apteryxxyz?style=social'></a>
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/apteryxxyz/next-ws/tree/main/examples).
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/apteryxxyz/next-ws/tree/main/examples/chat-room).
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.1.16";
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?.setupWebSocketServer(this);
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.6",
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/apteryxxyz/next-ws/issues"
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");
@@ -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 import_ws = __toESM(require("ws"));
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 = import_ws.WebSocketServer || import_ws.Server;
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
- logger2.ready("[next-ws] has started the WebSocket server");
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/apteryxxyz/next-ws#-usage"
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
@@ -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: () => node_http.Server<typeof node_http.IncomingMessage, typeof node_http.ServerResponse> | undefined;
45
- declare const setHttpServer: (value: node_http.Server<typeof node_http.IncomingMessage, typeof node_http.ServerResponse>) => boolean;
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.1.17",
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/apteryxxyz/next-ws#readme",
13
+ "homepage": "https://github.com/k0d13/next-ws#readme",
14
14
  "repository": {
15
15
  "type": "git",
16
- "url": "git+https://github.com/apteryxxyz/next-ws.git"
16
+ "url": "git+https://github.com/k0d13/next-ws.git"
17
17
  },
18
18
  "bugs": {
19
- "url": "https://github.com/apteryxxyz/next-ws/issues"
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.2"
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.27.12",
51
+ "@changesets/cli": "^2.30.0",
52
52
  "@favware/npm-deprecate": "^2.0.0",
53
- "@playwright/test": "^1.55.0",
53
+ "@playwright/test": "^1.58.2",
54
54
  "@types/jscodeshift": "^17.3.0",
55
55
  "@types/minimist": "^1.2.5",
56
- "@types/node": "^24.3.0",
56
+ "@types/node": "^24.11.0",
57
57
  "@types/react": "19.1.10",
58
- "@types/semver": "^7.7.0",
58
+ "@types/semver": "^7.7.1",
59
59
  "@types/ws": "^8.18.1",
60
- "chalk": "^5.6.0",
60
+ "chalk": "^5.6.2",
61
61
  "husky": "^9.1.7",
62
- "next": "16.1.6",
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.0",
67
- "typescript": "^5.9.2"
67
+ "tsup": "^8.5.1",
68
+ "typescript": "^5.9.3"
68
69
  },
69
70
  "scripts": {
70
71
  "check": "tsc --noEmit",