@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.
@@ -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
  }
@@ -35,7 +35,11 @@ module.exports = function(RED) {
35
35
  }
36
36
  else { node.previous = {}; }
37
37
  }
38
- var value = RED.util.getMessageProperty(msg,node.property);
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
- var ok = false;
492
+ let ok = true;
487
493
  if ($("#node-config-input-clientid").length) {
488
494
  // Currently editing the node
489
- ok = $("#node-config-input-cleansession").is(":checked") || (v||"").length > 0;
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
- ok = (this.cleansession===undefined || this.cleansession) || (v||"").length > 0;
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 ok;
505
+ if (!ok) {
506
+ return RED._("node-red:mqtt.errors.invalid-client-id");
495
507
  }
496
- return RED._("node-red:mqtt.errors.invalid-client-id");
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) {
@@ -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 v5 = node.brokerConn.options && node.brokerConn.options.protocolVersion == 5;
223
- var msg = {topic:topic, payload:null, qos:packet.qos, retain:packet.retain};
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")||(node.brokerConn.broker === "127.0.0.1")) {
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
- qos = Math.max(qos,node.subscriptions[s][r].qos);
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
- } else {
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
- reject();
880
+ resolve();
865
881
  }, ms);
866
882
  client.end(() => {
867
- clearTimeout(t);
868
- resolve()
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
- node.subscribe = function (topic,options,callback,ref) {
892
- ref = ref||0;
893
- var qos;
894
- if(typeof options == "object") {
895
- qos = options.qos;
896
- } else {
897
- qos = options;
898
- options = {};
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
- options.qos = qos;
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.properties = options.properties || {};
905
- options.properties.subscriptionIdentifier = node.subscriptionIds[topic];
906
-
907
- node.subscriptions[topic] = node.subscriptions[topic]||{};
908
- var sub = {
909
- topic:topic,
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._clientOn('message',sub.handler);
924
- node.client.subscribe(topic, options);
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
- node.unsubscribe = function (topic, ref, removed) {
1077
+ }
1078
+
1079
+ node.unsubscribe = function (topic, ref, removeClientSubscription) {
929
1080
  ref = ref||0;
930
- var sub = node.subscriptions[topic];
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
- if(node.client) {
934
- node._clientRemoveListeners('message',sub[ref].handler);
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
- //TODO: Review. The `if(removed)` was commented out to always delete and remove subscriptions.
939
- // if we dont then property changes dont get applied and old subs still trigger
940
- //if (removed) {
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
- delete node.subscriptions[topic];
943
- delete node.subscriptionIds[topic];
944
- if (node.connected) {
945
- node.client.unsubscribe(topic);
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, node.serverProperties.topicAliasMaximum || 0);
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 = require("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
- options.insecureHTTPParser = true
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 = response.request.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 MD5 and MD5-sess algorithms.
727
+ * RFC 2617: handle both standard and -sess algorithms.
706
728
  *
707
- * If the algorithm directive's value is "MD5" or unspecified, then HA1 is
708
- * HA1=MD5(username:realm:password)
709
- * If the algorithm directive's value is "MD5-sess", then HA1 is
710
- * HA1=MD5(MD5(username:realm:password):nonce:cnonce)
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 = md5(user + ':' + realm + ':' + pass)
713
- if (algorithm && algorithm.toLowerCase() === 'md5-sess') {
714
- return md5(ha1 + ':' + nonce + ':' + cnonce)
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 = md5(method + ':' + path)
757
+ var ha2 = digestCompute(challenge.algorithm, method + ':' + path)
736
758
  var digestResponse = qop
737
- ? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
738
- : md5(ha1 + ':' + challenge.nonce + ':' + ha2)
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,
@@ -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
- value = result;
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
  }
@@ -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",
@@ -414,6 +414,7 @@
414
414
  "port": "ポート",
415
415
  "keepalive": "キープアライブ時間",
416
416
  "cleansession": "セッションの初期化",
417
+ "autoUnsubscribe": "切断時に購読を自動解除",
417
418
  "cleanstart": "クリーンスタート",
418
419
  "use-tls": "TLSを使用",
419
420
  "tls-config": "TLS設定",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-red/nodes",
3
- "version": "3.1.0-beta.2",
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": "10.1.0",
31
- "got": "11.8.6",
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.4.23",
47
+ "xml2js": "0.6.0",
48
48
  "iconv-lite": "0.6.3"
49
49
  }
50
50
  }