next-ws 0.1.3 → 0.1.4
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 +7 -2
- package/package.json +8 -8
- package/client.d.ts +0 -20
- package/client.js +0 -40
- package/patch.d.ts +0 -1
- package/patch.js +0 -50
- package/server.d.ts +0 -3
- package/server.js +0 -75
package/README.md
CHANGED
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
<br>
|
|
11
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
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/vZQbMhwsKY"><img src="https://discordapp.com/api/guilds/829836158007115806/widget.png?style=shield" alt="
|
|
13
|
+
<a href="https://discord.gg/vZQbMhwsKY"><img src="https://discordapp.com/api/guilds/829836158007115806/widget.png?style=shield" alt="discord shield"/></a>
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
|
+
---
|
|
17
|
+
|
|
16
18
|
## 🤔 About
|
|
17
19
|
|
|
18
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,6 +23,8 @@ It's important to note that this module can only be used when working with a ser
|
|
|
21
23
|
|
|
22
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.
|
|
23
25
|
|
|
26
|
+
---
|
|
27
|
+
|
|
24
28
|
## 🏓 Table of Contents
|
|
25
29
|
|
|
26
30
|
- [🤔 About](#-about)
|
|
@@ -30,7 +34,7 @@ This module is inspired by the now outdated `next-plugin-websocket`, if you are
|
|
|
30
34
|
- [🌀 Example](#-example)
|
|
31
35
|
- [📁 Server](#-server)
|
|
32
36
|
- [📁 Client](#-client)
|
|
33
|
-
|
|
37
|
+
|
|
34
38
|
---
|
|
35
39
|
|
|
36
40
|
## 📦 Installation
|
|
@@ -61,6 +65,7 @@ The `SOCKET` function receives two arguments: the WebSocket client and the HTTP
|
|
|
61
65
|
export function SOCKET(
|
|
62
66
|
client: import('ws').WebSocket,
|
|
63
67
|
request: import('http').IncomingMessage,
|
|
68
|
+
server: import('ws').WebSocketServer,
|
|
64
69
|
) {
|
|
65
70
|
// ...
|
|
66
71
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-ws",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Add support for WebSockets in Next.js 13 app directory",
|
|
5
5
|
"packageManager": "yarn@3.6.0",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,18 +13,18 @@
|
|
|
13
13
|
"bugs": {
|
|
14
14
|
"url": "https://github.com/apteryxxyz/next-ws/issues"
|
|
15
15
|
},
|
|
16
|
-
"files": ["
|
|
16
|
+
"files": ["*.js", "*.d.ts"],
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsc",
|
|
19
|
-
"clean": "rimraf
|
|
19
|
+
"clean": "rimraf client server common",
|
|
20
20
|
"lint": "eslint --ext .ts,.tsx src",
|
|
21
21
|
"format": "prettier --write src/**/*.{ts,tsx} && eslint --fix --ext .ts,.tsx src/",
|
|
22
|
-
"postinstall": "node -e \"try{require('./patch.js')}catch{}\""
|
|
22
|
+
"postinstall": "node -e \"try{require('./common/patch.js')}catch{}\""
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@babel/generator": "^7.22.
|
|
26
|
-
"@babel/parser": "^7.22.
|
|
27
|
-
"@babel/template": "^7.
|
|
25
|
+
"@babel/generator": "^7.22.5",
|
|
26
|
+
"@babel/parser": "^7.22.5",
|
|
27
|
+
"@babel/template": "^7.22.5"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"next": "*",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@typescript-eslint/eslint-plugin": "^5.59.8",
|
|
45
45
|
"@typescript-eslint/parser": "^5.59.8",
|
|
46
46
|
"eslint": "^8.41.0",
|
|
47
|
-
"eslint-config-apteryx": "^2.1.
|
|
47
|
+
"eslint-config-apteryx": "^2.1.7",
|
|
48
48
|
"eslint-config-prettier": "^8.8.0",
|
|
49
49
|
"eslint-plugin-import": "^2.27.5",
|
|
50
50
|
"eslint-plugin-jsdoc": "^46.1.0",
|
package/client.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
|
-
declare const WebSocketContext: import("react").Context<WebSocket | null>;
|
|
3
|
-
/**
|
|
4
|
-
* Provides a WebSocket instance to its children via context,
|
|
5
|
-
* allowing for easy access to the websocket from anywhere in the app
|
|
6
|
-
* @param props WebSocket parameters and children
|
|
7
|
-
* @returns JSX Element
|
|
8
|
-
*/
|
|
9
|
-
declare function WebSocketProvider({ children, url, protocols, }: {
|
|
10
|
-
children: React.ReactNode;
|
|
11
|
-
url: string;
|
|
12
|
-
protocols?: string[] | string;
|
|
13
|
-
}): import("react/jsx-runtime").JSX.Element;
|
|
14
|
-
declare const WebSocketConsumer: import("react").Consumer<WebSocket | null>;
|
|
15
|
-
/**
|
|
16
|
-
* Access the websocket from anywhere in the app, so long as it's wrapped in a WebSocketProvider
|
|
17
|
-
* @returns WebSocket on the client, null on the server
|
|
18
|
-
*/
|
|
19
|
-
declare function useWebSocket(): WebSocket | null;
|
|
20
|
-
export { WebSocketContext, WebSocketProvider, WebSocketConsumer, useWebSocket };
|
package/client.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useWebSocket = exports.WebSocketConsumer = exports.WebSocketProvider = exports.WebSocketContext = void 0;
|
|
4
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
const react_1 = require("react");
|
|
6
|
-
const WebSocketContext = (0, react_1.createContext)(null);
|
|
7
|
-
exports.WebSocketContext = WebSocketContext;
|
|
8
|
-
WebSocketContext.displayName = 'WebSocketContext';
|
|
9
|
-
/**
|
|
10
|
-
* Provides a WebSocket instance to its children via context,
|
|
11
|
-
* allowing for easy access to the websocket from anywhere in the app
|
|
12
|
-
* @param props WebSocket parameters and children
|
|
13
|
-
* @returns JSX Element
|
|
14
|
-
*/
|
|
15
|
-
function WebSocketProvider({ children, url, protocols, }) {
|
|
16
|
-
const isBrowser = typeof window !== 'undefined';
|
|
17
|
-
const ws = (0, react_1.useMemo)(() => (isBrowser ? new WebSocket(url, protocols) : null), [isBrowser, url, protocols]);
|
|
18
|
-
(0, react_1.useEffect)(() => {
|
|
19
|
-
return () => {
|
|
20
|
-
if (ws && ws.readyState !== WebSocket.CLOSED)
|
|
21
|
-
ws.close();
|
|
22
|
-
};
|
|
23
|
-
}, []);
|
|
24
|
-
return (0, jsx_runtime_1.jsx)(WebSocketContext.Provider, Object.assign({ value: ws }, { children: children }));
|
|
25
|
-
}
|
|
26
|
-
exports.WebSocketProvider = WebSocketProvider;
|
|
27
|
-
const WebSocketConsumer = WebSocketContext.Consumer;
|
|
28
|
-
exports.WebSocketConsumer = WebSocketConsumer;
|
|
29
|
-
/**
|
|
30
|
-
* Access the websocket from anywhere in the app, so long as it's wrapped in a WebSocketProvider
|
|
31
|
-
* @returns WebSocket on the client, null on the server
|
|
32
|
-
*/
|
|
33
|
-
function useWebSocket() {
|
|
34
|
-
const context = (0, react_1.useContext)(WebSocketContext);
|
|
35
|
-
if (context === undefined)
|
|
36
|
-
throw new Error('useWebSocket must be used within a WebSocketProvider');
|
|
37
|
-
return context;
|
|
38
|
-
}
|
|
39
|
-
exports.useWebSocket = useWebSocket;
|
|
40
|
-
//# sourceMappingURL=client.js.map
|
package/patch.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/patch.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const tslib_1 = require("tslib");
|
|
4
|
-
/* eslint-disable no-template-curly-in-string */
|
|
5
|
-
const fs = tslib_1.__importStar(require("node:fs/promises"));
|
|
6
|
-
const generator_1 = tslib_1.__importDefault(require("@babel/generator"));
|
|
7
|
-
const parser = tslib_1.__importStar(require("@babel/parser"));
|
|
8
|
-
const template_1 = tslib_1.__importDefault(require("@babel/template"));
|
|
9
|
-
void main();
|
|
10
|
-
async function main() {
|
|
11
|
-
await patchNextNodeServer();
|
|
12
|
-
await patchNextTypesPlugin();
|
|
13
|
-
}
|
|
14
|
-
const mod = template_1.default.expression.ast `require("next-ws/server")`;
|
|
15
|
-
// Add `require('next-ws/server').hookNextNodeServer.call(this)` to the
|
|
16
|
-
// constructor of `NextNodeServer` in `next/dist/server/next-server.js`
|
|
17
|
-
async function patchNextNodeServer() {
|
|
18
|
-
const filePath = require.resolve('next/dist/server/next-server');
|
|
19
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
20
|
-
const ast = parser.parse(content);
|
|
21
|
-
const classDeclaration = ast.program.body.find(node => node.type === 'ClassDeclaration' &&
|
|
22
|
-
node.id.name === 'NextNodeServer');
|
|
23
|
-
if (!classDeclaration)
|
|
24
|
-
return;
|
|
25
|
-
const constructorMethod = classDeclaration.body.body.find(node => node.type === 'ClassMethod' && node.kind === 'constructor');
|
|
26
|
-
if (!constructorMethod)
|
|
27
|
-
return;
|
|
28
|
-
const statement = template_1.default.statement
|
|
29
|
-
.ast `${mod}.hookNextNodeServer.call(this)`;
|
|
30
|
-
const expression = (0, generator_1.default)(statement).code;
|
|
31
|
-
// Ensure the statement is not already in the constructor
|
|
32
|
-
const existingStatement = constructorMethod.body.body //
|
|
33
|
-
.some(state => (0, generator_1.default)(state).code === expression);
|
|
34
|
-
if (!existingStatement)
|
|
35
|
-
constructorMethod.body.body.push(statement);
|
|
36
|
-
await fs.writeFile(filePath, (0, generator_1.default)(ast).code);
|
|
37
|
-
}
|
|
38
|
-
// Add `SOCKET?: Function` to the page module interface check field thing in
|
|
39
|
-
// `next/dist/build/webpack/plugins/next-types-plugin.js`
|
|
40
|
-
async function patchNextTypesPlugin() {
|
|
41
|
-
const filePath = require.resolve('next/dist/build/webpack/plugins/next-types-plugin.js');
|
|
42
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
43
|
-
if (content.includes('SOCKET?: Function'))
|
|
44
|
-
return;
|
|
45
|
-
const toFind = '.map((method)=>`${method}?: Function`).join("\\n ")';
|
|
46
|
-
const replaceWith = `${toFind} + "; SOCKET?: Function"`;
|
|
47
|
-
const newContent = content.replace(toFind, replaceWith);
|
|
48
|
-
await fs.writeFile(filePath, newContent);
|
|
49
|
-
}
|
|
50
|
-
//# sourceMappingURL=patch.js.map
|
package/server.d.ts
DELETED
package/server.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.hookNextNodeServer = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const node_http_1 = require("node:http");
|
|
6
|
-
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
7
|
-
const Log = tslib_1.__importStar(require("next/dist/build/output/log"));
|
|
8
|
-
const next_server_1 = tslib_1.__importDefault(require("next/dist/server/next-server"));
|
|
9
|
-
const is_dynamic_1 = require("next/dist/shared/lib/router/utils/is-dynamic");
|
|
10
|
-
const ws_1 = require("ws");
|
|
11
|
-
const openSockets = new Set();
|
|
12
|
-
let existingWebSocketServer;
|
|
13
|
-
function hookNextNodeServer() {
|
|
14
|
-
var _a;
|
|
15
|
-
if (!this || !(this instanceof next_server_1.default))
|
|
16
|
-
throw new Error("[next-ws] 'this' of hookNextNodeServer is not a NextNodeServer");
|
|
17
|
-
const server = (_a = this.serverOptions) === null || _a === void 0 ? void 0 : _a.httpServer;
|
|
18
|
-
if (!server || !(server instanceof node_http_1.Server))
|
|
19
|
-
throw new Error("[next-ws] Failed to find NextNodeServer's HTTP server");
|
|
20
|
-
if (existingWebSocketServer)
|
|
21
|
-
return;
|
|
22
|
-
const wss = new ws_1.WebSocketServer({ noServer: true });
|
|
23
|
-
Log.ready('[next-ws] loaded successfully');
|
|
24
|
-
existingWebSocketServer = wss;
|
|
25
|
-
server.on('upgrade', async (request, socket, head) => {
|
|
26
|
-
var _a;
|
|
27
|
-
const url = new URL((_a = request.url) !== null && _a !== void 0 ? _a : '', 'http://next-ws');
|
|
28
|
-
// Ignore requests to Next.js' own internal files
|
|
29
|
-
if (url.pathname.startsWith('/_next'))
|
|
30
|
-
return;
|
|
31
|
-
// Attempt to find a matching page
|
|
32
|
-
const pathname = await isPageFound.call(this, url.pathname);
|
|
33
|
-
if (!pathname)
|
|
34
|
-
return;
|
|
35
|
-
const internalPathname = node_path_1.default
|
|
36
|
-
.join(pathname, 'route')
|
|
37
|
-
.replaceAll(node_path_1.default.sep, '/');
|
|
38
|
-
// Ensure the page is built
|
|
39
|
-
// @ts-expect-error HotReloader is private
|
|
40
|
-
await this.hotReloader.ensurePage({
|
|
41
|
-
page: internalPathname,
|
|
42
|
-
clientOnly: false,
|
|
43
|
-
});
|
|
44
|
-
let builtPagePath;
|
|
45
|
-
try {
|
|
46
|
-
builtPagePath = this.getPagePath(internalPathname);
|
|
47
|
-
}
|
|
48
|
-
catch (_b) {
|
|
49
|
-
return Log.error(`[next-ws] failed to get page ${pathname}`);
|
|
50
|
-
}
|
|
51
|
-
const pageModule = await require(builtPagePath);
|
|
52
|
-
// Equates to the exported "SOCKET" function in the route file
|
|
53
|
-
const socketHandler = pageModule.routeModule.userland.SOCKET;
|
|
54
|
-
if (!socketHandler || typeof socketHandler !== 'function')
|
|
55
|
-
return Log.error(`[next-ws] failed to find SOCKET handler for page ${pathname}`);
|
|
56
|
-
wss.handleUpgrade(request, socket, head, socketHandler);
|
|
57
|
-
if (this.serverOptions.dev) {
|
|
58
|
-
openSockets.add(socket);
|
|
59
|
-
socket.on('close', () => openSockets.delete(socket));
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
exports.hookNextNodeServer = hookNextNodeServer;
|
|
64
|
-
async function isPageFound(pathname) {
|
|
65
|
-
var _a;
|
|
66
|
-
if (!(0, is_dynamic_1.isDynamicRoute)(pathname) && (await this.hasPage(pathname)))
|
|
67
|
-
return pathname;
|
|
68
|
-
for (const route of (_a = this.dynamicRoutes) !== null && _a !== void 0 ? _a : []) {
|
|
69
|
-
const params = route.match(pathname) || undefined;
|
|
70
|
-
if (params)
|
|
71
|
-
return route.page;
|
|
72
|
-
}
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
//# sourceMappingURL=server.js.map
|