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 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
- `SocketServer` and `SecureSocketServer` accept a prebuilt Node server via `server`. They attach upgrade handling and then call `.listen(port)`, so only pass a server that is **not** already listening.
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 http = require('http');
138
- const express = require('express');
139
- const { SocketServer } = require('redweb');
212
+ const { HttpServer, METHODS, SocketServer } = require('redweb');
140
213
 
141
- const app = express();
142
- const server = http.createServer(app);
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
- app.get('/', (req, res) => res.send('hello'));
224
+ new SocketServer({
225
+ server: httpServer.server,
226
+ routes: [ClipboardRoute]
227
+ });
145
228
 
146
- new SocketServer({ server, port: 4000, routes: [ChatRoute] });
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 HttpServer } from 'http';
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
- listenCallback?: () => void;
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?: HttpServer;
29
- port?: number;
30
- routes?: Array<new () => SocketRoute>;
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: HttpServer;
102
+ server: NodeHttpServer;
101
103
  routes: SocketRoute[];
102
104
 
103
- constructor(server: HttpServer, options?: SocketServerOptions);
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 HttpServer {
157
- constructor(options?: RedWebOptions);
158
- }
159
-
160
- export class HttpsServer {
161
- constructor(options?: RedWebOptions);
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
- SocketServer,
8
+ HttpServer,
9
+ HttpsServer,
10
+ BaseHttpServer,
11
+ SocketServer,
11
12
  SecureSocketServer,
12
13
  BaseHandler,
13
14
  SocketRoute,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redweb",
3
- "version": "0.7.5",
3
+ "version": "0.7.7",
4
4
  "description": "A way to quickly set up an express server",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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 {Function} [listenCallback] - Callback function to execute once the server starts listening.
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] - Whether to automatically start listening.
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
- listenCallback: undefined,
34
+ publicPaths: ['./public'],
35
+ services: [],
36
+ listen: true,
37
+ listenCallback: undefined,
36
38
  encoding: ENCODINGS.json,
37
39
  ssl: null,
38
40
  server: undefined,
@@ -1,14 +1,18 @@
1
- const { BaseHttpServer } = require('./BaseHttpServer');
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 = this.app.listen(this.port, this.listenCallback ? this.listenCallback : () => console.log(`RedWeb HttpServer listening on port ${this.port}`));
11
- return this;
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;
@@ -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).listen(this.port, this.listenCallback ? this.listenCallback : () => console.log(`RedWeb HttpsServer listening on port ${this.port}`));
14
- return this;
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 {Array<new () => import('./SocketRoute').SocketRoute>} [routes]
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
- routes: []
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 sslOptions = loadSslConfig(options.ssl);
13
- const server = https.createServer(sslOptions);
14
- super(server, options);
15
- server.listen(this.port, () => console.log(`RedWeb SecureSocketServer listening on port ${this.port}`));
16
- return this;
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;
@@ -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 server = options?.server || http.createServer();
12
- super(server, options);
13
- server.listen(this.port, () => console.log(`RedWeb SocketServer listening on port ${this.port}`));
14
- return this;
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;