next-ws 0.0.0-beta-20250822123253 → 0.0.0-beta-20250824054249
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 +13 -14
- package/dist/cli.cjs +1 -1
- package/dist/server/index.cjs +70 -82
- package/dist/server/index.d.ts +25 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,14 +50,14 @@ To set up a WebSocket server with `next-ws`, you need to patch your local Next.j
|
|
|
50
50
|
|
|
51
51
|
## 🚀 Usage
|
|
52
52
|
|
|
53
|
-
Using WebSocket connections in your Next.js app directory is simple with `next-ws`. You can handle WebSocket connections directly in your API routes via exported `
|
|
53
|
+
Using WebSocket connections in your Next.js app directory is simple with `next-ws`. You can handle WebSocket connections directly in your API routes via exported `UPGRADE` functions.
|
|
54
54
|
|
|
55
55
|
```js
|
|
56
|
-
export function
|
|
56
|
+
export function UPGRADE(
|
|
57
57
|
client: import('ws').WebSocket,
|
|
58
|
-
request: import('http').IncomingMessage,
|
|
59
58
|
server: import('ws').WebSocketServer,
|
|
60
|
-
|
|
59
|
+
request: import('next/server').NextRequest,
|
|
60
|
+
context: import('next-ws/server').RouteContext<'/api/ws'>,
|
|
61
61
|
) {
|
|
62
62
|
// ...
|
|
63
63
|
}
|
|
@@ -70,25 +70,24 @@ export function SOCKET(
|
|
|
70
70
|
|
|
71
71
|
### Echo Server
|
|
72
72
|
|
|
73
|
-
This example demonstrates a simple WebSocket echo server that sends back any message it receives. Create a new API route file anywhere in your app directory and export a `
|
|
73
|
+
This example demonstrates a simple WebSocket echo server that sends back any message it receives. Create a new API route file anywhere in your app directory and export a `UPGRADE` function to handle WebSocket connections:
|
|
74
74
|
|
|
75
75
|
```ts
|
|
76
76
|
// app/api/ws/route.ts (can be any route file in the app directory)
|
|
77
77
|
|
|
78
|
-
export function
|
|
79
|
-
client: import(
|
|
80
|
-
|
|
81
|
-
server: import("ws").WebSocketServer
|
|
78
|
+
export function UPGRADE(
|
|
79
|
+
client: import('ws').WebSocket,
|
|
80
|
+
server: import('ws').WebSocketServer
|
|
82
81
|
) {
|
|
83
|
-
console.log(
|
|
82
|
+
console.log('A client connected');
|
|
84
83
|
|
|
85
|
-
client.on(
|
|
86
|
-
console.log(
|
|
84
|
+
client.on('message', (message) => {
|
|
85
|
+
console.log('Received message:', message);
|
|
87
86
|
client.send(message);
|
|
88
87
|
});
|
|
89
88
|
|
|
90
|
-
client.
|
|
91
|
-
console.log(
|
|
89
|
+
client.once('close', () => {
|
|
90
|
+
console.log('A client disconnected');
|
|
92
91
|
});
|
|
93
92
|
}
|
|
94
93
|
```
|
package/dist/cli.cjs
CHANGED
|
@@ -27,7 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
var import_minimist = __toESM(require("minimist"));
|
|
28
28
|
|
|
29
29
|
// package.json
|
|
30
|
-
var version = "0.0.0-beta-
|
|
30
|
+
var version = "0.0.0-beta-20250824054249";
|
|
31
31
|
|
|
32
32
|
// src/commands/helpers/define.ts
|
|
33
33
|
function defineCommandGroup(definition) {
|
package/dist/server/index.cjs
CHANGED
|
@@ -39,66 +39,35 @@ __export(server_exports, {
|
|
|
39
39
|
module.exports = __toCommonJS(server_exports);
|
|
40
40
|
|
|
41
41
|
// src/server/persistent.ts
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
);
|
|
58
|
-
} else if (!meta.isCustomServer) {
|
|
59
|
-
logger.warnOnce(
|
|
60
|
-
`[next-ws] The function '${callerName}' was called without a custom server.
|
|
61
|
-
This could lead to unintended behaviour, especially if you're attempting to interact with the WebSocket server outside of a SOCKET handler.
|
|
62
|
-
Please note, while such configurations might function during development, they will fail in production. This is because Next.js employs a worker process for routing in production, which do not have access to the WebSocket server on the main process.
|
|
63
|
-
You can resolve this by using a custom server.`
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
var kHttpServer = Symbol.for("kHttpServer");
|
|
68
|
-
function getHttpServer() {
|
|
69
|
-
mainProcessOnly("getHttpServer");
|
|
70
|
-
return Reflect.get(globalThis, kHttpServer);
|
|
71
|
-
}
|
|
72
|
-
function setHttpServer(server) {
|
|
73
|
-
mainProcessOnly("setHttpServer");
|
|
74
|
-
Reflect.set(globalThis, kHttpServer, server);
|
|
75
|
-
return getHttpServer();
|
|
76
|
-
}
|
|
77
|
-
function useHttpServer(server) {
|
|
78
|
-
mainProcessOnly("useHttpServer");
|
|
79
|
-
const existing = getHttpServer();
|
|
80
|
-
if (existing) return existing;
|
|
81
|
-
return setHttpServer(server());
|
|
82
|
-
}
|
|
83
|
-
var kWebSocketServer = Symbol.for("kWebSocketServer");
|
|
84
|
-
function getWebSocketServer() {
|
|
85
|
-
mainProcessOnly("getWebSocketServer");
|
|
86
|
-
return Reflect.get(globalThis, kWebSocketServer);
|
|
87
|
-
}
|
|
88
|
-
function setWebSocketServer(server) {
|
|
89
|
-
mainProcessOnly("setWebSocketServer");
|
|
90
|
-
Reflect.set(globalThis, kWebSocketServer, server);
|
|
91
|
-
return getWebSocketServer();
|
|
92
|
-
}
|
|
93
|
-
function useWebSocketServer(server) {
|
|
94
|
-
mainProcessOnly("useWebSocketServer");
|
|
95
|
-
const existing = getWebSocketServer();
|
|
96
|
-
if (existing) return existing;
|
|
97
|
-
return setWebSocketServer(server());
|
|
42
|
+
function useGlobal(key) {
|
|
43
|
+
return [
|
|
44
|
+
function get() {
|
|
45
|
+
return Reflect.get(globalThis, key);
|
|
46
|
+
},
|
|
47
|
+
function set(value) {
|
|
48
|
+
return Reflect.set(globalThis, key, value);
|
|
49
|
+
},
|
|
50
|
+
function use(getter) {
|
|
51
|
+
const existing = Reflect.get(globalThis, key);
|
|
52
|
+
if (existing) return existing;
|
|
53
|
+
Reflect.set(globalThis, key, getter());
|
|
54
|
+
return Reflect.get(globalThis, key);
|
|
55
|
+
}
|
|
56
|
+
];
|
|
98
57
|
}
|
|
58
|
+
var [getHttpServer, setHttpServer, useHttpServer] = (
|
|
59
|
+
//
|
|
60
|
+
useGlobal(
|
|
61
|
+
Symbol.for("next-ws.http-server")
|
|
62
|
+
//
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
var [getWebSocketServer, setWebSocketServer, useWebSocketServer] = useGlobal(
|
|
66
|
+
Symbol.for("next-ws.websocket-server")
|
|
67
|
+
);
|
|
99
68
|
|
|
100
69
|
// src/server/setup.ts
|
|
101
|
-
var
|
|
70
|
+
var logger2 = __toESM(require("next/dist/build/output/log.js"));
|
|
102
71
|
var import_ws = require("ws");
|
|
103
72
|
|
|
104
73
|
// src/server/helpers/match.ts
|
|
@@ -142,7 +111,7 @@ function findMatchingRoute(nextServer, requestPathname) {
|
|
|
142
111
|
}
|
|
143
112
|
|
|
144
113
|
// src/server/helpers/module.ts
|
|
145
|
-
var
|
|
114
|
+
var logger = __toESM(require("next/dist/build/output/log.js"));
|
|
146
115
|
async function importRouteModule(nextServer, filePath) {
|
|
147
116
|
try {
|
|
148
117
|
if ("hotReloader" in nextServer) {
|
|
@@ -153,7 +122,7 @@ async function importRouteModule(nextServer, filePath) {
|
|
|
153
122
|
} else if ("ensurePage" in nextServer) {
|
|
154
123
|
await nextServer.ensurePage({ page: filePath, clientOnly: false });
|
|
155
124
|
} else {
|
|
156
|
-
|
|
125
|
+
logger.warnOnce(
|
|
157
126
|
"[next-ws] unable to ensure page, you may need to open the route in your browser first so Next.js compiles it"
|
|
158
127
|
);
|
|
159
128
|
}
|
|
@@ -168,61 +137,80 @@ async function importRouteModule(nextServer, filePath) {
|
|
|
168
137
|
}
|
|
169
138
|
}
|
|
170
139
|
|
|
140
|
+
// src/server/helpers/request.ts
|
|
141
|
+
var import_server = require("next/server");
|
|
142
|
+
function toNextRequest(message) {
|
|
143
|
+
const controller = new AbortController();
|
|
144
|
+
const headers = new Headers(message.headers);
|
|
145
|
+
const protocol = "encrypted" in message.socket ? "https" : "http";
|
|
146
|
+
const url = `${protocol}://${headers.get("host")}${message.url}`;
|
|
147
|
+
message.once("aborted", () => controller.abort());
|
|
148
|
+
return new import_server.NextRequest(url, {
|
|
149
|
+
method: message.method,
|
|
150
|
+
headers,
|
|
151
|
+
body: message.method === "GET" || message.method === "HEAD" ? void 0 : message,
|
|
152
|
+
signal: controller.signal,
|
|
153
|
+
referrer: headers.get("referer") || void 0
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
171
157
|
// src/server/setup.ts
|
|
172
158
|
function setupWebSocketServer(nextServer) {
|
|
173
|
-
process.env.NEXT_WS_MAIN_PROCESS = String(1);
|
|
174
|
-
process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK = String(1);
|
|
175
159
|
const httpServer = (
|
|
176
160
|
//
|
|
177
161
|
// @ts-expect-error - serverOptions is protected
|
|
178
162
|
useHttpServer(() => nextServer.serverOptions?.httpServer)
|
|
179
163
|
);
|
|
180
164
|
if (!httpServer)
|
|
181
|
-
return
|
|
165
|
+
return logger2.error("[next-ws] was not able to find the HTTP server");
|
|
182
166
|
const wsServer = (
|
|
183
167
|
//
|
|
184
168
|
useWebSocketServer(() => new import_ws.WebSocketServer({ noServer: true }))
|
|
185
169
|
);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
process.env.NEXT_WS_SKIP_ENVIRONMENT_CHECK = String(0);
|
|
189
|
-
logger3.ready("[next-ws] has started the WebSocket server");
|
|
190
|
-
const kInstalled = Symbol.for("kInstalled");
|
|
170
|
+
logger2.ready("[next-ws] has started the WebSocket server");
|
|
171
|
+
const kInstalled = Symbol.for("next-ws.http-server.attached");
|
|
191
172
|
if (Reflect.has(httpServer, kInstalled)) return;
|
|
192
173
|
Reflect.set(httpServer, kInstalled, true);
|
|
193
174
|
httpServer.on("upgrade", async (message, socket, head) => {
|
|
194
|
-
const
|
|
195
|
-
const pathname =
|
|
175
|
+
const request = toNextRequest(message);
|
|
176
|
+
const pathname = request.nextUrl.pathname;
|
|
196
177
|
if (pathname.includes("/_next")) return;
|
|
197
178
|
const route = findMatchingRoute(nextServer, pathname);
|
|
198
179
|
if (!route) {
|
|
199
|
-
|
|
180
|
+
logger2.error(`[next-ws] could not find route for page ${pathname}`);
|
|
200
181
|
return socket.end();
|
|
201
182
|
}
|
|
202
183
|
const module2 = await importRouteModule(nextServer, route.filename);
|
|
203
184
|
if (!module2) {
|
|
204
|
-
|
|
185
|
+
logger2.error(`[next-ws] could not import module for page ${pathname}`);
|
|
205
186
|
return socket.end();
|
|
206
187
|
}
|
|
188
|
+
const handleUpgrade = module2.userland.UPGRADE;
|
|
207
189
|
const handleSocket = module2.userland.SOCKET;
|
|
208
|
-
if (!handleSocket || typeof handleSocket !== "function") {
|
|
209
|
-
|
|
210
|
-
`[next-ws] route '${pathname}' does not export a valid 'SOCKET' handler`
|
|
211
|
-
);
|
|
190
|
+
if ((!handleUpgrade || typeof handleUpgrade !== "function") && (!handleSocket || typeof handleSocket !== "function")) {
|
|
191
|
+
logger2.error(`[next-ws] route '${pathname}' does not export a handler`);
|
|
212
192
|
return socket.end();
|
|
213
193
|
}
|
|
194
|
+
if (handleSocket)
|
|
195
|
+
logger2.warnOnce(
|
|
196
|
+
"DeprecationWarning: [next-ws] SOCKET is deprecated, use UPGRADE instead, see https://github.com/apteryxxyz/next-ws#-usage"
|
|
197
|
+
);
|
|
214
198
|
wsServer.handleUpgrade(message, socket, head, async (client) => {
|
|
215
199
|
wsServer.emit("connection", client, message);
|
|
216
200
|
try {
|
|
217
201
|
const context = { params: route.params };
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
202
|
+
if (handleUpgrade) {
|
|
203
|
+
await handleUpgrade(client, wsServer, request, context);
|
|
204
|
+
} else if (handleSocket) {
|
|
205
|
+
const handleClose = (
|
|
206
|
+
//
|
|
207
|
+
await handleSocket(client, message, wsServer, context)
|
|
208
|
+
);
|
|
209
|
+
if (typeof handleClose === "function")
|
|
210
|
+
client.once("close", () => handleClose());
|
|
211
|
+
}
|
|
224
212
|
} catch (cause) {
|
|
225
|
-
|
|
213
|
+
logger2.error(
|
|
226
214
|
`[next-ws] error in socket handler for '${pathname}'`,
|
|
227
215
|
cause
|
|
228
216
|
);
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import * as next_server from 'next/server';
|
|
2
|
+
import * as http from 'http';
|
|
3
|
+
import * as ws from 'ws';
|
|
3
4
|
import NextNodeServer from 'next/dist/server/next-server.js';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
/** @deprecated Prefer UPGRADE and {@link UpgradeHandler} */
|
|
7
|
+
type SocketHandler = (client: ws.WebSocket, request: http.IncomingMessage, server: ws.WebSocketServer, context: {
|
|
8
|
+
params: Record<string, string | string[]>;
|
|
9
|
+
}) => unknown;
|
|
10
|
+
type UpgradeHandler = (client: ws.WebSocket, server: ws.WebSocketServer, request: next_server.NextRequest, context: RouteContext<string>) => unknown;
|
|
11
|
+
|
|
12
|
+
type RouteParams<Path extends string> = Path extends `${infer Before}[[...${infer Param}]]${infer After}` ? RouteParams<Before> & {
|
|
13
|
+
[K in Param]?: string[];
|
|
14
|
+
} & RouteParams<After> : Path extends `${infer Before}[...${infer Param}]${infer After}` ? RouteParams<Before> & {
|
|
15
|
+
[K in Param]: string[];
|
|
16
|
+
} & RouteParams<After> : Path extends `${infer Before}[${infer Param}]${infer After}` ? RouteParams<Before> & {
|
|
17
|
+
[K in Param]: string;
|
|
18
|
+
} & RouteParams<After> : {};
|
|
19
|
+
type RouteContext<Path extends string> = {
|
|
20
|
+
params: Record<string, string | string[] | undefined> & RouteParams<Path> & RouteParams<Path>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
declare const getHttpServer: () => http.Server<typeof http.IncomingMessage, typeof http.ServerResponse> | undefined;
|
|
24
|
+
declare const setHttpServer: (value: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>) => boolean;
|
|
25
|
+
declare const getWebSocketServer: () => ws.WebSocketServer | undefined;
|
|
26
|
+
declare const setWebSocketServer: (value: ws.WebSocketServer) => boolean;
|
|
9
27
|
|
|
10
28
|
declare function setupWebSocketServer(nextServer: NextNodeServer): void;
|
|
11
29
|
|
|
12
|
-
export { getHttpServer, getWebSocketServer, setHttpServer, setWebSocketServer, setupWebSocketServer };
|
|
30
|
+
export { type RouteContext, type SocketHandler, type UpgradeHandler, getHttpServer, getWebSocketServer, setHttpServer, setWebSocketServer, setupWebSocketServer };
|