next-ws 1.1.0-next.2 → 1.1.1

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.
@@ -43,7 +43,7 @@ function resolveFilename(nextServer, pathname) {
43
43
  for (let i = 0; i < keyParts.length; i++) {
44
44
  let keyPart = keyParts[i], pathPart = pathParts[i];
45
45
  if (keyPart.includes("[") && keyPart.includes("]") && (keyParts[i] = pathPart), keyParts[i] !== pathParts[i]) break;
46
- if (i === keyParts.length - 1 && !path?.endsWith("/route"))
46
+ if (i === keyParts.length - 1 && path?.endsWith("/route"))
47
47
  return path;
48
48
  }
49
49
  } else {
@@ -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":";;;;;;AAAA,YAAYA,aAAY;AAExB,SAAS,uBAAuB;;;ACFhC,YAAY,YAAY;AAQjB,SAAS,qBAAqB;AACnC,MAAM,iBAAiB,CAAC,QAAQ,MAAM,WAAW,OAAO,GAClD,gBAAgB,QAAQ,IAAI,yBAAyB,KACrD,gBAAgB,QAAQ,IAAI,aAAa;AAC/C,SAAO,EAAE,gBAAgB,eAAe,cAAc;AACxD;AALgB;AAaT,SAAS,gBAAgB,YAA4B,UAAkB;AAC5E,MAAM,YAAY,SAAS,MAAM,GAAG,GAC9B,YAAY;AAAA;AAAA,IAEhB,GAAG,WAAW;AAAA;AAAA,IAEd,GAAG,WAAW,iBAAiB;AAAA,EACjC;AAEA,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,OAAO,QAAQ,SAAS;AAGlD,QAFmB,IAAI,SAAS,GAAG,KAAK,IAAI,SAAS,GAAG,GAExC;AACd,UAAM,WAAW,IAAI,MAAM,GAAG;AAC9B,UAAI,SAAS,WAAW,UAAU,OAAQ;AAE1C,eAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,YAAM,UAAU,SAAS,CAAC,GACpB,WAAW,UAAU,CAAC;AAI5B,YAFkB,QAAQ,SAAS,GAAG,KAAK,QAAQ,SAAS,GAAG,MAChD,SAAS,CAAC,IAAI,WACzB,SAAS,CAAC,MAAM,UAAU,CAAC,EAAG;AAElC,YAAI,MAAM,SAAS,SAAS,KACtB,CAAC,MAAM,SAAS,QAAQ;AAAG,iBAAO;AAAA,MAC1C;AAAA,IACF,OAAO;AACL,UAAI,QAAQ,SAAU;AACtB,aAAK,MAAM,SAAS,QAAQ,IACrB,OAD+B;AAAA,IAExC;AAGF,SAAO;AACT;AAnCgB;AA2ChB,eAAsB,cACpB,YACA,UACA;AACA,MAAI;AAEF,IAAI,iBAAiB,aAEnB,MAAM,WAAW,aAAa,WAAW;AAAA,MACvC,MAAM;AAAA,MACN,YAAY;AAAA,IACd,CAAC,IACQ,gBAAgB,aAGzB,MAAM,WAAW,WAAW,EAAE,MAAM,UAAU,YAAY,GAAM,CAAC,IAG1D;AAAA,MACL;AAAA,IACF;AAAA,EAEJ,QAAQ;AAAA,EAAC;AAGT,MAAM,gBAAgB,WAAW,YAAY,QAAQ;AACrD,SAAO,UAAQ,aAAa;AAC9B;AA3BsB;;;AChEtB,YAAYA,aAAY;AAGxB,SAAS,gBAAgB,QAAgB;AACvC,MAAI,QAAQ,IAAI,mCAAmC,IAAK;AAExD,MAAM,OAAO,mBAAmB;AAChC,MAAK,KAAK;AAMH,IAAK,KAAK,kBACR;AAAA,MACL,oCAAoC,MAAM;AAAA;AAAA;AAAA;AAAA,IAI5C;AAAA,MAXA,OAAM,IAAI;AAAA,IACR,gCAAgC,MAAM;AAAA;AAAA;AAAA,EAGxC;AASJ;AAlBS;AAuBF,IAAM,mBAAmB,OAAO,IAAI,mBAAmB;AAMvD,SAAS,cAAc,QAAoB;AAChD,UAAQ,IAAI,YAAY,kBAAkB,MAAM;AAClD;AAFgB;AAUT,SAAS,gBAAgB;AAC9B,yBAAgB,eAAe,GACxB,QAAQ,IAAI,YAAY,gBAAgB;AACjD;AAHgB;AAKT,SAAS,cAAc,QAAqB;AACjD,MAAM,WAAW,cAAc;AAC/B,SAAI,aACA,UAAQ,cAAc,MAAM,GACzB;AACT;AALgB;AAUT,IAAM,wBAAwB,OAAO,IAAI,wBAAwB;AAMjE,SAAS,mBAAmB,UAA2B;AAC5D,UAAQ,IAAI,YAAY,uBAAuB,QAAQ;AACzD;AAFgB;AAUT,SAAS,qBAAqB;AACnC,yBAAgB,oBAAoB,GAC7B,QAAQ,IAAI,YAAY,qBAAqB;AACtD;AAHgB;AAKT,SAAS,mBAAmB,UAA4B;AAC7D,MAAM,WAAW,mBAAmB;AACpC,SAAI,aACA,YAAU,mBAAmB,QAAQ,GAClC;AACT;AALgB;;;AFxET,SAAS,qBAAqB,YAA4B;AAC/D,UAAQ,IAAI,uBAAuB,OAAO,CAAC,GAE3C,QAAQ,IAAI,iCAAiC,OAAO,CAAC;AAErD,MAAM,aAAa,cAAc,WAAW,eAAe,UAAU,GAC/D,WAAW,mBAAmB,IAAI,gBAAgB,EAAE,UAAU,GAAK,CAAC,CAAC;AAI3E,MAFA,OAAO,QAAQ,IAAI,gCAEf,CAAC;AACH,WAAc,cAAM,gDAAgD;AACtE,MAAI,CAAC;AACH,WAAc,cAAM,qDAAqD;AAE3E,EAAO,cAAM,4CAA4C,GAEzD,WAAW,GAAG,WAAW,OAAO,SAAS,QAAQ,SAAS;AAExD,QAAM,WADM,IAAI,IAAI,QAAQ,OAAO,IAAI,WAAW,EAC7B;AACrB,QAAI,SAAS,WAAW,QAAQ,EAAG;AAEnC,QAAM,WAAW,gBAAgB,YAAY,QAAQ;AACrD,QAAI,CAAC;AACH,aAAO,cAAM,4CAA4C,QAAQ,EAAE,GAC5D,OAAO,QAAQ;AAGxB,QAAM,aAAa,MAAM,cAAc,YAAY,QAAQ;AAC3D,QAAI,CAAC;AACH,aAAO,cAAM,4CAA4C,QAAQ,EAAE,GAC5D,OAAO,QAAQ;AAGxB,QAAM,gBAAgB,YAAY,aAAa,UAAU;AACzD,WAAI,CAAC,iBAAiB,OAAO,iBAAkB,cACtC,cAAM,aAAa,QAAQ,mCAAmC,GAC9D,OAAO,QAAQ,KAGjB,SAAS,cAAc,SAAS,QAAQ,MAAM,CAAC,GAAG,MAAM;AAC7D,UAAM,UAAU,cAAc,GAAG,GAAG,QAAQ;AAC5C,MAAI,OAAO,WAAY,cAAY,EAAE,KAAK,SAAS,MAAM,QAAQ,CAAC;AAAA,IACpE,CAAC;AAAA,EACH,CAAC;AACH;AA7CgB;AAiDT,SAAS,qBAAyC;AACvD,uBAAqB,IAAI;AAC3B;AAFgB;;;AG5CT,SAAS,cAAc;AAC5B,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;AAJgB","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/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"]}
@@ -21,7 +21,7 @@ function resolveFilename(nextServer, pathname) {
21
21
  for (let i = 0; i < keyParts.length; i++) {
22
22
  let keyPart = keyParts[i], pathPart = pathParts[i];
23
23
  if (keyPart.includes("[") && keyPart.includes("]") && (keyParts[i] = pathPart), keyParts[i] !== pathParts[i]) break;
24
- if (i === keyParts.length - 1 && !path?.endsWith("/route"))
24
+ if (i === keyParts.length - 1 && path?.endsWith("/route"))
25
25
  return path;
26
26
  }
27
27
  } else {
@@ -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/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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-ws",
3
- "version": "1.1.0-next.2",
3
+ "version": "1.1.1",
4
4
  "type": "module",
5
5
  "description": "Add support for WebSockets in Next.js 13 app directory",
6
6
  "license": "MIT",
package/readme.md CHANGED
@@ -13,8 +13,6 @@
13
13
  <a href="https://discord.gg/vZQbMhwsKY"><img src="https://discordapp.com/api/guilds/829836158007115806/widget.png?style=shield" alt="discord shield"/></a>
14
14
  </div>
15
15
 
16
-
17
-
18
16
  ## 🤔 About
19
17
 
20
18
  Next WS (`next-ws`) is an advanced Next.js plugin that seamlessly integrates WebSocket server capabilities directly into routes located in the **app directory**. With Next WS, you no longer require a separate server for WebSocket functionality.
@@ -24,9 +22,16 @@ Next WS (`next-ws`) is an advanced Next.js plugin that seamlessly integrates Web
24
22
 
25
23
  This module is inspired by the now outdated `next-plugin-websocket`, if you are using an older version of Next.js, that module may work for you.
26
24
 
27
-
28
25
  ## 🏓 Table of Contents
29
26
 
27
+ - [📦 Installation](#-installation)
28
+ - [🚀 Usage](#-usage)
29
+ - [🌀 Examples](#-examples)
30
+ - [Creating a Socket](#creating-a-socket)
31
+ - [Using a Custom Server](#using-a-custom-server)
32
+ - [Accessing the WebSocket Server](#accessing-the-websocket-server)
33
+ - [Client-Side Utilities](#client-side-utilities)
34
+
30
35
  ## 📦 Installation
31
36
 
32
37
  Setting up a WebSocket server with Next WS involves patching your local Next.js installation. Next WS simplifies this process with a CLI command that automatically detects and patches your Next.js version, ensuring compatibility. Note that Next.js version 13.1.1 or higher is required.
@@ -64,7 +69,7 @@ export function SOCKET(
64
69
 
65
70
  In production, Next.js uses a worker process for routes, which can make it difficult to access the WebSocket server from outside a `SOCKET` handler, especially when the WebSocket server exists on the main process. For those needing to overcome this challenge or preferring a custom server setup, Next WS provides a solution.
66
71
 
67
- The `next-ws/server` module offers functions for setting the HTTP and WebSocket servers. You use these functions to tell Next WS to use your server instances instead of creating its own. This allows you to then access the instances you created yourself from anywhere in your app. Refer to the [example below](#-using-a-custom-server).
72
+ The `next-ws/server` module offers functions for setting the HTTP and WebSocket servers. You use these functions to tell Next WS to use your server instances instead of creating its own. This allows you to then access the instances you created yourself from anywhere in your app. Refer to the [example below](#using-a-custom-server).
68
73
 
69
74
  ## 🌀 Examples
70
75
 
@@ -138,7 +143,7 @@ app.prepare().then(() => {
138
143
  Along with setters, Next WS also provides getters for the HTTP and WebSocket servers. These functions can be used to access the servers from anywhere in your app.
139
144
 
140
145
  > [!IMPORTANT]
141
- > In order to use the `getWebSocketServer` and `getHttpServer` functions, you must be using a [custom server](https://nextjs.org/docs/advanced-features/custom-server), this is due to a limitation in Next.js. Refer to the [With a Custom Server](#-with-a-custom-server).
146
+ > In order to use the `getWebSocketServer` and `getHttpServer` functions, you must be using a [custom server](https://nextjs.org/docs/advanced-features/custom-server), this is due to a limitation in Next.js. Refer to the [With a Custom Server](#with-a-custom-server).
142
147
 
143
148
  ```ts
144
149
  // app/api/stats/route.ts