next-ws 1.0.0 → 1.0.1-experimental.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/package.json +3 -2
- package/server/index.d.ts +2 -2
- package/server/index.js +5 -1
- package/server/setup.d.ts +1 -1
- package/server/setup.js +14 -10
- package/server/utilities/next.d.ts +4 -11
- package/server/utilities/next.js +23 -41
- package/server/utilities/server.d.ts +33 -0
- package/server/utilities/server.js +50 -0
- package/server/utilities/ws.d.ts +0 -15
- package/server/utilities/ws.js +0 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-ws",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1-experimental.1",
|
|
4
4
|
"description": "Add support for WebSockets in Next.js 13 app directory",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"next",
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
"react": "^18.2.0",
|
|
49
49
|
"react-dom": "^18.2.0",
|
|
50
50
|
"rimraf": "^5.0.1",
|
|
51
|
-
"typescript": "<5.1.0"
|
|
51
|
+
"typescript": "<5.1.0",
|
|
52
|
+
"ws": "^8.14.2"
|
|
52
53
|
},
|
|
53
54
|
"eslintConfig": {
|
|
54
55
|
"root": true,
|
package/server/index.d.ts
CHANGED
package/server/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CustomWsServer = exports.CustomHttpServer = void 0;
|
|
3
4
|
const tslib_1 = require("tslib");
|
|
4
|
-
tslib_1.__exportStar(require("./utilities/patch"), exports);
|
|
5
5
|
tslib_1.__exportStar(require("./setup"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./utilities/patch"), exports);
|
|
7
|
+
var server_1 = require("./utilities/server");
|
|
8
|
+
Object.defineProperty(exports, "CustomHttpServer", { enumerable: true, get: function () { return server_1.CustomHttpServer; } });
|
|
9
|
+
Object.defineProperty(exports, "CustomWsServer", { enumerable: true, get: function () { return server_1.CustomWsServer; } });
|
package/server/setup.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import
|
|
1
|
+
import NextNodeServer from 'next/dist/server/next-server';
|
|
2
2
|
export declare function setupWebSocketServer(nextServer: NextNodeServer): void;
|
|
3
3
|
export declare function hookNextNodeServer(this: NextNodeServer): void;
|
package/server/setup.js
CHANGED
|
@@ -4,31 +4,34 @@ exports.hookNextNodeServer = exports.setupWebSocketServer = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const Log = tslib_1.__importStar(require("next/dist/build/output/log"));
|
|
6
6
|
const next_1 = require("./utilities/next");
|
|
7
|
-
const
|
|
7
|
+
const server_1 = require("./utilities/server");
|
|
8
8
|
function setupWebSocketServer(nextServer) {
|
|
9
|
-
const httpServer = (0,
|
|
10
|
-
const wsServer = (0,
|
|
11
|
-
Log.ready('[next-ws]
|
|
9
|
+
const httpServer = (0, server_1.useHttpServer)(nextServer);
|
|
10
|
+
const wsServer = (0, server_1.useWsServer)();
|
|
11
|
+
Log.ready('[next-ws] has started the WebSocket server');
|
|
12
12
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
13
13
|
httpServer.on('upgrade', async (request, socket, head) => {
|
|
14
14
|
const url = new URL(request.url ?? '', 'ws://next');
|
|
15
15
|
const pathname = url.pathname;
|
|
16
|
+
// Ignore Next.js internal requests (aka HMR)
|
|
16
17
|
if (pathname.startsWith('/_next'))
|
|
17
18
|
return;
|
|
19
|
+
// Resolve the pathname to a file system path (eg /about -> /about/route)
|
|
18
20
|
const fsPathname = (0, next_1.resolvePathname)(nextServer, pathname);
|
|
19
21
|
if (!fsPathname) {
|
|
20
|
-
Log.error(
|
|
21
|
-
return socket.
|
|
22
|
+
Log.error('[next-ws] could not resolve ${pathname} to a route');
|
|
23
|
+
return socket.end();
|
|
22
24
|
}
|
|
25
|
+
// Get the page module for the pathname (aka require('/about/route'))
|
|
23
26
|
const pageModule = await (0, next_1.getPageModule)(nextServer, fsPathname);
|
|
24
27
|
if (!pageModule) {
|
|
25
|
-
Log.error(
|
|
26
|
-
return socket.
|
|
28
|
+
Log.error('[next-ws] could not find module for page ${pathname}');
|
|
29
|
+
return socket.end();
|
|
27
30
|
}
|
|
28
31
|
const socketHandler = pageModule?.routeModule?.userland?.SOCKET;
|
|
29
32
|
if (!socketHandler || typeof socketHandler !== 'function') {
|
|
30
|
-
Log.error(
|
|
31
|
-
return socket.
|
|
33
|
+
Log.error('[next-ws] ${pathname} does not export a SOCKET handler');
|
|
34
|
+
return socket.end();
|
|
32
35
|
}
|
|
33
36
|
return wsServer.handleUpgrade(request, socket, head, (client, request) => void socketHandler(client, request, wsServer));
|
|
34
37
|
});
|
|
@@ -37,6 +40,7 @@ exports.setupWebSocketServer = setupWebSocketServer;
|
|
|
37
40
|
// Next WS versions below 0.2.0 used a different method of setup
|
|
38
41
|
// This remains for backwards compatibility, but may be removed in a future version
|
|
39
42
|
function hookNextNodeServer() {
|
|
43
|
+
Log.warnOnce('[next-ws] is using a deprecated method of hooking into Next.js, this may break in future versions');
|
|
40
44
|
setupWebSocketServer(this);
|
|
41
45
|
}
|
|
42
46
|
exports.hookNextNodeServer = hookNextNodeServer;
|
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { Server } from 'node:http';
|
|
3
1
|
import NextNodeServer from 'next/dist/server/next-server';
|
|
4
|
-
import type { SocketHandler } from './ws';
|
|
5
2
|
/**
|
|
6
|
-
*
|
|
7
|
-
* @
|
|
8
|
-
* @
|
|
9
|
-
*/
|
|
10
|
-
export declare function getHttpServer(nextServer: NextNodeServer): Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>;
|
|
11
|
-
/**
|
|
12
|
-
* Resolve a pathname to a page.
|
|
3
|
+
* Resolve a pathname to a page, or null if the page could not be resolved.
|
|
4
|
+
* @example resolvePathname(nextServer, '/about') // '/about/route'
|
|
5
|
+
* @example resolvePathname(nextServer, '/user/1') // '/user/[id]/route'
|
|
13
6
|
* @param nextServer The NextNodeServer instance.
|
|
14
7
|
* @param pathname The pathname to resolve.
|
|
15
8
|
* @returns The resolved page, or null if the page could not be resolved.
|
|
@@ -25,7 +18,7 @@ export declare function getPageModule(nextServer: NextNodeServer, filename: stri
|
|
|
25
18
|
export interface PageModule {
|
|
26
19
|
routeModule?: {
|
|
27
20
|
userland?: {
|
|
28
|
-
SOCKET?:
|
|
21
|
+
SOCKET?: unknown;
|
|
29
22
|
};
|
|
30
23
|
};
|
|
31
24
|
}
|
package/server/utilities/next.js
CHANGED
|
@@ -1,34 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getPageModule = exports.resolvePathname =
|
|
3
|
+
exports.getPageModule = exports.resolvePathname = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const node_http_1 = require("node:http");
|
|
6
5
|
const Log = tslib_1.__importStar(require("next/dist/build/output/log"));
|
|
7
|
-
const next_server_1 = tslib_1.__importDefault(require("next/dist/server/next-server"));
|
|
8
6
|
/**
|
|
9
|
-
*
|
|
10
|
-
* @
|
|
11
|
-
* @
|
|
12
|
-
*/
|
|
13
|
-
function getHttpServer(nextServer) {
|
|
14
|
-
if (!nextServer || !(nextServer instanceof next_server_1.default))
|
|
15
|
-
throw new Error('Next WS is missing access to the NextNodeServer');
|
|
16
|
-
// @ts-expect-error - serverOptions is protected
|
|
17
|
-
const httpServer = nextServer.serverOptions?.httpServer;
|
|
18
|
-
if (!httpServer || !(httpServer instanceof node_http_1.Server))
|
|
19
|
-
throw new Error('Next WS is missing access to the http.Server');
|
|
20
|
-
return httpServer;
|
|
21
|
-
}
|
|
22
|
-
exports.getHttpServer = getHttpServer;
|
|
23
|
-
/**
|
|
24
|
-
* Resolve a pathname to a page.
|
|
7
|
+
* Resolve a pathname to a page, or null if the page could not be resolved.
|
|
8
|
+
* @example resolvePathname(nextServer, '/about') // '/about/route'
|
|
9
|
+
* @example resolvePathname(nextServer, '/user/1') // '/user/[id]/route'
|
|
25
10
|
* @param nextServer The NextNodeServer instance.
|
|
26
11
|
* @param pathname The pathname to resolve.
|
|
27
12
|
* @returns The resolved page, or null if the page could not be resolved.
|
|
28
13
|
*/
|
|
29
14
|
function resolvePathname(nextServer, pathname) {
|
|
30
|
-
if (pathname.startsWith('/_next'))
|
|
31
|
-
return null;
|
|
32
15
|
const pathParts = pathname.split('/');
|
|
33
16
|
const appRoutes = {
|
|
34
17
|
// @ts-expect-error - appPathRoutes is protected
|
|
@@ -75,29 +58,28 @@ exports.resolvePathname = resolvePathname;
|
|
|
75
58
|
* @returns The page module.
|
|
76
59
|
*/
|
|
77
60
|
async function getPageModule(nextServer, filename) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
61
|
+
if (process.env['NODE_ENV'] !== 'production') {
|
|
62
|
+
try {
|
|
63
|
+
if ('hotReloader' in nextServer) {
|
|
64
|
+
// @ts-expect-error - hotReloader only exists in Next.js 13
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
66
|
+
await nextServer.hotReloader.ensurePage({
|
|
67
|
+
page: filename,
|
|
68
|
+
clientOnly: false,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else if ('ensurePage' in nextServer) {
|
|
72
|
+
// @ts-expect-error - ensurePage is protected
|
|
73
|
+
await nextServer.ensurePage({ page: filename, clientOnly: false });
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
Log.warnOnce('[next-ws] cannot find a way to ensure page, you may need to open routes in your browser first so Next.js compiles them');
|
|
77
|
+
}
|
|
92
78
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
Log.warnOnce('[next-ws] was unable to ensure page, you may need to open the route in your browser first so Next.js compiles it');
|
|
79
|
+
catch (error) {
|
|
80
|
+
Log.error('[next-ws] was unable to ensure page, you may need to open the route in your browser first so Next.js compiles it');
|
|
96
81
|
}
|
|
97
82
|
}
|
|
98
|
-
catch {
|
|
99
|
-
void 0;
|
|
100
|
-
}
|
|
101
83
|
// @ts-expect-error - getPagePath is protected
|
|
102
84
|
const builtPagePath = nextServer.getPagePath(filename);
|
|
103
85
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Server } from 'node:http';
|
|
3
|
+
import NextNodeServer from 'next/dist/server/next-server';
|
|
4
|
+
import { WebSocketServer } from 'ws';
|
|
5
|
+
export declare const CustomHttpServer: unique symbol;
|
|
6
|
+
/**
|
|
7
|
+
* Get the HTTP Server instance from the NextNodeServer.
|
|
8
|
+
* @param nextServer The NextNodeServer instance.
|
|
9
|
+
* @returns The HTTP Server instance.
|
|
10
|
+
*/
|
|
11
|
+
export declare function useHttpServer(nextServer: NextNodeServer): Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>;
|
|
12
|
+
export declare const CustomWsServer: unique symbol;
|
|
13
|
+
/**
|
|
14
|
+
* Create a WebSocketServer.
|
|
15
|
+
* @returns The WebSocketServer instance.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useWsServer(): WebSocketServer;
|
|
18
|
+
/** A function that handles a WebSocket connection. */
|
|
19
|
+
export type SocketHandler = (
|
|
20
|
+
/** The WebSocket client that connected. */
|
|
21
|
+
client: import('ws').WebSocket,
|
|
22
|
+
/** The HTTP request that initiated the WebSocket connection. */
|
|
23
|
+
request: import('http').IncomingMessage,
|
|
24
|
+
/** The WebSocket server. */
|
|
25
|
+
server: import('ws').WebSocketServer) => unknown;
|
|
26
|
+
declare global {
|
|
27
|
+
export namespace NodeJS {
|
|
28
|
+
interface Global {
|
|
29
|
+
[CustomHttpServer]: import('node:http').Server;
|
|
30
|
+
[CustomWsServer]: import('ws').WebSocketServer;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useWsServer = exports.CustomWsServer = exports.useHttpServer = exports.CustomHttpServer = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const node_http_1 = require("node:http");
|
|
6
|
+
const Log = tslib_1.__importStar(require("next/dist/build/output/log"));
|
|
7
|
+
const next_server_1 = tslib_1.__importDefault(require("next/dist/server/next-server"));
|
|
8
|
+
const ws_1 = require("ws");
|
|
9
|
+
// =============== HTTP Server ===============
|
|
10
|
+
exports.CustomHttpServer = Symbol('NextWS::CustomHttpServer');
|
|
11
|
+
/**
|
|
12
|
+
* Get the HTTP Server instance from the NextNodeServer.
|
|
13
|
+
* @param nextServer The NextNodeServer instance.
|
|
14
|
+
* @returns The HTTP Server instance.
|
|
15
|
+
*/
|
|
16
|
+
function useHttpServer(nextServer) {
|
|
17
|
+
// NextNodeServer is always required, so check for it before attempting to use custom server
|
|
18
|
+
if (!nextServer || !(nextServer instanceof next_server_1.default)) {
|
|
19
|
+
Log.error('[next-ws] could not find the NextNodeServer instance');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const existing = Reflect.get(globalThis, exports.CustomHttpServer);
|
|
23
|
+
if (existing) {
|
|
24
|
+
Log.warnOnce('[next-ws] is using a custom HTTP Server, this is experimental and may not work as expected');
|
|
25
|
+
return existing;
|
|
26
|
+
}
|
|
27
|
+
// @ts-expect-error - serverOptions is protected
|
|
28
|
+
const httpServer = nextServer.serverOptions?.httpServer;
|
|
29
|
+
if (!httpServer || !(httpServer instanceof node_http_1.Server)) {
|
|
30
|
+
Log.error('[next-ws] could not find the HTTP Server instance');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
return httpServer;
|
|
34
|
+
}
|
|
35
|
+
exports.useHttpServer = useHttpServer;
|
|
36
|
+
// =============== WebSocket Server ===============
|
|
37
|
+
exports.CustomWsServer = Symbol('NextWS::CustomWsServer');
|
|
38
|
+
/**
|
|
39
|
+
* Create a WebSocketServer.
|
|
40
|
+
* @returns The WebSocketServer instance.
|
|
41
|
+
*/
|
|
42
|
+
function useWsServer() {
|
|
43
|
+
const existing = Reflect.get(globalThis, exports.CustomWsServer);
|
|
44
|
+
if (existing) {
|
|
45
|
+
Log.warnOnce('[next-ws] is using a custom WebSocketServer, this is experimental and may not work as expected');
|
|
46
|
+
return existing;
|
|
47
|
+
}
|
|
48
|
+
return new ws_1.WebSocketServer({ noServer: true });
|
|
49
|
+
}
|
|
50
|
+
exports.useWsServer = useWsServer;
|
package/server/utilities/ws.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/// <reference types="ws" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
|
-
/** A function that handles a WebSocket connection. */
|
|
4
|
-
export type SocketHandler = (
|
|
5
|
-
/** The WebSocket client that connected. */
|
|
6
|
-
client: import('ws').WebSocket,
|
|
7
|
-
/** The HTTP request that initiated the WebSocket connection. */
|
|
8
|
-
request: import('http').IncomingMessage,
|
|
9
|
-
/** The WebSocket server. */
|
|
10
|
-
server: import('ws').WebSocketServer) => unknown;
|
|
11
|
-
/**
|
|
12
|
-
* Get the WebSocketServer instance.
|
|
13
|
-
* @returns The WebSocketServer instance.
|
|
14
|
-
*/
|
|
15
|
-
export declare function getWsServer(): import("ws").Server<typeof import("ws"), typeof import("http").IncomingMessage>;
|
package/server/utilities/ws.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getWsServer = void 0;
|
|
4
|
-
/* eslint-disable @typescript-eslint/consistent-type-imports */
|
|
5
|
-
const ws_1 = require("ws");
|
|
6
|
-
/**
|
|
7
|
-
* Get the WebSocketServer instance.
|
|
8
|
-
* @returns The WebSocketServer instance.
|
|
9
|
-
*/
|
|
10
|
-
function getWsServer() {
|
|
11
|
-
return new ws_1.WebSocketServer({ noServer: true });
|
|
12
|
-
}
|
|
13
|
-
exports.getWsServer = getWsServer;
|