node-red-contrib-aedes 0.15.1 → 1.2.0

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.
@@ -1,349 +0,0 @@
1
- # Migration Plan: Aedes 0.51.x to 1.0.0
2
-
3
- ## Context
4
-
5
- Aedes v1.0.0 is ESM-only with async broker creation and replaces `websocket-stream` with native `ws`. This plan migrates node-red-contrib-aedes from Aedes ^0.51.3 to ^1.0.0.
6
-
7
- ## Decision: Stay CJS
8
-
9
- The project stays CommonJS (`module.exports = function(RED) {...}`). Node-RED has no native ESM support for custom nodes yet. Only `aedes` (ESM-only) needs dynamic `await import()`. All other dependencies (`aedes-persistence-mongodb` v9.4.1 = CJS, `ws` v8 = dual CJS/ESM, Node.js built-ins = both) work fine with `require()`.
10
-
11
- ## Breaking Changes
12
-
13
- | Area | v0.51.3 (Current) | v1.0.0 (Target) |
14
- |------|-------------------|------------------|
15
- | Module system | CJS `require('aedes')` | ESM only -- use `await import('aedes')` |
16
- | Broker creation | `aedes.createBroker(opts)` sync | `await Aedes.createBroker(opts)` async, named export |
17
- | WebSocket lib | `websocket-stream` package | Native `ws` with `WebSocketServer` + `createWebSocketStream` |
18
- | Default export | Callable function | Throws error -- must use named `{ Aedes }` |
19
- | Subscribe event | Array (but code treats as single object -- latent bug) | Array -- must iterate |
20
- | Unsubscribe event | Array of strings (but code treats as object -- latent bug) | Array of strings -- must iterate |
21
-
22
- ## Files to Modify
23
-
24
- - `package.json` -- dependency updates
25
- - `aedes.js` -- core broker logic (main work)
26
- - `test/aedes_spec.js` -- basic TCP tests
27
- - `test/aedes_ws_spec.js` -- WebSocket tests
28
- - `test/aedes_secure_spec.js` -- TLS tests
29
- - `test/aedes_qos_spec.js` -- QoS tests
30
- - `test/aedes_retain_spec.js` -- retain tests
31
- - `test/aedes_last_will_spec.js` -- last will tests
32
-
33
- No changes: `aedes.html`, `locales/`, `examples/`, `icons/`
34
-
35
- ## Unchanged APIs (no migration needed)
36
-
37
- - `broker.handle(conn, req)` -- same signature in v1
38
- - `broker.authenticate(client, username, password, callback)` -- same in v1
39
- - `broker.close(callback)` -- still callback-based in v1
40
- - All broker events -- same names and signatures
41
- - `broker.connectedClients` -- still a property
42
- - `aedes-persistence-mongodb` v9.4.1 -- compatible (peer dep min ^9.3.1)
43
- - `handleServerUpgrade` function -- uses standard `ws` API already
44
-
45
- ---
46
-
47
- ## TASK 0: Create migration branch
48
-
49
- ```bash
50
- git checkout -b migrate-aedes-v1
51
- ```
52
-
53
- All migration work happens on this branch. Merge to master after tests pass.
54
-
55
- ---
56
-
57
- ## TASK 1: Update package.json dependencies
58
-
59
- **File:** `package.json`
60
-
61
- ### 1.1 Update aedes version
62
- - Change `"aedes": "^0.51.3"` to `"aedes": "^1.0.0"`
63
-
64
- ### 1.2 Replace websocket-stream with ws
65
- - Remove `"websocket-stream": "^5.5.2"`
66
- - Add `"ws": "^8.18.0"`
67
-
68
- ### 1.3 Keep aedes-persistence-mongodb
69
- - No change needed: `"aedes-persistence-mongodb": "^9.4.1"` is compatible
70
-
71
- ---
72
-
73
- ## TASK 2: Update imports in aedes.js
74
-
75
- **File:** `aedes.js` lines 17-28
76
-
77
- ### 2.1 Remove aedes require
78
- - Delete line 22: `const aedes = require('aedes');`
79
- - Aedes will be dynamically imported inside async init (ESM-only, cannot require)
80
-
81
- ### 2.2 Replace websocket-stream require
82
- - Delete line 28: `const ws = require('websocket-stream');`
83
- - Add: `const { WebSocketServer, createWebSocketStream } = require('ws');`
84
- - Note: `ws` v8 supports CJS require
85
-
86
- ---
87
-
88
- ## TASK 3: Async broker initialization pattern
89
-
90
- **File:** `aedes.js` -- constructor `AedesBrokerNode` (line 54)
91
-
92
- The constructor must stay synchronous (Node-RED requirement). Use "init promise" pattern.
93
-
94
- ### 3.1 Add state tracking to constructor
95
- After synchronous config parsing (lines 54-133), add:
96
- ```javascript
97
- node._closing = false;
98
- node._broker = null;
99
- node._server = null;
100
- node._wss = null;
101
- node._httpServer = null;
102
- ```
103
-
104
- ### 3.2 Extract async init function
105
- Create `async function initializeBroker(node, config, aedesSettings, serverOptions)` containing all logic currently at lines 135-408:
106
- ```javascript
107
- async function initializeBroker (node, config, aedesSettings, serverOptions) {
108
- const { Aedes } = await import('aedes');
109
- const broker = await Aedes.createBroker(aedesSettings);
110
- if (node._closing) { broker.close(); return; }
111
- node._broker = broker;
112
- // ... TCP/TLS server setup (TASK 3.3)
113
- // ... WebSocket port setup (TASK 4)
114
- // ... WebSocket path setup (TASK 5)
115
- // ... server.listen (existing logic)
116
- // ... authentication setup (existing logic, unchanged)
117
- // ... event handlers (TASK 6)
118
- }
119
- ```
120
-
121
- ### 3.3 Call init from constructor
122
- Replace lines 135-408 with:
123
- ```javascript
124
- node._initPromise = initializeBroker(node, config, aedesSettings, serverOptions);
125
- node._initPromise.catch(function (err) {
126
- node.error('Failed to initialize Aedes broker: ' + err.toString());
127
- node.status({ fill: 'red', shape: 'ring', text: 'initialization failed' });
128
- });
129
- ```
130
-
131
- ### 3.4 TCP/TLS server creation inside initializeBroker
132
- Move lines 136-141 into initializeBroker. No API changes needed:
133
- ```javascript
134
- let server;
135
- if (node.usetls) {
136
- server = tls.createServer(serverOptions, broker.handle);
137
- } else {
138
- server = net.createServer(broker.handle);
139
- }
140
- node._server = server;
141
- ```
142
-
143
- ---
144
-
145
- ## TASK 4: Rewrite WebSocket port mode
146
-
147
- **File:** `aedes.js` lines 146-188
148
-
149
- ### 4.1 Replace ws.createServer with WebSocketServer
150
- **Old (lines 173-178):**
151
- ```javascript
152
- wss = ws.createServer({ server: httpServer }, broker.handle);
153
- ```
154
-
155
- **New:**
156
- ```javascript
157
- const wss = new WebSocketServer({ server: httpServer });
158
- wss.on('connection', function (websocket, req) {
159
- const stream = createWebSocketStream(websocket);
160
- broker.handle(stream, req);
161
- });
162
- node._wss = wss;
163
- node._httpServer = httpServer;
164
- ```
165
-
166
- ### 4.2 Promisify port availability test
167
- Wrap the testServer logic (lines 148-187) in a Promise for clean async flow inside initializeBroker. Keep same EADDRINUSE error handling.
168
-
169
- ---
170
-
171
- ## TASK 5: Rewrite WebSocket path mode
172
-
173
- **File:** `aedes.js` lines 190-227
174
-
175
- ### 5.1 Replace ws.createServer noServer with WebSocketServer
176
- **Old (lines 219-224):**
177
- ```javascript
178
- node.server = ws.createServer({ noServer: true }, broker.handle);
179
- ```
180
-
181
- **New:**
182
- ```javascript
183
- node.server = new WebSocketServer({ noServer: true });
184
- node.server.on('connection', function (websocket, req) {
185
- const stream = createWebSocketStream(websocket);
186
- broker.handle(stream, req);
187
- });
188
- ```
189
-
190
- ### 5.2 handleServerUpgrade -- NO CHANGES
191
- Function at lines 40-52 already uses standard `ws` API pattern (`handleUpgrade` + `emit('connection')`). Works as-is with `WebSocketServer`.
192
-
193
- ---
194
-
195
- ## TASK 6: Fix subscribe/unsubscribe event handlers
196
-
197
- **File:** `aedes.js` lines 368-390
198
-
199
- ### 6.1 Fix subscribe handler (lines 368-378)
200
- The first argument is an **array** of `{topic, qos}` objects. Iterate it:
201
- ```javascript
202
- broker.on('subscribe', function (subscriptions, client) {
203
- for (const subscription of subscriptions) {
204
- node.send([{
205
- topic: 'subscribe',
206
- payload: { topic: subscription.topic, qos: subscription.qos, client }
207
- }, null]);
208
- }
209
- });
210
- ```
211
-
212
- ### 6.2 Fix unsubscribe handler (lines 380-390)
213
- The first argument is an **array of topic strings** (not objects). Iterate it:
214
- ```javascript
215
- broker.on('unsubscribe', function (unsubscriptions, client) {
216
- for (const topic of unsubscriptions) {
217
- node.send([{
218
- topic: 'unsubscribe',
219
- payload: { topic: topic, client }
220
- }, null]);
221
- }
222
- });
223
- ```
224
-
225
- ---
226
-
227
- ## TASK 7: Restructure close handler
228
-
229
- **File:** `aedes.js` lines 410-452
230
-
231
- ### 7.1 Replace close handler with async-safe version
232
- Carries over the `removed` parameter from the Node-RED 4 migration (task 2.2):
233
- ```javascript
234
- this.on('close', async function (removed, done) {
235
- node._closing = true;
236
- if (removed) {
237
- node.debug('Node removed or disabled');
238
- } else {
239
- node.debug('Node restarting');
240
- }
241
- try {
242
- await node._initPromise;
243
- closeBroker(node, config, done);
244
- } catch (e) {
245
- done();
246
- }
247
- });
248
- ```
249
-
250
- ### 7.2 Create closeBroker function
251
- Extract cleanup into a standalone function. Access resources via `node._broker`, `node._server`, `node._wss`, `node._httpServer` with null checks:
252
- ```javascript
253
- function closeBroker (node, config, done) {
254
- process.nextTick(function () {
255
- function wsClose () {
256
- if (node._wss) {
257
- node._wss.close(function () {
258
- if (node._httpServer) {
259
- node._httpServer.close(function () { done(); });
260
- } else { done(); }
261
- });
262
- } else { done(); }
263
- }
264
- function serverClose () {
265
- if (node._server) {
266
- node._server.close(function () {
267
- if (node.mqtt_ws_path !== '' && node.fullPath) {
268
- delete listenerNodes[node.fullPath];
269
- if (node.server) {
270
- node.server.close(function () { wsClose(); });
271
- } else { wsClose(); }
272
- } else { wsClose(); }
273
- });
274
- } else { wsClose(); }
275
- }
276
- if (node._broker) {
277
- node._broker.close(function () { serverClose(); });
278
- } else { serverClose(); }
279
- });
280
- }
281
- ```
282
-
283
- ---
284
-
285
- ## TASK 8: Update test files for async init
286
-
287
- **Files:** all 6 files in `test/`
288
-
289
- ### 8.1 Add _initPromise wait pattern
290
- In every test that connects an MQTT client, use the async `helper.load()` (available since `node-red-node-test-helper` v0.3.5) and `await` the init promise:
291
- ```javascript
292
- await helper.load(aedesNode, flow);
293
- const n1 = helper.getNode('n1');
294
- await n1._initPromise;
295
- // ... existing test logic ...
296
- ```
297
-
298
- This replaces the nested callback + `.then()` pattern with flat async/await. Each `it()` callback must be declared `async`.
299
-
300
- ### 8.2 Tests that do NOT need changes
301
- Tests that only check `n1.name` or node existence ("should be loaded") work without waiting -- the name is set synchronously in the constructor.
302
-
303
- ### 8.3 Test files to update
304
- - `test/aedes_spec.js` -- all tests except "should be loaded"
305
- - `test/aedes_ws_spec.js` -- all tests except "should be loaded"
306
- - `test/aedes_secure_spec.js` -- all tests except "should be loaded"
307
- - `test/aedes_qos_spec.js` -- all tests
308
- - `test/aedes_retain_spec.js` -- all tests
309
- - `test/aedes_last_will_spec.js` -- all tests except "should be loaded"
310
-
311
- ---
312
-
313
- ## TASK 9: Install and verify
314
-
315
- ### 9.1 Install new dependencies
316
- ```bash
317
- npm install
318
- ```
319
-
320
- ### 9.2 Run full test suite
321
- ```bash
322
- npm test
323
- ```
324
-
325
- ### 9.3 Verify all scenarios
326
- - TCP plain connection
327
- - TCP with TLS (port 8883)
328
- - WebSocket via standalone port
329
- - WebSocket via Node-RED path
330
- - Secure WebSocket (WSS) on port
331
- - Secure WebSocket (WSS) on path
332
- - QoS 1 and QoS 2 persistence
333
- - Retained messages
334
- - Last will and testament
335
- - Authentication (valid + invalid credentials)
336
- - Multiple brokers on different ports
337
- - Port conflict detection
338
-
339
- ---
340
-
341
- ## Legacy branch relevance (v11, v0.15.x)
342
-
343
- Both legacy branches use Aedes 0.49.0. We checked whether Tasks 6 and 7.1 also apply there:
344
-
345
- - **Task 6 (subscribe/unsubscribe handlers) — YES, relevant for both branches.**
346
- Both `v11` and `v0.15.x` have the same latent bug: the handlers treat the first argument as a single object, but the Aedes API (even in 0.49.0) passes an array. This fix should be backported.
347
-
348
- - **Task 7.1 (close handler `removed` parameter) — NO, not relevant.**
349
- Both legacy branches use `this.on('close', function (done) {...})` with one parameter. Node-RED handles this via arity checking — if the function declares 1 param, Node-RED passes only `done`, so the handler works correctly on all Node-RED versions. The `removed` param is a nice-to-have, and the async-safe parts (`_initPromise`, `_closing`) only apply to aedes v1's async init.
@@ -1,107 +0,0 @@
1
- # Migration Plan: Node-RED 4 Best Practices
2
-
3
- ## Context
4
-
5
- The project was started with Node-RED v2 but `package.json` already requires `>=3.0.0`. Node-RED v3 went EOL June 2025. Only v4 is actively maintained. This plan aligns the project with Node-RED v4 best practices while keeping the changes minimal and low-risk.
6
-
7
- ## Recommended Order: Run this BEFORE the Aedes v1 migration
8
-
9
- Rationale:
10
- - These changes are small, low-risk, and independently testable
11
- - They establish a clean, verified baseline before the larger Aedes rewrite
12
- - The close handler update to `(removed, done)` carries over naturally into the Aedes migration's restructured close handler
13
-
14
- ---
15
-
16
- ## TASK 1: Update `node-red` version field in package.json
17
-
18
- **File:** `package.json` line 21
19
-
20
- ### 1.1 Keep minimum Node-RED version at >=3.0.0
21
- - No change needed -- `"version": ">=3.0.0"` already includes v4
22
- - Keeps broader compatibility for users still on v3
23
-
24
- ---
25
-
26
- ## TASK 2: Update close handler to use `removed` parameter
27
-
28
- **File:** `aedes.js` line 410
29
-
30
- ### 2.1 Add `removed` parameter
31
- Available since Node-RED v0.17. Compatible with v2, v3, and v4.
32
-
33
- **Current:**
34
- ```javascript
35
- this.on('close', function (done) {
36
- ```
37
-
38
- **New:**
39
- ```javascript
40
- this.on('close', function (removed, done) {
41
- ```
42
-
43
- ### 2.2 Optional: use `removed` for cleaner logging
44
- ```javascript
45
- this.on('close', function (removed, done) {
46
- if (removed) {
47
- node.debug('Node removed or disabled');
48
- } else {
49
- node.debug('Node restarting');
50
- }
51
- // ... existing cleanup ...
52
- ```
53
-
54
- Note: The close handler has a **15-second timeout** enforced by Node-RED. The current nested callback chain should stay well within that, but keep it in mind.
55
-
56
- ---
57
-
58
- ## TASK 3: Add missing dev dependencies to package.json
59
-
60
- **File:** `package.json`
61
-
62
- ### 3.1 Add semistandard and snazzy to devDependencies
63
- The test script uses `semistandard` and `snazzy` but they're **not listed in devDependencies**. They only work if globally installed, which breaks `npm test` for other contributors.
64
-
65
- Add to `devDependencies`:
66
- ```json
67
- "semistandard": "^17.0.0",
68
- "snazzy": "^9.0.0"
69
- ```
70
-
71
- ### 3.2 Alternative: Replace with ESLint (optional, larger scope)
72
- `semistandard` was [last updated Aug 2024](https://github.com/standard/semistandard) and is not actively maintained. ESLint is the modern standard. However, this is a larger change -- consider deferring to a separate effort.
73
-
74
- ---
75
-
76
- ## TASK 4: Verify test helper version
77
-
78
- **File:** `package.json` line 12
79
-
80
- ### 4.1 Already up to date
81
- `node-red-node-test-helper` v0.3.6 (Jan 2025) is the latest. Your `^0.3.6` is correct. No change needed.
82
-
83
- Notable: v0.3.5 added async `start`/`stop`/`load` support, which will be useful during the Aedes migration when tests need to `await _initPromise`.
84
-
85
- ---
86
-
87
- ## TASK 5: Run tests and verify
88
-
89
- ### 5.1 Install and test
90
- ```bash
91
- npm install
92
- npm test
93
- ```
94
-
95
- ### 5.2 Verify all existing tests still pass
96
- No functional changes were made -- only the close handler signature and package metadata.
97
-
98
- ---
99
-
100
- ## Summary of changes
101
-
102
- | Task | Risk | Effort | Backward compat |
103
- |------|------|--------|-----------------|
104
- | node-red version field | None | Trivial | v2+ (if >=3.0.0) or v4+ |
105
- | close(removed, done) | None | Trivial | v2+ (since v0.17) |
106
- | Add semistandard/snazzy to devDeps | None | Trivial | All versions |
107
- | Test helper version | None | None | Already current |