node-red-contrib-aedes 0.15.0 → 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.
- package/.github/workflows/nodejs.yml +1 -1
- package/CHANGELOG.md +10 -0
- package/README.md +22 -2
- package/aedes.html +29 -19
- package/aedes.js +199 -191
- package/locales/de/aedes.html +110 -0
- package/locales/de/aedes.json +50 -0
- package/locales/en-US/aedes.html +103 -12
- package/locales/en-US/aedes.json +10 -1
- package/package.json +6 -4
- package/test/aedes_last_will_spec.js +25 -48
- package/test/aedes_qos_spec.js +83 -77
- package/test/aedes_retain_spec.js +84 -78
- package/test/aedes_spec.js +260 -67
- package/test/aedes_ws_spec.js +112 -38
- package/test/test-utils.js +17 -0
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
|
|
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
|
|
|
@@ -41,6 +41,26 @@ When your Node-RED server address is `https://yourserver/`, you can set the WebS
|
|
|
41
41
|
|
|
42
42
|
You can also bind the WebSocket to the root `"/"` path and having `wss://yourserver/` WebSocket listening at port `443` (or `ws://yourserver/` at port `80`).
|
|
43
43
|
|
|
44
|
+
## Version Compatibility
|
|
45
|
+
|
|
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**.
|
|
54
|
+
|
|
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.
|
|
56
|
+
|
|
57
|
+
To install the compatible version using the `version-11` dist-tag:
|
|
58
|
+
```sh
|
|
59
|
+
npm install node-red-contrib-aedes@version-11
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For more details, see [issue #85](https://github.com/martin-doyle/node-red-contrib-aedes/issues/85).
|
|
63
|
+
|
|
44
64
|
## License
|
|
45
|
-
|
|
65
|
+
|
|
46
66
|
Licensed under [MIT](./LICENSE).
|
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-
|
|
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-
|
|
35
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
126
|
-
data-i18n="aedes-mqtt-broker.label.persistence_bind"></label>
|
|
127
|
-
<select id="node-input-persistence_bind" type="text" style="width:30
|
|
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"
|
|
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:
|
|
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:
|
|
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-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
}
|
|
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;
|
|
107
59
|
|
|
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
|
-
}
|
|
134
|
-
|
|
135
|
-
const broker = aedes.createBroker(aedesSettings);
|
|
136
60
|
let server;
|
|
137
|
-
if (
|
|
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
|
-
|
|
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
|
-
'
|
|
74
|
+
RED._('aedes-mqtt-broker.error.port-in-use', { port: config.mqtt_ws_port })
|
|
153
75
|
);
|
|
154
76
|
} else {
|
|
155
77
|
node.error(
|
|
156
|
-
'
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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 (
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
224
|
-
|
|
135
|
+
};
|
|
136
|
+
if (RED.settings.webSocketNodeVerifyClient) {
|
|
137
|
+
serverOptions_.verifyClient = RED.settings.webSocketNodeVerifyClient;
|
|
138
|
+
}
|
|
225
139
|
|
|
226
|
-
|
|
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(
|
|
232
|
-
|
|
233
|
-
|
|
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(
|
|
239
|
-
|
|
240
|
-
|
|
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 (
|
|
248
|
-
server.listen(
|
|
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 (
|
|
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 &&
|
|
@@ -365,31 +285,25 @@ module.exports = function (RED) {
|
|
|
365
285
|
});
|
|
366
286
|
});
|
|
367
287
|
|
|
368
|
-
broker.on('subscribe', function (
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
topic: subscription.topic,
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
};
|
|
377
|
-
node.send([msg, null]);
|
|
288
|
+
broker.on('subscribe', function (subscriptions, client) {
|
|
289
|
+
for (const subscription of subscriptions) {
|
|
290
|
+
node.send([{
|
|
291
|
+
topic: 'subscribe',
|
|
292
|
+
payload: { topic: subscription.topic, qos: subscription.qos, client }
|
|
293
|
+
}, null]);
|
|
294
|
+
}
|
|
378
295
|
});
|
|
379
296
|
|
|
380
|
-
broker.on('unsubscribe', function (
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
node.send([msg, null]);
|
|
297
|
+
broker.on('unsubscribe', function (unsubscriptions, client) {
|
|
298
|
+
for (const topic of unsubscriptions) {
|
|
299
|
+
node.send([{
|
|
300
|
+
topic: 'unsubscribe',
|
|
301
|
+
payload: { topic, client }
|
|
302
|
+
}, null]);
|
|
303
|
+
}
|
|
390
304
|
});
|
|
391
305
|
|
|
392
|
-
if (
|
|
306
|
+
if (node.wires && node.wires[1] && node.wires[1].length > 0) {
|
|
393
307
|
node.log('Publish output wired. Enable broker publish event messages.');
|
|
394
308
|
broker.on('publish', function (packet, client) {
|
|
395
309
|
const msg = {
|
|
@@ -406,49 +320,143 @@ module.exports = function (RED) {
|
|
|
406
320
|
broker.on('closed', function () {
|
|
407
321
|
node.debug('Closed event');
|
|
408
322
|
});
|
|
323
|
+
}
|
|
409
324
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
wss.close(function () {
|
|
418
|
-
node.debug('after wss.close(): ');
|
|
419
|
-
httpServer.close(function () {
|
|
420
|
-
node.debug('after httpServer.close(): ');
|
|
421
|
-
done();
|
|
422
|
-
});
|
|
423
|
-
});
|
|
424
|
-
} else {
|
|
425
|
-
done();
|
|
426
|
-
}
|
|
427
|
-
}
|
|
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;
|
|
428
332
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
+
}
|
|
345
|
+
|
|
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);
|
|
449
358
|
}
|
|
450
|
-
|
|
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
|
|
451
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;
|
|
420
|
+
if (removed) {
|
|
421
|
+
node.debug('Node removed or disabled');
|
|
422
|
+
} else {
|
|
423
|
+
node.debug('Node restarting');
|
|
424
|
+
}
|
|
425
|
+
try {
|
|
426
|
+
await node._initPromise;
|
|
427
|
+
closeBroker(node, done);
|
|
428
|
+
} catch (e) {
|
|
429
|
+
done();
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
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(); }
|
|
442
|
+
});
|
|
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(); }
|
|
452
460
|
});
|
|
453
461
|
}
|
|
454
462
|
|