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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-ws",
3
- "version": "1.0.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
@@ -1,3 +1,3 @@
1
- export * from './utilities/patch';
2
- export type { SocketHandler } from './utilities/ws';
3
1
  export * from './setup';
2
+ export * from './utilities/patch';
3
+ export { SocketHandler, CustomHttpServer, CustomWsServer, } from './utilities/server';
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 type NextNodeServer from 'next/dist/server/next-server';
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 ws_1 = require("./utilities/ws");
7
+ const server_1 = require("./utilities/server");
8
8
  function setupWebSocketServer(nextServer) {
9
- const httpServer = (0, next_1.getHttpServer)(nextServer);
10
- const wsServer = (0, ws_1.getWsServer)();
11
- Log.ready('[next-ws] websocket server started successfully');
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(`[next-ws] could not find module for page ${pathname}`);
21
- return socket.destroy();
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(`[next-ws] could not find module for page ${pathname}`);
26
- return socket.destroy();
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(`[next-ws] ${pathname} does not export a SOCKET handler`);
31
- return socket.destroy();
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
- * Get the http.Server instance from the NextNodeServer.
7
- * @param nextServer The NextNodeServer instance.
8
- * @returns The http.Server instance.
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?: SocketHandler;
21
+ SOCKET?: unknown;
29
22
  };
30
23
  };
31
24
  }
@@ -1,34 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getPageModule = exports.resolvePathname = exports.getHttpServer = void 0;
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
- * Get the http.Server instance from the NextNodeServer.
10
- * @param nextServer The NextNodeServer instance.
11
- * @returns The http.Server instance.
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
- try {
79
- // In Next.js 14, hotReloader was removed and ensurePage was moved to NextNodeServer
80
- if ('hotReloader' in nextServer) {
81
- // @ts-expect-error - hotReloader only exists in Next.js 13
82
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
83
- await nextServer.hotReloader?.ensurePage({
84
- page: filename,
85
- clientOnly: false,
86
- });
87
- }
88
- else if ('ensurePage' in nextServer) {
89
- // ensurePage throws an error in production, so we need to catch it
90
- // @ts-expect-error - ensurePage is protected
91
- await nextServer.ensurePage({ page: filename, clientOnly: false });
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
- else {
94
- // Future-proofing
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;
@@ -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>;
@@ -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;