redweb 0.7.5 → 0.7.6
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 +95 -8
- 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 +9 -5
- 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
|
|
@@ -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` 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,105 @@ 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 without binding a port. Then create one Node server from `httpServer.app` and pass it 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
212
|
const http = require('http');
|
|
138
|
-
const
|
|
139
|
-
const { SocketServer } = require('redweb');
|
|
213
|
+
const { HttpServer, METHODS, SocketServer } = require('redweb');
|
|
140
214
|
|
|
141
|
-
const
|
|
142
|
-
|
|
215
|
+
const httpServer = new HttpServer({
|
|
216
|
+
port: 3030,
|
|
217
|
+
listen: false,
|
|
218
|
+
publicPaths: ['./public'],
|
|
219
|
+
services: [
|
|
220
|
+
{ serviceName: '/health', method: METHODS.GET, function: (req, res) => res.json({ ok: true }) },
|
|
221
|
+
{ serviceName: '/session', method: METHODS.POST, function: createSession }
|
|
222
|
+
]
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const server = http.createServer(httpServer.app);
|
|
143
226
|
|
|
144
|
-
|
|
227
|
+
new SocketServer({
|
|
228
|
+
server,
|
|
229
|
+
routes: [ClipboardRoute]
|
|
230
|
+
});
|
|
145
231
|
|
|
146
|
-
|
|
232
|
+
server.listen(3030, () => console.log('HTTP and WebSocket server listening on 3030'));
|
|
147
233
|
```
|
|
148
234
|
|
|
149
235
|
### Socket services
|
|
@@ -182,8 +268,9 @@ Helpers: `add`, `remove(itemOrId, byKey = 'id')`, `all()`, `count()`.
|
|
|
182
268
|
|
|
183
269
|
## Defaults and lifecycle
|
|
184
270
|
|
|
185
|
-
- HTTP defaults: port `80`, bind `0.0.0.0`.
|
|
271
|
+
- HTTP defaults: port `80`, bind `0.0.0.0`, `listen: true`.
|
|
186
272
|
- WebSocket defaults: port `3000`, single connection per IP unless `allowDuplicateConnections` is set.
|
|
273
|
+
- `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
274
|
- If you do not supply `routes`, `SocketServer` registers a default route at `/` with `DefaultHandler` (it expects messages with `type: 'DefaultHandler'`).
|
|
188
275
|
- `BaseSocketServer.shutdown()` closes all routes, services, and the underlying server.
|
|
189
276
|
|
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
|
@@ -5,10 +5,14 @@ const { BaseHttpServer } = require('./BaseHttpServer');
|
|
|
5
5
|
* @param {RedWebOptions} options - Configuration options for RedWeb.
|
|
6
6
|
* @return {Object} Express application instance.
|
|
7
7
|
*/
|
|
8
|
-
function HttpServer(options = {}) {
|
|
9
|
-
BaseHttpServer.call(this, options);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
8
|
+
function HttpServer(options = {}) {
|
|
9
|
+
BaseHttpServer.call(this, options);
|
|
10
|
+
if (this.listen === false) {
|
|
11
|
+
return this;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
this.server = this.app.listen(this.port, this.listenCallback ? this.listenCallback : () => console.log(`RedWeb HttpServer listening on port ${this.port}`));
|
|
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;
|