@node-red/nodes 2.2.1 → 3.0.0-beta.2
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/99-sample.html.demo +1 -1
- package/core/common/05-junction.html +5 -0
- package/core/common/05-junction.js +12 -0
- package/core/common/20-inject.html +25 -13
- package/core/common/21-debug.html +59 -6
- package/core/common/21-debug.js +57 -27
- package/core/common/60-link.html +65 -29
- package/core/common/60-link.js +169 -20
- package/core/common/lib/debug/debug-utils.js +34 -1
- package/core/function/10-function.html +57 -21
- package/core/function/10-switch.html +19 -12
- package/core/function/10-switch.js +1 -0
- package/core/function/15-change.html +40 -12
- package/core/function/16-range.html +14 -5
- package/core/function/80-template.html +16 -12
- package/core/function/89-delay.html +46 -6
- package/core/function/89-trigger.html +12 -4
- package/core/function/rbe.html +7 -3
- package/core/network/05-tls.html +10 -4
- package/core/network/06-httpproxy.html +10 -1
- package/core/network/10-mqtt.html +73 -17
- package/core/network/10-mqtt.js +252 -105
- package/core/network/21-httpin.html +10 -6
- package/core/network/21-httprequest.html +219 -12
- package/core/network/21-httprequest.js +98 -17
- package/core/network/22-websocket.html +19 -5
- package/core/network/22-websocket.js +16 -13
- package/core/network/31-tcpin.html +47 -10
- package/core/network/31-tcpin.js +8 -3
- package/core/network/32-udp.html +14 -2
- package/core/parsers/70-CSV.html +4 -1
- package/core/parsers/70-JSON.html +3 -2
- package/core/parsers/70-XML.html +2 -1
- package/core/parsers/70-YAML.html +2 -1
- package/core/sequence/17-split.html +6 -2
- package/core/sequence/19-batch.html +28 -4
- package/core/storage/10-file.html +68 -8
- package/core/storage/10-file.js +46 -3
- package/core/storage/23-watch.html +2 -1
- package/core/storage/23-watch.js +21 -43
- package/locales/de/messages.json +1 -0
- package/locales/en-US/common/60-link.html +19 -3
- package/locales/en-US/messages.json +71 -18
- package/locales/en-US/network/21-httprequest.html +1 -1
- package/locales/en-US/storage/10-file.html +6 -2
- package/locales/ja/common/60-link.html +13 -0
- package/locales/ja/messages.json +84 -32
- package/locales/ja/network/21-httprequest.html +1 -1
- package/locales/ja/storage/10-file.html +8 -6
- package/locales/ko/messages.json +1 -0
- package/locales/ru/messages.json +1 -0
- package/locales/zh-CN/messages.json +1 -0
- package/locales/zh-TW/messages.json +1 -0
- package/package.json +12 -12
package/core/network/10-mqtt.js
CHANGED
|
@@ -20,7 +20,30 @@ module.exports = function(RED) {
|
|
|
20
20
|
var isUtf8 = require('is-utf8');
|
|
21
21
|
var HttpsProxyAgent = require('https-proxy-agent');
|
|
22
22
|
var url = require('url');
|
|
23
|
-
|
|
23
|
+
const knownMediaTypes = {
|
|
24
|
+
"text/css":"string",
|
|
25
|
+
"text/html":"string",
|
|
26
|
+
"text/plain":"string",
|
|
27
|
+
"text/html":"string",
|
|
28
|
+
"application/json":"json",
|
|
29
|
+
"application/octet-stream":"buffer",
|
|
30
|
+
"application/pdf":"buffer",
|
|
31
|
+
"application/x-gtar":"buffer",
|
|
32
|
+
"application/x-gzip":"buffer",
|
|
33
|
+
"application/x-tar":"buffer",
|
|
34
|
+
"application/xml":"string",
|
|
35
|
+
"application/zip":"buffer",
|
|
36
|
+
"audio/aac":"buffer",
|
|
37
|
+
"audio/ac3":"buffer",
|
|
38
|
+
"audio/basic":"buffer",
|
|
39
|
+
"audio/mp4":"buffer",
|
|
40
|
+
"audio/ogg":"buffer",
|
|
41
|
+
"image/bmp":"buffer",
|
|
42
|
+
"image/gif":"buffer",
|
|
43
|
+
"image/jpeg":"buffer",
|
|
44
|
+
"image/tiff":"buffer",
|
|
45
|
+
"image/png":"buffer",
|
|
46
|
+
}
|
|
24
47
|
//#region "Supporting functions"
|
|
25
48
|
function matchTopic(ts,t) {
|
|
26
49
|
if (ts == "#") {
|
|
@@ -68,12 +91,21 @@ module.exports = function(RED) {
|
|
|
68
91
|
}
|
|
69
92
|
|
|
70
93
|
/**
|
|
71
|
-
* Test a topic string is valid
|
|
94
|
+
* Test a topic string is valid for subscription
|
|
72
95
|
* @param {string} topic
|
|
73
96
|
* @returns `true` if it is a valid topic
|
|
74
97
|
*/
|
|
75
98
|
function isValidSubscriptionTopic(topic) {
|
|
76
|
-
return /^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/.test(topic)
|
|
99
|
+
return /^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/.test(topic);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Test a topic string is valid for publishing
|
|
104
|
+
* @param {string} topic
|
|
105
|
+
* @returns `true` if it is a valid topic
|
|
106
|
+
*/
|
|
107
|
+
function isValidPublishTopic(topic) {
|
|
108
|
+
return !/[\+#\b\f\n\r\t\v\0]/.test(topic);
|
|
77
109
|
}
|
|
78
110
|
|
|
79
111
|
/**
|
|
@@ -103,7 +135,7 @@ module.exports = function(RED) {
|
|
|
103
135
|
if(src[propName] === "true" || src[propName] === true) {
|
|
104
136
|
dst[propName] = true;
|
|
105
137
|
} else if(src[propName] === "false" || src[propName] === false) {
|
|
106
|
-
dst[propName] =
|
|
138
|
+
dst[propName] = false;
|
|
107
139
|
}
|
|
108
140
|
} else {
|
|
109
141
|
if(def != undefined) dst[propName] = def;
|
|
@@ -188,6 +220,19 @@ module.exports = function(RED) {
|
|
|
188
220
|
*/
|
|
189
221
|
function subscriptionHandler(node, datatype ,topic, payload, packet) {
|
|
190
222
|
const v5 = node.brokerConn.options && node.brokerConn.options.protocolVersion == 5;
|
|
223
|
+
var msg = {topic:topic, payload:null, qos:packet.qos, retain:packet.retain};
|
|
224
|
+
if(v5 && packet.properties) {
|
|
225
|
+
setStrProp(packet.properties, msg, "responseTopic");
|
|
226
|
+
setBufferProp(packet.properties, msg, "correlationData");
|
|
227
|
+
setStrProp(packet.properties, msg, "contentType");
|
|
228
|
+
setIntProp(packet.properties, msg, "messageExpiryInterval", 0);
|
|
229
|
+
setBoolProp(packet.properties, msg, "payloadFormatIndicator");
|
|
230
|
+
setStrProp(packet.properties, msg, "reasonString");
|
|
231
|
+
setUserProperties(packet.properties.userProperties, msg);
|
|
232
|
+
}
|
|
233
|
+
const v5isUtf8 = v5 ? msg.payloadFormatIndicator === true : null;
|
|
234
|
+
const v5HasMediaType = v5 ? !!msg.contentType : null;
|
|
235
|
+
const v5MediaTypeLC = v5 ? (msg.contentType + "").toLowerCase() : null;
|
|
191
236
|
|
|
192
237
|
if (datatype === "buffer") {
|
|
193
238
|
// payload = payload;
|
|
@@ -196,25 +241,65 @@ module.exports = function(RED) {
|
|
|
196
241
|
} else if (datatype === "utf8") {
|
|
197
242
|
payload = payload.toString('utf8');
|
|
198
243
|
} else if (datatype === "json") {
|
|
199
|
-
if (isUtf8(payload)) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
catch(e) {
|
|
244
|
+
if (v5isUtf8 || isUtf8(payload)) {
|
|
245
|
+
try {
|
|
246
|
+
payload = JSON.parse(payload.toString());
|
|
247
|
+
} catch (e) {
|
|
248
|
+
node.error(RED._("mqtt.errors.invalid-json-parse"), { payload: payload, topic: topic, qos: packet.qos, retain: packet.retain }); return;
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
node.error((RED._("mqtt.errors.invalid-json-string")), { payload: payload, topic: topic, qos: packet.qos, retain: packet.retain }); return;
|
|
203
252
|
}
|
|
204
|
-
else { node.error((RED._("mqtt.errors.invalid-json-string")),{payload:payload, topic:topic, qos:packet.qos, retain:packet.retain}); return; }
|
|
205
253
|
} else {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
254
|
+
//"auto" (legacy) or "auto-detect" (new default)
|
|
255
|
+
if (v5isUtf8 || v5HasMediaType) {
|
|
256
|
+
const outputType = knownMediaTypes[v5MediaTypeLC]
|
|
257
|
+
switch (outputType) {
|
|
258
|
+
case "string":
|
|
259
|
+
payload = payload.toString();
|
|
260
|
+
break;
|
|
261
|
+
case "buffer":
|
|
262
|
+
//no change
|
|
263
|
+
break;
|
|
264
|
+
case "json":
|
|
265
|
+
try {
|
|
266
|
+
//since v5 type states this should be JSON, parse it & error out if NOT JSON
|
|
267
|
+
payload = payload.toString()
|
|
268
|
+
const obj = JSON.parse(payload);
|
|
269
|
+
if (datatype === "auto-detect") {
|
|
270
|
+
payload = obj; //as mode is "auto-detect", return the parsed JSON
|
|
271
|
+
}
|
|
272
|
+
} catch (e) {
|
|
273
|
+
node.error(RED._("mqtt.errors.invalid-json-parse"), { payload: payload, topic: topic, qos: packet.qos, retain: packet.retain }); return;
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
default:
|
|
277
|
+
if (v5isUtf8 || isUtf8(payload)) {
|
|
278
|
+
payload = payload.toString(); //auto String
|
|
279
|
+
if (datatype === "auto-detect") {
|
|
280
|
+
try {
|
|
281
|
+
payload = JSON.parse(payload); //auto to parsed object (attempt)
|
|
282
|
+
} catch (e) {
|
|
283
|
+
/* mute error - it simply isnt JSON, just leave payload as a string */
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
} else if (isUtf8(payload)) {
|
|
290
|
+
payload = payload.toString(); //auto String
|
|
291
|
+
if (datatype === "auto-detect") {
|
|
292
|
+
try {
|
|
293
|
+
payload = JSON.parse(payload);
|
|
294
|
+
} catch (e) {
|
|
295
|
+
/* mute error - it simply isnt JSON, just leave payload as a string */
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} //else {
|
|
299
|
+
//leave as buffer
|
|
300
|
+
//}
|
|
217
301
|
}
|
|
302
|
+
msg.payload = payload;
|
|
218
303
|
if ((node.brokerConn.broker === "localhost")||(node.brokerConn.broker === "127.0.0.1")) {
|
|
219
304
|
msg._topic = topic;
|
|
220
305
|
}
|
|
@@ -264,38 +349,15 @@ module.exports = function(RED) {
|
|
|
264
349
|
msg.messageExpiryInterval = node.messageExpiryInterval;
|
|
265
350
|
}
|
|
266
351
|
}
|
|
267
|
-
if (msg.userProperties && typeof msg.userProperties !== "object") {
|
|
268
|
-
delete msg.userProperties;
|
|
269
|
-
}
|
|
270
|
-
if (hasProperty(msg, "topicAlias") && !isNaN(msg.topicAlias) && (msg.topicAlias === 0 || bsp.topicAliasMaximum === 0 || msg.topicAlias > bsp.topicAliasMaximum)) {
|
|
271
|
-
delete msg.topicAlias;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
352
|
if (hasProperty(msg, "payload")) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
//NOTE: A value of 0 (in server props topicAliasMaximum) indicates that the Server does not accept any Topic Aliases on this connection
|
|
281
|
-
if (hasProperty(msg, "topicAlias") && !isNaN(msg.topicAlias) && msg.topicAlias >= 0 && bsp.topicAliasMaximum && bsp.topicAliasMaximum >= msg.topicAlias) {
|
|
282
|
-
topicOK = true;
|
|
283
|
-
msg.topic = ""; //must be empty string
|
|
284
|
-
} else if (hasProperty(msg, "responseTopic") && (typeof msg.responseTopic === "string") && (msg.responseTopic !== "")) {
|
|
285
|
-
//TODO: if topic is empty but responseTopic has a string value, use that instead. Is this desirable?
|
|
286
|
-
topicOK = true;
|
|
287
|
-
msg.topic = msg.responseTopic;
|
|
288
|
-
//TODO: delete msg.responseTopic - to prevent it being resent?
|
|
353
|
+
// send the message
|
|
354
|
+
node.brokerConn.publish(msg, function(err) {
|
|
355
|
+
if(err && err.warn) {
|
|
356
|
+
node.warn(err);
|
|
357
|
+
return;
|
|
289
358
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (topicOK) {
|
|
294
|
-
node.brokerConn.publish(msg, done); // send the message
|
|
295
|
-
} else {
|
|
296
|
-
node.warn(RED._("mqtt.errors.invalid-topic"));
|
|
297
|
-
done();
|
|
298
|
-
}
|
|
359
|
+
done(err);
|
|
360
|
+
});
|
|
299
361
|
} else {
|
|
300
362
|
done();
|
|
301
363
|
}
|
|
@@ -391,6 +453,7 @@ module.exports = function(RED) {
|
|
|
391
453
|
node.options = {};
|
|
392
454
|
node.queue = [];
|
|
393
455
|
node.subscriptions = {};
|
|
456
|
+
node.clientListeners = []
|
|
394
457
|
/** @type {mqtt.MqttClient}*/ this.client;
|
|
395
458
|
node.setOptions = function(opts, init) {
|
|
396
459
|
if(!opts || typeof opts !== "object") {
|
|
@@ -415,8 +478,12 @@ module.exports = function(RED) {
|
|
|
415
478
|
setIfHasProperty(opts, node, "topicAliasMaximum", init);
|
|
416
479
|
setIfHasProperty(opts, node, "maximumPacketSize", init);
|
|
417
480
|
setIfHasProperty(opts, node, "receiveMaximum", init);
|
|
418
|
-
|
|
419
|
-
|
|
481
|
+
//https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901116
|
|
482
|
+
if (hasProperty(opts, "userProperties")) {
|
|
483
|
+
node.userProperties = opts.userProperties;
|
|
484
|
+
} else if (hasProperty(opts, "userProps")) {
|
|
485
|
+
node.userProperties = opts.userProps;
|
|
486
|
+
}
|
|
420
487
|
|
|
421
488
|
function createLWT(topic, payload, qos, retain, v5opts, v5SubPropName) {
|
|
422
489
|
let message = undefined;
|
|
@@ -465,7 +532,7 @@ module.exports = function(RED) {
|
|
|
465
532
|
};
|
|
466
533
|
if(hasProperty(opts, "willTopic")) {
|
|
467
534
|
//will v5 properties must be set in the "properties" sub object
|
|
468
|
-
node.options.will = createLWT(opts.willTopic, opts.willPayload, opts.willQos, opts.willRetain, opts.willMsg, "
|
|
535
|
+
node.options.will = createLWT(opts.willTopic, opts.willPayload, opts.willQos, opts.willRetain, opts.willMsg, "properties");
|
|
469
536
|
};
|
|
470
537
|
} else {
|
|
471
538
|
//update options
|
|
@@ -637,24 +704,8 @@ module.exports = function(RED) {
|
|
|
637
704
|
|
|
638
705
|
node.deregister = function(mqttNode,done) {
|
|
639
706
|
delete node.users[mqttNode.id];
|
|
640
|
-
if (node.closing) {
|
|
641
|
-
|
|
642
|
-
}
|
|
643
|
-
if (Object.keys(node.users).length === 0) {
|
|
644
|
-
if (node.client && node.client.connected) {
|
|
645
|
-
// Send close message
|
|
646
|
-
if (node.closeMessage) {
|
|
647
|
-
node.publish(node.closeMessage,function(err) {
|
|
648
|
-
node.client.end(done);
|
|
649
|
-
});
|
|
650
|
-
} else {
|
|
651
|
-
node.client.end(done);
|
|
652
|
-
}
|
|
653
|
-
return;
|
|
654
|
-
} else {
|
|
655
|
-
if (node.client) { node.client.end(); }
|
|
656
|
-
return done();
|
|
657
|
-
}
|
|
707
|
+
if (!node.closing && node.connected && Object.keys(node.users).length === 0) {
|
|
708
|
+
node.disconnect();
|
|
658
709
|
}
|
|
659
710
|
done();
|
|
660
711
|
};
|
|
@@ -663,15 +714,22 @@ module.exports = function(RED) {
|
|
|
663
714
|
}
|
|
664
715
|
node.connect = function (callback) {
|
|
665
716
|
if (node.canConnect()) {
|
|
717
|
+
node.closing = false;
|
|
666
718
|
node.connecting = true;
|
|
667
719
|
setStatusConnecting(node, true);
|
|
668
720
|
try {
|
|
669
721
|
node.serverProperties = {};
|
|
722
|
+
if(node.client) {
|
|
723
|
+
//belt and braces to avoid left over clients
|
|
724
|
+
node.client.end(true);
|
|
725
|
+
node._clientRemoveListeners();
|
|
726
|
+
}
|
|
670
727
|
node.client = mqtt.connect(node.brokerurl, node.options);
|
|
671
728
|
node.client.setMaxListeners(0);
|
|
672
|
-
let callbackDone = false; //prevent re-connects causing node.
|
|
729
|
+
let callbackDone = false; //prevent re-connects causing node._clientOn('connect' firing callback multiple times
|
|
673
730
|
// Register successful connect or reconnect handler
|
|
674
|
-
node.
|
|
731
|
+
node._clientOn('connect', function (connack) {
|
|
732
|
+
node.closing = false;
|
|
675
733
|
node.connecting = false;
|
|
676
734
|
node.connected = true;
|
|
677
735
|
if(!callbackDone && typeof callback == "function") {
|
|
@@ -702,7 +760,7 @@ module.exports = function(RED) {
|
|
|
702
760
|
}
|
|
703
761
|
setStatusConnected(node, true);
|
|
704
762
|
// Remove any existing listeners before resubscribing to avoid duplicates in the event of a re-connection
|
|
705
|
-
node.
|
|
763
|
+
node._clientRemoveListeners('message');
|
|
706
764
|
|
|
707
765
|
// Re-subscribe to stored topics
|
|
708
766
|
for (var s in node.subscriptions) {
|
|
@@ -714,7 +772,7 @@ module.exports = function(RED) {
|
|
|
714
772
|
if (node.subscriptions[s].hasOwnProperty(r)) {
|
|
715
773
|
qos = Math.max(qos,node.subscriptions[s][r].qos);
|
|
716
774
|
_options = node.subscriptions[s][r].options;
|
|
717
|
-
node.
|
|
775
|
+
node._clientOn('message',node.subscriptions[s][r].handler);
|
|
718
776
|
}
|
|
719
777
|
}
|
|
720
778
|
_options.qos = _options.qos || qos;
|
|
@@ -727,11 +785,11 @@ module.exports = function(RED) {
|
|
|
727
785
|
node.publish(node.birthMessage);
|
|
728
786
|
}
|
|
729
787
|
});
|
|
730
|
-
node.
|
|
788
|
+
node._clientOn("reconnect", function() {
|
|
731
789
|
setStatusConnecting(node, true);
|
|
732
790
|
});
|
|
733
791
|
//Broker Disconnect - V5 event
|
|
734
|
-
node.
|
|
792
|
+
node._clientOn("disconnect", function(packet) {
|
|
735
793
|
//Emitted after receiving disconnect packet from broker. MQTT 5.0 feature.
|
|
736
794
|
const rc = (packet && packet.properties && packet.reasonCode) || packet.reasonCode;
|
|
737
795
|
const rs = packet && packet.properties && packet.properties.reasonString || "";
|
|
@@ -740,11 +798,12 @@ module.exports = function(RED) {
|
|
|
740
798
|
reasonCode: rc,
|
|
741
799
|
reasonString: rs
|
|
742
800
|
}
|
|
801
|
+
node.connected = false;
|
|
743
802
|
node.log(RED._("mqtt.state.broker-disconnected", details));
|
|
744
803
|
setStatusDisconnected(node, true);
|
|
745
804
|
});
|
|
746
805
|
// Register disconnect handlers
|
|
747
|
-
node.
|
|
806
|
+
node._clientOn('close', function () {
|
|
748
807
|
if (node.connected) {
|
|
749
808
|
node.connected = false;
|
|
750
809
|
node.log(RED._("mqtt.state.disconnected",{broker:(node.clientid?node.clientid+"@":"")+node.brokerurl}));
|
|
@@ -756,33 +815,59 @@ module.exports = function(RED) {
|
|
|
756
815
|
|
|
757
816
|
// Register connect error handler
|
|
758
817
|
// The client's own reconnect logic will take care of errors
|
|
759
|
-
node.
|
|
818
|
+
node._clientOn('error', function (error) {
|
|
760
819
|
});
|
|
761
820
|
}catch(err) {
|
|
762
821
|
console.log(err);
|
|
763
822
|
}
|
|
764
823
|
}
|
|
765
824
|
};
|
|
825
|
+
|
|
766
826
|
node.disconnect = function (callback) {
|
|
767
827
|
const _callback = function () {
|
|
768
|
-
|
|
828
|
+
if(node.connected || node.connecting) {
|
|
829
|
+
setStatusDisconnected(node, true);
|
|
830
|
+
}
|
|
831
|
+
if(node.client) { node._clientRemoveListeners(); }
|
|
769
832
|
node.connecting = false;
|
|
770
833
|
node.connected = false;
|
|
771
834
|
callback && typeof callback == "function" && callback();
|
|
772
835
|
};
|
|
773
|
-
|
|
774
|
-
if(node.
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
836
|
+
if(!node.client) { return _callback(); }
|
|
837
|
+
if(node.closing) { return _callback(); }
|
|
838
|
+
|
|
839
|
+
let waitEnd = (client, ms) => {
|
|
840
|
+
return new Promise( (resolve, reject) => {
|
|
841
|
+
node.closing = true;
|
|
842
|
+
if(!client) {
|
|
843
|
+
resolve();
|
|
844
|
+
} else {
|
|
845
|
+
const t = setTimeout(() => {
|
|
846
|
+
//clean end() has exceeded WAIT_END, lets force end!
|
|
847
|
+
client && client.end(true);
|
|
848
|
+
reject();
|
|
849
|
+
}, ms);
|
|
850
|
+
client.end(() => {
|
|
851
|
+
clearTimeout(t);
|
|
852
|
+
resolve()
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
};
|
|
857
|
+
if(node.connected && node.closeMessage) {
|
|
858
|
+
node.publish(node.closeMessage, function (err) {
|
|
859
|
+
waitEnd(node.client, 2000).then(() => {
|
|
860
|
+
_callback();
|
|
861
|
+
}).catch((e) => {
|
|
862
|
+
_callback();
|
|
863
|
+
})
|
|
864
|
+
});
|
|
784
865
|
} else {
|
|
785
|
-
|
|
866
|
+
waitEnd(node.client, 2000).then(() => {
|
|
867
|
+
_callback();
|
|
868
|
+
}).catch((e) => {
|
|
869
|
+
_callback();
|
|
870
|
+
})
|
|
786
871
|
}
|
|
787
872
|
}
|
|
788
873
|
node.subscriptionIds = {};
|
|
@@ -819,7 +904,7 @@ module.exports = function(RED) {
|
|
|
819
904
|
};
|
|
820
905
|
node.subscriptions[topic][ref] = sub;
|
|
821
906
|
if (node.connected) {
|
|
822
|
-
node.
|
|
907
|
+
node._clientOn('message',sub.handler);
|
|
823
908
|
node.client.subscribe(topic, options);
|
|
824
909
|
}
|
|
825
910
|
};
|
|
@@ -830,7 +915,7 @@ module.exports = function(RED) {
|
|
|
830
915
|
if (sub) {
|
|
831
916
|
if (sub[ref]) {
|
|
832
917
|
if(node.client) {
|
|
833
|
-
node.
|
|
918
|
+
node._clientRemoveListeners('message',sub[ref].handler);
|
|
834
919
|
}
|
|
835
920
|
delete sub[ref];
|
|
836
921
|
}
|
|
@@ -864,8 +949,18 @@ module.exports = function(RED) {
|
|
|
864
949
|
qos: msg.qos || 0,
|
|
865
950
|
retain: msg.retain || false
|
|
866
951
|
};
|
|
952
|
+
let topicOK = hasProperty(msg, "topic") && (typeof msg.topic === "string") && (isValidPublishTopic(msg.topic));
|
|
867
953
|
//https://github.com/mqttjs/MQTT.js/blob/master/README.md#mqttclientpublishtopic-message-options-callback
|
|
868
954
|
if(node.options.protocolVersion == 5) {
|
|
955
|
+
const bsp = node.serverProperties || {};
|
|
956
|
+
if (msg.userProperties && typeof msg.userProperties !== "object") {
|
|
957
|
+
delete msg.userProperties;
|
|
958
|
+
}
|
|
959
|
+
if (hasProperty(msg, "topicAlias") && !isNaN(Number(msg.topicAlias))) {
|
|
960
|
+
msg.topicAlias = parseInt(msg.topicAlias);
|
|
961
|
+
} else {
|
|
962
|
+
delete msg.topicAlias;
|
|
963
|
+
}
|
|
869
964
|
options.properties = options.properties || {};
|
|
870
965
|
setStrProp(msg, options.properties, "responseTopic");
|
|
871
966
|
setBufferProp(msg, options.properties, "correlationData");
|
|
@@ -875,31 +970,75 @@ module.exports = function(RED) {
|
|
|
875
970
|
setIntProp(msg, options.properties, "topicAlias", 1, node.serverProperties.topicAliasMaximum || 0);
|
|
876
971
|
setBoolProp(msg, options.properties, "payloadFormatIndicator");
|
|
877
972
|
//FUTURE setIntProp(msg, options.properties, "subscriptionIdentifier", 1, 268435455);
|
|
878
|
-
|
|
879
|
-
|
|
973
|
+
|
|
974
|
+
//check & sanitise topic
|
|
975
|
+
if (topicOK && options.properties.topicAlias) {
|
|
976
|
+
let aliasValid = (bsp.topicAliasMaximum && bsp.topicAliasMaximum >= options.properties.topicAlias);
|
|
977
|
+
if (!aliasValid) {
|
|
880
978
|
done("Invalid topicAlias");
|
|
881
979
|
return
|
|
882
980
|
}
|
|
883
981
|
if (node.topicAliases[options.properties.topicAlias] === msg.topic) {
|
|
884
|
-
msg.topic = ""
|
|
982
|
+
msg.topic = "";
|
|
885
983
|
} else {
|
|
886
|
-
node.topicAliases[options.properties.topicAlias] = msg.topic
|
|
984
|
+
node.topicAliases[options.properties.topicAlias] = msg.topic;
|
|
887
985
|
}
|
|
986
|
+
} else if (!msg.topic && options.properties.responseTopic) {
|
|
987
|
+
msg.topic = msg.responseTopic;
|
|
988
|
+
topicOK = isValidPublishTopic(msg.topic);
|
|
989
|
+
delete msg.responseTopic; //prevent responseTopic being resent?
|
|
888
990
|
}
|
|
889
991
|
}
|
|
890
992
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
993
|
+
if (topicOK) {
|
|
994
|
+
node.client.publish(msg.topic, msg.payload, options, function(err) {
|
|
995
|
+
done && done(err);
|
|
996
|
+
return
|
|
997
|
+
});
|
|
998
|
+
} else {
|
|
999
|
+
const error = new Error(RED._("mqtt.errors.invalid-topic"));
|
|
1000
|
+
error.warn = true;
|
|
1001
|
+
done(error);
|
|
1002
|
+
}
|
|
895
1003
|
}
|
|
896
1004
|
};
|
|
897
1005
|
|
|
898
1006
|
node.on('close', function(done) {
|
|
899
|
-
node.
|
|
900
|
-
|
|
1007
|
+
node.disconnect(function() {
|
|
1008
|
+
done();
|
|
1009
|
+
});
|
|
901
1010
|
});
|
|
902
1011
|
|
|
1012
|
+
/**
|
|
1013
|
+
* Add event handlers to the MQTT.js client and track them so that
|
|
1014
|
+
* we do not remove any handlers that the MQTT client uses internally.
|
|
1015
|
+
* Use {@link node._clientRemoveListeners `node._clientRemoveListeners`} to remove handlers
|
|
1016
|
+
* @param {string} event The name of the event
|
|
1017
|
+
* @param {function} handler The handler for this event
|
|
1018
|
+
*/
|
|
1019
|
+
node._clientOn = function(event, handler) {
|
|
1020
|
+
node.clientListeners.push({event, handler})
|
|
1021
|
+
node.client.on(event, handler)
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Remove event handlers from the MQTT.js client & only the events
|
|
1026
|
+
* that we attached in {@link node._clientOn `node._clientOn`}.
|
|
1027
|
+
* * If `event` is omitted, then all events matching `handler` are removed
|
|
1028
|
+
* * If `handler` is omitted, then all events named `event` are removed
|
|
1029
|
+
* * If both parameters are omitted, then all events are removed
|
|
1030
|
+
* @param {string} [event] The name of the event (optional)
|
|
1031
|
+
* @param {function} [handler] The handler for this event (optional)
|
|
1032
|
+
*/
|
|
1033
|
+
node._clientRemoveListeners = function(event, handler) {
|
|
1034
|
+
node.clientListeners = node.clientListeners.filter((l) => {
|
|
1035
|
+
if (event && event !== l.event) { return true; }
|
|
1036
|
+
if (handler && handler !== l.handler) { return true; }
|
|
1037
|
+
node.client.removeListener(l.event, l.handler)
|
|
1038
|
+
return false; //found and removed, filter out this one
|
|
1039
|
+
})
|
|
1040
|
+
}
|
|
1041
|
+
|
|
903
1042
|
}
|
|
904
1043
|
|
|
905
1044
|
RED.nodes.registerType("mqtt-broker",MQTTBrokerNode,{
|
|
@@ -1074,6 +1213,9 @@ module.exports = function(RED) {
|
|
|
1074
1213
|
node.brokerConn.unsubscribe(node.topic,node.id, removed);
|
|
1075
1214
|
}
|
|
1076
1215
|
node.brokerConn.deregister(node, done);
|
|
1216
|
+
node.brokerConn = null;
|
|
1217
|
+
} else {
|
|
1218
|
+
done();
|
|
1077
1219
|
}
|
|
1078
1220
|
});
|
|
1079
1221
|
} else {
|
|
@@ -1134,7 +1276,12 @@ module.exports = function(RED) {
|
|
|
1134
1276
|
}
|
|
1135
1277
|
node.brokerConn.register(node);
|
|
1136
1278
|
node.on('close', function(done) {
|
|
1137
|
-
node.brokerConn
|
|
1279
|
+
if (node.brokerConn) {
|
|
1280
|
+
node.brokerConn.deregister(node,done);
|
|
1281
|
+
node.brokerConn = null;
|
|
1282
|
+
} else {
|
|
1283
|
+
done();
|
|
1284
|
+
}
|
|
1138
1285
|
});
|
|
1139
1286
|
} else {
|
|
1140
1287
|
node.error(RED._("mqtt.errors.missing-config"));
|
|
@@ -70,7 +70,8 @@
|
|
|
70
70
|
color:"rgb(231, 231, 174)",
|
|
71
71
|
defaults: {
|
|
72
72
|
name: {value:""},
|
|
73
|
-
url: {value:"",required:true
|
|
73
|
+
url: {value:"", required:true,
|
|
74
|
+
label:RED._("node-red:httpin.label.url")},
|
|
74
75
|
method: {value:"get",required:true},
|
|
75
76
|
upload: {value:false},
|
|
76
77
|
swaggerDoc: {type:"swagger-doc", required:false}
|
|
@@ -128,7 +129,7 @@
|
|
|
128
129
|
var headerTypes = [
|
|
129
130
|
{value:"content-type",label:"Content-Type",hasValue: false},
|
|
130
131
|
{value:"location",label:"Location",hasValue: false},
|
|
131
|
-
{value:"other",label:RED._("node-red:httpin.label.other"),icon:"red/images/typedInput/az.
|
|
132
|
+
{value:"other",label:RED._("node-red:httpin.label.other"),icon:"red/images/typedInput/az.svg"}
|
|
132
133
|
]
|
|
133
134
|
var contentTypes = [
|
|
134
135
|
{value:"application/json",label:"application/json",hasValue: false},
|
|
@@ -138,7 +139,7 @@
|
|
|
138
139
|
{value:"text/plain",label:"text/plain",hasValue: false},
|
|
139
140
|
{value:"image/gif",label:"image/gif",hasValue: false},
|
|
140
141
|
{value:"image/png",label:"image/png",hasValue: false},
|
|
141
|
-
{value:"other",label:RED._("node-red:httpin.label.other"),icon:"red/images/typedInput/az.
|
|
142
|
+
{value:"other",label:RED._("node-red:httpin.label.other"),icon:"red/images/typedInput/az.svg"}
|
|
142
143
|
];
|
|
143
144
|
|
|
144
145
|
RED.nodes.registerType('http response',{
|
|
@@ -146,7 +147,10 @@
|
|
|
146
147
|
color:"rgb(231, 231, 174)",
|
|
147
148
|
defaults: {
|
|
148
149
|
name: {value:""},
|
|
149
|
-
statusCode: {
|
|
150
|
+
statusCode: {
|
|
151
|
+
value:"",
|
|
152
|
+
label: RED._("node-red:httpin.label.status"),
|
|
153
|
+
validate: RED.validators.number(true)},
|
|
150
154
|
headers: {value:{}}
|
|
151
155
|
},
|
|
152
156
|
inputs:1,
|
|
@@ -176,7 +180,7 @@
|
|
|
176
180
|
var propertyValue = $('<input/>',{class:"node-input-header-value",type:"text",style:"width: 100%"})
|
|
177
181
|
.appendTo(propertyValueCell)
|
|
178
182
|
.typedInput({types:
|
|
179
|
-
header.h === 'content-type'?contentTypes:[{value:"other",label:"other",icon:"red/images/typedInput/az.
|
|
183
|
+
header.h === 'content-type'?contentTypes:[{value:"other",label:"other",icon:"red/images/typedInput/az.svg"}]
|
|
180
184
|
});
|
|
181
185
|
|
|
182
186
|
var matchedType = headerTypes.filter(function(ht) {
|
|
@@ -219,7 +223,7 @@
|
|
|
219
223
|
if (type === 'content-type') {
|
|
220
224
|
propertyValue.typedInput('types',contentTypes);
|
|
221
225
|
} else {
|
|
222
|
-
propertyValue.typedInput('types',[{value:"other",label:"other",icon:"red/images/typedInput/az.
|
|
226
|
+
propertyValue.typedInput('types',[{value:"other",label:"other",icon:"red/images/typedInput/az.svg"}]);
|
|
223
227
|
}
|
|
224
228
|
});
|
|
225
229
|
},
|