next-ws 1.2.0 → 2.0.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.
- package/README.md +100 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/cli.cjs +33 -0
- package/dist/client/index.cjs +64 -26
- package/dist/client/index.js +26 -17
- package/dist/server/index.cjs +157 -106
- package/dist/server/index.d.cts +2 -8
- package/dist/server/index.d.ts +2 -8
- package/dist/server/index.js +120 -78
- package/package.json +38 -15
- package/dist/chunk-PFW3KWBF.cjs +0 -14
- package/dist/chunk-PFW3KWBF.cjs.map +0 -1
- package/dist/chunk-WO25ABG2.js +0 -11
- package/dist/chunk-WO25ABG2.js.map +0 -1
- package/dist/client/index.cjs.map +0 -1
- package/dist/client/index.js.map +0 -1
- package/dist/server/index.cjs.map +0 -1
- package/dist/server/index.js.map +0 -1
- package/readme.md +0 -246
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-ws",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Add support for WebSockets in Next.js 13 app directory",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,26 +14,29 @@
|
|
|
14
14
|
"homepage": "https://github.com/apteryxxyz/next-ws#readme",
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
|
-
"url": "git+https://github.com/apteryxxyz/next-ws.git"
|
|
18
|
-
"directory": "packages/core"
|
|
17
|
+
"url": "git+https://github.com/apteryxxyz/next-ws.git"
|
|
19
18
|
},
|
|
20
19
|
"bugs": {
|
|
21
20
|
"url": "https://github.com/apteryxxyz/next-ws/issues"
|
|
22
21
|
},
|
|
22
|
+
"bin": {
|
|
23
|
+
"next-ws": "./dist/cli.cjs"
|
|
24
|
+
},
|
|
23
25
|
"files": [
|
|
24
26
|
"dist"
|
|
25
27
|
],
|
|
26
28
|
"exports": {
|
|
27
29
|
"./client": {
|
|
30
|
+
"types": "./dist/client/index.d.ts",
|
|
28
31
|
"import": "./dist/client/index.js",
|
|
29
|
-
"require": "./dist/client/index.cjs"
|
|
30
|
-
"types": "./dist/client/index.d.ts"
|
|
32
|
+
"require": "./dist/client/index.cjs"
|
|
31
33
|
},
|
|
32
34
|
"./server": {
|
|
35
|
+
"types": "./dist/server/index.d.ts",
|
|
33
36
|
"import": "./dist/server/index.js",
|
|
34
|
-
"require": "./dist/server/index.cjs"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
"require": "./dist/server/index.cjs"
|
|
38
|
+
},
|
|
39
|
+
"./package.json": "./package.json"
|
|
37
40
|
},
|
|
38
41
|
"peerDependencies": {
|
|
39
42
|
"next": ">=13.1.1",
|
|
@@ -41,15 +44,35 @@
|
|
|
41
44
|
"ws": "*"
|
|
42
45
|
},
|
|
43
46
|
"devDependencies": {
|
|
44
|
-
"@
|
|
45
|
-
"@
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
47
|
+
"@biomejs/biome": "^1.9.4",
|
|
48
|
+
"@changesets/changelog-git": "^0.2.0",
|
|
49
|
+
"@changesets/cli": "^2.27.12",
|
|
50
|
+
"@playwright/test": "^1.50.1",
|
|
51
|
+
"@types/minimist": "^1.2.5",
|
|
52
|
+
"@types/node": "^22.13.1",
|
|
53
|
+
"@types/react": "^19.0.8",
|
|
54
|
+
"@types/semver": "^7.5.8",
|
|
55
|
+
"@types/ws": "^8.5.14",
|
|
56
|
+
"chalk": "^5.4.1",
|
|
57
|
+
"husky": "^9.1.7",
|
|
58
|
+
"minimist": "^1.2.8",
|
|
59
|
+
"pinst": "^3.0.0",
|
|
60
|
+
"semver": "^7.7.1",
|
|
61
|
+
"tsup": "^8.3.6",
|
|
62
|
+
"typescript": "^5.7.3"
|
|
49
63
|
},
|
|
50
64
|
"scripts": {
|
|
65
|
+
"lint": "biome ci .",
|
|
66
|
+
"format": "biome check . --write",
|
|
51
67
|
"check": "tsc --noEmit",
|
|
52
|
-
"build": "
|
|
53
|
-
"dev": "
|
|
68
|
+
"build": "tsup",
|
|
69
|
+
"dev": "tsup --watch",
|
|
70
|
+
"test": "playwright test",
|
|
71
|
+
"_postinstall": "biome format package.json --write && pnpm build",
|
|
72
|
+
"change": "changeset",
|
|
73
|
+
"release:version": "changeset version && biome format package.json --write",
|
|
74
|
+
"release:snapshot:version": "changeset version --snapshot beta && biome format package.json --write",
|
|
75
|
+
"release:publish": "pnpm build && changeset publish",
|
|
76
|
+
"release:snapshot:publish": "pnpm build && changeset publish --tag beta --no-git-tag"
|
|
54
77
|
}
|
|
55
78
|
}
|
package/dist/chunk-PFW3KWBF.cjs
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __name = (target, value) => __defProp(target, "name", { value, configurable: !0 }), __require = /* @__PURE__ */ ((x) => typeof require < "u" ? require : typeof Proxy < "u" ? new Proxy(x, {
|
|
5
|
-
get: (a, b) => (typeof require < "u" ? require : a)[b]
|
|
6
|
-
}) : x)(function(x) {
|
|
7
|
-
if (typeof require < "u") return require.apply(this, arguments);
|
|
8
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
exports.__name = __name;
|
|
12
|
-
exports.__require = __require;
|
|
13
|
-
//# sourceMappingURL=out.js.map
|
|
14
|
-
//# sourceMappingURL=chunk-PFW3KWBF.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","sourcesContent":[]}
|
package/dist/chunk-WO25ABG2.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __name = (target, value) => __defProp(target, "name", { value, configurable: !0 }), __require = /* @__PURE__ */ ((x) => typeof require < "u" ? require : typeof Proxy < "u" ? new Proxy(x, {
|
|
3
|
-
get: (a, b) => (typeof require < "u" ? require : a)[b]
|
|
4
|
-
}) : x)(function(x) {
|
|
5
|
-
if (typeof require < "u") return require.apply(this, arguments);
|
|
6
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
export { __name, __require };
|
|
10
|
-
//# sourceMappingURL=out.js.map
|
|
11
|
-
//# sourceMappingURL=chunk-WO25ABG2.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","sourcesContent":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/context.tsx"],"names":["client"],"mappings":";;;;;AAGA,OAAO,WAAW;AAClB,SAAS,eAAe,YAAY,WAAW,eAAe;AAEvD,IAAM,mBAAmB,cAAgC,IAAI;AACpE,iBAAiB,cAAc;AACxB,IAAM,oBAAoB,iBAAiB;AAQ3C,SAAS,kBACd,GAQA;AACA,MAAM,SAAS,QAAQ,MAAM;AAC3B,QAAI,OAAO,SAAW,IAAa,QAAO;AAC1C,QAAMA,UAAS,IAAI,UAAU,EAAE,KAAK,EAAE,SAAS;AAC/C,WAAI,EAAE,eAAYA,QAAO,aAAa,EAAE,aACjCA;AAAA,EACT,GAAG,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,CAAC;AAErC,mBAAU,MAAM;AACd,QAAI,QAAQ,eAAe,UAAU;AACrC,aAAO,MAAM,OAAO,MAAM;AAAA,EAC5B,GAAG,CAAC,MAAM,CAAC,GAGT,oCAAC,iBAAiB,UAAjB,EAA0B,OAAO,UAC/B,EAAE,QACL;AAEJ;AA3BgB;AAiCT,SAAS,eAAe;AAC7B,MAAM,UAAU,WAAW,gBAAgB;AAC3C,MAAI,YAAY;AACd,UAAM,IAAI,MAAM,sDAAsD;AACxE,SAAO;AACT;AALgB","sourcesContent":["'use client';\n\n// biome-ignore lint/style/useImportType: <explanation>\nimport React from 'react';\nimport { createContext, useContext, useEffect, useMemo } from 'react';\n\nexport const WebSocketContext = createContext<WebSocket | null>(null);\nWebSocketContext.displayName = 'WebSocketContext';\nexport const WebSocketConsumer = WebSocketContext.Consumer;\n\n/**\n * Provides a WebSocket client to its children via context,\n * allowing for easy access to the WebSocket from anywhere in the app.\n * @param props WebSocket parameters and children.\n * @returns JSX Element\n */\nexport function WebSocketProvider(\n p: React.PropsWithChildren<{\n /** The URL for the WebSocket to connect to. */\n url: string;\n /** The subprotocols to use. */\n protocols?: string[] | string;\n /** The binary type to use. */\n binaryType?: BinaryType;\n }>,\n) {\n const client = useMemo(() => {\n if (typeof window === 'undefined') return null;\n const client = new WebSocket(p.url, p.protocols);\n if (p.binaryType) client.binaryType = p.binaryType;\n return client;\n }, [p.url, p.protocols, p.binaryType]);\n\n useEffect(() => {\n if (client?.readyState !== WebSocket.OPEN) return;\n return () => client.close();\n }, [client]);\n\n return (\n <WebSocketContext.Provider value={client}>\n {p.children}\n </WebSocketContext.Provider>\n );\n}\n\n/**\n * Access the websocket from anywhere in the app, so long as it's wrapped in a WebSocketProvider.\n * @returns WebSocket client when connected, null when disconnected.\n */\nexport function useWebSocket() {\n const context = useContext(WebSocketContext);\n if (context === undefined)\n throw new Error('useWebSocket must be used within a WebSocketProvider');\n return context;\n}\n"]}
|
package/dist/client/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/context.tsx"],"names":["client"],"mappings":";;;;;AAGA,OAAO,WAAW;AAClB,SAAS,eAAe,YAAY,WAAW,eAAe;AAEvD,IAAM,mBAAmB,cAAgC,IAAI;AACpE,iBAAiB,cAAc;AACxB,IAAM,oBAAoB,iBAAiB;AAQ3C,SAAS,kBACd,GAQA;AACA,MAAM,SAAS,QAAQ,MAAM;AAC3B,QAAI,OAAO,SAAW,IAAa,QAAO;AAC1C,QAAMA,UAAS,IAAI,UAAU,EAAE,KAAK,EAAE,SAAS;AAC/C,WAAI,EAAE,eAAYA,QAAO,aAAa,EAAE,aACjCA;AAAA,EACT,GAAG,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,CAAC;AAErC,mBAAU,MAAM;AACd,QAAI,QAAQ,eAAe,UAAU;AACrC,aAAO,MAAM,OAAO,MAAM;AAAA,EAC5B,GAAG,CAAC,MAAM,CAAC,GAGT,oCAAC,iBAAiB,UAAjB,EAA0B,OAAO,UAC/B,EAAE,QACL;AAEJ;AA3BgB;AAiCT,SAAS,eAAe;AAC7B,MAAM,UAAU,WAAW,gBAAgB;AAC3C,MAAI,YAAY;AACd,UAAM,IAAI,MAAM,sDAAsD;AACxE,SAAO;AACT;AALgB","sourcesContent":["'use client';\n\n// biome-ignore lint/style/useImportType: <explanation>\nimport React from 'react';\nimport { createContext, useContext, useEffect, useMemo } from 'react';\n\nexport const WebSocketContext = createContext<WebSocket | null>(null);\nWebSocketContext.displayName = 'WebSocketContext';\nexport const WebSocketConsumer = WebSocketContext.Consumer;\n\n/**\n * Provides a WebSocket client to its children via context,\n * allowing for easy access to the WebSocket from anywhere in the app.\n * @param props WebSocket parameters and children.\n * @returns JSX Element\n */\nexport function WebSocketProvider(\n p: React.PropsWithChildren<{\n /** The URL for the WebSocket to connect to. */\n url: string;\n /** The subprotocols to use. */\n protocols?: string[] | string;\n /** The binary type to use. */\n binaryType?: BinaryType;\n }>,\n) {\n const client = useMemo(() => {\n if (typeof window === 'undefined') return null;\n const client = new WebSocket(p.url, p.protocols);\n if (p.binaryType) client.binaryType = p.binaryType;\n return client;\n }, [p.url, p.protocols, p.binaryType]);\n\n useEffect(() => {\n if (client?.readyState !== WebSocket.OPEN) return;\n return () => client.close();\n }, [client]);\n\n return (\n <WebSocketContext.Provider value={client}>\n {p.children}\n </WebSocketContext.Provider>\n );\n}\n\n/**\n * Access the websocket from anywhere in the app, so long as it's wrapped in a WebSocketProvider.\n * @returns WebSocket client when connected, null when disconnected.\n */\nexport function useWebSocket() {\n const context = useContext(WebSocketContext);\n if (context === undefined)\n throw new Error('useWebSocket must be used within a WebSocketProvider');\n return context;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
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/dist/server/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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/readme.md
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
<div align='center'>
|
|
2
|
-
<h1><strong>Next WS</strong></h1>
|
|
3
|
-
<i>Add support for WebSockets in Next.js app directory</i><br>
|
|
4
|
-
<code>npm install next-ws ws</code>
|
|
5
|
-
</div>
|
|
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'>
|
|
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/B2rEQ9g2vf'><img src='https://discordapp.com/api/guilds/829836158007115806/widget.png?style=shield' alt='discord shield'/></a>
|
|
14
|
-
</div>
|
|
15
|
-
|
|
16
|
-
## 🤔 About
|
|
17
|
-
|
|
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.
|
|
19
|
-
|
|
20
|
-
> [!IMPORTANT]
|
|
21
|
-
> Next WS is designed for use in server-based environments. It is not suitable for serverless platforms like Vercel, where WebSocket servers are not supported. Furthermore, this plugin is built for the app directory and does not support the older pages directory.
|
|
22
|
-
|
|
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.
|
|
24
|
-
|
|
25
|
-
## 🏓 Table of Contents
|
|
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
|
-
|
|
35
|
-
## 📦 Installation
|
|
36
|
-
|
|
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.
|
|
38
|
-
|
|
39
|
-
```sh
|
|
40
|
-
npx next-ws-cli@latest patch
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
> [!NOTE]
|
|
44
|
-
> If at any point your local Next.js installation is changed or updated you will need to re-run the patch command.
|
|
45
|
-
|
|
46
|
-
After successfully patching Next.js, install the Next WS package along with its peer dependency, ws, into your project:
|
|
47
|
-
|
|
48
|
-
```sh
|
|
49
|
-
npm install next-ws ws
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## 🚀 Usage
|
|
53
|
-
|
|
54
|
-
Using WebSocket functionality in your Next.js application with Next WS is simple and requires no additional configuration. Simply export a `SOCKET` function from any route file. This function will be invoked whenever a client establishes a WebSocket connection to that specific route.
|
|
55
|
-
|
|
56
|
-
The `SOCKET` function receives three arguments: the WebSocket client instance, the incoming HTTP request - which you can use to get the URL path, query parameters, and headers - and the WebSocket server instance.
|
|
57
|
-
|
|
58
|
-
```ts
|
|
59
|
-
export function SOCKET(
|
|
60
|
-
client: import('ws').WebSocket,
|
|
61
|
-
request: import('http').IncomingMessage,
|
|
62
|
-
server: import('ws').WebSocketServer
|
|
63
|
-
) {
|
|
64
|
-
// ...
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### With a Custom Server
|
|
69
|
-
|
|
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.
|
|
71
|
-
|
|
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).
|
|
73
|
-
|
|
74
|
-
## 🌀 Examples
|
|
75
|
-
|
|
76
|
-
For more detailed examples, refer the [`examples` directory](https://github.com/apteryxxyz/next-ws/tree/main/examples).
|
|
77
|
-
|
|
78
|
-
### Creating a Socket
|
|
79
|
-
|
|
80
|
-
Creating an API route anywhere within the app directory and exporting a `SOCKET` function from it is all that is required. Below is an example of a simple echo server, which sends back any message it receives.
|
|
81
|
-
|
|
82
|
-
```ts
|
|
83
|
-
// app/api/ws/route.ts (can be any route file in the app directory)
|
|
84
|
-
|
|
85
|
-
export function SOCKET(
|
|
86
|
-
client: import('ws').WebSocket,
|
|
87
|
-
request: import('http').IncomingMessage,
|
|
88
|
-
server: import('ws').WebSocketServer
|
|
89
|
-
) {
|
|
90
|
-
console.log('A client connected');
|
|
91
|
-
|
|
92
|
-
client.on('message', (message) => {
|
|
93
|
-
console.log('Received message:', message);
|
|
94
|
-
client.send(message);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
client.on('close', () => {
|
|
98
|
-
console.log('A client disconnected');
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
### Using a Custom Server
|
|
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
|
-
|
|
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.
|
|
109
|
-
|
|
110
|
-
```ts
|
|
111
|
-
// server.js
|
|
112
|
-
|
|
113
|
-
const { setHttpServer, setWebSocketServer } = require('next-ws/server');
|
|
114
|
-
const { Server } = require('node:http');
|
|
115
|
-
const { parse } = require('node:url');
|
|
116
|
-
const next = require('next');
|
|
117
|
-
const { WebSocketServer } = require('ws');
|
|
118
|
-
|
|
119
|
-
const dev = process.env.NODE_ENV !== 'production';
|
|
120
|
-
const hostname = 'localhost';
|
|
121
|
-
const port = 3000;
|
|
122
|
-
|
|
123
|
-
const httpServer = new Server();
|
|
124
|
-
const webSocketServer = new WebSocketServer({ noServer: true });
|
|
125
|
-
// Tell Next WS about the HTTP and WebSocket servers before starting the custom server
|
|
126
|
-
setHttpServer(httpServer);
|
|
127
|
-
setWebSocketServer(webSocketServer);
|
|
128
|
-
|
|
129
|
-
const app = next({ dev, hostname, port, customServer: true });
|
|
130
|
-
const handle = app.getRequestHandler();
|
|
131
|
-
|
|
132
|
-
app.prepare().then(() => {
|
|
133
|
-
httpServer
|
|
134
|
-
.on('request', async (req, res) => {
|
|
135
|
-
const parsedUrl = parse(req.url, true);
|
|
136
|
-
await handle(req, res, parsedUrl);
|
|
137
|
-
})
|
|
138
|
-
.listen(port, () => {
|
|
139
|
-
console.log(` ▲ Ready on http://${hostname}:${port}`);
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Accessing the WebSocket Server
|
|
145
|
-
|
|
146
|
-
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.
|
|
147
|
-
|
|
148
|
-
> [!IMPORTANT]
|
|
149
|
-
> 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).
|
|
150
|
-
|
|
151
|
-
```ts
|
|
152
|
-
// app/api/stats/route.ts
|
|
153
|
-
|
|
154
|
-
import { getWebSocketServer } from 'next-ws/server';
|
|
155
|
-
// There is also a `getHttpServer` function available
|
|
156
|
-
|
|
157
|
-
export function GET() {
|
|
158
|
-
const wsServer = getWebSocketServer();
|
|
159
|
-
// Response with the number of connected clients
|
|
160
|
-
return Response.json({ count: wsServer.clients.size });
|
|
161
|
-
}
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
### Client-Side Utilities
|
|
165
|
-
|
|
166
|
-
To make it easier to connect to your new WebSocket server, Next WS also provides some client-side utilities. These are completely optional, you can use your own implementation if you wish.
|
|
167
|
-
|
|
168
|
-
```tsx
|
|
169
|
-
// layout.tsx
|
|
170
|
-
'use client';
|
|
171
|
-
|
|
172
|
-
import { WebSocketProvider } from 'next-ws/client';
|
|
173
|
-
|
|
174
|
-
export default function Layout({ children }: React.PropsWithChildren) {
|
|
175
|
-
return (
|
|
176
|
-
<html>
|
|
177
|
-
<body>
|
|
178
|
-
<WebSocketProvider url='ws://localhost:3000/api/ws'>
|
|
179
|
-
{children}
|
|
180
|
-
</WebSocketProvider>
|
|
181
|
-
</body>
|
|
182
|
-
</html>
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
The following is the props interface for the `WebSocketProvider` component, containing all the available options.
|
|
188
|
-
|
|
189
|
-
```ts
|
|
190
|
-
interface WebSocketProviderProps {
|
|
191
|
-
children: React.ReactNode;
|
|
192
|
-
|
|
193
|
-
/** The URL for the WebSocket to connect to. */
|
|
194
|
-
url: string;
|
|
195
|
-
/** The subprotocols to use. */
|
|
196
|
-
protocols?: string[] | string;
|
|
197
|
-
/** The binary type to use. */
|
|
198
|
-
binaryType?: BinaryType;
|
|
199
|
-
}
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
Now you can use the `useWebSocket` hook to get the WebSocket instance, and send and receive messages.
|
|
203
|
-
|
|
204
|
-
```tsx
|
|
205
|
-
// page.tsx
|
|
206
|
-
'use client';
|
|
207
|
-
|
|
208
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
209
|
-
import { useWebSocket } from 'next-ws/client';
|
|
210
|
-
|
|
211
|
-
export default function Page() {
|
|
212
|
-
const ws = useWebSocket();
|
|
213
|
-
// ^? WebSocket on the client, null on the server
|
|
214
|
-
|
|
215
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
216
|
-
const [message, setMessage] = useState<string | null>(null);
|
|
217
|
-
|
|
218
|
-
useEffect(() => {
|
|
219
|
-
async function onMessage(event: MessageEvent) {
|
|
220
|
-
const payload =
|
|
221
|
-
typeof event.data === 'string' ? event.data : await event.data.text();
|
|
222
|
-
const message = JSON.parse(payload) as Message;
|
|
223
|
-
setMessages((p) => [...p, message]);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
ws?.addEventListener('message', onMessage);
|
|
227
|
-
return () => ws?.removeEventListener('message', onMessage);
|
|
228
|
-
}, [ws]);
|
|
229
|
-
|
|
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
|
-
}
|
|
246
|
-
```
|