next-ws 0.0.0-beta-20250823112013 → 0.0.0-beta-20250824084203

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/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 = "0.0.0-beta-20250823112013";
30
+ var version = "0.0.0-beta-20250824084203";
31
31
 
32
32
  // src/commands/helpers/define.ts
33
33
  function defineCommandGroup(definition) {
@@ -757,14 +757,78 @@ var patchRouterServer = definePatchStep({
757
757
  }).toSource();
758
758
  }
759
759
  });
760
+ var patchHeaders = definePatchStep({
761
+ title: "Add WebSocket contextual headers resolution to request headers",
762
+ path: "next:dist/client/components/headers.js",
763
+ async transform(code) {
764
+ const marker = "@patch headers";
765
+ const snippet = (0, import_jscodeshift.default)(`
766
+ // ${marker}
767
+ const kRequestStorage = Symbol.for('next-ws.request-store');
768
+ const requestStorage = Reflect.get(globalThis, kRequestStorage);
769
+ const contextualHeaders = requestStorage?.getStore()?.headers;
770
+ if (contextualHeaders) return contextualHeaders;
771
+ `);
772
+ const block = import_jscodeshift.default.blockStatement(snippet.nodes()[0].program.body);
773
+ return (0, import_jscodeshift.default)(code).find(import_jscodeshift.default.FunctionDeclaration, { id: { name: "headers" } }).forEach(({ node }) => {
774
+ const body = node.body.body;
775
+ const existing = (0, import_jscodeshift.default)(body).find(CommentLine, { value: ` ${marker}` }).paths()[0];
776
+ const idx = body.indexOf(existing?.parent.node);
777
+ if (existing && idx > -1) body[idx] = block;
778
+ else body.unshift(block);
779
+ }).toSource();
780
+ }
781
+ });
782
+ var patchCookies = definePatchStep({
783
+ title: "Add WebSocket contextual cookies resolution to request cookies",
784
+ path: "next:dist/client/components/headers.js",
785
+ async transform(code) {
786
+ const marker = "@patch cookies";
787
+ const snippet = (0, import_jscodeshift.default)(`
788
+ // ${marker}
789
+ const kRequestStorage = Symbol.for('next-ws.request-store');
790
+ const requestStorage = Reflect.get(globalThis, kRequestStorage);
791
+ const contextualCookies = requestStorage?.getStore()?.cookies;
792
+ if (contextualCookies) return contextualCookies;
793
+ `);
794
+ const block = import_jscodeshift.default.blockStatement(snippet.nodes()[0].program.body);
795
+ return (0, import_jscodeshift.default)(code).find(import_jscodeshift.default.FunctionDeclaration, { id: { name: "cookies" } }).forEach(({ node }) => {
796
+ const body = node.body.body;
797
+ const existing = (0, import_jscodeshift.default)(body).find(CommentLine, { value: ` ${marker}` }).paths()[0];
798
+ const idx = body.indexOf(existing?.parent.node);
799
+ if (existing && idx > -1) body[idx] = block;
800
+ else body.unshift(block);
801
+ }).toSource();
802
+ }
803
+ });
760
804
  var patch_1_default = definePatch({
761
805
  name: "patch-1",
762
- versions: ">=13.5.1 <=15.5.0",
763
- steps: [patchNextNodeServer, patchRouterServer]
806
+ versions: ">=13.5.1 <=14.2.32",
807
+ steps: [patchNextNodeServer, patchRouterServer, patchHeaders, patchCookies]
808
+ });
809
+
810
+ // src/patches/patch-2.ts
811
+ var patchHeaders2 = definePatchStep({
812
+ ...patchHeaders,
813
+ path: "next:dist/server/request/headers.js"
814
+ });
815
+ var patchCookies2 = definePatchStep({
816
+ ...patchCookies,
817
+ path: "next:dist/server/request/cookies.js"
818
+ });
819
+ var patch_2_default = definePatch({
820
+ name: "patch-2",
821
+ versions: ">=15.0.0 <=15.5.0",
822
+ steps: [
823
+ patchNextNodeServer,
824
+ patchRouterServer,
825
+ patchHeaders2,
826
+ patchCookies2
827
+ ]
764
828
  });
765
829
 
766
830
  // src/patches/index.ts
767
- var patches_default = [patch_1_default];
831
+ var patches_default = [patch_1_default, patch_2_default];
768
832
 
769
833
  // src/commands/helpers/semver.ts
770
834
  var import_range = __toESM(require("semver/classes/range.js"));
@@ -39,66 +39,43 @@ __export(server_exports, {
39
39
  module.exports = __toCommonJS(server_exports);
40
40
 
41
41
  // src/server/persistent.ts
42
- var logger = __toESM(require("next/dist/build/output/log.js"));
43
- function getEnvironmentMeta() {
44
- const isCustomServer = !process.title.startsWith("next-");
45
- const isMainProcess = process.env.NEXT_WS_MAIN_PROCESS === "1";
46
- const isDevelopment = process.env.NODE_ENV === "development";
47
- return { isCustomServer, isMainProcess, isDevelopment };
48
- }
49
- function mainProcessOnly(callerName) {
50
- if (process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK === "1") return;
51
- const meta = getEnvironmentMeta();
52
- if (!meta.isCustomServer && !meta.isMainProcess) {
53
- throw new Error(
54
- `[next-ws] Attempt to call '${callerName}' outside the main process.
55
- You may be attempting to interact with the WebSocket server outside of an UPGRADE handler. This will fail in production, as Next.js employs a worker process for routing, which do not have access to the WebSocket server on the main process.
56
- You can resolve this by using a custom server.`
57
- );
58
- } else if (!meta.isCustomServer) {
59
- logger.warnOnce(
60
- `[next-ws] The function '${callerName}' was called without a custom server.
61
- This could lead to unintended behaviour, especially if you're attempting to interact with the WebSocket server outside of an UPGRADE handler.
62
- Please note, while such configurations might function during development, they will fail in production. This is because Next.js employs a worker process for routing in production, which do not have access to the WebSocket server on the main process.
63
- You can resolve this by using a custom server.`
64
- );
65
- }
66
- }
67
- var kHttpServer = Symbol.for("kHttpServer");
68
- function getHttpServer() {
69
- mainProcessOnly("getHttpServer");
70
- return Reflect.get(globalThis, kHttpServer);
71
- }
72
- function setHttpServer(server) {
73
- mainProcessOnly("setHttpServer");
74
- Reflect.set(globalThis, kHttpServer, server);
75
- return getHttpServer();
76
- }
77
- function useHttpServer(server) {
78
- mainProcessOnly("useHttpServer");
79
- const existing = getHttpServer();
80
- if (existing) return existing;
81
- return setHttpServer(server());
82
- }
83
- var kWebSocketServer = Symbol.for("kWebSocketServer");
84
- function getWebSocketServer() {
85
- mainProcessOnly("getWebSocketServer");
86
- return Reflect.get(globalThis, kWebSocketServer);
87
- }
88
- function setWebSocketServer(server) {
89
- mainProcessOnly("setWebSocketServer");
90
- Reflect.set(globalThis, kWebSocketServer, server);
91
- return getWebSocketServer();
92
- }
93
- function useWebSocketServer(server) {
94
- mainProcessOnly("useWebSocketServer");
95
- const existing = getWebSocketServer();
96
- if (existing) return existing;
97
- return setWebSocketServer(server());
42
+ function useGlobal(key) {
43
+ return [
44
+ function get() {
45
+ return Reflect.get(globalThis, key);
46
+ },
47
+ function set(value) {
48
+ return Reflect.set(globalThis, key, value);
49
+ },
50
+ function use(getter) {
51
+ const existing = Reflect.get(globalThis, key);
52
+ if (existing) return existing;
53
+ Reflect.set(globalThis, key, getter());
54
+ return Reflect.get(globalThis, key);
55
+ }
56
+ ];
98
57
  }
58
+ var [getHttpServer, setHttpServer, useHttpServer] = (
59
+ //
60
+ useGlobal(
61
+ Symbol.for("next-ws.http-server")
62
+ //
63
+ )
64
+ );
65
+ var [getWebSocketServer, setWebSocketServer, useWebSocketServer] = useGlobal(
66
+ Symbol.for("next-ws.websocket-server")
67
+ );
68
+ var [getRequestStorage, setRequestStorage, useRequestStorage] = (
69
+ //
70
+ useGlobal(
71
+ Symbol.for("next-ws.request-store")
72
+ //
73
+ )
74
+ );
99
75
 
100
76
  // src/server/setup.ts
101
- var logger3 = __toESM(require("next/dist/build/output/log.js"));
77
+ var import_node_async_hooks2 = require("async_hooks");
78
+ var logger2 = __toESM(require("next/dist/build/output/log.js"));
102
79
  var import_ws = require("ws");
103
80
 
104
81
  // src/server/helpers/match.ts
@@ -142,7 +119,7 @@ function findMatchingRoute(nextServer, requestPathname) {
142
119
  }
143
120
 
144
121
  // src/server/helpers/module.ts
145
- var logger2 = __toESM(require("next/dist/build/output/log.js"));
122
+ var logger = __toESM(require("next/dist/build/output/log.js"));
146
123
  async function importRouteModule(nextServer, filePath) {
147
124
  try {
148
125
  if ("hotReloader" in nextServer) {
@@ -153,7 +130,7 @@ async function importRouteModule(nextServer, filePath) {
153
130
  } else if ("ensurePage" in nextServer) {
154
131
  await nextServer.ensurePage({ page: filePath, clientOnly: false });
155
132
  } else {
156
- logger2.warnOnce(
133
+ logger.warnOnce(
157
134
  "[next-ws] unable to ensure page, you may need to open the route in your browser first so Next.js compiles it"
158
135
  );
159
136
  }
@@ -185,26 +162,55 @@ function toNextRequest(message) {
185
162
  });
186
163
  }
187
164
 
165
+ // src/server/helpers/store.ts
166
+ var import_node_async_hooks = require("async_hooks");
167
+ var import_cookies = require("next/dist/compiled/@edge-runtime/cookies");
168
+ var ReadonlyHeaders = class extends Headers {
169
+ append() {
170
+ throw new Error("Headers are read-only");
171
+ }
172
+ set() {
173
+ throw new Error("Headers are read-only");
174
+ }
175
+ delete() {
176
+ throw new Error("Headers are read-only");
177
+ }
178
+ };
179
+ var ReadonlyRequestsCookies = class extends import_cookies.RequestCookies {
180
+ set() {
181
+ throw new Error("Cookies are read-only");
182
+ }
183
+ delete() {
184
+ throw new Error("Cookies are read-only");
185
+ }
186
+ };
187
+ function createRequestStore(request) {
188
+ return {
189
+ headers: new ReadonlyHeaders(request.headers),
190
+ cookies: new ReadonlyRequestsCookies(request.headers)
191
+ };
192
+ }
193
+ var requestStorage = new import_node_async_hooks.AsyncLocalStorage();
194
+
188
195
  // src/server/setup.ts
189
196
  function setupWebSocketServer(nextServer) {
190
- process.env.NEXT_WS_MAIN_PROCESS = String(1);
191
- process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK = String(1);
192
197
  const httpServer = (
193
198
  //
194
199
  // @ts-expect-error - serverOptions is protected
195
200
  useHttpServer(() => nextServer.serverOptions?.httpServer)
196
201
  );
197
202
  if (!httpServer)
198
- return logger3.error("[next-ws] was not able to find the HTTP server");
203
+ return logger2.error("[next-ws] was not able to find the HTTP server");
199
204
  const wsServer = (
200
205
  //
201
206
  useWebSocketServer(() => new import_ws.WebSocketServer({ noServer: true }))
202
207
  );
203
- if (!wsServer)
204
- return logger3.error("[next-ws] was not able to find the WebSocket server");
205
- process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK = String(0);
206
- logger3.ready("[next-ws] has started the WebSocket server");
207
- const kInstalled = Symbol.for("kInstalled");
208
+ const requestStorage2 = (
209
+ //
210
+ useRequestStorage(() => new import_node_async_hooks2.AsyncLocalStorage())
211
+ );
212
+ logger2.ready("[next-ws] has started the WebSocket server");
213
+ const kInstalled = Symbol.for("next-ws.http-server.attached");
208
214
  if (Reflect.has(httpServer, kInstalled)) return;
209
215
  Reflect.set(httpServer, kInstalled, true);
210
216
  httpServer.on("upgrade", async (message, socket, head) => {
@@ -213,30 +219,34 @@ function setupWebSocketServer(nextServer) {
213
219
  if (pathname.includes("/_next")) return;
214
220
  const route = findMatchingRoute(nextServer, pathname);
215
221
  if (!route) {
216
- logger3.error(`[next-ws] could not find route for page ${pathname}`);
222
+ logger2.error(`[next-ws] could not find route for page ${pathname}`);
217
223
  return socket.end();
218
224
  }
219
225
  const module2 = await importRouteModule(nextServer, route.filename);
220
226
  if (!module2) {
221
- logger3.error(`[next-ws] could not import module for page ${pathname}`);
227
+ logger2.error(`[next-ws] could not import module for page ${pathname}`);
222
228
  return socket.end();
223
229
  }
224
230
  const handleUpgrade = module2.userland.UPGRADE;
225
231
  const handleSocket = module2.userland.SOCKET;
226
232
  if ((!handleUpgrade || typeof handleUpgrade !== "function") && (!handleSocket || typeof handleSocket !== "function")) {
227
- logger3.error(`[next-ws] route '${pathname}' does not export a handler`);
233
+ logger2.error(`[next-ws] route '${pathname}' does not export a handler`);
228
234
  return socket.end();
229
235
  }
230
236
  if (handleSocket)
231
- logger3.warnOnce(
232
- "DeprecationWarning: [next-ws] SOCKET is deprecated, use UPGRADE instead, see https://github.com/apteryxxyz/next-ws#-usage"
237
+ logger2.warnOnce(
238
+ "DeprecationWarning: [next-ws] SOCKET is deprecated, prefer UPGRADE instead, see https://github.com/apteryxxyz/next-ws#-usage"
233
239
  );
234
240
  wsServer.handleUpgrade(message, socket, head, async (client) => {
235
241
  wsServer.emit("connection", client, message);
236
242
  try {
237
243
  const context = { params: route.params };
238
244
  if (handleUpgrade) {
239
- await handleUpgrade(client, wsServer, request, context);
245
+ await requestStorage2.run(
246
+ createRequestStore(request),
247
+ //
248
+ () => handleUpgrade(client, wsServer, request, context)
249
+ );
240
250
  } else if (handleSocket) {
241
251
  const handleClose = (
242
252
  //
@@ -246,7 +256,7 @@ function setupWebSocketServer(nextServer) {
246
256
  client.once("close", () => handleClose());
247
257
  }
248
258
  } catch (cause) {
249
- logger3.error(
259
+ logger2.error(
250
260
  `[next-ws] error in socket handler for '${pathname}'`,
251
261
  cause
252
262
  );
@@ -1,8 +1,6 @@
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 { WebSocketServer } from 'ws';
5
- import { Server } from 'node:http';
6
4
  import NextNodeServer from 'next/dist/server/next-server.js';
7
5
 
8
6
  /** @deprecated Prefer UPGRADE and {@link UpgradeHandler} */
@@ -22,10 +20,10 @@ type RouteContext<Path extends string> = {
22
20
  params: Record<string, string | string[] | undefined> & RouteParams<Path> & RouteParams<Path>;
23
21
  };
24
22
 
25
- declare function getHttpServer(): Server | undefined;
26
- declare function setHttpServer<T extends Server | undefined>(server: T): T;
27
- declare function getWebSocketServer(): WebSocketServer | undefined;
28
- declare function setWebSocketServer<T extends WebSocketServer | undefined>(server: T): T;
23
+ declare const getHttpServer: () => http.Server<typeof http.IncomingMessage, typeof http.ServerResponse> | undefined;
24
+ declare const setHttpServer: (value: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>) => boolean;
25
+ declare const getWebSocketServer: () => ws.WebSocketServer | undefined;
26
+ declare const setWebSocketServer: (value: ws.WebSocketServer) => boolean;
29
27
 
30
28
  declare function setupWebSocketServer(nextServer: NextNodeServer): void;
31
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-ws",
3
- "version": "0.0.0-beta-20250823112013",
3
+ "version": "0.0.0-beta-20250824084203",
4
4
  "description": "Add support for WebSockets in the Next.js app directory",
5
5
  "license": "MIT",
6
6
  "keywords": [