@node-red/nodes 3.1.0-beta.2 → 3.1.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/core/common/25-catch.html +5 -0
- package/core/common/25-status.html +5 -0
- package/core/function/rbe.js +5 -1
- package/core/network/10-mqtt.html +31 -6
- package/core/network/10-mqtt.js +243 -69
- package/core/network/21-httprequest.js +44 -22
- package/core/parsers/70-XML.js +1 -2
- package/locales/de/messages.json +3 -0
- package/locales/en-US/messages.json +3 -0
- package/locales/ja/messages.json +1 -0
- package/package.json +4 -4
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<label style="width: auto" for="node-input-scope" data-i18n="catch.label.source"></label>
|
|
5
5
|
<select id="node-input-scope-select">
|
|
6
6
|
<option value="all" data-i18n="catch.scope.all"></option>
|
|
7
|
+
<option value="group" data-i18n="catch.scope.group"></option>
|
|
7
8
|
<option value="target" data-i18n="catch.scope.selected"></option>
|
|
8
9
|
</select>
|
|
9
10
|
</div>
|
|
@@ -170,6 +171,8 @@
|
|
|
170
171
|
});
|
|
171
172
|
if (this.scope === null) {
|
|
172
173
|
$("#node-input-scope-select").val("all");
|
|
174
|
+
} else if(this.scope === "group"){
|
|
175
|
+
$("#node-input-scope-select").val("group");
|
|
173
176
|
} else {
|
|
174
177
|
$("#node-input-scope-select").val("target");
|
|
175
178
|
}
|
|
@@ -179,6 +182,8 @@
|
|
|
179
182
|
var scope = $("#node-input-scope-select").val();
|
|
180
183
|
if (scope === 'all') {
|
|
181
184
|
this.scope = null;
|
|
185
|
+
} else if(scope === 'group') {
|
|
186
|
+
this.scope = "group";
|
|
182
187
|
} else {
|
|
183
188
|
$("#node-input-uncaught").prop("checked",false);
|
|
184
189
|
this.scope = $("#node-input-catch-target-container-div").treeList('selected').map(function(i) { return i.node.id})
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<label style="width: auto" for="node-input-scope" data-i18n="status.label.source"></label>
|
|
5
5
|
<select id="node-input-scope-select">
|
|
6
6
|
<option value="all" data-i18n="status.scope.all"></option>
|
|
7
|
+
<option value="group" data-i18n="status.scope.group"></option>
|
|
7
8
|
<option value="target" data-i18n="status.scope.selected"></option>
|
|
8
9
|
</select>
|
|
9
10
|
</div>
|
|
@@ -157,6 +158,8 @@
|
|
|
157
158
|
});
|
|
158
159
|
if (this.scope === null) {
|
|
159
160
|
$("#node-input-scope-select").val("all");
|
|
161
|
+
} else if(this.scope === "group"){
|
|
162
|
+
$("#node-input-scope-select").val("group");
|
|
160
163
|
} else {
|
|
161
164
|
$("#node-input-scope-select").val("target");
|
|
162
165
|
}
|
|
@@ -166,6 +169,8 @@
|
|
|
166
169
|
var scope = $("#node-input-scope-select").val();
|
|
167
170
|
if (scope === 'all') {
|
|
168
171
|
this.scope = null;
|
|
172
|
+
} else if(scope === 'group') {
|
|
173
|
+
this.scope = "group";
|
|
169
174
|
} else {
|
|
170
175
|
this.scope = $("#node-input-status-target-container-div").treeList('selected').map(function(i) { return i.node.id})
|
|
171
176
|
}
|
package/core/function/rbe.js
CHANGED
|
@@ -35,7 +35,11 @@ module.exports = function(RED) {
|
|
|
35
35
|
}
|
|
36
36
|
else { node.previous = {}; }
|
|
37
37
|
}
|
|
38
|
-
var value
|
|
38
|
+
var value;
|
|
39
|
+
try {
|
|
40
|
+
value = RED.util.getMessageProperty(msg,node.property);
|
|
41
|
+
}
|
|
42
|
+
catch(e) { }
|
|
39
43
|
if (value !== undefined) {
|
|
40
44
|
var t = "_no_topic";
|
|
41
45
|
if (node.septopics) { t = topic || t; }
|
|
@@ -249,6 +249,12 @@
|
|
|
249
249
|
<span id="node-config-input-cleansession-label" data-i18n="mqtt.label.cleansession"></span>
|
|
250
250
|
</label>
|
|
251
251
|
</div>
|
|
252
|
+
<div class="form-row mqtt-persistence">
|
|
253
|
+
<label for="node-config-input-autoUnsubscribe" style="width: auto;">
|
|
254
|
+
<input type="checkbox" id="node-config-input-autoUnsubscribe" style="position: relative;vertical-align: bottom; top: -2px; width: 15px;height: 15px;">
|
|
255
|
+
<span id="node-config-input-autoUnsubscribe-label" data-i18n="mqtt.label.autoUnsubscribe"></span>
|
|
256
|
+
</label>
|
|
257
|
+
</div>
|
|
252
258
|
<div class="form-row mqtt5">
|
|
253
259
|
<label style="width:auto" for="node-config-input-sessionExpiry"><span data-i18n="mqtt.label.sessionExpiry"></span></label>
|
|
254
260
|
<input type="number" min="0" id="node-config-input-sessionExpiry" style="width: 100px" >
|
|
@@ -483,17 +489,23 @@
|
|
|
483
489
|
tls: {type:"tls-config",required: false,
|
|
484
490
|
label:RED._("node-red:mqtt.label.use-tls") },
|
|
485
491
|
clientid: {value:"", validate: function(v, opt) {
|
|
486
|
-
|
|
492
|
+
let ok = true;
|
|
487
493
|
if ($("#node-config-input-clientid").length) {
|
|
488
494
|
// Currently editing the node
|
|
489
|
-
|
|
495
|
+
let needClientId = !$("#node-config-input-cleansession").is(":checked") || !$("#node-config-input-autoUnsubscribe").is(":checked")
|
|
496
|
+
if (needClientId) {
|
|
497
|
+
ok = (v||"").length > 0;
|
|
498
|
+
}
|
|
490
499
|
} else {
|
|
491
|
-
|
|
500
|
+
let needClientId = !(this.cleansession===undefined || this.cleansession) || this.autoUnsubscribe;
|
|
501
|
+
if (needClientId) {
|
|
502
|
+
ok = (v||"").length > 0;
|
|
503
|
+
}
|
|
492
504
|
}
|
|
493
|
-
if (ok) {
|
|
494
|
-
return
|
|
505
|
+
if (!ok) {
|
|
506
|
+
return RED._("node-red:mqtt.errors.invalid-client-id");
|
|
495
507
|
}
|
|
496
|
-
return
|
|
508
|
+
return true;
|
|
497
509
|
}},
|
|
498
510
|
autoConnect: {value: true},
|
|
499
511
|
usetls: {value: false},
|
|
@@ -505,6 +517,7 @@
|
|
|
505
517
|
label: RED._("node-red:mqtt.label.keepalive"),
|
|
506
518
|
validate:RED.validators.number(false)},
|
|
507
519
|
cleansession: {value: true},
|
|
520
|
+
autoUnsubscribe: {value: true},
|
|
508
521
|
birthTopic: {value:"", validate:validateMQTTPublishTopic},
|
|
509
522
|
birthQos: {value:"0"},
|
|
510
523
|
birthRetain: {value:"false"},
|
|
@@ -620,6 +633,10 @@
|
|
|
620
633
|
this.cleansession = true;
|
|
621
634
|
$("#node-config-input-cleansession").prop("checked",true);
|
|
622
635
|
}
|
|
636
|
+
if (typeof this.autoUnsubscribe === 'undefined') {
|
|
637
|
+
this.autoUnsubscribe = true;
|
|
638
|
+
$("#node-config-input-autoUnsubscribe").prop("checked",true);
|
|
639
|
+
}
|
|
623
640
|
if (typeof this.usetls === 'undefined') {
|
|
624
641
|
this.usetls = false;
|
|
625
642
|
$("#node-config-input-usetls").prop("checked",false);
|
|
@@ -635,6 +652,14 @@
|
|
|
635
652
|
if (typeof this.protocolVersion === 'undefined') {
|
|
636
653
|
this.protocolVersion = 4;
|
|
637
654
|
}
|
|
655
|
+
$("#node-config-input-cleansession").on("change", function() {
|
|
656
|
+
const useCleanSession = $("#node-config-input-cleansession").is(':checked');
|
|
657
|
+
if(useCleanSession) {
|
|
658
|
+
$("div.form-row.mqtt-persistence").hide();
|
|
659
|
+
} else {
|
|
660
|
+
$("div.form-row.mqtt-persistence").show();
|
|
661
|
+
}
|
|
662
|
+
});
|
|
638
663
|
$("#node-config-input-protocolVersion").on("change", function() {
|
|
639
664
|
var v5 = $("#node-config-input-protocolVersion").val() == "5";
|
|
640
665
|
if(v5) {
|
package/core/network/10-mqtt.js
CHANGED
|
@@ -219,8 +219,10 @@ module.exports = function(RED) {
|
|
|
219
219
|
* Handle the payload / packet recieved in MQTT In and MQTT Sub nodes
|
|
220
220
|
*/
|
|
221
221
|
function subscriptionHandler(node, datatype ,topic, payload, packet) {
|
|
222
|
-
const
|
|
223
|
-
|
|
222
|
+
const msg = {topic:topic, payload:null, qos:packet.qos, retain:packet.retain};
|
|
223
|
+
const v5 = (node && node.brokerConn)
|
|
224
|
+
? node.brokerConn.v5()
|
|
225
|
+
: Object.prototype.hasOwnProperty.call(packet, "properties");
|
|
224
226
|
if(v5 && packet.properties) {
|
|
225
227
|
setStrProp(packet.properties, msg, "responseTopic");
|
|
226
228
|
setBufferProp(packet.properties, msg, "correlationData");
|
|
@@ -300,7 +302,7 @@ module.exports = function(RED) {
|
|
|
300
302
|
//}
|
|
301
303
|
}
|
|
302
304
|
msg.payload = payload;
|
|
303
|
-
if ((node.brokerConn.broker === "localhost"
|
|
305
|
+
if (node.brokerConn && (node.brokerConn.broker === "localhost" || node.brokerConn.broker === "127.0.0.1")) {
|
|
304
306
|
msg._topic = topic;
|
|
305
307
|
}
|
|
306
308
|
node.send(msg);
|
|
@@ -412,6 +414,12 @@ module.exports = function(RED) {
|
|
|
412
414
|
}
|
|
413
415
|
}
|
|
414
416
|
|
|
417
|
+
/**
|
|
418
|
+
* Perform the connect action
|
|
419
|
+
* @param {MQTTInNode|MQTTOutNode} node
|
|
420
|
+
* @param {Object} msg
|
|
421
|
+
* @param {Function} done
|
|
422
|
+
*/
|
|
415
423
|
function handleConnectAction(node, msg, done) {
|
|
416
424
|
let actionData = typeof msg.broker === 'object' ? msg.broker : null;
|
|
417
425
|
if (node.brokerConn.canConnect()) {
|
|
@@ -442,12 +450,17 @@ module.exports = function(RED) {
|
|
|
442
450
|
}
|
|
443
451
|
}
|
|
444
452
|
|
|
453
|
+
/**
|
|
454
|
+
* Perform the disconnect action
|
|
455
|
+
* @param {MQTTInNode|MQTTOutNode} node
|
|
456
|
+
* @param {Function} done
|
|
457
|
+
*/
|
|
445
458
|
function handleDisconnectAction(node, done) {
|
|
446
459
|
node.brokerConn.disconnect(function () {
|
|
447
460
|
done();
|
|
448
461
|
});
|
|
449
462
|
}
|
|
450
|
-
|
|
463
|
+
const unsubscribeCandidates = {}
|
|
451
464
|
//#endregion "Supporting functions"
|
|
452
465
|
|
|
453
466
|
//#region "Broker node"
|
|
@@ -482,6 +495,7 @@ module.exports = function(RED) {
|
|
|
482
495
|
setIfHasProperty(opts, node, "protocolVersion", init);
|
|
483
496
|
setIfHasProperty(opts, node, "keepalive", init);
|
|
484
497
|
setIfHasProperty(opts, node, "cleansession", init);
|
|
498
|
+
setIfHasProperty(opts, node, "autoUnsubscribe", init);
|
|
485
499
|
setIfHasProperty(opts, node, "topicAliasMaximum", init);
|
|
486
500
|
setIfHasProperty(opts, node, "maximumPacketSize", init);
|
|
487
501
|
setIfHasProperty(opts, node, "receiveMaximum", init);
|
|
@@ -590,7 +604,9 @@ module.exports = function(RED) {
|
|
|
590
604
|
if (typeof node.cleansession === 'undefined') {
|
|
591
605
|
node.cleansession = true;
|
|
592
606
|
}
|
|
593
|
-
|
|
607
|
+
if (typeof node.autoUnsubscribe !== 'boolean') {
|
|
608
|
+
node.autoUnsubscribe = true;
|
|
609
|
+
}
|
|
594
610
|
//use url or build a url from usetls://broker:port
|
|
595
611
|
if (node.url && node.brokerurl !== node.url) {
|
|
596
612
|
node.brokerurl = node.url;
|
|
@@ -697,7 +713,8 @@ module.exports = function(RED) {
|
|
|
697
713
|
node.options.rejectUnauthorized = (node.verifyservercert == "true" || node.verifyservercert === true);
|
|
698
714
|
}
|
|
699
715
|
}
|
|
700
|
-
|
|
716
|
+
node.v5 = () => node.options && node.options.protocolVersion == 5
|
|
717
|
+
node.subscriptionIdentifiersAvailable = () => node.v5() && node.serverProperties && node.serverProperties.subscriptionIdentifiersAvailable
|
|
701
718
|
n.autoConnect = n.autoConnect === "false" || n.autoConnect === false ? false : true;
|
|
702
719
|
node.setOptions(n, true);
|
|
703
720
|
|
|
@@ -779,18 +796,11 @@ module.exports = function(RED) {
|
|
|
779
796
|
// Re-subscribe to stored topics
|
|
780
797
|
for (var s in node.subscriptions) {
|
|
781
798
|
if (node.subscriptions.hasOwnProperty(s)) {
|
|
782
|
-
let topic = s;
|
|
783
|
-
let qos = 0;
|
|
784
|
-
let _options = {};
|
|
785
799
|
for (var r in node.subscriptions[s]) {
|
|
786
800
|
if (node.subscriptions[s].hasOwnProperty(r)) {
|
|
787
|
-
|
|
788
|
-
_options = node.subscriptions[s][r].options;
|
|
789
|
-
node._clientOn('message',node.subscriptions[s][r].handler);
|
|
801
|
+
node.subscribe(node.subscriptions[s][r])
|
|
790
802
|
}
|
|
791
803
|
}
|
|
792
|
-
_options.qos = _options.qos || qos;
|
|
793
|
-
node.client.subscribe(topic, _options);
|
|
794
804
|
}
|
|
795
805
|
}
|
|
796
806
|
|
|
@@ -852,22 +862,28 @@ module.exports = function(RED) {
|
|
|
852
862
|
if(!node.client) { return _callback(); }
|
|
853
863
|
if(node.closing) { return _callback(); }
|
|
854
864
|
|
|
865
|
+
/**
|
|
866
|
+
* Call end and wait for the client to end (or timeout)
|
|
867
|
+
* @param {mqtt.MqttClient} client The broker client
|
|
868
|
+
* @param {number} ms The time to wait for the client to end
|
|
869
|
+
* @returns
|
|
870
|
+
*/
|
|
855
871
|
let waitEnd = (client, ms) => {
|
|
856
872
|
return new Promise( (resolve, reject) => {
|
|
857
873
|
node.closing = true;
|
|
858
|
-
if(!client) {
|
|
874
|
+
if (!client) {
|
|
859
875
|
resolve();
|
|
860
|
-
|
|
876
|
+
} else {
|
|
861
877
|
const t = setTimeout(() => {
|
|
862
878
|
//clean end() has exceeded WAIT_END, lets force end!
|
|
863
879
|
client && client.end(true);
|
|
864
|
-
|
|
880
|
+
resolve();
|
|
865
881
|
}, ms);
|
|
866
882
|
client.end(() => {
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
883
|
+
clearTimeout(t);
|
|
884
|
+
resolve()
|
|
885
|
+
});
|
|
886
|
+
}
|
|
871
887
|
});
|
|
872
888
|
};
|
|
873
889
|
if(node.connected && node.closeMessage) {
|
|
@@ -888,64 +904,222 @@ module.exports = function(RED) {
|
|
|
888
904
|
}
|
|
889
905
|
node.subscriptionIds = {};
|
|
890
906
|
node.subid = 1;
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
907
|
+
|
|
908
|
+
//typedef for subscription object:
|
|
909
|
+
/**
|
|
910
|
+
* @typedef {Object} Subscription
|
|
911
|
+
* @property {String} topic - topic to subscribe to
|
|
912
|
+
* @property {Object} [options] - options object
|
|
913
|
+
* @property {Number} [options.qos] - quality of service
|
|
914
|
+
* @property {Number} [options.nl] - no local
|
|
915
|
+
* @property {Number} [options.rap] - retain as published
|
|
916
|
+
* @property {Number} [options.rh] - retain handling
|
|
917
|
+
* @property {Number} [options.properties] - MQTT 5.0 properties
|
|
918
|
+
* @property {Number} [options.properties.subscriptionIdentifier] - MQTT 5.0 subscription identifier
|
|
919
|
+
* @property {Number} [options.properties.userProperties] - MQTT 5.0 user properties
|
|
920
|
+
* @property {Function} callback
|
|
921
|
+
* @property {String} ref - reference to the node that created the subscription
|
|
922
|
+
*/
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Create a subscription object
|
|
926
|
+
* @param {String} _topic - topic to subscribe to
|
|
927
|
+
* @param {Object} _options - options object
|
|
928
|
+
* @param {String} _ref - reference to the node that created the subscription
|
|
929
|
+
* @returns {Subscription}
|
|
930
|
+
*/
|
|
931
|
+
function createSubscriptionObject(_topic, _options, _ref, _brokerId) {
|
|
932
|
+
/** @type {Subscription} */
|
|
933
|
+
const subscription = {};
|
|
934
|
+
const ref = _ref || 0;
|
|
935
|
+
let options
|
|
936
|
+
let qos = 1 // default to QoS 1 (AWS and several other brokers don't support QoS 2)
|
|
937
|
+
|
|
938
|
+
// if options is an object, then clone it
|
|
939
|
+
if (typeof _options == "object") {
|
|
940
|
+
options = RED.util.cloneMessage(_options || {})
|
|
941
|
+
qos = _options.qos;
|
|
942
|
+
} else if (typeof _options == "number") {
|
|
943
|
+
qos = _options;
|
|
944
|
+
}
|
|
945
|
+
options = options || {};
|
|
946
|
+
|
|
947
|
+
// sanitise qos
|
|
948
|
+
if (typeof qos === "number" && qos >= 0 && qos <= 2) {
|
|
949
|
+
options.qos = qos;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
subscription.topic = _topic;
|
|
953
|
+
subscription.qos = qos;
|
|
954
|
+
subscription.options = RED.util.cloneMessage(options);
|
|
955
|
+
subscription.ref = ref;
|
|
956
|
+
subscription.brokerId = _brokerId;
|
|
957
|
+
return subscription;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* If topic is a subscription object, then use that, otherwise look up the topic in
|
|
962
|
+
* the subscriptions object. If the topic is not found, then create a new subscription
|
|
963
|
+
* object and add it to the subscriptions object.
|
|
964
|
+
* @param {Subscription|String} topic
|
|
965
|
+
* @param {*} options
|
|
966
|
+
* @param {*} callback
|
|
967
|
+
* @param {*} ref
|
|
968
|
+
*/
|
|
969
|
+
node.subscribe = function (topic, options, callback, ref) {
|
|
970
|
+
/** @type {Subscription} */
|
|
971
|
+
let subscription
|
|
972
|
+
let doCompare = false
|
|
973
|
+
let changesFound = false
|
|
974
|
+
|
|
975
|
+
// function signature 1: subscribe(subscription: Subscription)
|
|
976
|
+
if (typeof topic === "object" && topic !== null) {
|
|
977
|
+
subscription = topic
|
|
978
|
+
topic = subscription.topic
|
|
979
|
+
options = subscription.options
|
|
980
|
+
ref = subscription.ref
|
|
981
|
+
callback = subscription.callback
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// function signature 2: subscribe(topic: String, options: Object, callback: Function, ref: String)
|
|
985
|
+
else if (typeof topic === "string") {
|
|
986
|
+
// since this is a call where all params are provided, it might be
|
|
987
|
+
// a node change (modification) so we need to check for changes
|
|
988
|
+
doCompare = true
|
|
989
|
+
subscription = node.subscriptions[topic] && node.subscriptions[topic][ref]
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// bad function call
|
|
993
|
+
else {
|
|
994
|
+
console.warn('Invalid call to node.subscribe')
|
|
995
|
+
return
|
|
899
996
|
}
|
|
900
|
-
|
|
997
|
+
const thisBrokerId = node.type === 'mqtt-broker' ? node.id : node.broker
|
|
998
|
+
|
|
999
|
+
// unsubscribe topics where the broker has changed
|
|
1000
|
+
const oldBrokerSubs = (unsubscribeCandidates[ref] || []).filter(sub => sub.brokerId !== thisBrokerId)
|
|
1001
|
+
oldBrokerSubs.forEach(sub => {
|
|
1002
|
+
/** @type {MQTTBrokerNode} */
|
|
1003
|
+
const _brokerConn = RED.nodes.getNode(sub.brokerId)
|
|
1004
|
+
if (_brokerConn) {
|
|
1005
|
+
_brokerConn.unsubscribe(sub.topic, sub.ref, true)
|
|
1006
|
+
}
|
|
1007
|
+
})
|
|
1008
|
+
|
|
1009
|
+
// if subscription is found (or sent in as a parameter), then check for changes.
|
|
1010
|
+
// if there are any changes requested, tidy up the old subscription
|
|
1011
|
+
if (subscription) {
|
|
1012
|
+
if (doCompare) {
|
|
1013
|
+
// compare the current sub to the passed in parameters. Use RED.util.compareObjects against
|
|
1014
|
+
// only the minimal set of properties to identify if the subscription has changed
|
|
1015
|
+
const currentSubscription = createSubscriptionObject(subscription.topic, subscription.options, subscription.ref)
|
|
1016
|
+
const newSubscription = createSubscriptionObject(topic, options, ref)
|
|
1017
|
+
changesFound = RED.util.compareObjects(currentSubscription, newSubscription) === false
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
if (changesFound) {
|
|
1022
|
+
if (subscription.handler) {
|
|
1023
|
+
node._clientRemoveListeners('message', subscription.handler)
|
|
1024
|
+
subscription.handler = null
|
|
1025
|
+
}
|
|
1026
|
+
const _brokerConn = RED.nodes.getNode(subscription.brokerId)
|
|
1027
|
+
if (_brokerConn) {
|
|
1028
|
+
_brokerConn.unsubscribe(subscription.topic, subscription.ref, true)
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// clean up the unsubscribe candidate list
|
|
1033
|
+
delete unsubscribeCandidates[ref]
|
|
1034
|
+
|
|
1035
|
+
// determine if this is an existing subscription
|
|
1036
|
+
const existingSubscription = typeof subscription === "object" && subscription !== null
|
|
1037
|
+
|
|
1038
|
+
// if existing subscription is not found or has changed, create a new subscription object
|
|
1039
|
+
if (existingSubscription === false || changesFound) {
|
|
1040
|
+
subscription = createSubscriptionObject(topic, options, ref, node.id)
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// setup remainder of subscription properties and event handling
|
|
1044
|
+
node.subscriptions[topic] = node.subscriptions[topic] || {};
|
|
1045
|
+
node.subscriptions[topic][ref] = subscription
|
|
901
1046
|
if (!node.subscriptionIds[topic]) {
|
|
902
1047
|
node.subscriptionIds[topic] = node.subid++;
|
|
903
1048
|
}
|
|
904
|
-
options
|
|
905
|
-
options.properties
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
qos:qos,
|
|
911
|
-
options:options,
|
|
912
|
-
handler:function(mtopic,mpayload, mpacket) {
|
|
913
|
-
if(mpacket.properties && options.properties && mpacket.properties.subscriptionIdentifier && options.properties.subscriptionIdentifier && (mpacket.properties.subscriptionIdentifier !== options.properties.subscriptionIdentifier) ) {
|
|
914
|
-
//do nothing as subscriptionIdentifier does not match
|
|
915
|
-
} else if (matchTopic(topic,mtopic)) {
|
|
916
|
-
callback(mtopic,mpayload, mpacket);
|
|
917
|
-
}
|
|
918
|
-
},
|
|
919
|
-
ref: ref
|
|
920
|
-
};
|
|
921
|
-
node.subscriptions[topic][ref] = sub;
|
|
1049
|
+
subscription.options = subscription.options || {};
|
|
1050
|
+
subscription.options.properties = options.properties || {};
|
|
1051
|
+
subscription.options.properties.subscriptionIdentifier = node.subscriptionIds[topic];
|
|
1052
|
+
subscription.callback = callback;
|
|
1053
|
+
|
|
1054
|
+
// if the client is connected, then setup the handler and subscribe
|
|
922
1055
|
if (node.connected) {
|
|
923
|
-
node.
|
|
924
|
-
|
|
1056
|
+
const subIdsAvailable = node.subscriptionIdentifiersAvailable()
|
|
1057
|
+
|
|
1058
|
+
if (!subscription.handler) {
|
|
1059
|
+
subscription.handler = function (mtopic, mpayload, mpacket) {
|
|
1060
|
+
const sops = subscription.options ? subscription.options.properties : {}
|
|
1061
|
+
const pops = mpacket.properties || {}
|
|
1062
|
+
if (subIdsAvailable && pops.subscriptionIdentifier && sops.subscriptionIdentifier && (pops.subscriptionIdentifier !== sops.subscriptionIdentifier)) {
|
|
1063
|
+
//do nothing as subscriptionIdentifier does not match
|
|
1064
|
+
} else if (matchTopic(topic, mtopic)) {
|
|
1065
|
+
subscription.callback && subscription.callback(mtopic, mpayload, mpacket)
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
node._clientOn('message', subscription.handler)
|
|
1070
|
+
// if the broker doesn't support subscription identifiers, then don't send them (AWS support)
|
|
1071
|
+
if (subscription.options.properties && subscription.options.properties.subscriptionIdentifier && subIdsAvailable !== true) {
|
|
1072
|
+
delete subscription.options.properties.subscriptionIdentifier
|
|
1073
|
+
}
|
|
1074
|
+
node.client.subscribe(topic, subscription.options)
|
|
925
1075
|
}
|
|
926
|
-
};
|
|
927
1076
|
|
|
928
|
-
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
node.unsubscribe = function (topic, ref, removeClientSubscription) {
|
|
929
1080
|
ref = ref||0;
|
|
930
|
-
|
|
1081
|
+
const unsub = removeClientSubscription || node.autoUnsubscribe !== false
|
|
1082
|
+
const sub = node.subscriptions[topic];
|
|
1083
|
+
let brokerId = node.id
|
|
931
1084
|
if (sub) {
|
|
932
1085
|
if (sub[ref]) {
|
|
933
|
-
|
|
934
|
-
|
|
1086
|
+
brokerId = sub[ref].brokerId || brokerId
|
|
1087
|
+
if(node.client && sub[ref].handler) {
|
|
1088
|
+
node._clientRemoveListeners('message', sub[ref].handler);
|
|
1089
|
+
sub[ref].handler = null
|
|
1090
|
+
}
|
|
1091
|
+
if (unsub) {
|
|
1092
|
+
delete sub[ref]
|
|
935
1093
|
}
|
|
936
|
-
delete sub[ref];
|
|
937
1094
|
}
|
|
938
|
-
//
|
|
939
|
-
|
|
940
|
-
|
|
1095
|
+
// if instructed to remove the actual MQTT client subscription
|
|
1096
|
+
if (unsub) {
|
|
1097
|
+
// if there are no more subscriptions for the topic, then remove the topic
|
|
941
1098
|
if (Object.keys(sub).length === 0) {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1099
|
+
try {
|
|
1100
|
+
node.client.unsubscribe(topic)
|
|
1101
|
+
} catch (_err) {
|
|
1102
|
+
// do nothing
|
|
1103
|
+
} finally {
|
|
1104
|
+
// remove unsubscribe candidate as it is now REALLY unsubscribed
|
|
1105
|
+
delete node.subscriptions[topic];
|
|
1106
|
+
delete node.subscriptionIds[topic];
|
|
1107
|
+
if (unsubscribeCandidates[ref]) {
|
|
1108
|
+
unsubscribeCandidates[ref] = unsubscribeCandidates[ref].filter(sub => sub.topic !== topic)
|
|
1109
|
+
}
|
|
946
1110
|
}
|
|
947
1111
|
}
|
|
948
|
-
|
|
1112
|
+
} else {
|
|
1113
|
+
// if instructed to not remove the client subscription, then add it to the candidate list
|
|
1114
|
+
// of subscriptions to be removed when the the same ref is used in a subsequent subscribe
|
|
1115
|
+
// and the topic has changed
|
|
1116
|
+
unsubscribeCandidates[ref] = unsubscribeCandidates[ref] || [];
|
|
1117
|
+
unsubscribeCandidates[ref].push({
|
|
1118
|
+
topic: topic,
|
|
1119
|
+
ref: ref,
|
|
1120
|
+
brokerId: brokerId
|
|
1121
|
+
})
|
|
1122
|
+
}
|
|
949
1123
|
}
|
|
950
1124
|
};
|
|
951
1125
|
node.topicAliases = {};
|
|
@@ -983,7 +1157,7 @@ module.exports = function(RED) {
|
|
|
983
1157
|
setStrProp(msg, options.properties, "contentType");
|
|
984
1158
|
setIntProp(msg, options.properties, "messageExpiryInterval", 0);
|
|
985
1159
|
setUserProperties(msg.userProperties, options.properties);
|
|
986
|
-
setIntProp(msg, options.properties, "topicAlias", 1,
|
|
1160
|
+
setIntProp(msg, options.properties, "topicAlias", 1, bsp.topicAliasMaximum || 0);
|
|
987
1161
|
setBoolProp(msg, options.properties, "payloadFormatIndicator");
|
|
988
1162
|
//FUTURE setIntProp(msg, options.properties, "subscriptionIdentifier", 1, 268435455);
|
|
989
1163
|
|
|
@@ -1119,7 +1293,7 @@ module.exports = function(RED) {
|
|
|
1119
1293
|
if(node.rap === "true" || node.rap === true) options.rap = true;
|
|
1120
1294
|
else if(node.rap === "false" || node.rap === false) options.rap = false;
|
|
1121
1295
|
}
|
|
1122
|
-
|
|
1296
|
+
node._topic = node.topic; // store the original topic incase node is later changed
|
|
1123
1297
|
node.brokerConn.subscribe(node.topic,options,function(topic, payload, packet) {
|
|
1124
1298
|
subscriptionHandler(node, node.datatype, topic, payload, packet);
|
|
1125
1299
|
},node.id);
|
|
@@ -1172,7 +1346,7 @@ module.exports = function(RED) {
|
|
|
1172
1346
|
}
|
|
1173
1347
|
if (action === Actions.UNSUBSCRIBE) {
|
|
1174
1348
|
subscriptions.forEach(function (sub) {
|
|
1175
|
-
node.brokerConn.unsubscribe(sub.topic, node.id);
|
|
1349
|
+
node.brokerConn.unsubscribe(sub.topic, node.id, true);
|
|
1176
1350
|
delete node.dynamicSubs[sub.topic];
|
|
1177
1351
|
})
|
|
1178
1352
|
//user can access current subscriptions through the complete node is so desired
|
|
@@ -1182,7 +1356,7 @@ module.exports = function(RED) {
|
|
|
1182
1356
|
subscriptions.forEach(function (sub) {
|
|
1183
1357
|
//always unsubscribe before subscribe to prevent multiple subs to same topic
|
|
1184
1358
|
if (node.dynamicSubs[sub.topic]) {
|
|
1185
|
-
node.brokerConn.unsubscribe(sub.topic, node.id);
|
|
1359
|
+
node.brokerConn.unsubscribe(sub.topic, node.id, true);
|
|
1186
1360
|
delete node.dynamicSubs[sub.topic];
|
|
1187
1361
|
}
|
|
1188
1362
|
|
|
@@ -1233,7 +1407,7 @@ module.exports = function(RED) {
|
|
|
1233
1407
|
});
|
|
1234
1408
|
node.dynamicSubs = {};
|
|
1235
1409
|
} else {
|
|
1236
|
-
node.brokerConn.unsubscribe(node.topic,node.id, removed);
|
|
1410
|
+
node.brokerConn.unsubscribe(node.topic, node.id, removed);
|
|
1237
1411
|
}
|
|
1238
1412
|
node.brokerConn.deregister(node, done, removed);
|
|
1239
1413
|
node.brokerConn = null;
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
**/
|
|
16
16
|
|
|
17
|
-
module.exports = function(RED) {
|
|
17
|
+
module.exports = async function(RED) {
|
|
18
18
|
"use strict";
|
|
19
|
-
const got =
|
|
19
|
+
const { got } = await import('got')
|
|
20
20
|
const {CookieJar} = require("tough-cookie");
|
|
21
21
|
const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent');
|
|
22
22
|
const FormData = require('form-data');
|
|
@@ -210,24 +210,24 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|
|
210
210
|
// set defaultport, else when using HttpsProxyAgent, it's defaultPort of 443 will be used :(.
|
|
211
211
|
// Had to remove this to get http->https redirect to work
|
|
212
212
|
// opts.defaultPort = isHttps?443:80;
|
|
213
|
-
opts.timeout = node.reqTimeout;
|
|
213
|
+
opts.timeout = { request: node.reqTimeout || 5000 };
|
|
214
214
|
opts.throwHttpErrors = false;
|
|
215
215
|
// TODO: add UI option to auto decompress. Setting to false for 1.x compatibility
|
|
216
216
|
opts.decompress = false;
|
|
217
217
|
opts.method = method;
|
|
218
|
-
opts.retry = 0;
|
|
218
|
+
opts.retry = { limit: 0 };
|
|
219
219
|
opts.responseType = 'buffer';
|
|
220
220
|
opts.maxRedirects = 21;
|
|
221
221
|
opts.cookieJar = new CookieJar();
|
|
222
222
|
opts.ignoreInvalidCookies = true;
|
|
223
|
-
opts.forever = nodeHTTPPersistent;
|
|
223
|
+
// opts.forever = nodeHTTPPersistent;
|
|
224
224
|
if (msg.requestTimeout !== undefined) {
|
|
225
225
|
if (isNaN(msg.requestTimeout)) {
|
|
226
226
|
node.warn(RED._("httpin.errors.timeout-isnan"));
|
|
227
227
|
} else if (msg.requestTimeout < 1) {
|
|
228
228
|
node.warn(RED._("httpin.errors.timeout-isnegative"));
|
|
229
229
|
} else {
|
|
230
|
-
opts.timeout = msg.requestTimeout;
|
|
230
|
+
opts.timeout = { request: msg.requestTimeout };
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
const originalHeaderMap = {};
|
|
@@ -245,9 +245,12 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|
|
245
245
|
delete options.headers[h];
|
|
246
246
|
}
|
|
247
247
|
})
|
|
248
|
-
|
|
249
248
|
if (node.insecureHTTPParser) {
|
|
250
|
-
|
|
249
|
+
// Setting the property under _unixOptions as pretty
|
|
250
|
+
// much the only hack available to get got to apply
|
|
251
|
+
// a core http option it doesn't think we should be
|
|
252
|
+
// allowed to set
|
|
253
|
+
options._unixOptions = { ...options.unixOptions, insecureHTTPParser: true }
|
|
251
254
|
}
|
|
252
255
|
}
|
|
253
256
|
],
|
|
@@ -403,15 +406,16 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|
|
403
406
|
return response
|
|
404
407
|
}
|
|
405
408
|
const requestUrl = new URL(response.request.requestUrl);
|
|
406
|
-
const options =
|
|
409
|
+
const options = { headers: {} }
|
|
407
410
|
const normalisedHeaders = {};
|
|
408
411
|
Object.keys(response.headers).forEach(k => {
|
|
409
412
|
normalisedHeaders[k.toLowerCase()] = response.headers[k]
|
|
410
413
|
})
|
|
411
414
|
if (normalisedHeaders['www-authenticate']) {
|
|
412
|
-
let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, options.method, requestUrl.pathname, normalisedHeaders['www-authenticate'])
|
|
415
|
+
let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, response.request.options.method, requestUrl.pathname, normalisedHeaders['www-authenticate'])
|
|
413
416
|
options.headers.Authorization = authHeader;
|
|
414
417
|
}
|
|
418
|
+
// response.request.options.merge(options)
|
|
415
419
|
sentCreds = true;
|
|
416
420
|
return retry(options);
|
|
417
421
|
}
|
|
@@ -699,25 +703,43 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|
|
699
703
|
});
|
|
700
704
|
|
|
701
705
|
const md5 = (value) => { return crypto.createHash('md5').update(value).digest('hex') }
|
|
706
|
+
const sha256 = (value) => { return crypto.createHash('sha256').update(value).digest('hex') }
|
|
707
|
+
const sha512 = (value) => { return crypto.createHash('sha512').update(value).digest('hex') }
|
|
708
|
+
|
|
709
|
+
function digestCompute(algorithm, value) {
|
|
710
|
+
var lowercaseAlgorithm = ""
|
|
711
|
+
if (algorithm) {
|
|
712
|
+
lowercaseAlgorithm = algorithm.toLowerCase().replace(/-sess$/, '')
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (lowercaseAlgorithm === "sha-256") {
|
|
716
|
+
return sha256(value)
|
|
717
|
+
} else if (lowercaseAlgorithm === "sha-512-256") {
|
|
718
|
+
var hash = sha512(value)
|
|
719
|
+
return hash.slice(0, 64) // Only use the first 256 bits
|
|
720
|
+
} else {
|
|
721
|
+
return md5(value)
|
|
722
|
+
}
|
|
723
|
+
}
|
|
702
724
|
|
|
703
725
|
function ha1Compute(algorithm, user, realm, pass, nonce, cnonce) {
|
|
704
726
|
/**
|
|
705
|
-
* RFC 2617: handle both
|
|
727
|
+
* RFC 2617: handle both standard and -sess algorithms.
|
|
706
728
|
*
|
|
707
|
-
* If the algorithm directive's value
|
|
708
|
-
* HA1=
|
|
709
|
-
*
|
|
710
|
-
*
|
|
729
|
+
* If the algorithm directive's value ends with "-sess", then HA1 is
|
|
730
|
+
* HA1=digestCompute(digestCompute(username:realm:password):nonce:cnonce)
|
|
731
|
+
*
|
|
732
|
+
* If the algorithm directive's value does not end with "-sess", then HA1 is
|
|
733
|
+
* HA1=digestCompute(username:realm:password)
|
|
711
734
|
*/
|
|
712
|
-
var ha1 =
|
|
713
|
-
if (algorithm &&
|
|
714
|
-
return
|
|
735
|
+
var ha1 = digestCompute(algorithm, user + ':' + realm + ':' + pass)
|
|
736
|
+
if (algorithm && /-sess$/i.test(algorithm)) {
|
|
737
|
+
return digestCompute(algorithm, ha1 + ':' + nonce + ':' + cnonce)
|
|
715
738
|
} else {
|
|
716
739
|
return ha1
|
|
717
740
|
}
|
|
718
741
|
}
|
|
719
742
|
|
|
720
|
-
|
|
721
743
|
function buildDigestHeader(user, pass, method, path, authHeader) {
|
|
722
744
|
var challenge = {}
|
|
723
745
|
var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
|
|
@@ -732,10 +754,10 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
|
|
732
754
|
var nc = qop && '00000001'
|
|
733
755
|
var cnonce = qop && uuid().replace(/-/g, '')
|
|
734
756
|
var ha1 = ha1Compute(challenge.algorithm, user, challenge.realm, pass, challenge.nonce, cnonce)
|
|
735
|
-
var ha2 =
|
|
757
|
+
var ha2 = digestCompute(challenge.algorithm, method + ':' + path)
|
|
736
758
|
var digestResponse = qop
|
|
737
|
-
?
|
|
738
|
-
:
|
|
759
|
+
? digestCompute(challenge.algorithm, ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
|
|
760
|
+
: digestCompute(challenge.algorithm, ha1 + ':' + challenge.nonce + ':' + ha2)
|
|
739
761
|
var authValues = {
|
|
740
762
|
username: user,
|
|
741
763
|
realm: challenge.realm,
|
package/core/parsers/70-XML.js
CHANGED
|
@@ -33,8 +33,7 @@ module.exports = function(RED) {
|
|
|
33
33
|
parseString(value, options, function (err, result) {
|
|
34
34
|
if (err) { done(err); }
|
|
35
35
|
else {
|
|
36
|
-
|
|
37
|
-
RED.util.setMessageProperty(msg,node.property,value);
|
|
36
|
+
RED.util.setMessageProperty(msg,node.property,result);
|
|
38
37
|
send(msg);
|
|
39
38
|
done();
|
|
40
39
|
}
|
package/locales/de/messages.json
CHANGED
|
@@ -98,6 +98,7 @@
|
|
|
98
98
|
},
|
|
99
99
|
"scope": {
|
|
100
100
|
"all": "allen Nodes",
|
|
101
|
+
"group": "in gleicher Gruppe",
|
|
101
102
|
"selected": "ausgewählten Nodes"
|
|
102
103
|
}
|
|
103
104
|
},
|
|
@@ -110,6 +111,7 @@
|
|
|
110
111
|
},
|
|
111
112
|
"scope": {
|
|
112
113
|
"all": "allen Nodes",
|
|
114
|
+
"group": "in gleicher Gruppe",
|
|
113
115
|
"selected": "ausgewählten Nodes"
|
|
114
116
|
}
|
|
115
117
|
},
|
|
@@ -362,6 +364,7 @@
|
|
|
362
364
|
"port": "Port",
|
|
363
365
|
"keepalive": "Keep-Alive",
|
|
364
366
|
"cleansession": "Bereinigte Sitzung (clean session) verwenden",
|
|
367
|
+
"autoUnsubscribe": "Abonnement bei Verbindungsende automatisch beenden",
|
|
365
368
|
"cleanstart": "Verwende bereinigten Start",
|
|
366
369
|
"use-tls": "TLS",
|
|
367
370
|
"tls-config": "TLS-Konfiguration",
|
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
},
|
|
104
104
|
"scope": {
|
|
105
105
|
"all": "all nodes",
|
|
106
|
+
"group": "in same group",
|
|
106
107
|
"selected": "selected nodes"
|
|
107
108
|
}
|
|
108
109
|
},
|
|
@@ -115,6 +116,7 @@
|
|
|
115
116
|
},
|
|
116
117
|
"scope": {
|
|
117
118
|
"all": "all nodes",
|
|
119
|
+
"group": "in same group",
|
|
118
120
|
"selected": "selected nodes"
|
|
119
121
|
}
|
|
120
122
|
},
|
|
@@ -414,6 +416,7 @@
|
|
|
414
416
|
"port": "Port",
|
|
415
417
|
"keepalive": "Keep Alive",
|
|
416
418
|
"cleansession": "Use clean session",
|
|
419
|
+
"autoUnsubscribe": "Automatically unsubscribe when disconnecting",
|
|
417
420
|
"cleanstart": "Use clean start",
|
|
418
421
|
"use-tls": "Use TLS",
|
|
419
422
|
"tls-config": "TLS Configuration",
|
package/locales/ja/messages.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node-red/nodes",
|
|
3
|
-
"version": "3.1.0-beta.
|
|
3
|
+
"version": "3.1.0-beta.3",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"cronosjs": "1.7.1",
|
|
28
28
|
"denque": "2.1.0",
|
|
29
29
|
"form-data": "4.0.0",
|
|
30
|
-
"fs-extra": "
|
|
31
|
-
"got": "
|
|
30
|
+
"fs-extra": "11.1.1",
|
|
31
|
+
"got": "12.6.0",
|
|
32
32
|
"hash-sum": "2.0.0",
|
|
33
33
|
"hpagent": "1.2.0",
|
|
34
34
|
"https-proxy-agent": "5.0.1",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"tough-cookie": "4.1.2",
|
|
45
45
|
"uuid": "9.0.0",
|
|
46
46
|
"ws": "7.5.6",
|
|
47
|
-
"xml2js": "0.
|
|
47
|
+
"xml2js": "0.6.0",
|
|
48
48
|
"iconv-lite": "0.6.3"
|
|
49
49
|
}
|
|
50
50
|
}
|