node-red-contrib-aedes 0.15.1 → 1.1.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.
@@ -9,7 +9,7 @@ jobs:
9
9
 
10
10
  strategy:
11
11
  matrix:
12
- node-version: [20.x, 22.x]
12
+ node-version: [22.x, 24.x]
13
13
  node-red: [3.x, 4.x]
14
14
 
15
15
  steps:
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # node-red-contrib-aedes Changelog
2
2
 
3
+ ## Feb 15, 2026, Version 1.1.0
4
+ ### Notable changes
5
+ - Improved help documentation
6
+ - Improved UI
7
+ - Added German locale (de)
8
+
9
+ ## Feb 14, 2026, Version 1.0.0
10
+ ### Notable changes
11
+ - Update aedes to version 1.0.0
12
+
3
13
  ## Jul 22, 2025, Version 0.15.0
4
14
  ### Notable changes
5
15
  - TLS configuration with local filess
package/README.md CHANGED
@@ -30,7 +30,7 @@ Just put this node on Node-RED and hit the deploy button. The MQTT Broker will r
30
30
  - Standard TCP Support
31
31
  - WebSocket Support via port or path
32
32
  - SSL / TLS
33
- - Message Persistence (In-memory, LevelDB or MongoDB)
33
+ - Message Persistence (In-memory or MongoDB)
34
34
 
35
35
  For more information see [Aedes](https://github.com/moscajs/aedes/blob/master/README.md#features).
36
36
 
@@ -43,7 +43,14 @@ You can also bind the WebSocket to the root `"/"` path and having `wss://yourser
43
43
 
44
44
  ## Version Compatibility
45
45
 
46
- The current version requires **Node.js >= 20** and **Node-RED >= 3.0**.
46
+ The current version is based on **Aedes version 1.0**, which introduces several breaking changes. If your environment requires Aedes version 0.51, you can switch to version 0.15.x of this package.
47
+
48
+ To install the compatible version using the `version-11` dist-tag:
49
+ ```sh
50
+ npm install node-red-contrib-aedes@version-15
51
+ ```
52
+
53
+ The current version and version 0.15.x require **Node.js >= 20** and **Node-RED >= 3.0**.
47
54
 
48
55
  If you are running an older Node.js version (e.g. on locked-down or legacy hardware), please use version **0.11.x** of this package.
49
56
 
package/aedes.html CHANGED
@@ -25,27 +25,28 @@
25
25
  <div id="node-config-aedes-broker-tabs-content" style="min-height:150px;">
26
26
  <div id="aedes-broker-tab-connection" style="display:none">
27
27
  <div class="form-row">
28
- <label for="node-input-mqtt_port"><i class="fa fa-globe"></i> <span
28
+ <label for="node-input-mqtt_port"><i class="fa fa-plug"></i> <span
29
29
  data-i18n="aedes-mqtt-broker.label.mqtt_port"></span></label>
30
30
  <input type="text" id="node-input-mqtt_port"
31
31
  data-i18n="[placeholder]aedes-mqtt-broker.placeholder.mqtt_port">
32
32
  </div>
33
33
  <div class="form-row">
34
- <label for="node-input-mqtt_ws_bind"><i class="fa fa-globe"></i> WS bind</label>
35
- <select id="node-input-mqtt_ws_bind" type="text" style="width:30%" text-center>
34
+ <label for="node-input-mqtt_ws_bind"><i class="fa fa-exchange"></i> <span
35
+ data-i18n="aedes-mqtt-broker.label.mqtt_ws_bind"></span></label>
36
+ <select id="node-input-mqtt_ws_bind" type="text" style="width:30%; text-align:center;">
36
37
  <option value="path" default>path</option>
37
38
  <option value="port">port</option>
38
39
  </select>
39
40
  </div>
40
41
  <div class="form-row" id="node-input-mqtt_ws_path-label">
41
- <label for="node-input-mqtt_ws_path"><i class="fa fa-globe"></i> <span
42
- data-i18n="aedes-mqtt-broker.label.mqtt_ws_path"></label>
42
+ <label for="node-input-mqtt_ws_path"><i class="fa fa-link"></i> <span
43
+ data-i18n="aedes-mqtt-broker.label.mqtt_ws_path"></span></label>
43
44
  <input type="text" id="node-input-mqtt_ws_path"
44
45
  data-i18n="[placeholder]aedes-mqtt-broker.placeholder.mqtt_ws_path">
45
46
  </div>
46
47
  <div class="form-row" id="node-input-mqtt_ws_port-label">
47
- <label for="node-input-mqtt_ws_port"><i class="fa fa-globe"></i> <span
48
- data-i18n="aedes-mqtt-broker.label.mqtt_ws_port"></label>
48
+ <label for="node-input-mqtt_ws_port"><i class="fa fa-plug"></i> <span
49
+ data-i18n="aedes-mqtt-broker.label.mqtt_ws_port"></span></label>
49
50
  <input type="text" id="node-input-mqtt_ws_port"
50
51
  data-i18n="[placeholder]aedes-mqtt-broker.placeholder.mqtt_ws_port">
51
52
  </div>
@@ -58,7 +59,7 @@
58
59
  <input type="checkbox" id="node-input-uselocalfiles"
59
60
  style="display: inline-block; width: auto; vertical-align: top;">
60
61
  <label for="node-input-uselocalfiles" style="width: 70%;"><span
61
- data-i18n="aedes-mqtt-broker.label.use-local-files"></label>
62
+ data-i18n="aedes-mqtt-broker.label.use-local-files"></span></label>
62
63
  </div>
63
64
 
64
65
  <div class="form-row">
@@ -122,17 +123,17 @@
122
123
  </div>
123
124
  <div id="aedes-broker-tab-persistence" style="display:none">
124
125
  <div class="form-row">
125
- <label for="node-input-persistence_bind"><i class="fa fa-globe"></i> <span
126
- data-i18n="aedes-mqtt-broker.label.persistence_bind"></label>
127
- <select id="node-input-persistence_bind" type="text" style="width:30%" text-center>
126
+ <label for="node-input-persistence_bind"><i class="fa fa-hdd-o"></i> <span
127
+ data-i18n="aedes-mqtt-broker.label.persistence_bind"></span></label>
128
+ <select id="node-input-persistence_bind" type="text" style="width:30%; text-align:center;">
128
129
  <option value="memory" data-i18n="aedes-mqtt-broker.label.persistence_memory" default></option>
129
130
  <option value="mongodb" data-i18n="aedes-mqtt-broker.label.persistence_mongodb"></option>
130
131
  <!-- <option value="level" data-i18n="aedes-mqtt-broker.label.persistence_level"></option> -->
131
132
  </select>
132
133
  </div>
133
- <div class="form-row" id="node-row-dburl" class="hide">
134
+ <div class="form-row hide" id="node-row-dburl">
134
135
  <label for="node-input-dburl"><i class="fa fa-database"></i> <span
135
- data-i18n="aedes-mqtt-broker.label.dburl"></label>
136
+ data-i18n="aedes-mqtt-broker.label.dburl"></span></label>
136
137
  <input type="text" id="node-input-dburl" data-i18n="[placeholder]aedes-mqtt-broker.placeholder.dburl">
137
138
  </div>
138
139
  </div>
@@ -157,9 +158,16 @@
157
158
  category: 'network',
158
159
  defaults: {
159
160
  name: { value: '' },
160
- mqtt_port: { value: 1883, validate: RED.validators.number() },
161
+ mqtt_port: { value: 1883, validate: function (v) {
162
+ var n = parseInt(v, 10);
163
+ return !isNaN(n) && n >= 1 && n <= 65535;
164
+ }},
161
165
  mqtt_ws_bind: { value: 'port', required: true },
162
- mqtt_ws_port: { value: null, required: false, validate: RED.validators.number(true) },
166
+ mqtt_ws_port: { value: null, required: false, validate: function (v) {
167
+ if (v === '' || v === null || v === undefined) return true;
168
+ var n = parseInt(v, 10);
169
+ return !isNaN(n) && n >= 1 && n <= 65535;
170
+ }},
163
171
  mqtt_ws_path: { value: '', required: false },
164
172
  cert: {
165
173
  value: '',
@@ -331,7 +339,7 @@
331
339
  $('#tls-button-key-clear').on('click', function () {
332
340
  clearNameData('key');
333
341
  });
334
- $("#tls-config-button-ca-clear").on("click", function() {
342
+ $("#tls-button-ca-clear").on("click", function() {
335
343
  clearNameData("ca");
336
344
  });
337
345
 
@@ -360,9 +368,11 @@
360
368
  $('#node-input-tls').val('');
361
369
  }
362
370
  if ($("#node-input-uselocalfiles").is(':checked')) {
363
- clearNameData("ca");
364
- clearNameData("cert");
365
- clearNameData("key");
371
+ ['ca', 'cert', 'key'].forEach(function (prop) {
372
+ $('#tls-' + prop + 'name').text('');
373
+ $('#node-input-' + prop + 'data').val('');
374
+ $('#node-input-' + prop + 'name').val('');
375
+ });
366
376
  } else {
367
377
  $("#node-input-ca").val("");
368
378
  $("#node-input-cert").val("");
package/aedes.js CHANGED
@@ -19,13 +19,13 @@ module.exports = function (RED) {
19
19
  const MongoPersistence = require('aedes-persistence-mongodb');
20
20
  // const { Level } = require('level');
21
21
  // const LevelPersistence = require('aedes-persistence-level');
22
- const aedes = require('aedes');
22
+ // aedes is ESM-only in v1 -- dynamically imported in initializeBroker()
23
23
  const fs = require('fs');
24
24
  const net = require('net');
25
25
  const tls = require('tls');
26
26
  const http = require('http');
27
27
  const https = require('https');
28
- const ws = require('websocket-stream');
28
+ const { WebSocketServer, createWebSocketStream } = require('ws');
29
29
 
30
30
  let serverUpgradeAdded = false;
31
31
  const listenerNodes = {};
@@ -51,131 +51,53 @@ module.exports = function (RED) {
51
51
  }
52
52
  }
53
53
 
54
- function AedesBrokerNode (config) {
55
- RED.nodes.createNode(this, config);
56
- this.mqtt_port = parseInt(config.mqtt_port, 10);
57
- this.mqtt_ws_port = parseInt(config.mqtt_ws_port, 10);
58
- this.mqtt_ws_path = '' + config.mqtt_ws_path;
59
- this.mqtt_ws_bind = config.mqtt_ws_bind;
60
- this.usetls = config.usetls;
61
-
62
- const certPath = config.cert ? config.cert.trim() : '';
63
- const keyPath = config.key ? config.key.trim() : '';
64
- const caPath = config.ca ? config.ca.trim() : '';
65
-
66
- this.uselocalfiles = config.uselocalfiles;
67
- this.dburl = config.dburl;
68
-
69
- if (this.mqtt_ws_bind === 'path') {
70
- this.mqtt_ws_port = 0;
71
- } else {
72
- this.mqtt_ws_path = '';
73
- }
74
-
75
- if (certPath.length > 0 || keyPath.length > 0 || caPath.length > 0) {
76
- if ((certPath.length > 0) !== (keyPath.length > 0)) {
77
- this.valid = false;
78
- this.error(RED._('tls.error.missing-file'));
79
- return;
80
- }
81
- try {
82
- if (certPath) {
83
- this.cert = fs.readFileSync(certPath);
84
- }
85
- if (keyPath) {
86
- this.key = fs.readFileSync(keyPath);
87
- }
88
- if (caPath) {
89
- this.ca = fs.readFileSync(caPath);
90
- }
91
- } catch (err) {
92
- this.valid = false;
93
- this.error(err.toString());
94
- return;
95
- }
96
- } else {
97
- if (this.credentials) {
98
- this.cert = this.credentials.certdata || '';
99
- this.key = this.credentials.keydata || '';
100
- this.ca = this.credentials.cadata || '';
101
- }
102
- }
103
- if (this.credentials) {
104
- this.username = this.credentials.username;
105
- this.password = this.credentials.password;
106
- }
107
-
108
- if (typeof this.usetls === 'undefined') {
109
- this.usetls = false;
110
- }
111
-
112
- const node = this;
113
-
114
- const aedesSettings = {};
115
- const serverOptions = {};
116
-
117
- if (config.persistence_bind === 'mongodb' && config.dburl) {
118
- aedesSettings.persistence = MongoPersistence({
119
- url: config.dburl
120
- });
121
- node.log('Start persistence to MongeDB');
122
- /*
123
- } else if (config.persistence_bind === 'level') {
124
- aedesSettings.persistence = LevelPersistence(new Level('leveldb'));
125
- node.log('Start persistence to LevelDB');
126
- */
127
- }
128
-
129
- if (this.cert && this.key && this.usetls) {
130
- serverOptions.cert = this.cert;
131
- serverOptions.key = this.key;
132
- serverOptions.ca = this.ca;
133
- }
54
+ async function initializeBroker (node, config, aedesSettings, serverOptions) {
55
+ const { Aedes } = await import('aedes');
56
+ const broker = await Aedes.createBroker(aedesSettings);
57
+ if (node._closing) { broker.close(); return; }
58
+ node._broker = broker;
134
59
 
135
- const broker = aedes.createBroker(aedesSettings);
136
60
  let server;
137
- if (this.usetls) {
61
+ if (node.usetls) {
138
62
  server = tls.createServer(serverOptions, broker.handle);
139
63
  } else {
140
64
  server = net.createServer(broker.handle);
141
65
  }
66
+ node._server = server;
142
67
 
143
- let wss = null;
144
- let httpServer = null;
145
-
146
- if (this.mqtt_ws_port) {
68
+ if (node.mqtt_ws_port) {
147
69
  // Awkward check since http or ws do not fire an error event in case the port is in use
148
70
  const testServer = net.createServer();
149
71
  testServer.once('error', function (err) {
150
72
  if (err.code === 'EADDRINUSE') {
151
73
  node.error(
152
- 'Error: Port ' + config.mqtt_ws_port + ' is already in use'
74
+ RED._('aedes-mqtt-broker.error.port-in-use', { port: config.mqtt_ws_port })
153
75
  );
154
76
  } else {
155
77
  node.error(
156
- 'Error creating net server on port ' +
157
- config.mqtt_ws_port +
158
- ', ' +
159
- err.toString()
78
+ RED._('aedes-mqtt-broker.error.server-error', { port: config.mqtt_ws_port, error: err.toString() })
160
79
  );
161
80
  }
81
+ node.status({ fill: 'red', shape: 'ring', text: 'aedes-mqtt-broker.status.error' });
162
82
  });
163
83
  testServer.once('listening', function () {
164
84
  testServer.close();
165
85
  });
166
86
 
167
87
  testServer.once('close', function () {
88
+ let httpServer;
168
89
  if (node.usetls) {
169
90
  httpServer = https.createServer(serverOptions);
170
91
  } else {
171
92
  httpServer = http.createServer();
172
93
  }
173
- wss = ws.createServer(
174
- {
175
- server: httpServer
176
- },
177
- broker.handle
178
- );
94
+ node._httpServer = httpServer;
95
+ const wss = new WebSocketServer({ server: httpServer });
96
+ wss.on('connection', function (websocket, req) {
97
+ const stream = createWebSocketStream(websocket);
98
+ broker.handle(stream, req);
99
+ });
100
+ node._wss = wss;
179
101
  httpServer.listen(config.mqtt_ws_port, function () {
180
102
  node.log(
181
103
  'Binding aedes mqtt server on ws port: ' + config.mqtt_ws_port
@@ -187,7 +109,7 @@ module.exports = function (RED) {
187
109
  });
188
110
  }
189
111
 
190
- if (this.mqtt_ws_path !== '') {
112
+ if (node.mqtt_ws_path !== '') {
191
113
  if (!serverUpgradeAdded) {
192
114
  RED.server.on('upgrade', handleServerUpgrade);
193
115
  serverUpgradeAdded = true;
@@ -206,46 +128,44 @@ module.exports = function (RED) {
206
128
  node.error(
207
129
  RED._('websocket.errors.duplicate-path', { path: node.mqtt_ws_path })
208
130
  );
209
- return;
210
- }
211
- listenerNodes[node.fullPath] = node;
212
- const serverOptions_ = {
213
- noServer: true
214
- };
215
- if (RED.settings.webSocketNodeVerifyClient) {
216
- serverOptions_.verifyClient = RED.settings.webSocketNodeVerifyClient;
217
- }
218
-
219
- node.server = ws.createServer(
220
- {
131
+ } else {
132
+ listenerNodes[node.fullPath] = node;
133
+ const serverOptions_ = {
221
134
  noServer: true
222
- },
223
- broker.handle
224
- );
135
+ };
136
+ if (RED.settings.webSocketNodeVerifyClient) {
137
+ serverOptions_.verifyClient = RED.settings.webSocketNodeVerifyClient;
138
+ }
225
139
 
226
- node.log('Binding aedes mqtt server on ws path: ' + node.fullPath);
140
+ node.server = new WebSocketServer(serverOptions_);
141
+ node.server.on('connection', function (websocket, req) {
142
+ const stream = createWebSocketStream(websocket);
143
+ broker.handle(stream, req);
144
+ });
145
+
146
+ node.log('Binding aedes mqtt server on ws path: ' + node.fullPath);
147
+ }
227
148
  }
228
149
 
229
150
  server.once('error', function (err) {
230
151
  if (err.code === 'EADDRINUSE') {
231
- node.error('Error: Port ' + config.mqtt_port + ' is already in use');
232
- node.status({
233
- fill: 'red',
234
- shape: 'ring',
235
- text: 'node-red:common.status.disconnected'
236
- });
152
+ node.error(
153
+ RED._('aedes-mqtt-broker.error.port-in-use', { port: config.mqtt_port })
154
+ );
237
155
  } else {
238
- node.error('Error: Port ' + config.mqtt_port + ' ' + err.toString());
239
- node.status({
240
- fill: 'red',
241
- shape: 'ring',
242
- text: 'node-red:common.status.disconnected'
243
- });
156
+ node.error(
157
+ RED._('aedes-mqtt-broker.error.server-error', { port: config.mqtt_port, error: err.toString() })
158
+ );
244
159
  }
160
+ node.status({
161
+ fill: 'red',
162
+ shape: 'ring',
163
+ text: 'aedes-mqtt-broker.status.error'
164
+ });
245
165
  });
246
166
 
247
- if (this.mqtt_port) {
248
- server.listen(this.mqtt_port, function () {
167
+ if (node.mqtt_port) {
168
+ server.listen(node.mqtt_port, function () {
249
169
  node.log('Binding aedes mqtt server on port: ' + config.mqtt_port);
250
170
  node.status({
251
171
  fill: 'green',
@@ -255,7 +175,7 @@ module.exports = function (RED) {
255
175
  });
256
176
  }
257
177
 
258
- if (this.credentials && this.username && this.password) {
178
+ if (node.credentials && node.username && node.password) {
259
179
  broker.authenticate = function (client, username, password, callback) {
260
180
  const authorized =
261
181
  username === node.username &&
@@ -383,7 +303,7 @@ module.exports = function (RED) {
383
303
  }
384
304
  });
385
305
 
386
- if (this.wires && this.wires[1] && this.wires[1].length > 0) {
306
+ if (node.wires && node.wires[1] && node.wires[1].length > 0) {
387
307
  node.log('Publish output wired. Enable broker publish event messages.');
388
308
  broker.on('publish', function (packet, client) {
389
309
  const msg = {
@@ -400,54 +320,143 @@ module.exports = function (RED) {
400
320
  broker.on('closed', function () {
401
321
  node.debug('Closed event');
402
322
  });
323
+ }
324
+
325
+ function AedesBrokerNode (config) {
326
+ RED.nodes.createNode(this, config);
327
+ this.mqtt_port = parseInt(config.mqtt_port, 10);
328
+ this.mqtt_ws_port = parseInt(config.mqtt_ws_port, 10);
329
+ this.mqtt_ws_path = '' + config.mqtt_ws_path;
330
+ this.mqtt_ws_bind = config.mqtt_ws_bind;
331
+ this.usetls = config.usetls;
332
+
333
+ const certPath = config.cert ? config.cert.trim() : '';
334
+ const keyPath = config.key ? config.key.trim() : '';
335
+ const caPath = config.ca ? config.ca.trim() : '';
336
+
337
+ this.uselocalfiles = config.uselocalfiles;
338
+ this.dburl = config.dburl;
339
+
340
+ if (this.mqtt_ws_bind === 'path') {
341
+ this.mqtt_ws_port = 0;
342
+ } else {
343
+ this.mqtt_ws_path = '';
344
+ }
403
345
 
404
- this.on('close', function (removed, done) {
346
+ if (certPath.length > 0 || keyPath.length > 0 || caPath.length > 0) {
347
+ if ((certPath.length > 0) !== (keyPath.length > 0)) {
348
+ this.valid = false;
349
+ this.error(RED._('tls.error.missing-file'));
350
+ return;
351
+ }
352
+ try {
353
+ if (certPath) {
354
+ this.cert = fs.readFileSync(certPath);
355
+ }
356
+ if (keyPath) {
357
+ this.key = fs.readFileSync(keyPath);
358
+ }
359
+ if (caPath) {
360
+ this.ca = fs.readFileSync(caPath);
361
+ }
362
+ } catch (err) {
363
+ this.valid = false;
364
+ this.error(err.toString());
365
+ return;
366
+ }
367
+ } else {
368
+ if (this.credentials) {
369
+ this.cert = this.credentials.certdata || '';
370
+ this.key = this.credentials.keydata || '';
371
+ this.ca = this.credentials.cadata || '';
372
+ }
373
+ }
374
+ if (this.credentials) {
375
+ this.username = this.credentials.username;
376
+ this.password = this.credentials.password;
377
+ }
378
+
379
+ if (typeof this.usetls === 'undefined') {
380
+ this.usetls = false;
381
+ }
382
+
383
+ const node = this;
384
+
385
+ const aedesSettings = {};
386
+ const serverOptions = {};
387
+
388
+ if (config.persistence_bind === 'mongodb' && config.dburl) {
389
+ aedesSettings.persistence = MongoPersistence({
390
+ url: config.dburl
391
+ });
392
+ node.log('Start persistence to MongoDB');
393
+ /*
394
+ } else if (config.persistence_bind === 'level') {
395
+ aedesSettings.persistence = LevelPersistence(new Level('leveldb'));
396
+ node.log('Start persistence to LevelDB');
397
+ */
398
+ }
399
+
400
+ if (this.cert && this.key && this.usetls) {
401
+ serverOptions.cert = this.cert;
402
+ serverOptions.key = this.key;
403
+ serverOptions.ca = this.ca;
404
+ }
405
+
406
+ node._closing = false;
407
+ node._broker = null;
408
+ node._server = null;
409
+ node._wss = null;
410
+ node._httpServer = null;
411
+
412
+ node._initPromise = initializeBroker(node, config, aedesSettings, serverOptions);
413
+ node._initPromise.catch(function (err) {
414
+ node.error(RED._('aedes-mqtt-broker.error.init-failed', { error: err.toString() }));
415
+ node.status({ fill: 'red', shape: 'ring', text: 'aedes-mqtt-broker.status.init-failed' });
416
+ });
417
+
418
+ this.on('close', async function (removed, done) {
419
+ node._closing = true;
405
420
  if (removed) {
406
421
  node.debug('Node removed or disabled');
407
422
  } else {
408
423
  node.debug('Node restarting');
409
424
  }
410
- process.nextTick(function onCloseDelayed () {
411
- function wsClose () {
412
- if (wss) {
413
- node.log(
414
- 'Unbinding aedes mqtt server from ws port: ' + config.mqtt_ws_port
415
- );
416
- wss.close(function () {
417
- node.debug('after wss.close(): ');
418
- httpServer.close(function () {
419
- node.debug('after httpServer.close(): ');
420
- done();
421
- });
422
- });
423
- } else {
424
- done();
425
- }
426
- }
425
+ try {
426
+ await node._initPromise;
427
+ closeBroker(node, done);
428
+ } catch (e) {
429
+ done();
430
+ }
431
+ });
432
+ }
427
433
 
428
- function brokerClose () {
429
- broker.close(function () {
430
- node.log(
431
- 'Unbinding aedes mqtt server from port: ' + config.mqtt_port
432
- );
433
- server.close(function () {
434
- node.debug('after server.close(): ');
435
- if (node.mqtt_ws_path !== '') {
436
- node.log(
437
- 'Unbinding aedes mqtt server from ws path: ' + node.fullPath
438
- );
439
- delete listenerNodes[node.fullPath];
440
- node.server.close(function () {
441
- wsClose();
442
- });
443
- } else {
444
- wsClose();
445
- }
446
- });
434
+ function closeBroker (node, done) {
435
+ process.nextTick(function () {
436
+ function wsClose () {
437
+ if (node._wss) {
438
+ node._wss.close(function () {
439
+ if (node._httpServer) {
440
+ node._httpServer.close(function () { done(); });
441
+ } else { done(); }
447
442
  });
448
- }
449
- brokerClose();
450
- });
443
+ } else { done(); }
444
+ }
445
+ function serverClose () {
446
+ if (node._server) {
447
+ node._server.close(function () {
448
+ if (node.mqtt_ws_path !== '' && node.fullPath) {
449
+ delete listenerNodes[node.fullPath];
450
+ if (node.server) {
451
+ node.server.close(function () { wsClose(); });
452
+ } else { wsClose(); }
453
+ } else { wsClose(); }
454
+ });
455
+ } else { wsClose(); }
456
+ }
457
+ if (node._broker) {
458
+ node._broker.close(function () { serverClose(); });
459
+ } else { serverClose(); }
451
460
  });
452
461
  }
453
462