redweb 0.7.5 → 0.7.7
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 +94 -10
- package/index.d.ts +24 -16
- package/index.js +5 -4
- package/package.json +1 -1
- package/src/http/BaseHttpServer.js +8 -6
- package/src/http/HttpServer.js +10 -6
- package/src/http/HttpsServer.js +11 -6
- package/src/http/index.js +2 -2
- package/src/ws/BaseSocketServer.js +10 -8
- package/src/ws/SecureSocketServer.js +12 -9
- package/src/ws/SocketServer.js +11 -8
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ const {
|
|
|
19
19
|
SocketRoute, // Per-path WebSocket routing
|
|
20
20
|
SocketService, // Route-scoped background/tick logic
|
|
21
21
|
SocketRegistry, // Evented in-memory store
|
|
22
|
+
BaseHttpServer, // Express app builder for advanced composition
|
|
22
23
|
BaseHandler, // WebSocket message handler base
|
|
23
24
|
sendJson, // Utility to stringify+send
|
|
24
25
|
SOCKET_OPTIONS, // Defaults for socket servers
|
|
@@ -28,7 +29,7 @@ const {
|
|
|
28
29
|
|
|
29
30
|
## HTTP servers (Express)
|
|
30
31
|
|
|
31
|
-
`new HttpServer(options)` starts listening immediately (default port `80`). `new HttpsServer({ ssl: { key, cert }, ... })` does the same over TLS.
|
|
32
|
+
`new HttpServer(options)` creates a Node HTTP server and starts listening immediately by default (default port `80`). `new HttpsServer({ ssl: { key, cert }, ... })` does the same over TLS.
|
|
32
33
|
|
|
33
34
|
Options:
|
|
34
35
|
|
|
@@ -36,6 +37,7 @@ Options:
|
|
|
36
37
|
- `bind` (string): defaults to `0.0.0.0`.
|
|
37
38
|
- `publicPaths` (string[]): folders served as static assets.
|
|
38
39
|
- `services` (array): `{ serviceName, method, function }` for REST endpoints.
|
|
40
|
+
- `listen` (boolean): defaults to `true`; set `false` to build `.app` and `.server` without binding a port.
|
|
39
41
|
- `listenCallback` (function): invoked after `.listen`.
|
|
40
42
|
- `encoding` (`'json' | 'urlencoded'`): body parser selection.
|
|
41
43
|
- `corsOptions`: passed to `cors`.
|
|
@@ -129,21 +131,102 @@ Each connected socket gets:
|
|
|
129
131
|
|
|
130
132
|
Invalid JSON triggers an error response and closes the socket.
|
|
131
133
|
|
|
134
|
+
### Binary WebSocket messages
|
|
135
|
+
|
|
136
|
+
Text frames are still parsed as JSON and routed by `message.type`. Binary frames are dispatched separately, so handlers can receive raw `Buffer` payloads without triggering JSON parse errors.
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
const { BaseHandler, SocketRoute } = require('redweb');
|
|
140
|
+
|
|
141
|
+
class UploadHandler extends BaseHandler {
|
|
142
|
+
constructor() { super('upload'); }
|
|
143
|
+
|
|
144
|
+
onMessage(socket, message) {
|
|
145
|
+
socket.sendJson({ type: 'upload:control', action: message.action });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
onBinaryMessage(socket, buffer) {
|
|
149
|
+
socket.sendJson({ type: 'upload:chunk', bytes: buffer.length });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
class UploadRoute extends SocketRoute {
|
|
154
|
+
constructor() {
|
|
155
|
+
super({
|
|
156
|
+
path: '/upload',
|
|
157
|
+
handlers: [UploadHandler],
|
|
158
|
+
allowDuplicateConnections: true,
|
|
159
|
+
websocketOptions: {
|
|
160
|
+
maxPayload: 2 * 1024 * 1024
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`BaseHandler` provides `handleBinaryMessage(socket, buffer)` and `onBinaryMessage(socket, buffer)`. Override `onBinaryMessage` for normal use. If a handler does not override it, RedWeb sends:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{ "error": "Binary messages are not supported by this handler" }
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Routes may also select a binary-capable handler with `acceptsBinary(socket, buffer)`:
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
class ImageHandler extends BaseHandler {
|
|
177
|
+
constructor() { super('image'); }
|
|
178
|
+
|
|
179
|
+
acceptsBinary(socket, buffer) {
|
|
180
|
+
return buffer.length > 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
onMessage(socket, message) {}
|
|
184
|
+
onBinaryMessage(socket, buffer) {}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### WebSocket route options
|
|
189
|
+
|
|
190
|
+
`SocketRoute` accepts `websocketOptions`, which are passed to `new WebSocketServer(...)`. Use this for `ws` server settings such as `maxPayload` or `perMessageDeflate`.
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
class ClipboardRoute extends SocketRoute {
|
|
194
|
+
constructor() {
|
|
195
|
+
super({
|
|
196
|
+
path: '/clipboard',
|
|
197
|
+
handlers: [ClipboardHandler],
|
|
198
|
+
websocketOptions: {
|
|
199
|
+
maxPayload: 1024 * 1024,
|
|
200
|
+
perMessageDeflate: false
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
132
207
|
### Sharing an HTTP/HTTPS server
|
|
133
208
|
|
|
134
|
-
`
|
|
209
|
+
Use `listen: false` on `HttpServer` to build the Express app and Node server without binding a port. Then pass `httpServer.server` to `SocketServer`. When `SocketServer` receives a prebuilt `server`, it attaches upgrade handling but does not call `.listen()` unless you explicitly set `listen: true`.
|
|
135
210
|
|
|
136
211
|
```js
|
|
137
|
-
const
|
|
138
|
-
const express = require('express');
|
|
139
|
-
const { SocketServer } = require('redweb');
|
|
212
|
+
const { HttpServer, METHODS, SocketServer } = require('redweb');
|
|
140
213
|
|
|
141
|
-
const
|
|
142
|
-
|
|
214
|
+
const httpServer = new HttpServer({
|
|
215
|
+
port: 3030,
|
|
216
|
+
listen: false,
|
|
217
|
+
publicPaths: ['./public'],
|
|
218
|
+
services: [
|
|
219
|
+
{ serviceName: '/health', method: METHODS.GET, function: (req, res) => res.json({ ok: true }) },
|
|
220
|
+
{ serviceName: '/session', method: METHODS.POST, function: createSession }
|
|
221
|
+
]
|
|
222
|
+
});
|
|
143
223
|
|
|
144
|
-
|
|
224
|
+
new SocketServer({
|
|
225
|
+
server: httpServer.server,
|
|
226
|
+
routes: [ClipboardRoute]
|
|
227
|
+
});
|
|
145
228
|
|
|
146
|
-
|
|
229
|
+
httpServer.server.listen(3030, () => console.log('HTTP and WebSocket server listening on 3030'));
|
|
147
230
|
```
|
|
148
231
|
|
|
149
232
|
### Socket services
|
|
@@ -182,8 +265,9 @@ Helpers: `add`, `remove(itemOrId, byKey = 'id')`, `all()`, `count()`.
|
|
|
182
265
|
|
|
183
266
|
## Defaults and lifecycle
|
|
184
267
|
|
|
185
|
-
- HTTP defaults: port `80`, bind `0.0.0.0`.
|
|
268
|
+
- HTTP defaults: port `80`, bind `0.0.0.0`, `listen: true`.
|
|
186
269
|
- WebSocket defaults: port `3000`, single connection per IP unless `allowDuplicateConnections` is set.
|
|
270
|
+
- `SocketServer` owns and listens on its own server by default; if you pass `server`, you own calling `.listen()` unless you also pass `listen: true`.
|
|
187
271
|
- If you do not supply `routes`, `SocketServer` registers a default route at `/` with `DefaultHandler` (it expects messages with `type: 'DefaultHandler'`).
|
|
188
272
|
- `BaseSocketServer.shutdown()` closes all routes, services, and the underlying server.
|
|
189
273
|
|
package/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
declare module 'redweb' {
|
|
2
2
|
import { Application } from 'express';
|
|
3
3
|
import { CorsOptions } from 'cors';
|
|
4
|
-
import { Server as
|
|
4
|
+
import { Server as NodeHttpServer } from 'http';
|
|
5
5
|
import { WebSocket, ServerOptions } from 'ws';
|
|
6
6
|
import { Buffer } from 'buffer';
|
|
7
7
|
|
|
@@ -12,9 +12,10 @@ declare module 'redweb' {
|
|
|
12
12
|
export interface RedWebOptions {
|
|
13
13
|
port?: number;
|
|
14
14
|
bind?: string;
|
|
15
|
-
publicPaths?: string[];
|
|
16
|
-
services?: Array<{ serviceName: string; method: string; function: Function }>;
|
|
17
|
-
|
|
15
|
+
publicPaths?: string[];
|
|
16
|
+
services?: Array<{ serviceName: string; method: string; function: Function }>;
|
|
17
|
+
listen?: boolean;
|
|
18
|
+
listenCallback?: () => void;
|
|
18
19
|
encoding?: RedWebEncoding;
|
|
19
20
|
ssl?: { key: string; cert: string };
|
|
20
21
|
server?: Application;
|
|
@@ -25,9 +26,10 @@ declare module 'redweb' {
|
|
|
25
26
|
/** ─────────────────── SOCKET SERVER ─────────────────── */
|
|
26
27
|
|
|
27
28
|
export interface SocketServerOptions {
|
|
28
|
-
server?:
|
|
29
|
-
port?: number;
|
|
30
|
-
|
|
29
|
+
server?: NodeHttpServer;
|
|
30
|
+
port?: number;
|
|
31
|
+
listen?: boolean;
|
|
32
|
+
routes?: Array<new () => SocketRoute>;
|
|
31
33
|
ssl?: { key: string; cert: string };
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -97,10 +99,10 @@ declare module 'redweb' {
|
|
|
97
99
|
|
|
98
100
|
export class BaseSocketServer {
|
|
99
101
|
clients: Map<string, WebSocket>;
|
|
100
|
-
server:
|
|
102
|
+
server: NodeHttpServer;
|
|
101
103
|
routes: SocketRoute[];
|
|
102
104
|
|
|
103
|
-
constructor(server:
|
|
105
|
+
constructor(server: NodeHttpServer, options?: SocketServerOptions);
|
|
104
106
|
|
|
105
107
|
addRoute(route: new () => SocketRoute): void;
|
|
106
108
|
}
|
|
@@ -153,13 +155,19 @@ declare module 'redweb' {
|
|
|
153
155
|
constructor(options?: SocketServerOptions);
|
|
154
156
|
}
|
|
155
157
|
|
|
156
|
-
export class
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
158
|
+
export class BaseHttpServer {
|
|
159
|
+
app: Application;
|
|
160
|
+
server?: NodeHttpServer;
|
|
161
|
+
constructor(options?: RedWebOptions);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export class HttpServer extends BaseHttpServer {
|
|
165
|
+
constructor(options?: RedWebOptions);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export class HttpsServer extends BaseHttpServer {
|
|
169
|
+
constructor(options?: RedWebOptions);
|
|
170
|
+
}
|
|
163
171
|
|
|
164
172
|
/** ─────────────────── CONSTANTS ─────────────────── */
|
|
165
173
|
|
package/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
const { METHODS } = require('./src/http');
|
|
1
|
+
const { BaseHttpServer, METHODS } = require('./src/http');
|
|
2
2
|
const { sendJson } = require('./src/ws/util');
|
|
3
3
|
const { SocketServer, SecureSocketServer, SOCKET_OPTIONS, SocketRoute, SocketService, SocketRegistry } = require('./src/ws');
|
|
4
4
|
const { BaseHandler } = require('./src/ws/BaseHandler');
|
|
5
5
|
const HttpServer = require('./src/http/HttpServer');
|
|
6
6
|
const HttpsServer = require('./src/http/HttpsServer');
|
|
7
7
|
module.exports = {
|
|
8
|
-
HttpServer,
|
|
9
|
-
HttpsServer,
|
|
10
|
-
|
|
8
|
+
HttpServer,
|
|
9
|
+
HttpsServer,
|
|
10
|
+
BaseHttpServer,
|
|
11
|
+
SocketServer,
|
|
11
12
|
SecureSocketServer,
|
|
12
13
|
BaseHandler,
|
|
13
14
|
SocketRoute,
|
package/package.json
CHANGED
|
@@ -15,13 +15,14 @@ const HtmxRenderer = require('../htmx/HtmxRenderer'); // Import the HtmxRenderer
|
|
|
15
15
|
* @property {number} [port=80] - The port number to bind the server.
|
|
16
16
|
* @property {string} [bind='0.0.0.0'] - The bind address for the server.
|
|
17
17
|
* @property {string[]} [publicPaths=['./public']] - An array of paths to serve static files from.
|
|
18
|
-
* @property {Array<{serviceName: string, method: string, function: Function}>} [services=[]] - An array of services with their endpoints and handlers.
|
|
19
|
-
* @property {
|
|
18
|
+
* @property {Array<{serviceName: string, method: string, function: Function}>} [services=[]] - An array of services with their endpoints and handlers.
|
|
19
|
+
* @property {boolean} [listen=true] - Whether HttpServer/HttpsServer should automatically start listening.
|
|
20
|
+
* @property {Function} [listenCallback] - Callback function to execute once the server starts listening.
|
|
20
21
|
* @property {RedWebEncoding} [encoding='json'] - The encoding type for the request bodies ('json' or 'urlencoded').
|
|
21
22
|
* @property {Object} [ssl] - SSL configuration for HTTPS server.
|
|
22
23
|
* @property {string} [ssl.key] - Path to the SSL key file.
|
|
23
24
|
* @property {string} [ssl.cert] - Path to the SSL certificate file.
|
|
24
|
-
* @property {import('express').Application} [server] -
|
|
25
|
+
* @property {import('express').Application} [server] - Existing Express application to configure.
|
|
25
26
|
* @property {import('cors').CorsOptions} [corsOptions] - The CORS Options.
|
|
26
27
|
* @property {boolean} [enableHtmxRendering=false] - Enable dynamic HTMX file rendering.
|
|
27
28
|
*/
|
|
@@ -30,9 +31,10 @@ const ENCODINGS = { json: 'json', urlencoded: 'urlencoded' };
|
|
|
30
31
|
const HTTP_OPTIONS = {
|
|
31
32
|
port: 80,
|
|
32
33
|
bind: '0.0.0.0',
|
|
33
|
-
publicPaths: ['./public'],
|
|
34
|
-
services: [],
|
|
35
|
-
|
|
34
|
+
publicPaths: ['./public'],
|
|
35
|
+
services: [],
|
|
36
|
+
listen: true,
|
|
37
|
+
listenCallback: undefined,
|
|
36
38
|
encoding: ENCODINGS.json,
|
|
37
39
|
ssl: null,
|
|
38
40
|
server: undefined,
|
package/src/http/HttpServer.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
const
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const { BaseHttpServer } = require('./BaseHttpServer');
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* HTTP Server
|
|
5
6
|
* @param {RedWebOptions} options - Configuration options for RedWeb.
|
|
6
7
|
* @return {Object} Express application instance.
|
|
7
8
|
*/
|
|
8
|
-
function HttpServer(options = {}) {
|
|
9
|
-
BaseHttpServer.call(this, options);
|
|
10
|
-
this.server =
|
|
11
|
-
|
|
12
|
-
}
|
|
9
|
+
function HttpServer(options = {}) {
|
|
10
|
+
BaseHttpServer.call(this, options);
|
|
11
|
+
this.server = http.createServer(this.app);
|
|
12
|
+
if (this.listen !== false) {
|
|
13
|
+
this.server.listen(this.port, this.listenCallback ? this.listenCallback : () => console.log(`RedWeb HttpServer listening on port ${this.port}`));
|
|
14
|
+
}
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
13
17
|
|
|
14
18
|
module.exports = HttpServer;
|
package/src/http/HttpsServer.js
CHANGED
|
@@ -7,11 +7,16 @@ const loadSslConfig = require('../sslConfig');
|
|
|
7
7
|
* @param {RedWebOptions} options - Configuration options for RedWeb.
|
|
8
8
|
* @return {Object} Express application instance.
|
|
9
9
|
*/
|
|
10
|
-
function HttpsServer(options = {}) {
|
|
11
|
-
BaseHttpServer.call(this, options);
|
|
12
|
-
const sslOptions = loadSslConfig(this.ssl);
|
|
13
|
-
https.createServer(sslOptions, this.app)
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
function HttpsServer(options = {}) {
|
|
11
|
+
BaseHttpServer.call(this, options);
|
|
12
|
+
const sslOptions = loadSslConfig(this.ssl);
|
|
13
|
+
this.server = https.createServer(sslOptions, this.app);
|
|
14
|
+
if (this.listen === false) {
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this.server.listen(this.port, this.listenCallback ? this.listenCallback : () => console.log(`RedWeb HttpsServer listening on port ${this.port}`));
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
16
21
|
|
|
17
22
|
module.exports = HttpsServer;
|
package/src/http/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const { METHODS } = require('./BaseHttpServer');
|
|
2
|
-
module.exports = { HttpServer: require('./HttpServer'), HttpsServer: require('./HttpsServer'), METHODS}
|
|
1
|
+
const { BaseHttpServer, METHODS } = require('./BaseHttpServer');
|
|
2
|
+
module.exports = { BaseHttpServer, HttpServer: require('./HttpServer'), HttpsServer: require('./HttpsServer'), METHODS}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {Object} SocketServerOptions
|
|
3
|
-
* @property {import('http').Server} [server] HTTP server to bind to
|
|
4
|
-
* @property {number} [port=3000] Port to listen on
|
|
5
|
-
* @property {
|
|
6
|
-
|
|
3
|
+
* @property {import('http').Server} [server] HTTP server to bind to
|
|
4
|
+
* @property {number} [port=3000] Port to listen on
|
|
5
|
+
* @property {boolean} [listen=true] Whether owned servers should automatically start listening
|
|
6
|
+
* @property {Array<new () => import('./SocketRoute').SocketRoute>} [routes]
|
|
7
|
+
*/
|
|
7
8
|
|
|
8
9
|
const DefaultRoute = require('./DefaultRoute');
|
|
9
10
|
|
|
10
11
|
const SOCKET_OPTIONS = {
|
|
11
|
-
port: 3000,
|
|
12
|
-
ssl: null,
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
port: 3000,
|
|
13
|
+
ssl: null,
|
|
14
|
+
listen: true,
|
|
15
|
+
routes: []
|
|
16
|
+
};
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Base WebSocket server
|
|
@@ -7,14 +7,17 @@ const { BaseSocketServer } = require('./BaseSocketServer');
|
|
|
7
7
|
* @param {SocketServerOptions} options - Configuration options for SecureSocketServer.
|
|
8
8
|
* @return {Object} WebSocket server instance.
|
|
9
9
|
*/
|
|
10
|
-
class SecureSocketServer extends BaseSocketServer {
|
|
11
|
-
constructor(options = {}) {
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
server
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
10
|
+
class SecureSocketServer extends BaseSocketServer {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
const ownsServer = !options?.server;
|
|
13
|
+
const sslOptions = ownsServer ? loadSslConfig(options.ssl) : null;
|
|
14
|
+
const server = options?.server || https.createServer(sslOptions);
|
|
15
|
+
super(server, options);
|
|
16
|
+
if ((ownsServer && this.listen !== false) || (!ownsServer && options.listen === true)) {
|
|
17
|
+
server.listen(this.port, () => console.log(`RedWeb SecureSocketServer listening on port ${this.port}`));
|
|
18
|
+
}
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
19
22
|
|
|
20
23
|
module.exports = SecureSocketServer;
|
package/src/ws/SocketServer.js
CHANGED
|
@@ -6,13 +6,16 @@ const { BaseSocketServer } = require('./BaseSocketServer');
|
|
|
6
6
|
* @param {SocketServerOptions} options - Configuration options for SocketServer.
|
|
7
7
|
* @return {Object} WebSocket server instance.
|
|
8
8
|
*/
|
|
9
|
-
class SocketServer extends BaseSocketServer {
|
|
10
|
-
constructor(options = {}) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
server
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
9
|
+
class SocketServer extends BaseSocketServer {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
const ownsServer = !options?.server;
|
|
12
|
+
const server = options?.server || http.createServer();
|
|
13
|
+
super(server, options);
|
|
14
|
+
if ((ownsServer && this.listen !== false) || (!ownsServer && options.listen === true)) {
|
|
15
|
+
server.listen(this.port, () => console.log(`RedWeb SocketServer listening on port ${this.port}`));
|
|
16
|
+
}
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
17
20
|
|
|
18
21
|
module.exports = SocketServer;
|