next-ws 2.1.16 → 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) {
@@ -122,7 +122,7 @@ var import_node_path2 = require("path");
122
122
  var import_node_readline = __toESM(require("readline"));
123
123
  var import_node_util = require("util");
124
124
 
125
- // node_modules/.pnpm/chalk@5.6.0/node_modules/chalk/source/vendor/ansi-styles/index.js
125
+ // node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
126
126
  var ANSI_BACKGROUND_OFFSET = 10;
127
127
  var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
128
128
  var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
@@ -308,7 +308,7 @@ function assembleStyles() {
308
308
  var ansiStyles = assembleStyles();
309
309
  var ansi_styles_default = ansiStyles;
310
310
 
311
- // node_modules/.pnpm/chalk@5.6.0/node_modules/chalk/source/vendor/supports-color/index.js
311
+ // node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/supports-color/index.js
312
312
  var import_node_process = __toESM(require("process"), 1);
313
313
  var import_node_os = __toESM(require("os"), 1);
314
314
  var import_node_tty = __toESM(require("tty"), 1);
@@ -440,7 +440,7 @@ var supportsColor = {
440
440
  };
441
441
  var supports_color_default = supportsColor;
442
442
 
443
- // node_modules/.pnpm/chalk@5.6.0/node_modules/chalk/source/utilities.js
443
+ // node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/utilities.js
444
444
  function stringReplaceAll(string, substring, replacer) {
445
445
  let index = string.indexOf(substring);
446
446
  if (index === -1) {
@@ -470,11 +470,11 @@ function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
470
470
  return returnValue;
471
471
  }
472
472
 
473
- // node_modules/.pnpm/chalk@5.6.0/node_modules/chalk/source/index.js
473
+ // node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/index.js
474
474
  var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
475
- var GENERATOR = Symbol("GENERATOR");
476
- var STYLER = Symbol("STYLER");
477
- var IS_EMPTY = Symbol("IS_EMPTY");
475
+ var GENERATOR = /* @__PURE__ */ Symbol("GENERATOR");
476
+ var STYLER = /* @__PURE__ */ Symbol("STYLER");
477
+ var IS_EMPTY = /* @__PURE__ */ Symbol("IS_EMPTY");
478
478
  var levelMapping = [
479
479
  "ansi",
480
480
  "ansi",
@@ -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
@@ -58,21 +60,28 @@ function useGlobal(key) {
58
60
  var [getHttpServer, setHttpServer, useHttpServer] = (
59
61
  //
60
62
  useGlobal(
61
- Symbol.for("next-ws.http-server")
63
+ /* @__PURE__ */ Symbol.for("next-ws.http-server")
62
64
  //
63
65
  )
64
66
  );
65
67
  var [getWebSocketServer, setWebSocketServer, useWebSocketServer] = (
66
68
  //
67
69
  useGlobal(
68
- Symbol.for("next-ws.websocket-server")
70
+ /* @__PURE__ */ Symbol.for("next-ws.websocket-server")
69
71
  //
70
72
  )
71
73
  );
72
74
  var [getRequestStorage, setRequestStorage, useRequestStorage] = (
73
75
  //
74
76
  useGlobal(
75
- Symbol.for("next-ws.request-store")
77
+ /* @__PURE__ */ Symbol.for("next-ws.request-store")
78
+ //
79
+ )
80
+ );
81
+ var [getAdapter, setAdapter, useAdapter] = (
82
+ //
83
+ useGlobal(
84
+ /* @__PURE__ */ Symbol.for("next-ws.adapter")
76
85
  //
77
86
  )
78
87
  );
@@ -80,7 +89,7 @@ var [getRequestStorage, setRequestStorage, useRequestStorage] = (
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 = require("ws");
92
+ var ws = __toESM(require("ws"));
84
93
 
85
94
  // src/server/helpers/match.ts
86
95
  function compileRoutePattern(routePattern) {
@@ -209,7 +218,8 @@ function createRequestStore(request) {
209
218
  }
210
219
 
211
220
  // src/server/setup.ts
212
- function setupWebSocketServer(nextServer) {
221
+ var WebSocketServer2 = ws.WebSocketServer || ws.Server;
222
+ function setupWebSocketServer(nextServer, options) {
213
223
  const httpServer = (
214
224
  //
215
225
  // @ts-expect-error - serverOptions is protected
@@ -219,16 +229,22 @@ function setupWebSocketServer(nextServer) {
219
229
  return logger2.error("[next-ws] was not able to find the HTTP server");
220
230
  const wsServer = (
221
231
  //
222
- useWebSocketServer(() => new import_ws.WebSocketServer({ noServer: true }))
232
+ useWebSocketServer(() => new WebSocketServer2({ noServer: true }))
223
233
  );
224
234
  const requestStorage = (
225
235
  //
226
236
  useRequestStorage(() => new import_node_async_hooks.AsyncLocalStorage())
227
237
  );
228
- logger2.ready("[next-ws] has started the WebSocket server");
229
- const kAttached = Symbol.for("next-ws.http-server.attached");
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
+ }
244
+ const kAttached = /* @__PURE__ */ Symbol.for("next-ws.http-server.attached");
230
245
  if (Reflect.has(httpServer, kAttached)) return;
231
246
  Reflect.set(httpServer, kAttached, true);
247
+ const routeClients = /* @__PURE__ */ new Map();
232
248
  httpServer.on("upgrade", async (message, socket, head) => {
233
249
  const request = toNextRequest(message);
234
250
  const pathname = request.nextUrl.pathname;
@@ -251,10 +267,27 @@ function setupWebSocketServer(nextServer) {
251
267
  }
252
268
  if (handleSocket)
253
269
  logger2.warnOnce(
254
- "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"
255
271
  );
256
272
  wsServer.handleUpgrade(message, socket, head, async (client) => {
257
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
+ }
258
291
  try {
259
292
  const context = { params: route.params };
260
293
  if (handleUpgrade) {
@@ -271,6 +304,13 @@ function setupWebSocketServer(nextServer) {
271
304
  if (typeof handleClose === "function")
272
305
  client.once("close", () => handleClose());
273
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
+ }
274
314
  } catch (cause) {
275
315
  logger2.error(
276
316
  `[next-ws] error in socket handler for '${pathname}'`,
@@ -281,14 +321,28 @@ function setupWebSocketServer(nextServer) {
281
321
  } catch {
282
322
  }
283
323
  }
324
+ client.once("close", () => {
325
+ if (adapter) {
326
+ routeClients.get(pathname)?.delete(client);
327
+ }
328
+ });
284
329
  });
285
330
  return;
286
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
+ }
287
339
  }
288
340
  // Annotate the CommonJS export names for ESM import in node:
289
341
  0 && (module.exports = {
342
+ getAdapter,
290
343
  getHttpServer,
291
344
  getWebSocketServer,
345
+ setAdapter,
292
346
  setHttpServer,
293
347
  setWebSocketServer,
294
348
  setupWebSocketServer
@@ -3,6 +3,52 @@ import * as http from 'http';
3
3
  import * as ws from 'ws';
4
4
  import NextNodeServer from 'next/dist/server/next-server.js';
5
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
+
6
52
  /**
7
53
  * WebSocket socket handler.
8
54
  * @param client WebSocket client instance
@@ -44,11 +90,17 @@ declare const getHttpServer: () => http.Server<typeof http.IncomingMessage, type
44
90
  declare const setHttpServer: (value: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>) => boolean;
45
91
  declare const getWebSocketServer: () => ws.WebSocketServer | undefined;
46
92
  declare const setWebSocketServer: (value: ws.WebSocketServer) => boolean;
93
+ declare const getAdapter: () => Adapter | undefined;
94
+ declare const setAdapter: (value: Adapter | undefined) => boolean;
47
95
 
96
+ interface SetupOptions {
97
+ adapter?: Adapter;
98
+ }
48
99
  /**
49
100
  * Attach the WebSocket server to the HTTP server.
50
101
  * @param nextServer Next.js Node server instance
102
+ * @param options Setup options including optional adapter
51
103
  */
52
- declare function setupWebSocketServer(nextServer: NextNodeServer): void;
104
+ declare function setupWebSocketServer(nextServer: NextNodeServer, options?: SetupOptions): void;
53
105
 
54
- 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.16",
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",