redweb 0.7.2 β†’ 0.7.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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 Arkam Mazrui
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Arkam Mazrui
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,29 +1,47 @@
1
1
  # RedWeb
2
2
 
3
- **RedWeb** is a flexible Node.js framework built on top of **Express.js** and **WebSocket**. It enables quick setup of HTTP(S) and WebSocket servers with a modular route and handler system.
3
+ RedWeb is a small Node.js helper that wires together Express HTTP/HTTPS servers and `ws` WebSocket servers with simple defaults. Use it to serve static files plus JSON APIs and to route WebSocket traffic to handler classes.
4
4
 
5
- ---
6
-
7
- ## πŸ“¦ Installation
5
+ ## Install
8
6
 
9
7
  ```bash
10
8
  npm install redweb
11
9
  ```
12
10
 
13
- ---
14
-
15
- ## πŸš€ Quick Start
11
+ ## Exports
16
12
 
17
13
  ```js
18
- const { HttpServer, SocketServer } = require('redweb');
19
-
20
- new HttpServer(); // serves public/ by default
21
- new SocketServer(); // starts WS on :3000
14
+ const {
15
+ HttpServer, // HTTP over Express
16
+ HttpsServer, // HTTP with TLS (key/cert required)
17
+ SocketServer, // WebSocket over HTTP
18
+ SecureSocketServer, // WebSocket over HTTPS
19
+ SocketRoute, // Per-path WebSocket routing
20
+ SocketService, // Route-scoped background/tick logic
21
+ SocketRegistry, // Evented in-memory store
22
+ BaseHandler, // WebSocket message handler base
23
+ sendJson, // Utility to stringify+send
24
+ SOCKET_OPTIONS, // Defaults for socket servers
25
+ METHODS // Express method helpers: get/post/put/delete
26
+ } = require('redweb');
22
27
  ```
23
28
 
24
- ---
29
+ ## HTTP servers (Express)
30
+
31
+ `new HttpServer(options)` starts listening immediately (default port `80`). `new HttpsServer({ ssl: { key, cert }, ... })` does the same over TLS.
32
+
33
+ Options:
34
+
35
+ - `port` (number): defaults to `80`.
36
+ - `bind` (string): defaults to `0.0.0.0`.
37
+ - `publicPaths` (string[]): folders served as static assets.
38
+ - `services` (array): `{ serviceName, method, function }` for REST endpoints.
39
+ - `listenCallback` (function): invoked after `.listen`.
40
+ - `encoding` (`'json' | 'urlencoded'`): body parser selection.
41
+ - `corsOptions`: passed to `cors`.
42
+ - `enableHtmxRendering` (boolean): render `.htmx` files with the built-in renderer.
25
43
 
26
- ## 🌐 HTTP Server Example (HTMX Support)
44
+ Example:
27
45
 
28
46
  ```js
29
47
  const { HttpServer, METHODS } = require('redweb');
@@ -31,324 +49,144 @@ const { HttpServer, METHODS } = require('redweb');
31
49
  new HttpServer({
32
50
  port: 3000,
33
51
  publicPaths: ['./public'],
34
- enableHtmxRendering: true,
35
52
  services: [
36
53
  {
37
- serviceName: '/submit',
38
- method: METHODS.POST,
39
- function: (req, res) => {
40
- if (!req.body.name) return res.status(400).json({ error: 'Missing name' });
41
- res.status(200).json({ message: `Thanks, ${req.body.name}!` });
42
- }
54
+ serviceName: '/api/hello',
55
+ method: METHODS.GET,
56
+ function: (req, res) => res.json({ hello: 'world' })
43
57
  }
44
58
  ]
45
59
  });
46
60
  ```
47
61
 
48
- `.htmx` files under `public/` will render server-side. Example:
62
+ HTMX rendering example (`enableHtmxRendering: true`):
63
+
64
+ ```js
65
+ new HttpServer({ publicPaths: ['./public'], enableHtmxRendering: true });
66
+ ```
67
+
68
+ `public/example.htmx`:
69
+
70
+ ```js
71
+ const name = 'RedWeb';
49
72
 
50
- ```html
51
- <!-- public/hello.htmx -->
52
73
  <@>
53
74
  <h1>Hello, {{name}}!</h1>
54
75
  <@/>
55
76
  ```
56
77
 
57
- ---
78
+ Requesting `/example.htmx` returns rendered HTML.
58
79
 
59
- ## πŸ”Œ WebSocket Broadcast Chat (πŸ”₯ Instant Testing)
80
+ ## WebSocket servers
60
81
 
61
- ### 1. `ChatHandler.js`
82
+ `SocketServer` uses `ws` and routes connections to `SocketRoute` instances. Clients must send JSON containing a `type` that matches a handler name.
83
+
84
+ Handler:
62
85
 
63
86
  ```js
64
87
  const { BaseHandler } = require('redweb');
65
88
 
66
89
  class ChatHandler extends BaseHandler {
67
- constructor() {
68
- super('chat');
69
- }
90
+ constructor() { super('chat'); }
70
91
 
71
92
  onMessage(socket, message) {
72
- const text = message.text;
73
- socket.broadcast({ type: 'chat', text });
93
+ socket.broadcast({ type: 'chat', text: message.text });
74
94
  }
75
95
  }
76
-
77
- module.exports = ChatHandler;
78
96
  ```
79
97
 
80
- ---
81
-
82
- ### 2. `ChatRoute.js`
98
+ Route:
83
99
 
84
100
  ```js
85
101
  const { SocketRoute } = require('redweb');
86
- const ChatHandler = require('./ChatHandler');
87
102
 
88
103
  class ChatRoute extends SocketRoute {
89
104
  constructor() {
90
105
  super({
91
106
  path: '/chat',
92
107
  handlers: [ChatHandler],
93
- allowDuplicateConnections: true
108
+ allowDuplicateConnections: true // otherwise one connection per IP
94
109
  });
95
110
  }
96
111
  }
97
-
98
- module.exports = ChatRoute;
99
112
  ```
100
113
 
101
- ---
102
-
103
- ### 3. `server.js`
114
+ Server:
104
115
 
105
116
  ```js
106
117
  const { SocketServer } = require('redweb');
107
- const ChatRoute = require('./ChatRoute');
108
118
 
109
119
  new SocketServer({
110
- port: 3000,
111
- routes: [ChatRoute]
120
+ port: 3000, // default
121
+ routes: [ChatRoute], // defaults to a route at "/" with DefaultHandler if omitted
112
122
  });
113
123
  ```
114
124
 
115
- ---
116
-
117
- ### 4. `client.html`
118
-
119
- ```html
120
- <!DOCTYPE html>
121
- <html>
122
- <body>
123
- <h1>Broadcast Chat</h1>
124
- <input id="msg" placeholder="Type message..." />
125
- <button onclick="send()">Send</button>
126
- <pre id="log"></pre>
127
-
128
- <script>
129
- const log = document.getElementById('log');
130
- const ws = new WebSocket('ws://localhost:3000/chat');
131
-
132
- ws.onmessage = (e) => {
133
- const msg = JSON.parse(e.data);
134
- log.textContent += `\n${msg.text}`;
135
- };
136
-
137
- function send() {
138
- const text = document.getElementById('msg').value;
139
- ws.send(JSON.stringify({ type: 'chat', text }));
140
- }
141
- </script>
142
- </body>
143
- </html>
144
- ```
125
+ Each connected socket gets:
145
126
 
146
- Open multiple tabs to test.
127
+ - `socket.sendJson(data)` to send JSON.
128
+ - `socket.broadcast(data)` to send JSON to all other clients on the same route.
147
129
 
148
- ---
130
+ Invalid JSON triggers an error response and closes the socket.
149
131
 
150
- ## 🧩 Socket Architecture
132
+ ### Sharing an HTTP/HTTPS server
151
133
 
152
- ### `SocketRoute`
153
-
154
- Defines a WebSocket path, handlers, and optional route-scoped services:
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.
155
135
 
156
136
  ```js
157
- new SocketRoute({
158
- path: '/game',
159
- handlers: [ChatHandler, MoveHandler],
160
- services: [MatchService], // βœ… Scoped only to this route
161
- allowDuplicateConnections: true
162
- });
163
- ```
164
-
165
- ---
137
+ const http = require('http');
138
+ const express = require('express');
139
+ const { SocketServer } = require('redweb');
166
140
 
167
- ### `BaseHandler`
141
+ const app = express();
142
+ const server = http.createServer(app);
168
143
 
169
- Handlers are message-type keyed classes:
144
+ app.get('/', (req, res) => res.send('hello'));
170
145
 
171
- ```js
172
- class MoveHandler extends BaseHandler {
173
- constructor() {
174
- super('move');
175
- }
176
-
177
- onMessage(socket, message) {
178
- // handle movement logic
179
- }
180
- }
146
+ new SocketServer({ server, port: 4000, routes: [ChatRoute] });
181
147
  ```
182
148
 
183
- ---
184
-
185
- ### `SocketService` (NEW)
149
+ ### Socket services
186
150
 
187
- Socket services run alongside handlers on a route. Use for timers, logic, cleanup.
151
+ Route-scoped background logic:
188
152
 
189
153
  ```js
190
- class MatchService extends SocketService {
191
- constructor() {
192
- super('match', 1000); // tick every 1s
193
- }
194
-
195
- onInit(route) {
196
- route.registry.on('maxPlayersReached', () => this.startMatch());
197
- }
154
+ const { SocketService } = require('redweb');
198
155
 
156
+ class ClockService extends SocketService {
157
+ constructor() { super('clock', 1000); } // tick every 1s
199
158
  onTick() {
200
- // tick logic
201
- }
202
-
203
- onShutdown() {
204
- // cleanup
159
+ this.route.clients.forEach((socket) => socket.sendJson({ type: 'time', now: Date.now() }));
205
160
  }
206
161
  }
207
162
  ```
208
163
 
209
- ### πŸ“¦ `SocketRegistry` (NEW) – Event-Driven Socket Object Store
164
+ Add with `services: [ClockService]` when constructing a `SocketRoute`.
210
165
 
211
- `SocketRegistry` is a lightweight, extendable class for managing WebSocket-connected clients (or any socket-bound object). It provides add/remove/get/broadcast utilities with full `EventEmitter` support.
166
+ ### Socket registries
212
167
 
213
- Useful for managing players, NPCs, chat members, rooms, etc.
214
-
215
- ---
216
-
217
- ### πŸ”§ Basic Usage
168
+ `SocketRegistry` is a small evented list for socket-bound objects.
218
169
 
219
170
  ```js
220
171
  const { SocketRegistry } = require('redweb');
221
172
 
222
- class Player {
223
- constructor(socket, id) {
224
- this.socket = socket;
225
- this.id = id;
226
- }
227
-
228
- send(type, payload) {
229
- this.socket.send(JSON.stringify({ type, ...payload }));
230
- }
231
-
232
- getSanitized() {
233
- return { id: this.id };
234
- }
235
- }
236
- ```
237
-
238
- ---
239
-
240
- ### πŸš€ Extending `SocketRegistry` to Create a Player Registry
241
-
242
- ```js
243
173
  class PlayerRegistry extends SocketRegistry {
244
- create(socket, id) {
245
- return new Player(socket, id);
246
- }
247
-
248
- addPlayer(socket, id) {
249
- const player = this.create(socket, id);
250
- const success = this.add(player);
251
- if (success) this.emit('playerJoined', player);
252
- return success;
253
- }
254
-
255
- removePlayer(id) {
256
- const success = this.remove(id);
257
- if (success) this.emit('playerLeft', id);
258
- return success;
259
- }
260
-
261
- broadcastToAll(message) {
262
- this.items.forEach(player => player.send(message.type, message));
263
- }
174
+ addPlayer(player) {
175
+ this.add(player);
176
+ this.emit('playerJoined', player);
177
+ }
264
178
  }
265
179
  ```
266
180
 
267
- ---
268
-
269
- ### πŸ“£ Built-in Events
270
-
271
- You can listen to events:
272
-
273
- ```js
274
- const registry = new PlayerRegistry();
275
-
276
- registry.on('playerJoined', player => {
277
- console.log('New player:', player.id);
278
- });
279
-
280
- registry.on('playerLeft', id => {
281
- console.log('Player left:', id);
282
- });
283
- ```
284
-
285
- ---
286
-
287
- ### πŸ”„ Built-in Methods
288
-
289
- * `add(player)`
290
- * `remove(id)`
291
- * `getById(id)`
292
- * `getBySocket(socket)`
293
- * `all()`
294
- * `count()`
295
- * `broadcast(message, excludeSocket?)`
296
- * `getSanitizedList()`
297
-
298
- ---
299
-
300
- ## πŸ”§ Configuration
301
-
302
- ### HTTP / HTTPS Options
303
-
304
- | Option | Type | Default | Description |
305
- | --------------------- | --------- | -------------- | ------------------------------ |
306
- | `port` | number | `80` | Port to listen on |
307
- | `bind` | string | `'0.0.0.0'` | Bind address |
308
- | `publicPaths` | string\[] | `['./public']` | Serve static and `.htmx` files |
309
- | `services` | object\[] | `[]` | REST endpoints |
310
- | `enableHtmxRendering` | boolean | `false` | Enables `.htmx` file rendering |
311
- | `ssl` | object | `undefined` | Used in `HttpsServer` |
312
-
313
- ---
314
-
315
- ### WebSocket Server Options
316
-
317
- | Option | Type | Default | Description |
318
- | -------- | --------------- | ------- | ---------------------------- |
319
- | `port` | number | `3000` | WebSocket port |
320
- | `routes` | `SocketRoute[]` | `[]` | List of custom route classes |
321
-
322
- &nbsp;
323
-
324
- # Changelog
325
- Here’s the updated `CHANGELOG.md` entry for **RedWeb v0.7.1**, written professionally and focused only on the framework-level additions:
326
-
327
- ---
328
-
329
- ## πŸ“¦ RedWeb v0.7.1 – Socket Services & Registries
330
-
331
- ### ✨ Added
332
-
333
- * `SocketService`: A new class for running autonomous, lifecycle-aware logic alongside a `SocketRoute`. Ideal for game loops, timers, state machines, or server-side AI.
334
-
335
- * Hooks: `onInit(route)`, `onTick()`, `onShutdown()`
336
- * Optional `tickRateMs` support for periodic execution
337
-
338
- * `SocketRegistry`: A generic, event-driven registry for managing WebSocket-bound entities
339
-
340
- * Includes `.add()`, `.remove()`, `.getById()`, `.broadcast()`
341
- * Fully compatible with custom socket wrappers and `EventEmitter`
342
-
343
- ## 0.7.0 Update Highlights
181
+ Helpers: `add`, `remove(itemOrId, byKey = 'id')`, `all()`, `count()`.
344
182
 
345
- * allowDuplicateConnections for multi-tab testing
346
- * Robust message validation
347
- * socket.broadcast() now excludes sender
348
- * Better error handling
183
+ ## Defaults and lifecycle
349
184
 
350
- ---
185
+ - HTTP defaults: port `80`, bind `0.0.0.0`.
186
+ - WebSocket defaults: port `3000`, single connection per IP unless `allowDuplicateConnections` is set.
187
+ - If you do not supply `routes`, `SocketServer` registers a default route at `/` with `DefaultHandler` (it expects messages with `type: 'DefaultHandler'`).
188
+ - `BaseSocketServer.shutdown()` closes all routes, services, and the underlying server.
351
189
 
352
- ## πŸͺͺ License
190
+ ## Developing
353
191
 
354
- MIT
192
+ - Run tests with `npm test` (Jest).