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 +21 -21
- package/README.md +90 -252
- package/index.d.ts +173 -173
- package/index.js +19 -19
- package/package.json +27 -27
- package/src/htmx/HtmxRenderer.js +65 -65
- package/src/htmx/RedWebHtmxComponent.js +11 -11
- package/src/http/BaseHttpServer.js +105 -105
- package/src/http/HttpServer.js +14 -14
- package/src/http/HttpsServer.js +17 -17
- package/src/http/index.js +1 -1
- package/src/sslConfig.js +20 -20
- package/src/ws/BaseHandler.js +39 -39
- package/src/ws/BaseSocketServer.js +76 -62
- package/src/ws/DefaultHandler.js +13 -13
- package/src/ws/DefaultRoute.js +13 -13
- package/src/ws/SecureSocketServer.js +20 -20
- package/src/ws/SocketRegistry.js +37 -37
- package/src/ws/SocketRoute.js +149 -149
- package/src/ws/SocketServer.js +18 -18
- package/src/ws/SocketService.js +32 -32
- package/src/ws/index.js +8 -8
- package/src/ws/util.js +9 -9
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
|
-
|
|
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 {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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: '/
|
|
38
|
-
method: METHODS.
|
|
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
|
-
|
|
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
|
-
##
|
|
80
|
+
## WebSocket servers
|
|
60
81
|
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
132
|
+
### Sharing an HTTP/HTTPS server
|
|
151
133
|
|
|
152
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
141
|
+
const app = express();
|
|
142
|
+
const server = http.createServer(app);
|
|
168
143
|
|
|
169
|
-
|
|
144
|
+
app.get('/', (req, res) => res.send('hello'));
|
|
170
145
|
|
|
171
|
-
|
|
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
|
-
|
|
151
|
+
Route-scoped background logic:
|
|
188
152
|
|
|
189
153
|
```js
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
164
|
+
Add with `services: [ClockService]` when constructing a `SocketRoute`.
|
|
210
165
|
|
|
211
|
-
|
|
166
|
+
### Socket registries
|
|
212
167
|
|
|
213
|
-
|
|
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
|
-
|
|
245
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
190
|
+
## Developing
|
|
353
191
|
|
|
354
|
-
|
|
192
|
+
- Run tests with `npm test` (Jest).
|