next-ws 1.1.1 → 1.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.
@@ -8,4 +8,4 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
8
8
 
9
9
  export { __name, __require };
10
10
  //# sourceMappingURL=out.js.map
11
- //# sourceMappingURL=chunk-6VWOYYWX.mjs.map
11
+ //# sourceMappingURL=chunk-WO25ABG2.js.map
@@ -1,4 +1,4 @@
1
- import { __name } from '../chunk-6VWOYYWX.mjs';
1
+ import { __name } from '../chunk-WO25ABG2.js';
2
2
  import React, { createContext, useMemo, useEffect, useContext } from 'react';
3
3
 
4
4
  var WebSocketContext = createContext(null);
@@ -26,4 +26,4 @@ __name(useWebSocket, "useWebSocket");
26
26
 
27
27
  export { WebSocketConsumer, WebSocketContext, WebSocketProvider, useWebSocket };
28
28
  //# sourceMappingURL=out.js.map
29
- //# sourceMappingURL=index.mjs.map
29
+ //# sourceMappingURL=index.js.map
@@ -29,44 +29,6 @@ function getEnvironmentMeta() {
29
29
  return { isCustomServer, isMainProcess, isDevelopment };
30
30
  }
31
31
  chunkPFW3KWBF_cjs.__name(getEnvironmentMeta, "getEnvironmentMeta");
32
- function resolveFilename(nextServer, pathname) {
33
- let pathParts = pathname.split("/"), appRoutes = {
34
- // @ts-expect-error - appPathRoutes is protected
35
- ...nextServer.appPathRoutes,
36
- // @ts-expect-error - getAppPathRoutes is protected
37
- ...nextServer.getAppPathRoutes()
38
- };
39
- for (let [key, [path]] of Object.entries(appRoutes))
40
- if (key.includes("[") && key.includes("]")) {
41
- let keyParts = key.split("/");
42
- if (keyParts.length !== pathParts.length) continue;
43
- for (let i = 0; i < keyParts.length; i++) {
44
- let keyPart = keyParts[i], pathPart = pathParts[i];
45
- if (keyPart.includes("[") && keyPart.includes("]") && (keyParts[i] = pathPart), keyParts[i] !== pathParts[i]) break;
46
- if (i === keyParts.length - 1 && path?.endsWith("/route"))
47
- return path;
48
- }
49
- } else {
50
- if (key !== pathname) continue;
51
- return path?.endsWith("/route") ? path : null;
52
- }
53
- return null;
54
- }
55
- chunkPFW3KWBF_cjs.__name(resolveFilename, "resolveFilename");
56
- async function getPageModule(nextServer, filename) {
57
- try {
58
- "hotReloader" in nextServer ? await nextServer.hotReloader?.ensurePage({
59
- page: filename,
60
- clientOnly: !1
61
- }) : "ensurePage" in nextServer ? await nextServer.ensurePage({ page: filename, clientOnly: !1 }) : logger3__namespace.warnOnce(
62
- "[next-ws] unable to ensure page, you may need to open the route in your browser first so Next.js compiles it"
63
- );
64
- } catch {
65
- }
66
- let buildPagePath = nextServer.getPagePath(filename);
67
- return chunkPFW3KWBF_cjs.__require(buildPagePath);
68
- }
69
- chunkPFW3KWBF_cjs.__name(getPageModule, "getPageModule");
70
32
  function mainProcessOnly(fnName) {
71
33
  if (process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK === "1") return;
72
34
  let meta = getEnvironmentMeta();
@@ -112,6 +74,49 @@ function useWebSocketServer(wsServer) {
112
74
  return existing || (wsServer && setWebSocketServer(wsServer), wsServer);
113
75
  }
114
76
  chunkPFW3KWBF_cjs.__name(useWebSocketServer, "useWebSocketServer");
77
+ function createRouteRegex(routePattern) {
78
+ let paramRegex = routePattern.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&").replace(/\\\[([a-zA-Z0-9_]+)\\\]/g, "(?<$1>[^/]+)").replace(/\\\[(?:\\\.){3}([a-zA-Z0-9_]+)\\\]/g, "(?<rest_$1>.+)");
79
+ return new RegExp(`^${paramRegex}$`);
80
+ }
81
+ chunkPFW3KWBF_cjs.__name(createRouteRegex, "createRouteRegex");
82
+ function getRouteParams(routePattern, routePath) {
83
+ let routeRegex = createRouteRegex(routePattern), match = routePath.match(routeRegex);
84
+ if (!match) return null;
85
+ if (!match.groups) return {};
86
+ let params = {};
87
+ for (let [k, v] of Object.entries(match.groups))
88
+ k.startsWith("rest_") && (k = k.slice(5), v = v.split("/")), Reflect.set(params, k, v);
89
+ return params;
90
+ }
91
+ chunkPFW3KWBF_cjs.__name(getRouteParams, "getRouteParams");
92
+ function resolvePathToRoute(nextServer, requestPath) {
93
+ let routes = {
94
+ // @ts-expect-error - appPathRoutes is protected
95
+ ...nextServer.appPathRoutes,
96
+ // @ts-expect-error - getAppPathRoutes is protected
97
+ ...nextServer.getAppPathRoutes()
98
+ };
99
+ for (let [routePath, [filePath]] of Object.entries(routes)) {
100
+ let routeParams = getRouteParams(routePath, requestPath);
101
+ if (routeParams) return { filePath, routeParams };
102
+ }
103
+ return null;
104
+ }
105
+ chunkPFW3KWBF_cjs.__name(resolvePathToRoute, "resolvePathToRoute");
106
+ async function importRouteModule(nextServer, filePath) {
107
+ try {
108
+ "hotReloader" in nextServer ? await nextServer.hotReloader?.ensurePage({
109
+ page: filePath,
110
+ clientOnly: !1
111
+ }) : "ensurePage" in nextServer ? await nextServer.ensurePage({ page: filePath, clientOnly: !1 }) : logger3__namespace.warnOnce(
112
+ "[next-ws] unable to ensure page, you may need to open the route in your browser first so Next.js compiles it"
113
+ );
114
+ } catch {
115
+ }
116
+ let buildPagePath = nextServer.getPagePath(filePath);
117
+ return chunkPFW3KWBF_cjs.__require(buildPagePath);
118
+ }
119
+ chunkPFW3KWBF_cjs.__name(importRouteModule, "importRouteModule");
115
120
 
116
121
  // src/server/setup.ts
117
122
  function setupWebSocketServer(nextServer) {
@@ -124,16 +129,16 @@ function setupWebSocketServer(nextServer) {
124
129
  logger3__namespace.ready("[next-ws] has started the WebSocket server"), httpServer.on("upgrade", async (request, socket, head) => {
125
130
  let pathname = new URL(request.url ?? "", "ws://next").pathname;
126
131
  if (pathname.startsWith("/_next")) return;
127
- let filename = resolveFilename(nextServer, pathname);
128
- if (!filename)
132
+ let routeInfo = resolvePathToRoute(nextServer, pathname);
133
+ if (!routeInfo)
129
134
  return logger3__namespace.error(`[next-ws] could not find module for page ${pathname}`), socket.destroy();
130
- let pageModule = await getPageModule(nextServer, filename);
131
- if (!pageModule)
135
+ let routeModule = await importRouteModule(nextServer, routeInfo.filePath);
136
+ if (!routeModule)
132
137
  return logger3__namespace.error(`[next-ws] could not find module for page ${pathname}`), socket.destroy();
133
- let socketHandler = pageModule?.routeModule?.userland?.SOCKET;
134
- return !socketHandler || typeof socketHandler != "function" ? (logger3__namespace.error(`[next-ws] ${pathname} does not export a SOCKET handler`), socket.destroy()) : wsServer.handleUpgrade(request, socket, head, (c, r) => {
135
- let dispose = socketHandler(c, r, wsServer);
136
- typeof dispose == "function" && c.once("close", () => dispose());
138
+ let socketHandler = routeModule?.routeModule?.userland?.SOCKET;
139
+ return !socketHandler || typeof socketHandler != "function" ? (logger3__namespace.error(`[next-ws] ${pathname} does not export a SOCKET handler`), socket.destroy()) : wsServer.handleUpgrade(request, socket, head, async (c, r) => {
140
+ let routeContext = { params: routeInfo.routeParams }, handleClose = await socketHandler(c, r, wsServer, routeContext);
141
+ typeof handleClose == "function" && c.once("close", () => handleClose());
137
142
  });
138
143
  });
139
144
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/setup.ts","../../src/server/helpers/next.ts","../../src/server/helpers/persistent.ts","../../src/server/index.ts"],"names":["logger"],"mappings":"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","sourcesContent":["import * as logger from 'next/dist/build/output/log';\nimport type NextNodeServer from 'next/dist/server/next-server';\nimport { WebSocketServer } from 'ws';\nimport { getPageModule, resolveFilename } from './helpers/next';\nimport { useHttpServer, useWebSocketServer } from './helpers/persistent';\n\nexport function setupWebSocketServer(nextServer: NextNodeServer) {\n process.env.NEXT_WS_MAIN_PROCESS = String(1);\n\n process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK = String(1);\n // @ts-expect-error - serverOptions is protected\n const httpServer = useHttpServer(nextServer.serverOptions?.httpServer);\n const wsServer = useWebSocketServer(new WebSocketServer({ noServer: true }));\n // biome-ignore lint/performance/noDelete: <explanation>\n delete process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK;\n\n if (!httpServer)\n return logger.error('[next-ws] was not able to find the HTTP server');\n if (!wsServer)\n return logger.error('[next-ws] was not able to find the WebSocket server');\n\n logger.ready('[next-ws] has started the WebSocket server');\n\n httpServer.on('upgrade', async (request, socket, head) => {\n const url = new URL(request.url ?? '', 'ws://next');\n const pathname = url.pathname;\n if (pathname.startsWith('/_next')) return;\n\n const filename = resolveFilename(nextServer, pathname);\n if (!filename) {\n logger.error(`[next-ws] could not find module for page ${pathname}`);\n return socket.destroy();\n }\n\n const pageModule = await getPageModule(nextServer, filename);\n if (!pageModule) {\n logger.error(`[next-ws] could not find module for page ${pathname}`);\n return socket.destroy();\n }\n\n const socketHandler = pageModule?.routeModule?.userland?.SOCKET;\n if (!socketHandler || typeof socketHandler !== 'function') {\n logger.error(`[next-ws] ${pathname} does not export a SOCKET handler`);\n return socket.destroy();\n }\n\n return wsServer.handleUpgrade(request, socket, head, (c, r) => {\n const dispose = socketHandler(c, r, wsServer);\n if (typeof dispose === 'function') c.once('close', () => dispose());\n });\n });\n}\n\n// Next WS versions below 0.2.0 used a different method of setup\n// This remains for backwards compatibility, but may be removed in a future version\nexport function hookNextNodeServer(this: NextNodeServer) {\n setupWebSocketServer(this);\n}\n","import * as logger from 'next/dist/build/output/log';\nimport type NextNodeServer from 'next/dist/server/next-server';\nimport type { SocketHandler } from './persistent';\n\n/**\n * Get the environment metadata.\n * @returns The environment metadata.\n */\nexport function getEnvironmentMeta() {\n const isCustomServer = !process.title.startsWith('next-');\n const isMainProcess = process.env.NEXT_WS_MAIN_PROCESS === '1';\n const isDevelopment = process.env.NODE_ENV === 'development';\n return { isCustomServer, isMainProcess, isDevelopment };\n}\n\n/**\n * Resolve a filename to a page.\n * @param nextServer The NextNodeServer instance.\n * @param pathname The pathname to resolve.\n * @returns The resolved page filename, or null if the page could not be resolved.\n */\nexport function resolveFilename(nextServer: NextNodeServer, pathname: string) {\n const pathParts = pathname.split('/');\n const appRoutes = {\n // @ts-expect-error - appPathRoutes is protected\n ...nextServer.appPathRoutes,\n // @ts-expect-error - getAppPathRoutes is protected\n ...nextServer.getAppPathRoutes(),\n };\n\n for (const [key, [path]] of Object.entries(appRoutes)) {\n const hasDynamic = key.includes('[') && key.includes(']');\n\n if (hasDynamic) {\n const keyParts = key.split('/');\n if (keyParts.length !== pathParts.length) continue;\n\n for (let i = 0; i < keyParts.length; i++) {\n const keyPart = keyParts[i]!;\n const pathPart = pathParts[i]!;\n\n const isDynamic = keyPart.includes('[') && keyPart.includes(']');\n if (isDynamic) keyParts[i] = pathPart;\n if (keyParts[i] !== pathParts[i]) break;\n\n if (i === keyParts.length - 1)\n if (path?.endsWith('/route')) return path;\n }\n } else {\n if (key !== pathname) continue;\n if (!path?.endsWith('/route')) return null;\n return path;\n }\n }\n\n return null;\n}\n\n/**\n * Get the page module for a page.\n * @param nextServer The NextNodeServer instance.\n * @param filename The filename of the page.\n * @returns The page module.\n */\nexport async function getPageModule(\n nextServer: NextNodeServer,\n filename: string,\n) {\n try {\n // In Next.js 14, hotReloader was removed and ensurePage was moved to NextNodeServer\n if ('hotReloader' in nextServer) {\n // @ts-expect-error - hotReloader only exists in Next.js 13\n await nextServer.hotReloader?.ensurePage({\n page: filename,\n clientOnly: false,\n });\n } else if ('ensurePage' in nextServer) {\n // ensurePage throws an error in production, so we need to catch it\n // @ts-expect-error - ensurePage is protected\n await nextServer.ensurePage({ page: filename, clientOnly: false });\n } else {\n // Future-proofing\n logger.warnOnce(\n '[next-ws] unable to ensure page, you may need to open the route in your browser first so Next.js compiles it',\n );\n }\n } catch {}\n\n // @ts-expect-error - getPageModule is protected\n const buildPagePath = nextServer.getPagePath(filename);\n return require(buildPagePath) as PageModule;\n}\n\nexport interface PageModule {\n routeModule?: {\n userland?: {\n SOCKET?: SocketHandler;\n };\n };\n}\n","import * as logger from 'next/dist/build/output/log';\nimport { getEnvironmentMeta } from './next';\n\nfunction mainProcessOnly(fnName: string) {\n if (process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK === '1') return;\n\n const meta = getEnvironmentMeta();\n if (!meta.isMainProcess) {\n throw new Error(\n `[next-ws] Attempt to invoke '${fnName}' outside the main process.\nYou may be attempting to interact with the WebSocket server outside of a SOCKET 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.\nYou can resolve this by using a custom server.`,\n );\n } else if (!meta.isCustomServer) {\n logger.warnOnce(\n `[next-ws] Caution: The function '${fnName}' was invoked without a custom server.\nThis could lead to unintended behaviour, especially if you're attempting to interact with the WebSocket server outside of a SOCKET handler.\nPlease 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.\nYou can resolve this by using a custom server.`,\n );\n }\n}\n\n// ========== HTTP Server ==========\n\nimport type { Server as HttpServer } from 'node:http';\nexport const NextWsHttpServer = Symbol.for('NextWs_HttpServer');\n\n/**\n * Set the HTTP server that the WebSocket server should listen on, must be called before the WebSocket server is created.\n * @param server The HTTP server.\n */\nexport function setHttpServer(server: HttpServer) {\n Reflect.set(globalThis, NextWsHttpServer, server);\n}\n\n/**\n * Get the HTTP server that the WebSocket server is listening on.\n * @remark If you want to access the HTTP server outside of a SOCKET handler, you must be using a custom server.\n * @returns The HTTP server.\n * @throws If attempting to access the HTTP server outside of the main process.\n */\nexport function getHttpServer() {\n mainProcessOnly('getHttpServer');\n return Reflect.get(globalThis, NextWsHttpServer) as HttpServer;\n}\n\nexport function useHttpServer(server?: HttpServer) {\n const existing = getHttpServer();\n if (existing) return existing;\n if (server) setHttpServer(server);\n return server;\n}\n\n// ========== WebSocket Server ==========\n\nimport type { WebSocketServer } from 'ws';\nexport const NextWsWebSocketServer = Symbol.for('NextWs_WebSocketServer');\n\n/**\n * Set the WebSocket server that the WebSocket server should listen on, must be called before the WebSocket server is created.\n * @param wsServer The WebSocket server.\n */\nexport function setWebSocketServer(wsServer: WebSocketServer) {\n Reflect.set(globalThis, NextWsWebSocketServer, wsServer);\n}\n\n/**\n * Get the WebSocket server that the WebSocket server is listening on.\n * @remark If you want to access the WebSocket server outside of a SOCKET handler, you must be using a custom server.\n * @returns The WebSocket server.\n * @throws If attempting to access the WebSocket server outside of the main process.\n */\nexport function getWebSocketServer() {\n mainProcessOnly('getWebSocketServer');\n return Reflect.get(globalThis, NextWsWebSocketServer) as WebSocketServer;\n}\n\nexport function useWebSocketServer(wsServer?: WebSocketServer) {\n const existing = getWebSocketServer();\n if (existing) return existing;\n if (wsServer) setWebSocketServer(wsServer);\n return wsServer;\n}\n\n/** A function that handles a WebSocket connection. */\nexport type SocketHandler = (\n /** The WebSocket client that connected. */\n client: import('ws').WebSocket,\n /** The HTTP request that initiated the WebSocket connection. */\n request: import('http').IncomingMessage,\n /** The WebSocket server. */\n server: import('ws').WebSocketServer,\n) => unknown | (() => void);\n","export * from './setup';\nexport {\n setHttpServer,\n getHttpServer,\n setWebSocketServer,\n getWebSocketServer,\n} from './helpers/persistent';\n\n/**\n * @deprecated\n */\nexport function verifyPatch() {\n throw new Error(\n \"The 'verifyPatch' function has been deprecated in favour of the `npx next-ws-cli@latest verify` command.\",\n );\n}\n"]}
1
+ {"version":3,"sources":["../../src/server/setup.ts","../../src/server/helpers/persistent.ts","../../src/server/helpers/route.ts","../../src/server/index.ts"],"names":["logger"],"mappings":"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","sourcesContent":["import * as logger from 'next/dist/build/output/log';\nimport type NextNodeServer from 'next/dist/server/next-server';\nimport { WebSocketServer } from 'ws';\nimport { useHttpServer, useWebSocketServer } from './helpers/persistent';\nimport { importRouteModule, resolvePathToRoute } from './helpers/route';\n\nexport function setupWebSocketServer(nextServer: NextNodeServer) {\n process.env.NEXT_WS_MAIN_PROCESS = String(1);\n\n process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK = String(1);\n // @ts-expect-error - serverOptions is protected\n const httpServer = useHttpServer(nextServer.serverOptions?.httpServer);\n const wsServer = useWebSocketServer(new WebSocketServer({ noServer: true }));\n // biome-ignore lint/performance/noDelete: <explanation>\n delete process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK;\n\n if (!httpServer)\n return logger.error('[next-ws] was not able to find the HTTP server');\n if (!wsServer)\n return logger.error('[next-ws] was not able to find the WebSocket server');\n\n logger.ready('[next-ws] has started the WebSocket server');\n\n httpServer.on('upgrade', async (request, socket, head) => {\n const url = new URL(request.url ?? '', 'ws://next');\n const pathname = url.pathname;\n if (pathname.startsWith('/_next')) return;\n\n const routeInfo = resolvePathToRoute(nextServer, pathname);\n if (!routeInfo) {\n logger.error(`[next-ws] could not find module for page ${pathname}`);\n return socket.destroy();\n }\n\n const routeModule = await importRouteModule(nextServer, routeInfo.filePath);\n if (!routeModule) {\n logger.error(`[next-ws] could not find module for page ${pathname}`);\n return socket.destroy();\n }\n\n const socketHandler = routeModule?.routeModule?.userland?.SOCKET;\n if (!socketHandler || typeof socketHandler !== 'function') {\n logger.error(`[next-ws] ${pathname} does not export a SOCKET handler`);\n return socket.destroy();\n }\n\n return wsServer.handleUpgrade(request, socket, head, async (c, r) => {\n const routeContext = { params: routeInfo.routeParams };\n const handleClose = await socketHandler(c, r, wsServer, routeContext);\n if (typeof handleClose === 'function')\n c.once('close', () => handleClose());\n });\n });\n}\n\n// Next WS versions below 0.2.0 used a different method of setup\n// This remains for backwards compatibility, but may be removed in a future version\nexport function hookNextNodeServer(this: NextNodeServer) {\n setupWebSocketServer(this);\n}\n","import * as logger from 'next/dist/build/output/log';\n\n/**\n * Get the environment metadata.\n * @returns The environment metadata.\n */\nfunction getEnvironmentMeta() {\n const isCustomServer = !process.title.startsWith('next-');\n const isMainProcess = process.env.NEXT_WS_MAIN_PROCESS === '1';\n const isDevelopment = process.env.NODE_ENV === 'development';\n return { isCustomServer, isMainProcess, isDevelopment };\n}\n\nfunction mainProcessOnly(fnName: string) {\n if (process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK === '1') return;\n\n const meta = getEnvironmentMeta();\n if (!meta.isMainProcess) {\n throw new Error(\n `[next-ws] Attempt to invoke '${fnName}' outside the main process.\nYou may be attempting to interact with the WebSocket server outside of a SOCKET 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.\nYou can resolve this by using a custom server.`,\n );\n } else if (!meta.isCustomServer) {\n logger.warnOnce(\n `[next-ws] Caution: The function '${fnName}' was invoked without a custom server.\nThis could lead to unintended behaviour, especially if you're attempting to interact with the WebSocket server outside of a SOCKET handler.\nPlease 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.\nYou can resolve this by using a custom server.`,\n );\n }\n}\n\n// ========== HTTP Server ==========\n\nimport type { Server as HttpServer } from 'node:http';\nexport const NextWsHttpServer = Symbol.for('NextWs_HttpServer');\n\n/**\n * Set the HTTP server that the WebSocket server should listen on, must be called before the WebSocket server is created.\n * @param server The HTTP server.\n */\nexport function setHttpServer(server: HttpServer) {\n Reflect.set(globalThis, NextWsHttpServer, server);\n}\n\n/**\n * Get the HTTP server that the WebSocket server is listening on.\n * @remark If you want to access the HTTP server outside of a SOCKET handler, you must be using a custom server.\n * @returns The HTTP server.\n * @throws If attempting to access the HTTP server outside of the main process.\n */\nexport function getHttpServer() {\n mainProcessOnly('getHttpServer');\n return Reflect.get(globalThis, NextWsHttpServer) as HttpServer;\n}\n\nexport function useHttpServer(server?: HttpServer) {\n const existing = getHttpServer();\n if (existing) return existing;\n if (server) setHttpServer(server);\n return server;\n}\n\n// ========== WebSocket Server ==========\n\nimport type { WebSocketServer } from 'ws';\nexport const NextWsWebSocketServer = Symbol.for('NextWs_WebSocketServer');\n\n/**\n * Set the WebSocket server that the WebSocket server should listen on, must be called before the WebSocket server is created.\n * @param wsServer The WebSocket server.\n */\nexport function setWebSocketServer(wsServer: WebSocketServer) {\n Reflect.set(globalThis, NextWsWebSocketServer, wsServer);\n}\n\n/**\n * Get the WebSocket server that the WebSocket server is listening on.\n * @remark If you want to access the WebSocket server outside of a SOCKET handler, you must be using a custom server.\n * @returns The WebSocket server.\n * @throws If attempting to access the WebSocket server outside of the main process.\n */\nexport function getWebSocketServer() {\n mainProcessOnly('getWebSocketServer');\n return Reflect.get(globalThis, NextWsWebSocketServer) as WebSocketServer;\n}\n\nexport function useWebSocketServer(wsServer?: WebSocketServer) {\n const existing = getWebSocketServer();\n if (existing) return existing;\n if (wsServer) setWebSocketServer(wsServer);\n return wsServer;\n}\n","import * as logger from 'next/dist/build/output/log';\nimport type NextNodeServer from 'next/dist/server/next-server';\nimport type { SocketHandler } from './socket';\n\nfunction createRouteRegex(routePattern: string) {\n const escapedPattern = routePattern.replace(/[-/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');\n const paramRegex = escapedPattern\n .replace(/\\\\\\[([a-zA-Z0-9_]+)\\\\\\]/g, '(?<$1>[^/]+)') // Match [param]\n .replace(/\\\\\\[(?:\\\\\\.){3}([a-zA-Z0-9_]+)\\\\\\]/g, '(?<rest_$1>.+)'); // Match [...param]\n return new RegExp(`^${paramRegex}$`);\n}\n\nfunction getRouteParams(routePattern: string, routePath: string) {\n const routeRegex = createRouteRegex(routePattern);\n const match = routePath.match(routeRegex);\n if (!match) return null;\n if (!match.groups) return {};\n\n const params: Record<string, string | string[]> = {};\n for (let [k, v] of Object.entries(match.groups)) {\n if (k.startsWith('rest_')) (k = k.slice(5)), (v = v.split('/') as never);\n Reflect.set(params, k, v);\n }\n return params;\n}\n\n/**\n * Resolve a request path to a route file path and route parameters.\n * @param nextServer The NextNodeServer instance.\n * @param requestPath The request path to resolve for.\n * @returns The resolved file path and route parameters, or null if the route could not be resolved.\n */\nexport function resolvePathToRoute(\n nextServer: NextNodeServer,\n requestPath: string,\n) {\n const routes = {\n // @ts-expect-error - appPathRoutes is protected\n ...nextServer.appPathRoutes,\n // @ts-expect-error - getAppPathRoutes is protected\n ...nextServer.getAppPathRoutes(),\n };\n\n for (const [routePath, [filePath]] of Object.entries(routes)) {\n const routeParams = getRouteParams(routePath, requestPath);\n if (routeParams) return { filePath: filePath!, routeParams };\n }\n\n return null;\n}\n\n/**\n * Import the route module for a route.\n * @param nextServer The NextNodeServer instance.\n * @param filePath The file path of the route.\n * @returns\n */\nexport async function importRouteModule(\n nextServer: NextNodeServer,\n filePath: string,\n) {\n try {\n // In Next.js 14, hotReloader was removed and ensurePage was moved to NextNodeServer\n if ('hotReloader' in nextServer) {\n // @ts-expect-error - hotReloader only exists in Next.js 13\n await nextServer.hotReloader?.ensurePage({\n page: filePath,\n clientOnly: false,\n });\n } else if ('ensurePage' in nextServer) {\n // ensurePage throws an error in production, so we need to catch it\n // @ts-expect-error - ensurePage is protected\n await nextServer.ensurePage({ page: filePath, clientOnly: false });\n } else {\n // Future-proofing\n logger.warnOnce(\n '[next-ws] unable to ensure page, you may need to open the route in your browser first so Next.js compiles it',\n );\n }\n } catch {}\n\n // @ts-expect-error - getPageModule is protected\n const buildPagePath = nextServer.getPagePath(filePath);\n return require(buildPagePath) as RouteModule;\n}\n\nexport interface RouteModule {\n routeModule?: {\n userland?: {\n SOCKET?: SocketHandler;\n };\n };\n}\n","export * from './setup';\nexport {\n setHttpServer,\n getHttpServer,\n setWebSocketServer,\n getWebSocketServer,\n} from './helpers/persistent';\n\n/**\n * @deprecated\n */\nexport function verifyPatch() {\n throw new Error(\n \"The 'verifyPatch' function has been deprecated in favour of the `npx next-ws-cli@latest verify` command.\",\n );\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import NextNodeServer from 'next/dist/server/next-server';
2
- import { WebSocketServer } from 'ws';
3
2
  import { Server } from 'node:http';
3
+ import { WebSocketServer } from 'ws';
4
4
 
5
5
  declare function setupWebSocketServer(nextServer: NextNodeServer): void;
6
6
  declare function hookNextNodeServer(this: NextNodeServer): void;
@@ -1,6 +1,6 @@
1
1
  import NextNodeServer from 'next/dist/server/next-server';
2
- import { WebSocketServer } from 'ws';
3
2
  import { Server } from 'node:http';
3
+ import { WebSocketServer } from 'ws';
4
4
 
5
5
  declare function setupWebSocketServer(nextServer: NextNodeServer): void;
6
6
  declare function hookNextNodeServer(this: NextNodeServer): void;
@@ -1,4 +1,4 @@
1
- import { __name, __require } from '../chunk-6VWOYYWX.mjs';
1
+ import { __name, __require } from '../chunk-WO25ABG2.js';
2
2
  import * as logger3 from 'next/dist/build/output/log';
3
3
  import { WebSocketServer } from 'ws';
4
4
 
@@ -7,44 +7,6 @@ function getEnvironmentMeta() {
7
7
  return { isCustomServer, isMainProcess, isDevelopment };
8
8
  }
9
9
  __name(getEnvironmentMeta, "getEnvironmentMeta");
10
- function resolveFilename(nextServer, pathname) {
11
- let pathParts = pathname.split("/"), appRoutes = {
12
- // @ts-expect-error - appPathRoutes is protected
13
- ...nextServer.appPathRoutes,
14
- // @ts-expect-error - getAppPathRoutes is protected
15
- ...nextServer.getAppPathRoutes()
16
- };
17
- for (let [key, [path]] of Object.entries(appRoutes))
18
- if (key.includes("[") && key.includes("]")) {
19
- let keyParts = key.split("/");
20
- if (keyParts.length !== pathParts.length) continue;
21
- for (let i = 0; i < keyParts.length; i++) {
22
- let keyPart = keyParts[i], pathPart = pathParts[i];
23
- if (keyPart.includes("[") && keyPart.includes("]") && (keyParts[i] = pathPart), keyParts[i] !== pathParts[i]) break;
24
- if (i === keyParts.length - 1 && path?.endsWith("/route"))
25
- return path;
26
- }
27
- } else {
28
- if (key !== pathname) continue;
29
- return path?.endsWith("/route") ? path : null;
30
- }
31
- return null;
32
- }
33
- __name(resolveFilename, "resolveFilename");
34
- async function getPageModule(nextServer, filename) {
35
- try {
36
- "hotReloader" in nextServer ? await nextServer.hotReloader?.ensurePage({
37
- page: filename,
38
- clientOnly: !1
39
- }) : "ensurePage" in nextServer ? await nextServer.ensurePage({ page: filename, clientOnly: !1 }) : logger3.warnOnce(
40
- "[next-ws] unable to ensure page, you may need to open the route in your browser first so Next.js compiles it"
41
- );
42
- } catch {
43
- }
44
- let buildPagePath = nextServer.getPagePath(filename);
45
- return __require(buildPagePath);
46
- }
47
- __name(getPageModule, "getPageModule");
48
10
  function mainProcessOnly(fnName) {
49
11
  if (process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK === "1") return;
50
12
  let meta = getEnvironmentMeta();
@@ -90,6 +52,49 @@ function useWebSocketServer(wsServer) {
90
52
  return existing || (wsServer && setWebSocketServer(wsServer), wsServer);
91
53
  }
92
54
  __name(useWebSocketServer, "useWebSocketServer");
55
+ function createRouteRegex(routePattern) {
56
+ let paramRegex = routePattern.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&").replace(/\\\[([a-zA-Z0-9_]+)\\\]/g, "(?<$1>[^/]+)").replace(/\\\[(?:\\\.){3}([a-zA-Z0-9_]+)\\\]/g, "(?<rest_$1>.+)");
57
+ return new RegExp(`^${paramRegex}$`);
58
+ }
59
+ __name(createRouteRegex, "createRouteRegex");
60
+ function getRouteParams(routePattern, routePath) {
61
+ let routeRegex = createRouteRegex(routePattern), match = routePath.match(routeRegex);
62
+ if (!match) return null;
63
+ if (!match.groups) return {};
64
+ let params = {};
65
+ for (let [k, v] of Object.entries(match.groups))
66
+ k.startsWith("rest_") && (k = k.slice(5), v = v.split("/")), Reflect.set(params, k, v);
67
+ return params;
68
+ }
69
+ __name(getRouteParams, "getRouteParams");
70
+ function resolvePathToRoute(nextServer, requestPath) {
71
+ let routes = {
72
+ // @ts-expect-error - appPathRoutes is protected
73
+ ...nextServer.appPathRoutes,
74
+ // @ts-expect-error - getAppPathRoutes is protected
75
+ ...nextServer.getAppPathRoutes()
76
+ };
77
+ for (let [routePath, [filePath]] of Object.entries(routes)) {
78
+ let routeParams = getRouteParams(routePath, requestPath);
79
+ if (routeParams) return { filePath, routeParams };
80
+ }
81
+ return null;
82
+ }
83
+ __name(resolvePathToRoute, "resolvePathToRoute");
84
+ async function importRouteModule(nextServer, filePath) {
85
+ try {
86
+ "hotReloader" in nextServer ? await nextServer.hotReloader?.ensurePage({
87
+ page: filePath,
88
+ clientOnly: !1
89
+ }) : "ensurePage" in nextServer ? await nextServer.ensurePage({ page: filePath, clientOnly: !1 }) : logger3.warnOnce(
90
+ "[next-ws] unable to ensure page, you may need to open the route in your browser first so Next.js compiles it"
91
+ );
92
+ } catch {
93
+ }
94
+ let buildPagePath = nextServer.getPagePath(filePath);
95
+ return __require(buildPagePath);
96
+ }
97
+ __name(importRouteModule, "importRouteModule");
93
98
 
94
99
  // src/server/setup.ts
95
100
  function setupWebSocketServer(nextServer) {
@@ -102,16 +107,16 @@ function setupWebSocketServer(nextServer) {
102
107
  logger3.ready("[next-ws] has started the WebSocket server"), httpServer.on("upgrade", async (request, socket, head) => {
103
108
  let pathname = new URL(request.url ?? "", "ws://next").pathname;
104
109
  if (pathname.startsWith("/_next")) return;
105
- let filename = resolveFilename(nextServer, pathname);
106
- if (!filename)
110
+ let routeInfo = resolvePathToRoute(nextServer, pathname);
111
+ if (!routeInfo)
107
112
  return logger3.error(`[next-ws] could not find module for page ${pathname}`), socket.destroy();
108
- let pageModule = await getPageModule(nextServer, filename);
109
- if (!pageModule)
113
+ let routeModule = await importRouteModule(nextServer, routeInfo.filePath);
114
+ if (!routeModule)
110
115
  return logger3.error(`[next-ws] could not find module for page ${pathname}`), socket.destroy();
111
- let socketHandler = pageModule?.routeModule?.userland?.SOCKET;
112
- return !socketHandler || typeof socketHandler != "function" ? (logger3.error(`[next-ws] ${pathname} does not export a SOCKET handler`), socket.destroy()) : wsServer.handleUpgrade(request, socket, head, (c, r) => {
113
- let dispose = socketHandler(c, r, wsServer);
114
- typeof dispose == "function" && c.once("close", () => dispose());
116
+ let socketHandler = routeModule?.routeModule?.userland?.SOCKET;
117
+ return !socketHandler || typeof socketHandler != "function" ? (logger3.error(`[next-ws] ${pathname} does not export a SOCKET handler`), socket.destroy()) : wsServer.handleUpgrade(request, socket, head, async (c, r) => {
118
+ let routeContext = { params: routeInfo.routeParams }, handleClose = await socketHandler(c, r, wsServer, routeContext);
119
+ typeof handleClose == "function" && c.once("close", () => handleClose());
115
120
  });
116
121
  });
117
122
  }
@@ -131,4 +136,4 @@ __name(verifyPatch, "verifyPatch");
131
136
 
132
137
  export { getHttpServer, getWebSocketServer, hookNextNodeServer, setHttpServer, setWebSocketServer, setupWebSocketServer, verifyPatch };
133
138
  //# sourceMappingURL=out.js.map
134
- //# sourceMappingURL=index.mjs.map
139
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/setup.ts","../../src/server/helpers/persistent.ts","../../src/server/helpers/route.ts","../../src/server/index.ts"],"names":["logger"],"mappings":"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","sourcesContent":["import * as logger from 'next/dist/build/output/log';\nimport type NextNodeServer from 'next/dist/server/next-server';\nimport { WebSocketServer } from 'ws';\nimport { useHttpServer, useWebSocketServer } from './helpers/persistent';\nimport { importRouteModule, resolvePathToRoute } from './helpers/route';\n\nexport function setupWebSocketServer(nextServer: NextNodeServer) {\n process.env.NEXT_WS_MAIN_PROCESS = String(1);\n\n process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK = String(1);\n // @ts-expect-error - serverOptions is protected\n const httpServer = useHttpServer(nextServer.serverOptions?.httpServer);\n const wsServer = useWebSocketServer(new WebSocketServer({ noServer: true }));\n // biome-ignore lint/performance/noDelete: <explanation>\n delete process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK;\n\n if (!httpServer)\n return logger.error('[next-ws] was not able to find the HTTP server');\n if (!wsServer)\n return logger.error('[next-ws] was not able to find the WebSocket server');\n\n logger.ready('[next-ws] has started the WebSocket server');\n\n httpServer.on('upgrade', async (request, socket, head) => {\n const url = new URL(request.url ?? '', 'ws://next');\n const pathname = url.pathname;\n if (pathname.startsWith('/_next')) return;\n\n const routeInfo = resolvePathToRoute(nextServer, pathname);\n if (!routeInfo) {\n logger.error(`[next-ws] could not find module for page ${pathname}`);\n return socket.destroy();\n }\n\n const routeModule = await importRouteModule(nextServer, routeInfo.filePath);\n if (!routeModule) {\n logger.error(`[next-ws] could not find module for page ${pathname}`);\n return socket.destroy();\n }\n\n const socketHandler = routeModule?.routeModule?.userland?.SOCKET;\n if (!socketHandler || typeof socketHandler !== 'function') {\n logger.error(`[next-ws] ${pathname} does not export a SOCKET handler`);\n return socket.destroy();\n }\n\n return wsServer.handleUpgrade(request, socket, head, async (c, r) => {\n const routeContext = { params: routeInfo.routeParams };\n const handleClose = await socketHandler(c, r, wsServer, routeContext);\n if (typeof handleClose === 'function')\n c.once('close', () => handleClose());\n });\n });\n}\n\n// Next WS versions below 0.2.0 used a different method of setup\n// This remains for backwards compatibility, but may be removed in a future version\nexport function hookNextNodeServer(this: NextNodeServer) {\n setupWebSocketServer(this);\n}\n","import * as logger from 'next/dist/build/output/log';\n\n/**\n * Get the environment metadata.\n * @returns The environment metadata.\n */\nfunction getEnvironmentMeta() {\n const isCustomServer = !process.title.startsWith('next-');\n const isMainProcess = process.env.NEXT_WS_MAIN_PROCESS === '1';\n const isDevelopment = process.env.NODE_ENV === 'development';\n return { isCustomServer, isMainProcess, isDevelopment };\n}\n\nfunction mainProcessOnly(fnName: string) {\n if (process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK === '1') return;\n\n const meta = getEnvironmentMeta();\n if (!meta.isMainProcess) {\n throw new Error(\n `[next-ws] Attempt to invoke '${fnName}' outside the main process.\nYou may be attempting to interact with the WebSocket server outside of a SOCKET 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.\nYou can resolve this by using a custom server.`,\n );\n } else if (!meta.isCustomServer) {\n logger.warnOnce(\n `[next-ws] Caution: The function '${fnName}' was invoked without a custom server.\nThis could lead to unintended behaviour, especially if you're attempting to interact with the WebSocket server outside of a SOCKET handler.\nPlease 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.\nYou can resolve this by using a custom server.`,\n );\n }\n}\n\n// ========== HTTP Server ==========\n\nimport type { Server as HttpServer } from 'node:http';\nexport const NextWsHttpServer = Symbol.for('NextWs_HttpServer');\n\n/**\n * Set the HTTP server that the WebSocket server should listen on, must be called before the WebSocket server is created.\n * @param server The HTTP server.\n */\nexport function setHttpServer(server: HttpServer) {\n Reflect.set(globalThis, NextWsHttpServer, server);\n}\n\n/**\n * Get the HTTP server that the WebSocket server is listening on.\n * @remark If you want to access the HTTP server outside of a SOCKET handler, you must be using a custom server.\n * @returns The HTTP server.\n * @throws If attempting to access the HTTP server outside of the main process.\n */\nexport function getHttpServer() {\n mainProcessOnly('getHttpServer');\n return Reflect.get(globalThis, NextWsHttpServer) as HttpServer;\n}\n\nexport function useHttpServer(server?: HttpServer) {\n const existing = getHttpServer();\n if (existing) return existing;\n if (server) setHttpServer(server);\n return server;\n}\n\n// ========== WebSocket Server ==========\n\nimport type { WebSocketServer } from 'ws';\nexport const NextWsWebSocketServer = Symbol.for('NextWs_WebSocketServer');\n\n/**\n * Set the WebSocket server that the WebSocket server should listen on, must be called before the WebSocket server is created.\n * @param wsServer The WebSocket server.\n */\nexport function setWebSocketServer(wsServer: WebSocketServer) {\n Reflect.set(globalThis, NextWsWebSocketServer, wsServer);\n}\n\n/**\n * Get the WebSocket server that the WebSocket server is listening on.\n * @remark If you want to access the WebSocket server outside of a SOCKET handler, you must be using a custom server.\n * @returns The WebSocket server.\n * @throws If attempting to access the WebSocket server outside of the main process.\n */\nexport function getWebSocketServer() {\n mainProcessOnly('getWebSocketServer');\n return Reflect.get(globalThis, NextWsWebSocketServer) as WebSocketServer;\n}\n\nexport function useWebSocketServer(wsServer?: WebSocketServer) {\n const existing = getWebSocketServer();\n if (existing) return existing;\n if (wsServer) setWebSocketServer(wsServer);\n return wsServer;\n}\n","import * as logger from 'next/dist/build/output/log';\nimport type NextNodeServer from 'next/dist/server/next-server';\nimport type { SocketHandler } from './socket';\n\nfunction createRouteRegex(routePattern: string) {\n const escapedPattern = routePattern.replace(/[-/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');\n const paramRegex = escapedPattern\n .replace(/\\\\\\[([a-zA-Z0-9_]+)\\\\\\]/g, '(?<$1>[^/]+)') // Match [param]\n .replace(/\\\\\\[(?:\\\\\\.){3}([a-zA-Z0-9_]+)\\\\\\]/g, '(?<rest_$1>.+)'); // Match [...param]\n return new RegExp(`^${paramRegex}$`);\n}\n\nfunction getRouteParams(routePattern: string, routePath: string) {\n const routeRegex = createRouteRegex(routePattern);\n const match = routePath.match(routeRegex);\n if (!match) return null;\n if (!match.groups) return {};\n\n const params: Record<string, string | string[]> = {};\n for (let [k, v] of Object.entries(match.groups)) {\n if (k.startsWith('rest_')) (k = k.slice(5)), (v = v.split('/') as never);\n Reflect.set(params, k, v);\n }\n return params;\n}\n\n/**\n * Resolve a request path to a route file path and route parameters.\n * @param nextServer The NextNodeServer instance.\n * @param requestPath The request path to resolve for.\n * @returns The resolved file path and route parameters, or null if the route could not be resolved.\n */\nexport function resolvePathToRoute(\n nextServer: NextNodeServer,\n requestPath: string,\n) {\n const routes = {\n // @ts-expect-error - appPathRoutes is protected\n ...nextServer.appPathRoutes,\n // @ts-expect-error - getAppPathRoutes is protected\n ...nextServer.getAppPathRoutes(),\n };\n\n for (const [routePath, [filePath]] of Object.entries(routes)) {\n const routeParams = getRouteParams(routePath, requestPath);\n if (routeParams) return { filePath: filePath!, routeParams };\n }\n\n return null;\n}\n\n/**\n * Import the route module for a route.\n * @param nextServer The NextNodeServer instance.\n * @param filePath The file path of the route.\n * @returns\n */\nexport async function importRouteModule(\n nextServer: NextNodeServer,\n filePath: string,\n) {\n try {\n // In Next.js 14, hotReloader was removed and ensurePage was moved to NextNodeServer\n if ('hotReloader' in nextServer) {\n // @ts-expect-error - hotReloader only exists in Next.js 13\n await nextServer.hotReloader?.ensurePage({\n page: filePath,\n clientOnly: false,\n });\n } else if ('ensurePage' in nextServer) {\n // ensurePage throws an error in production, so we need to catch it\n // @ts-expect-error - ensurePage is protected\n await nextServer.ensurePage({ page: filePath, clientOnly: false });\n } else {\n // Future-proofing\n logger.warnOnce(\n '[next-ws] unable to ensure page, you may need to open the route in your browser first so Next.js compiles it',\n );\n }\n } catch {}\n\n // @ts-expect-error - getPageModule is protected\n const buildPagePath = nextServer.getPagePath(filePath);\n return require(buildPagePath) as RouteModule;\n}\n\nexport interface RouteModule {\n routeModule?: {\n userland?: {\n SOCKET?: SocketHandler;\n };\n };\n}\n","export * from './setup';\nexport {\n setHttpServer,\n getHttpServer,\n setWebSocketServer,\n getWebSocketServer,\n} from './helpers/persistent';\n\n/**\n * @deprecated\n */\nexport function verifyPatch() {\n throw new Error(\n \"The 'verifyPatch' function has been deprecated in favour of the `npx next-ws-cli@latest verify` command.\",\n );\n}\n"]}
package/package.json CHANGED
@@ -1,10 +1,16 @@
1
1
  {
2
2
  "name": "next-ws",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "Add support for WebSockets in Next.js 13 app directory",
6
6
  "license": "MIT",
7
- "keywords": ["next", "websocket", "ws", "server", "client"],
7
+ "keywords": [
8
+ "next",
9
+ "websocket",
10
+ "ws",
11
+ "server",
12
+ "client"
13
+ ],
8
14
  "homepage": "https://github.com/apteryxxyz/next-ws#readme",
9
15
  "repository": {
10
16
  "type": "git",
@@ -14,37 +20,36 @@
14
20
  "bugs": {
15
21
  "url": "https://github.com/apteryxxyz/next-ws/issues"
16
22
  },
17
- "files": ["dist"],
23
+ "files": [
24
+ "dist"
25
+ ],
18
26
  "exports": {
19
27
  "./client": {
20
- "import": "./dist/client/index.mjs",
28
+ "import": "./dist/client/index.js",
21
29
  "require": "./dist/client/index.cjs",
22
30
  "types": "./dist/client/index.d.ts"
23
31
  },
24
32
  "./server": {
25
- "import": "./dist/server/index.mjs",
33
+ "import": "./dist/server/index.js",
26
34
  "require": "./dist/server/index.cjs",
27
35
  "types": "./dist/server/index.d.ts"
28
36
  }
29
37
  },
30
- "scripts": {
31
- "lint": "biome lint . --write",
32
- "format": "biome format . --write",
33
- "check": "tsc --noEmit",
34
- "build": "cp ../../readme.md . && tsup",
35
- "dev": "pnpm build --watch"
36
- },
37
38
  "peerDependencies": {
38
39
  "next": ">=13.1.1",
39
40
  "react": "*",
40
41
  "ws": "*"
41
42
  },
42
43
  "devDependencies": {
43
- "@configs/tsup": "workspace:^",
44
44
  "@types/react": "^18.3.3",
45
45
  "@types/ws": "^8.5.10",
46
46
  "next": "^14.2.4",
47
47
  "react": "^18.3.1",
48
48
  "ws": "^8.17.1"
49
+ },
50
+ "scripts": {
51
+ "check": "tsc --noEmit",
52
+ "build": "cp ../../readme.md . && tsup",
53
+ "dev": "pnpm build --watch"
49
54
  }
50
- }
55
+ }
package/readme.md CHANGED
@@ -1,16 +1,16 @@
1
- <div align="center">
1
+ <div align='center'>
2
2
  <h1><strong>Next WS</strong></h1>
3
3
  <i>Add support for WebSockets in Next.js app directory</i><br>
4
4
  <code>npm install next-ws ws</code>
5
5
  </div>
6
6
 
7
- <div align="center">
8
- <img alt="package version" src="https://img.shields.io/npm/v/next-ws?label=version">
9
- <img alt="total downloads" src="https://img.shields.io/npm/dt/next-ws">
7
+ <div align='center'>
8
+ <img alt='package version' src='https://img.shields.io/npm/v/next-ws?label=version'>
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>
13
- <a href="https://discord.gg/vZQbMhwsKY"><img src="https://discordapp.com/api/guilds/829836158007115806/widget.png?style=shield" alt="discord shield"/></a>
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>
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
 
16
16
  ## 🤔 About
@@ -59,7 +59,7 @@ The `SOCKET` function receives three arguments: the WebSocket client instance, t
59
59
  export function SOCKET(
60
60
  client: import('ws').WebSocket,
61
61
  request: import('http').IncomingMessage,
62
- server: import('ws').WebSocketServer,
62
+ server: import('ws').WebSocketServer
63
63
  ) {
64
64
  // ...
65
65
  }
@@ -85,7 +85,7 @@ Creating an API route anywhere within the app directory and exporting a `SOCKET`
85
85
  export function SOCKET(
86
86
  client: import('ws').WebSocket,
87
87
  request: import('http').IncomingMessage,
88
- server: import('ws').WebSocketServer,
88
+ server: import('ws').WebSocketServer
89
89
  ) {
90
90
  console.log('A client connected');
91
91
 
@@ -102,6 +102,9 @@ export function SOCKET(
102
102
 
103
103
  ### Using a Custom Server
104
104
 
105
+ > [!IMPORTANT]
106
+ > Next WS was made to avoid the need for a custom server, if you are using one, you don't need this package and can just use a websocket server directly.
107
+
105
108
  To use a custom server, all you need to do is tell Next WS to use your server instead of creating its own. This can be done by calling the `setHttpServer` and `setWebSocketServer` functions from `next-ws/server` and passing your server instances.
106
109
 
107
110
  ```ts
@@ -123,7 +126,7 @@ const webSocketServer = new WebSocketServer({ noServer: true });
123
126
  setHttpServer(httpServer);
124
127
  setWebSocketServer(webSocketServer);
125
128
 
126
- const app = next({ dev, hostname, port, customServer: httpServer });
129
+ const app = next({ dev, hostname, port, customServer: true });
127
130
  const handle = app.getRequestHandler();
128
131
 
129
132
  app.prepare().then(() => {
@@ -169,15 +172,15 @@ To make it easier to connect to your new WebSocket server, Next WS also provides
169
172
  import { WebSocketProvider } from 'next-ws/client';
170
173
 
171
174
  export default function Layout({ children }: React.PropsWithChildren) {
172
- return <html>
173
- <body>
174
- <WebSocketProvider
175
- url="ws://localhost:3000/api/ws"
176
- >
177
- {children}
178
- </WebSocketProvider>
179
- </body>
180
- </html>;
175
+ return (
176
+ <html>
177
+ <body>
178
+ <WebSocketProvider url='ws://localhost:3000/api/ws'>
179
+ {children}
180
+ </WebSocketProvider>
181
+ </body>
182
+ </html>
183
+ );
181
184
  }
182
185
  ```
183
186
 
@@ -224,23 +227,20 @@ export default function Page() {
224
227
  return () => ws?.removeEventListener('message', onMessage);
225
228
  }, [ws]);
226
229
 
227
- return <>
228
- <input
229
- ref={inputRef}
230
- type="text"
231
- />
232
-
233
- <button
234
- onClick={() => ws?.send(inputRef.current?.value ?? '')}
235
- >
236
- Send message to server
237
- </button>
238
-
239
- <p>
240
- {message === null
241
- ? 'Waiting to receive message...'
242
- : `Got message: ${message}`}
243
- </p>
244
- </>;
230
+ return (
231
+ <>
232
+ <input ref={inputRef} type='text' />
233
+
234
+ <button onClick={() => ws?.send(inputRef.current?.value ?? '')}>
235
+ Send message to server
236
+ </button>
237
+
238
+ <p>
239
+ {message === null
240
+ ? 'Waiting to receive message...'
241
+ : `Got message: ${message}`}
242
+ </p>
243
+ </>
244
+ );
245
245
  }
246
- ```
246
+ ```
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/server/setup.ts","../../src/server/helpers/next.ts","../../src/server/helpers/persistent.ts","../../src/server/index.ts"],"names":["logger"],"mappings":"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","sourcesContent":["import * as logger from 'next/dist/build/output/log';\nimport type NextNodeServer from 'next/dist/server/next-server';\nimport { WebSocketServer } from 'ws';\nimport { getPageModule, resolveFilename } from './helpers/next';\nimport { useHttpServer, useWebSocketServer } from './helpers/persistent';\n\nexport function setupWebSocketServer(nextServer: NextNodeServer) {\n process.env.NEXT_WS_MAIN_PROCESS = String(1);\n\n process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK = String(1);\n // @ts-expect-error - serverOptions is protected\n const httpServer = useHttpServer(nextServer.serverOptions?.httpServer);\n const wsServer = useWebSocketServer(new WebSocketServer({ noServer: true }));\n // biome-ignore lint/performance/noDelete: <explanation>\n delete process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK;\n\n if (!httpServer)\n return logger.error('[next-ws] was not able to find the HTTP server');\n if (!wsServer)\n return logger.error('[next-ws] was not able to find the WebSocket server');\n\n logger.ready('[next-ws] has started the WebSocket server');\n\n httpServer.on('upgrade', async (request, socket, head) => {\n const url = new URL(request.url ?? '', 'ws://next');\n const pathname = url.pathname;\n if (pathname.startsWith('/_next')) return;\n\n const filename = resolveFilename(nextServer, pathname);\n if (!filename) {\n logger.error(`[next-ws] could not find module for page ${pathname}`);\n return socket.destroy();\n }\n\n const pageModule = await getPageModule(nextServer, filename);\n if (!pageModule) {\n logger.error(`[next-ws] could not find module for page ${pathname}`);\n return socket.destroy();\n }\n\n const socketHandler = pageModule?.routeModule?.userland?.SOCKET;\n if (!socketHandler || typeof socketHandler !== 'function') {\n logger.error(`[next-ws] ${pathname} does not export a SOCKET handler`);\n return socket.destroy();\n }\n\n return wsServer.handleUpgrade(request, socket, head, (c, r) => {\n const dispose = socketHandler(c, r, wsServer);\n if (typeof dispose === 'function') c.once('close', () => dispose());\n });\n });\n}\n\n// Next WS versions below 0.2.0 used a different method of setup\n// This remains for backwards compatibility, but may be removed in a future version\nexport function hookNextNodeServer(this: NextNodeServer) {\n setupWebSocketServer(this);\n}\n","import * as logger from 'next/dist/build/output/log';\nimport type NextNodeServer from 'next/dist/server/next-server';\nimport type { SocketHandler } from './persistent';\n\n/**\n * Get the environment metadata.\n * @returns The environment metadata.\n */\nexport function getEnvironmentMeta() {\n const isCustomServer = !process.title.startsWith('next-');\n const isMainProcess = process.env.NEXT_WS_MAIN_PROCESS === '1';\n const isDevelopment = process.env.NODE_ENV === 'development';\n return { isCustomServer, isMainProcess, isDevelopment };\n}\n\n/**\n * Resolve a filename to a page.\n * @param nextServer The NextNodeServer instance.\n * @param pathname The pathname to resolve.\n * @returns The resolved page filename, or null if the page could not be resolved.\n */\nexport function resolveFilename(nextServer: NextNodeServer, pathname: string) {\n const pathParts = pathname.split('/');\n const appRoutes = {\n // @ts-expect-error - appPathRoutes is protected\n ...nextServer.appPathRoutes,\n // @ts-expect-error - getAppPathRoutes is protected\n ...nextServer.getAppPathRoutes(),\n };\n\n for (const [key, [path]] of Object.entries(appRoutes)) {\n const hasDynamic = key.includes('[') && key.includes(']');\n\n if (hasDynamic) {\n const keyParts = key.split('/');\n if (keyParts.length !== pathParts.length) continue;\n\n for (let i = 0; i < keyParts.length; i++) {\n const keyPart = keyParts[i]!;\n const pathPart = pathParts[i]!;\n\n const isDynamic = keyPart.includes('[') && keyPart.includes(']');\n if (isDynamic) keyParts[i] = pathPart;\n if (keyParts[i] !== pathParts[i]) break;\n\n if (i === keyParts.length - 1)\n if (path?.endsWith('/route')) return path;\n }\n } else {\n if (key !== pathname) continue;\n if (!path?.endsWith('/route')) return null;\n return path;\n }\n }\n\n return null;\n}\n\n/**\n * Get the page module for a page.\n * @param nextServer The NextNodeServer instance.\n * @param filename The filename of the page.\n * @returns The page module.\n */\nexport async function getPageModule(\n nextServer: NextNodeServer,\n filename: string,\n) {\n try {\n // In Next.js 14, hotReloader was removed and ensurePage was moved to NextNodeServer\n if ('hotReloader' in nextServer) {\n // @ts-expect-error - hotReloader only exists in Next.js 13\n await nextServer.hotReloader?.ensurePage({\n page: filename,\n clientOnly: false,\n });\n } else if ('ensurePage' in nextServer) {\n // ensurePage throws an error in production, so we need to catch it\n // @ts-expect-error - ensurePage is protected\n await nextServer.ensurePage({ page: filename, clientOnly: false });\n } else {\n // Future-proofing\n logger.warnOnce(\n '[next-ws] unable to ensure page, you may need to open the route in your browser first so Next.js compiles it',\n );\n }\n } catch {}\n\n // @ts-expect-error - getPageModule is protected\n const buildPagePath = nextServer.getPagePath(filename);\n return require(buildPagePath) as PageModule;\n}\n\nexport interface PageModule {\n routeModule?: {\n userland?: {\n SOCKET?: SocketHandler;\n };\n };\n}\n","import * as logger from 'next/dist/build/output/log';\nimport { getEnvironmentMeta } from './next';\n\nfunction mainProcessOnly(fnName: string) {\n if (process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK === '1') return;\n\n const meta = getEnvironmentMeta();\n if (!meta.isMainProcess) {\n throw new Error(\n `[next-ws] Attempt to invoke '${fnName}' outside the main process.\nYou may be attempting to interact with the WebSocket server outside of a SOCKET 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.\nYou can resolve this by using a custom server.`,\n );\n } else if (!meta.isCustomServer) {\n logger.warnOnce(\n `[next-ws] Caution: The function '${fnName}' was invoked without a custom server.\nThis could lead to unintended behaviour, especially if you're attempting to interact with the WebSocket server outside of a SOCKET handler.\nPlease 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.\nYou can resolve this by using a custom server.`,\n );\n }\n}\n\n// ========== HTTP Server ==========\n\nimport type { Server as HttpServer } from 'node:http';\nexport const NextWsHttpServer = Symbol.for('NextWs_HttpServer');\n\n/**\n * Set the HTTP server that the WebSocket server should listen on, must be called before the WebSocket server is created.\n * @param server The HTTP server.\n */\nexport function setHttpServer(server: HttpServer) {\n Reflect.set(globalThis, NextWsHttpServer, server);\n}\n\n/**\n * Get the HTTP server that the WebSocket server is listening on.\n * @remark If you want to access the HTTP server outside of a SOCKET handler, you must be using a custom server.\n * @returns The HTTP server.\n * @throws If attempting to access the HTTP server outside of the main process.\n */\nexport function getHttpServer() {\n mainProcessOnly('getHttpServer');\n return Reflect.get(globalThis, NextWsHttpServer) as HttpServer;\n}\n\nexport function useHttpServer(server?: HttpServer) {\n const existing = getHttpServer();\n if (existing) return existing;\n if (server) setHttpServer(server);\n return server;\n}\n\n// ========== WebSocket Server ==========\n\nimport type { WebSocketServer } from 'ws';\nexport const NextWsWebSocketServer = Symbol.for('NextWs_WebSocketServer');\n\n/**\n * Set the WebSocket server that the WebSocket server should listen on, must be called before the WebSocket server is created.\n * @param wsServer The WebSocket server.\n */\nexport function setWebSocketServer(wsServer: WebSocketServer) {\n Reflect.set(globalThis, NextWsWebSocketServer, wsServer);\n}\n\n/**\n * Get the WebSocket server that the WebSocket server is listening on.\n * @remark If you want to access the WebSocket server outside of a SOCKET handler, you must be using a custom server.\n * @returns The WebSocket server.\n * @throws If attempting to access the WebSocket server outside of the main process.\n */\nexport function getWebSocketServer() {\n mainProcessOnly('getWebSocketServer');\n return Reflect.get(globalThis, NextWsWebSocketServer) as WebSocketServer;\n}\n\nexport function useWebSocketServer(wsServer?: WebSocketServer) {\n const existing = getWebSocketServer();\n if (existing) return existing;\n if (wsServer) setWebSocketServer(wsServer);\n return wsServer;\n}\n\n/** A function that handles a WebSocket connection. */\nexport type SocketHandler = (\n /** The WebSocket client that connected. */\n client: import('ws').WebSocket,\n /** The HTTP request that initiated the WebSocket connection. */\n request: import('http').IncomingMessage,\n /** The WebSocket server. */\n server: import('ws').WebSocketServer,\n) => unknown | (() => void);\n","export * from './setup';\nexport {\n setHttpServer,\n getHttpServer,\n setWebSocketServer,\n getWebSocketServer,\n} from './helpers/persistent';\n\n/**\n * @deprecated\n */\nexport function verifyPatch() {\n throw new Error(\n \"The 'verifyPatch' function has been deprecated in favour of the `npx next-ws-cli@latest verify` command.\",\n );\n}\n"]}
File without changes