next-ws 0.2.7 → 1.0.1-canary.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
  <h1><strong>Next WS</strong></h1>
3
- <i>Add support for WebSockets in Next.js 13 app directory</i><br>
3
+ <i>Add support for WebSockets in Next.js app directory</i><br>
4
4
  <code>npm install next-ws ws</code>
5
5
  </div>
6
6
 
@@ -17,14 +17,10 @@
17
17
 
18
18
  ## 🤔 About
19
19
 
20
- Next WS (`next-ws`) is an advanced Next.js **13** plugin designed to seamlessly integrate WebSocket server functionality into API routes within the **app directory**. With Next WS, you no longer require a separate server for WebSocket functionality.
21
-
22
- > The last supported version of Next.js is 13.4.12, read more [here](https://github.com/apteryxxyz/next-ws/issues/6).
20
+ Next WS (`next-ws`) is an advanced Next.js plugin designed to seamlessly integrate WebSocket server functionality into API routes within the **app directory**. With Next WS, you no longer require a separate server for WebSocket functionality.
23
21
 
24
22
  It's **important** to note that this module can only be used when working with a server. Unfortunately, in serverless environments like Vercel, WebSocket servers cannot be used. Additionally, this module was built for the app directory and is incompatible with the older pages directory.
25
23
 
26
- Next WS is still pre its 1.0 release, and as such, things may change. If you find any bugs or have any suggestions, please open an issue on the GitHub repository.
27
-
28
24
  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.
29
25
 
30
26
  ---
@@ -138,8 +134,6 @@ export default function Layout() {
138
134
  }
139
135
  ```
140
136
 
141
- To make it easier to connect to your new WebSocker server, Next WS also provides some client-side utilities. These are completely optional, you can use your own implementation if you wish.
142
-
143
137
  The following is the props interface for the `WebSocketProvider` component, containing all the available options.
144
138
 
145
139
  ```ts
@@ -155,6 +149,8 @@ interface WebSocketProviderProps {
155
149
  }
156
150
  ```
157
151
 
152
+ Now you can use the `useWebSocket` hook to get the WebSocket instance, and send and receive messages.
153
+
158
154
  ```tsx
159
155
  // page.tsx
160
156
  'use client';
package/client/context.js CHANGED
@@ -26,7 +26,7 @@ function WebSocketProvider({ children, url, protocols, binaryType, }) {
26
26
  return;
27
27
  return () => instance.close();
28
28
  }, []);
29
- return (0, jsx_runtime_1.jsx)(exports.WebSocketContext.Provider, { value: instance, children: children });
29
+ return ((0, jsx_runtime_1.jsx)(exports.WebSocketContext.Provider, { value: instance, children: children }));
30
30
  }
31
31
  exports.WebSocketProvider = WebSocketProvider;
32
32
  exports.WebSocketConsumer = exports.WebSocketContext.Consumer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-ws",
3
- "version": "0.2.7",
3
+ "version": "1.0.1-canary.1",
4
4
  "description": "Add support for WebSockets in Next.js 13 app directory",
5
5
  "keywords": [
6
6
  "next",
@@ -43,12 +43,13 @@
43
43
  "@types/react-dom": "^18",
44
44
  "@types/ws": "^8",
45
45
  "eslint": "^8.43.0",
46
- "next": "^13.4.6",
46
+ "next": "^14.0.1",
47
47
  "prettier": "^2.8.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,33 +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
- const next_server_1 = tslib_1.__importDefault(require("next/dist/server/next-server"));
5
+ const Log = tslib_1.__importStar(require("next/dist/build/output/log"));
7
6
  /**
8
- * Get the http.Server instance from the NextNodeServer.
9
- * @param nextServer The NextNodeServer instance.
10
- * @returns The http.Server instance.
11
- */
12
- function getHttpServer(nextServer) {
13
- if (!nextServer || !(nextServer instanceof next_server_1.default))
14
- throw new Error('Next WS is missing access to the NextNodeServer');
15
- // @ts-expect-error - serverOptions is protected
16
- const httpServer = nextServer.serverOptions?.httpServer;
17
- if (!httpServer || !(httpServer instanceof node_http_1.Server))
18
- throw new Error('Next WS is missing access to the http.Server');
19
- return httpServer;
20
- }
21
- exports.getHttpServer = getHttpServer;
22
- /**
23
- * 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'
24
10
  * @param nextServer The NextNodeServer instance.
25
11
  * @param pathname The pathname to resolve.
26
12
  * @returns The resolved page, or null if the page could not be resolved.
27
13
  */
28
14
  function resolvePathname(nextServer, pathname) {
29
- if (pathname.startsWith('/_next'))
30
- return null;
31
15
  const pathParts = pathname.split('/');
32
16
  const appRoutes = {
33
17
  // @ts-expect-error - appPathRoutes is protected
@@ -74,12 +58,28 @@ exports.resolvePathname = resolvePathname;
74
58
  * @returns The page module.
75
59
  */
76
60
  async function getPageModule(nextServer, filename) {
77
- // @ts-expect-error - hotReloader is private
78
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
79
- await nextServer.hotReloader?.ensurePage({
80
- page: filename,
81
- clientOnly: false,
82
- });
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
+ }
78
+ }
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');
81
+ }
82
+ }
83
83
  // @ts-expect-error - getPagePath is protected
84
84
  const builtPagePath = nextServer.getPagePath(filename);
85
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,49 @@
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
+ const existing = Reflect.get(globalThis, exports.CustomHttpServer);
18
+ if (existing) {
19
+ Log.warnOnce('[next-ws] is using a custom HTTP Server, this is experimental and may not work as expected');
20
+ return existing;
21
+ }
22
+ if (!nextServer || !(nextServer instanceof next_server_1.default)) {
23
+ Log.error('[next-ws] could not find the NextNodeServer instance');
24
+ process.exit(1);
25
+ }
26
+ // @ts-expect-error - serverOptions is protected
27
+ const httpServer = nextServer.serverOptions?.httpServer;
28
+ if (!httpServer || !(httpServer instanceof node_http_1.Server)) {
29
+ Log.error('[next-ws] could not find the HTTP Server instance');
30
+ process.exit(1);
31
+ }
32
+ return httpServer;
33
+ }
34
+ exports.useHttpServer = useHttpServer;
35
+ // =============== WebSocket Server ===============
36
+ exports.CustomWsServer = Symbol('NextWS::CustomWsServer');
37
+ /**
38
+ * Create a WebSocketServer.
39
+ * @returns The WebSocketServer instance.
40
+ */
41
+ function useWsServer() {
42
+ const existing = Reflect.get(globalThis, exports.CustomWsServer);
43
+ if (existing) {
44
+ Log.warnOnce('[next-ws] is using a custom WebSocketServer, this is experimental and may not work as expected');
45
+ return existing;
46
+ }
47
+ return new ws_1.WebSocketServer({ noServer: true });
48
+ }
49
+ 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;