@node-red/nodes 2.2.2 → 3.0.0-beta.3
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/20-inject.js +3 -2
- package/core/common/21-debug.html +58 -5
- 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 +58 -22
- 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 +239 -91
- 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 +23 -20
- 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 +66 -6
- 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 +136 -83
- 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 +85 -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 +13 -13
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
|
|
@@ -525,7 +592,7 @@ module.exports = function(RED) {
|
|
|
525
592
|
// Only for ws or wss, check if proxy env var for additional configuration
|
|
526
593
|
if (node.brokerurl.indexOf("wss://") > -1 || node.brokerurl.indexOf("ws://") > -1) {
|
|
527
594
|
// check if proxy is set in env
|
|
528
|
-
let prox, noprox;
|
|
595
|
+
let prox, noprox, noproxy;
|
|
529
596
|
if (process.env.http_proxy) { prox = process.env.http_proxy; }
|
|
530
597
|
if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
|
|
531
598
|
if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
|
|
@@ -652,11 +719,16 @@ module.exports = function(RED) {
|
|
|
652
719
|
setStatusConnecting(node, true);
|
|
653
720
|
try {
|
|
654
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
|
+
}
|
|
655
727
|
node.client = mqtt.connect(node.brokerurl, node.options);
|
|
656
728
|
node.client.setMaxListeners(0);
|
|
657
|
-
let callbackDone = false; //prevent re-connects causing node.
|
|
729
|
+
let callbackDone = false; //prevent re-connects causing node._clientOn('connect' firing callback multiple times
|
|
658
730
|
// Register successful connect or reconnect handler
|
|
659
|
-
node.
|
|
731
|
+
node._clientOn('connect', function (connack) {
|
|
660
732
|
node.closing = false;
|
|
661
733
|
node.connecting = false;
|
|
662
734
|
node.connected = true;
|
|
@@ -688,7 +760,7 @@ module.exports = function(RED) {
|
|
|
688
760
|
}
|
|
689
761
|
setStatusConnected(node, true);
|
|
690
762
|
// Remove any existing listeners before resubscribing to avoid duplicates in the event of a re-connection
|
|
691
|
-
node.
|
|
763
|
+
node._clientRemoveListeners('message');
|
|
692
764
|
|
|
693
765
|
// Re-subscribe to stored topics
|
|
694
766
|
for (var s in node.subscriptions) {
|
|
@@ -700,7 +772,7 @@ module.exports = function(RED) {
|
|
|
700
772
|
if (node.subscriptions[s].hasOwnProperty(r)) {
|
|
701
773
|
qos = Math.max(qos,node.subscriptions[s][r].qos);
|
|
702
774
|
_options = node.subscriptions[s][r].options;
|
|
703
|
-
node.
|
|
775
|
+
node._clientOn('message',node.subscriptions[s][r].handler);
|
|
704
776
|
}
|
|
705
777
|
}
|
|
706
778
|
_options.qos = _options.qos || qos;
|
|
@@ -713,11 +785,11 @@ module.exports = function(RED) {
|
|
|
713
785
|
node.publish(node.birthMessage);
|
|
714
786
|
}
|
|
715
787
|
});
|
|
716
|
-
node.
|
|
788
|
+
node._clientOn("reconnect", function() {
|
|
717
789
|
setStatusConnecting(node, true);
|
|
718
790
|
});
|
|
719
791
|
//Broker Disconnect - V5 event
|
|
720
|
-
node.
|
|
792
|
+
node._clientOn("disconnect", function(packet) {
|
|
721
793
|
//Emitted after receiving disconnect packet from broker. MQTT 5.0 feature.
|
|
722
794
|
const rc = (packet && packet.properties && packet.reasonCode) || packet.reasonCode;
|
|
723
795
|
const rs = packet && packet.properties && packet.properties.reasonString || "";
|
|
@@ -731,7 +803,7 @@ module.exports = function(RED) {
|
|
|
731
803
|
setStatusDisconnected(node, true);
|
|
732
804
|
});
|
|
733
805
|
// Register disconnect handlers
|
|
734
|
-
node.
|
|
806
|
+
node._clientOn('close', function () {
|
|
735
807
|
if (node.connected) {
|
|
736
808
|
node.connected = false;
|
|
737
809
|
node.log(RED._("mqtt.state.disconnected",{broker:(node.clientid?node.clientid+"@":"")+node.brokerurl}));
|
|
@@ -743,39 +815,59 @@ module.exports = function(RED) {
|
|
|
743
815
|
|
|
744
816
|
// Register connect error handler
|
|
745
817
|
// The client's own reconnect logic will take care of errors
|
|
746
|
-
node.
|
|
818
|
+
node._clientOn('error', function (error) {
|
|
747
819
|
});
|
|
748
820
|
}catch(err) {
|
|
749
821
|
console.log(err);
|
|
750
822
|
}
|
|
751
823
|
}
|
|
752
824
|
};
|
|
825
|
+
|
|
753
826
|
node.disconnect = function (callback) {
|
|
754
|
-
const _callback = function (
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
node.closing = true;
|
|
758
|
-
node.connecting = false;
|
|
759
|
-
node.connected = false;
|
|
827
|
+
const _callback = function () {
|
|
828
|
+
if(node.connected || node.connecting) {
|
|
829
|
+
setStatusDisconnected(node, true);
|
|
760
830
|
}
|
|
831
|
+
if(node.client) { node._clientRemoveListeners(); }
|
|
832
|
+
node.connecting = false;
|
|
833
|
+
node.connected = false;
|
|
761
834
|
callback && typeof callback == "function" && callback();
|
|
762
835
|
};
|
|
836
|
+
if(!node.client) { return _callback(); }
|
|
837
|
+
if(node.closing) { return _callback(); }
|
|
763
838
|
|
|
764
|
-
|
|
765
|
-
return
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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
|
+
};
|
|
769
857
|
if(node.connected && node.closeMessage) {
|
|
770
858
|
node.publish(node.closeMessage, function (err) {
|
|
771
|
-
node.client.
|
|
772
|
-
|
|
859
|
+
waitEnd(node.client, 2000).then(() => {
|
|
860
|
+
_callback();
|
|
861
|
+
}).catch((e) => {
|
|
862
|
+
_callback();
|
|
863
|
+
})
|
|
773
864
|
});
|
|
774
|
-
} else if(node.connected) {
|
|
775
|
-
node.client.end(endCallBack);
|
|
776
|
-
_callback(true);
|
|
777
865
|
} else {
|
|
778
|
-
|
|
866
|
+
waitEnd(node.client, 2000).then(() => {
|
|
867
|
+
_callback();
|
|
868
|
+
}).catch((e) => {
|
|
869
|
+
_callback();
|
|
870
|
+
})
|
|
779
871
|
}
|
|
780
872
|
}
|
|
781
873
|
node.subscriptionIds = {};
|
|
@@ -812,7 +904,7 @@ module.exports = function(RED) {
|
|
|
812
904
|
};
|
|
813
905
|
node.subscriptions[topic][ref] = sub;
|
|
814
906
|
if (node.connected) {
|
|
815
|
-
node.
|
|
907
|
+
node._clientOn('message',sub.handler);
|
|
816
908
|
node.client.subscribe(topic, options);
|
|
817
909
|
}
|
|
818
910
|
};
|
|
@@ -823,7 +915,7 @@ module.exports = function(RED) {
|
|
|
823
915
|
if (sub) {
|
|
824
916
|
if (sub[ref]) {
|
|
825
917
|
if(node.client) {
|
|
826
|
-
node.
|
|
918
|
+
node._clientRemoveListeners('message',sub[ref].handler);
|
|
827
919
|
}
|
|
828
920
|
delete sub[ref];
|
|
829
921
|
}
|
|
@@ -857,8 +949,18 @@ module.exports = function(RED) {
|
|
|
857
949
|
qos: msg.qos || 0,
|
|
858
950
|
retain: msg.retain || false
|
|
859
951
|
};
|
|
952
|
+
let topicOK = hasProperty(msg, "topic") && (typeof msg.topic === "string") && (isValidPublishTopic(msg.topic));
|
|
860
953
|
//https://github.com/mqttjs/MQTT.js/blob/master/README.md#mqttclientpublishtopic-message-options-callback
|
|
861
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
|
+
}
|
|
862
964
|
options.properties = options.properties || {};
|
|
863
965
|
setStrProp(msg, options.properties, "responseTopic");
|
|
864
966
|
setBufferProp(msg, options.properties, "correlationData");
|
|
@@ -868,31 +970,75 @@ module.exports = function(RED) {
|
|
|
868
970
|
setIntProp(msg, options.properties, "topicAlias", 1, node.serverProperties.topicAliasMaximum || 0);
|
|
869
971
|
setBoolProp(msg, options.properties, "payloadFormatIndicator");
|
|
870
972
|
//FUTURE setIntProp(msg, options.properties, "subscriptionIdentifier", 1, 268435455);
|
|
871
|
-
|
|
872
|
-
|
|
973
|
+
|
|
974
|
+
//check & sanitise topic
|
|
975
|
+
if (topicOK && options.properties.topicAlias) {
|
|
976
|
+
let aliasValid = (bsp.topicAliasMaximum && bsp.topicAliasMaximum >= options.properties.topicAlias);
|
|
977
|
+
if (!aliasValid) {
|
|
873
978
|
done("Invalid topicAlias");
|
|
874
979
|
return
|
|
875
980
|
}
|
|
876
981
|
if (node.topicAliases[options.properties.topicAlias] === msg.topic) {
|
|
877
|
-
msg.topic = ""
|
|
982
|
+
msg.topic = "";
|
|
878
983
|
} else {
|
|
879
|
-
node.topicAliases[options.properties.topicAlias] = msg.topic
|
|
984
|
+
node.topicAliases[options.properties.topicAlias] = msg.topic;
|
|
880
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?
|
|
881
990
|
}
|
|
882
991
|
}
|
|
883
992
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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
|
+
}
|
|
888
1003
|
}
|
|
889
1004
|
};
|
|
890
1005
|
|
|
891
1006
|
node.on('close', function(done) {
|
|
892
|
-
node.
|
|
893
|
-
|
|
1007
|
+
node.disconnect(function() {
|
|
1008
|
+
done();
|
|
1009
|
+
});
|
|
894
1010
|
});
|
|
895
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
|
+
|
|
896
1042
|
}
|
|
897
1043
|
|
|
898
1044
|
RED.nodes.registerType("mqtt-broker",MQTTBrokerNode,{
|
|
@@ -1067,6 +1213,7 @@ module.exports = function(RED) {
|
|
|
1067
1213
|
node.brokerConn.unsubscribe(node.topic,node.id, removed);
|
|
1068
1214
|
}
|
|
1069
1215
|
node.brokerConn.deregister(node, done);
|
|
1216
|
+
node.brokerConn = null;
|
|
1070
1217
|
} else {
|
|
1071
1218
|
done();
|
|
1072
1219
|
}
|
|
@@ -1131,6 +1278,7 @@ module.exports = function(RED) {
|
|
|
1131
1278
|
node.on('close', function(done) {
|
|
1132
1279
|
if (node.brokerConn) {
|
|
1133
1280
|
node.brokerConn.deregister(node,done);
|
|
1281
|
+
node.brokerConn = null;
|
|
1134
1282
|
} else {
|
|
1135
1283
|
done();
|
|
1136
1284
|
}
|
|
@@ -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
|
},
|