@node-red/nodes 3.1.0-beta.2 → 3.1.0-beta.4

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>
@@ -40,7 +41,9 @@
40
41
  if (this.name) {
41
42
  return this.name;
42
43
  }
43
- if (this.scope) {
44
+ if (this.scope === "group") {
45
+ return this._("catch.catchGroup");
46
+ } else if (Array.isArray(this.scope)) {
44
47
  return this._("catch.catchNodes",{number:this.scope.length});
45
48
  }
46
49
  return this.uncaught?this._("catch.catchUncaught"):this._("catch.catch")
@@ -170,6 +173,8 @@
170
173
  });
171
174
  if (this.scope === null) {
172
175
  $("#node-input-scope-select").val("all");
176
+ } else if(this.scope === "group"){
177
+ $("#node-input-scope-select").val("group");
173
178
  } else {
174
179
  $("#node-input-scope-select").val("target");
175
180
  }
@@ -179,6 +184,8 @@
179
184
  var scope = $("#node-input-scope-select").val();
180
185
  if (scope === 'all') {
181
186
  this.scope = null;
187
+ } else if(scope === 'group') {
188
+ this.scope = "group";
182
189
  } else {
183
190
  $("#node-input-uncaught").prop("checked",false);
184
191
  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>
@@ -32,7 +33,15 @@
32
33
  outputs:1,
33
34
  icon: "status.svg",
34
35
  label: function() {
35
- return this.name||(this.scope?this._("status.statusNodes",{number:this.scope.length}):this._("status.status"));
36
+ if (this.name) {
37
+ return this.name;
38
+ }
39
+ if (this.scope === "group") {
40
+ return this._("status.statusGroup");
41
+ } else if (Array.isArray(this.scope)) {
42
+ return this._("status.statusNodes",{number:this.scope.length});
43
+ }
44
+ return this._("status.status")
36
45
  },
37
46
  labelStyle: function() {
38
47
  return this.name?"node_label_italic":"";
@@ -157,6 +166,8 @@
157
166
  });
158
167
  if (this.scope === null) {
159
168
  $("#node-input-scope-select").val("all");
169
+ } else if(this.scope === "group"){
170
+ $("#node-input-scope-select").val("group");
160
171
  } else {
161
172
  $("#node-input-scope-select").val("target");
162
173
  }
@@ -166,6 +177,8 @@
166
177
  var scope = $("#node-input-scope-select").val();
167
178
  if (scope === 'all') {
168
179
  this.scope = null;
180
+ } else if(scope === 'group') {
181
+ this.scope = "group";
169
182
  } else {
170
183
  this.scope = $("#node-input-status-target-container-div").treeList('selected').map(function(i) { return i.node.id})
171
184
  }
@@ -28,7 +28,7 @@
28
28
  <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
29
29
  </div>
30
30
  <div class="form-row">
31
- <label for="node-input-timeout"><span data-i18n="exec.label.timeout"></span></label>
31
+ <label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="exec.label.timeout"></span></label>
32
32
  <input type="text" id="node-input-timeout" placeholder="30" style="width: 70px; margin-right: 5px;"><span data-i18n="inject.seconds"></span>
33
33
  </div>
34
34
  <div class="form-row">
@@ -82,6 +82,11 @@
82
82
  <input id="node-input-outputs" style="width: 60px;" value="1">
83
83
  </div>
84
84
 
85
+ <div class="form-row">
86
+ <label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="function.label.timeout"></span></label>
87
+ <input id="node-input-timeout" style="width: 60px;" data-i18n="[placeholder]join.seconds">
88
+ </div>
89
+
85
90
  <div class="form-row node-input-libs-row hide" style="margin-bottom: 0px;">
86
91
  <label><i class="fa fa-cubes"></i> <span data-i18n="function.label.modules"></span></label>
87
92
  </div>
@@ -360,6 +365,7 @@
360
365
  name: {value:"_DEFAULT_"},
361
366
  func: {value:"\nreturn msg;"},
362
367
  outputs: {value:1},
368
+ timeout:{value:0},
363
369
  noerr: {value:0,required:true,
364
370
  validate: function(v, opt) {
365
371
  if (!v) {
@@ -464,6 +470,26 @@
464
470
  }
465
471
  });
466
472
 
473
+ // 4294967 is max in node.js timeout.
474
+ $( "#node-input-timeout" ).spinner({
475
+ min: 0,
476
+ max: 4294967,
477
+ change: function(event, ui) {
478
+ var value = this.value;
479
+ if(value == ""){
480
+ value = 0;
481
+ }
482
+ else
483
+ {
484
+ value = parseInt(value);
485
+ }
486
+ value = isNaN(value) ? 1 : value;
487
+ value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
488
+ value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
489
+ if (value !== this.value) { $(this).spinner("value", value); }
490
+ }
491
+ });
492
+
467
493
  var buildEditor = function(id, stateId, focus, value, defaultValue, extraLibs, offset) {
468
494
  var editor = RED.editor.createEditor({
469
495
  id: id,
@@ -503,7 +529,7 @@
503
529
  editor:this.editor, // the field name the main text body goes to
504
530
  mode:"ace/mode/nrjavascript",
505
531
  fields:[
506
- 'name', 'outputs',
532
+ 'name', 'outputs', 'timeout',
507
533
  {
508
534
  name: 'initialize',
509
535
  get: function() {
@@ -96,6 +96,13 @@ module.exports = function(RED) {
96
96
  node.name = n.name;
97
97
  node.func = n.func;
98
98
  node.outputs = n.outputs;
99
+ node.timeout = n.timeout*1000;
100
+ if(node.timeout>0){
101
+ node.timeoutOptions = {
102
+ timeout:node.timeout,
103
+ breakOnSigint:true
104
+ }
105
+ }
99
106
  node.ini = n.initialize ? n.initialize.trim() : "";
100
107
  node.fin = n.finalize ? n.finalize.trim() : "";
101
108
  node.libs = n.libs || [];
@@ -362,6 +369,10 @@ module.exports = function(RED) {
362
369
  })(__initSend__);`;
363
370
  iniOpt = createVMOpt(node, " setup");
364
371
  iniScript = new vm.Script(iniText, iniOpt);
372
+ if(node.timeout>0){
373
+ iniOpt.timeout = node.timeout;
374
+ iniOpt.breakOnSigint = true;
375
+ }
365
376
  }
366
377
  node.script = vm.createScript(functionText, createVMOpt(node, ""));
367
378
  if (node.fin && (node.fin !== "")) {
@@ -385,6 +396,10 @@ module.exports = function(RED) {
385
396
  })();`;
386
397
  finOpt = createVMOpt(node, " cleanup");
387
398
  finScript = new vm.Script(finText, finOpt);
399
+ if(node.timeout>0){
400
+ finOpt.timeout = node.timeout;
401
+ finOpt.breakOnSigint = true;
402
+ }
388
403
  }
389
404
  var promise = Promise.resolve();
390
405
  if (iniScript) {
@@ -396,9 +411,12 @@ module.exports = function(RED) {
396
411
  var start = process.hrtime();
397
412
  context.msg = msg;
398
413
  context.__send__ = send;
399
- context.__done__ = done;
400
-
401
- node.script.runInContext(context);
414
+ context.__done__ = done;
415
+ var opts = {};
416
+ if (node.timeout>0){
417
+ opts = node.timeoutOptions;
418
+ }
419
+ node.script.runInContext(context,opts);
402
420
  context.results.then(function(results) {
403
421
  sendResults(node,send,msg._msgid,results,false);
404
422
  if (handleNodeDoneCall) {
@@ -103,6 +103,7 @@
103
103
  } else if (type === "istype") {
104
104
  r.v = rule.find(".node-input-rule-type-value").typedInput('type');
105
105
  r.vt = rule.find(".node-input-rule-type-value").typedInput('type');
106
+ r.vt = (r.vt === "number") ? "num" : "str";
106
107
  } else if (type === "jsonata_exp") {
107
108
  r.v = rule.find(".node-input-rule-exp-value").typedInput('value');
108
109
  r.vt = rule.find(".node-input-rule-exp-value").typedInput('type');
@@ -229,6 +229,7 @@ module.exports = function(RED) {
229
229
  node.on("input", function(msg, send, done) {
230
230
  if (!node.drop) {
231
231
  var m = RED.util.cloneMessage(msg);
232
+ delete m.flush;
232
233
  if (Object.keys(m).length > 1) {
233
234
  if (node.intervalID !== -1) {
234
235
  if (node.allowrate && m.hasOwnProperty("rate") && !isNaN(parseFloat(m.rate)) && node.rate !== m.rate) {
@@ -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;
@@ -93,7 +93,7 @@
93
93
 
94
94
  <div class="form-row">
95
95
  <input type="checkbox" id="node-input-insecureHTTPParser" style="display: inline-block; width: auto; vertical-align: top;">
96
- <label for="node-input-insecureHTTPParser", style="width: auto;" data-i18n="httpin.insecureHTTPParser"></label>
96
+ <label for="node-input-insecureHTTPParser" style="width: auto;" data-i18n="httpin.insecureHTTPParser"></label>
97
97
  </div>
98
98
 
99
99
 
@@ -14,15 +14,17 @@
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');
23
23
  const { v4: uuid } = require('uuid');
24
24
  const crypto = require('crypto');
25
25
  const URL = require("url").URL
26
+ const http = require("http")
27
+ const https = require("https")
26
28
  var mustache = require("mustache");
27
29
  var querystring = require("querystring");
28
30
  var cookie = require("cookie");
@@ -65,16 +67,27 @@ in your Node-RED user directory (${RED.settings.userDir}).
65
67
  function HTTPRequest(n) {
66
68
  RED.nodes.createNode(this,n);
67
69
  checkNodeAgentPatch();
68
- var node = this;
69
- var nodeUrl = n.url;
70
- var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
71
- var nodeMethod = n.method || "GET";
72
- var paytoqs = false;
73
- var paytobody = false;
74
- var redirectList = [];
75
- var sendErrorsToCatch = n.senderr;
70
+ const node = this;
71
+ const nodeUrl = n.url;
72
+ const isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
73
+ const nodeMethod = n.method || "GET";
74
+ let paytoqs = false;
75
+ let paytobody = false;
76
+ let redirectList = [];
77
+ const sendErrorsToCatch = n.senderr;
76
78
  node.headers = n.headers || [];
77
- var nodeHTTPPersistent = n["persist"];
79
+ const useKeepAlive = n["persist"];
80
+ let agents = null
81
+ if (useKeepAlive) {
82
+ agents = {
83
+ http: new http.Agent({ keepAlive: true }),
84
+ https: new https.Agent({ keepAlive: true })
85
+ }
86
+ node.on('close', function () {
87
+ agents.http.destroy()
88
+ agents.https.destroy()
89
+ })
90
+ }
78
91
  if (n.tls) {
79
92
  var tlsNode = RED.nodes.getNode(n.tls);
80
93
  }
@@ -210,24 +223,24 @@ in your Node-RED user directory (${RED.settings.userDir}).
210
223
  // set defaultport, else when using HttpsProxyAgent, it's defaultPort of 443 will be used :(.
211
224
  // Had to remove this to get http->https redirect to work
212
225
  // opts.defaultPort = isHttps?443:80;
213
- opts.timeout = node.reqTimeout;
226
+ opts.timeout = { request: node.reqTimeout || 5000 };
214
227
  opts.throwHttpErrors = false;
215
228
  // TODO: add UI option to auto decompress. Setting to false for 1.x compatibility
216
229
  opts.decompress = false;
217
230
  opts.method = method;
218
- opts.retry = 0;
231
+ opts.retry = { limit: 0 };
219
232
  opts.responseType = 'buffer';
220
233
  opts.maxRedirects = 21;
221
234
  opts.cookieJar = new CookieJar();
222
235
  opts.ignoreInvalidCookies = true;
223
- opts.forever = nodeHTTPPersistent;
236
+ // opts.forever = nodeHTTPPersistent;
224
237
  if (msg.requestTimeout !== undefined) {
225
238
  if (isNaN(msg.requestTimeout)) {
226
239
  node.warn(RED._("httpin.errors.timeout-isnan"));
227
240
  } else if (msg.requestTimeout < 1) {
228
241
  node.warn(RED._("httpin.errors.timeout-isnegative"));
229
242
  } else {
230
- opts.timeout = msg.requestTimeout;
243
+ opts.timeout = { request: msg.requestTimeout };
231
244
  }
232
245
  }
233
246
  const originalHeaderMap = {};
@@ -245,9 +258,12 @@ in your Node-RED user directory (${RED.settings.userDir}).
245
258
  delete options.headers[h];
246
259
  }
247
260
  })
248
-
249
261
  if (node.insecureHTTPParser) {
250
- options.insecureHTTPParser = true
262
+ // Setting the property under _unixOptions as pretty
263
+ // much the only hack available to get got to apply
264
+ // a core http option it doesn't think we should be
265
+ // allowed to set
266
+ options._unixOptions = { ...options.unixOptions, insecureHTTPParser: true }
251
267
  }
252
268
  }
253
269
  ],
@@ -403,15 +419,16 @@ in your Node-RED user directory (${RED.settings.userDir}).
403
419
  return response
404
420
  }
405
421
  const requestUrl = new URL(response.request.requestUrl);
406
- const options = response.request.options;
422
+ const options = { headers: {} }
407
423
  const normalisedHeaders = {};
408
424
  Object.keys(response.headers).forEach(k => {
409
425
  normalisedHeaders[k.toLowerCase()] = response.headers[k]
410
426
  })
411
427
  if (normalisedHeaders['www-authenticate']) {
412
- let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, options.method, requestUrl.pathname, normalisedHeaders['www-authenticate'])
428
+ let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, response.request.options.method, requestUrl.pathname, normalisedHeaders['www-authenticate'])
413
429
  options.headers.Authorization = authHeader;
414
430
  }
431
+ // response.request.options.merge(options)
415
432
  sentCreds = true;
416
433
  return retry(options);
417
434
  }
@@ -556,12 +573,14 @@ in your Node-RED user directory (${RED.settings.userDir}).
556
573
  opts.agent = {
557
574
  http: new HttpProxyAgent(proxyOptions),
558
575
  https: new HttpsProxyAgent(proxyOptions)
559
- };
560
-
576
+ }
561
577
  } else {
562
578
  node.warn("Bad proxy url: "+ prox);
563
579
  }
564
580
  }
581
+ if (useKeepAlive && !opts.agent) {
582
+ opts.agent = agents
583
+ }
565
584
  if (tlsNode) {
566
585
  opts.https = {};
567
586
  tlsNode.addTLSOptions(opts.https);
@@ -699,25 +718,43 @@ in your Node-RED user directory (${RED.settings.userDir}).
699
718
  });
700
719
 
701
720
  const md5 = (value) => { return crypto.createHash('md5').update(value).digest('hex') }
721
+ const sha256 = (value) => { return crypto.createHash('sha256').update(value).digest('hex') }
722
+ const sha512 = (value) => { return crypto.createHash('sha512').update(value).digest('hex') }
723
+
724
+ function digestCompute(algorithm, value) {
725
+ var lowercaseAlgorithm = ""
726
+ if (algorithm) {
727
+ lowercaseAlgorithm = algorithm.toLowerCase().replace(/-sess$/, '')
728
+ }
729
+
730
+ if (lowercaseAlgorithm === "sha-256") {
731
+ return sha256(value)
732
+ } else if (lowercaseAlgorithm === "sha-512-256") {
733
+ var hash = sha512(value)
734
+ return hash.slice(0, 64) // Only use the first 256 bits
735
+ } else {
736
+ return md5(value)
737
+ }
738
+ }
702
739
 
703
740
  function ha1Compute(algorithm, user, realm, pass, nonce, cnonce) {
704
741
  /**
705
- * RFC 2617: handle both MD5 and MD5-sess algorithms.
742
+ * RFC 2617: handle both standard and -sess algorithms.
706
743
  *
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)
744
+ * If the algorithm directive's value ends with "-sess", then HA1 is
745
+ * HA1=digestCompute(digestCompute(username:realm:password):nonce:cnonce)
746
+ *
747
+ * If the algorithm directive's value does not end with "-sess", then HA1 is
748
+ * HA1=digestCompute(username:realm:password)
711
749
  */
712
- var ha1 = md5(user + ':' + realm + ':' + pass)
713
- if (algorithm && algorithm.toLowerCase() === 'md5-sess') {
714
- return md5(ha1 + ':' + nonce + ':' + cnonce)
750
+ var ha1 = digestCompute(algorithm, user + ':' + realm + ':' + pass)
751
+ if (algorithm && /-sess$/i.test(algorithm)) {
752
+ return digestCompute(algorithm, ha1 + ':' + nonce + ':' + cnonce)
715
753
  } else {
716
754
  return ha1
717
755
  }
718
756
  }
719
757
 
720
-
721
758
  function buildDigestHeader(user, pass, method, path, authHeader) {
722
759
  var challenge = {}
723
760
  var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
@@ -732,10 +769,10 @@ in your Node-RED user directory (${RED.settings.userDir}).
732
769
  var nc = qop && '00000001'
733
770
  var cnonce = qop && uuid().replace(/-/g, '')
734
771
  var ha1 = ha1Compute(challenge.algorithm, user, challenge.realm, pass, challenge.nonce, cnonce)
735
- var ha2 = md5(method + ':' + path)
772
+ var ha2 = digestCompute(challenge.algorithm, method + ':' + path)
736
773
  var digestResponse = qop
737
- ? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
738
- : md5(ha1 + ':' + challenge.nonce + ':' + ha2)
774
+ ? digestCompute(challenge.algorithm, ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
775
+ : digestCompute(challenge.algorithm, ha1 + ':' + challenge.nonce + ':' + ha2)
739
776
  var authValues = {
740
777
  username: user,
741
778
  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
  }
@@ -629,6 +629,9 @@ module.exports = function(RED) {
629
629
  joinChar = node.joiner;
630
630
  if (n.count === "" && msg.hasOwnProperty('parts')) {
631
631
  targetCount = msg.parts.count || 0;
632
+ if (msg.parts.hasOwnProperty('id')) {
633
+ partId = msg.parts.id;
634
+ }
632
635
  }
633
636
  if (node.build === 'object') {
634
637
  propertyKey = RED.util.getMessageProperty(msg,node.key);
@@ -68,9 +68,12 @@ module.exports = function(RED) {
68
68
  node.error(err,msg);
69
69
  return done();
70
70
  } else {
71
- filename = value;
71
+ processMsg2(msg,nodeSend,value,done);
72
72
  }
73
73
  });
74
+ }
75
+
76
+ function processMsg2(msg,nodeSend,filename,done) {
74
77
  filename = filename || "";
75
78
  msg.filename = filename;
76
79
  var fullFilename = filename;
@@ -311,9 +314,12 @@ module.exports = function(RED) {
311
314
  node.error(err,msg);
312
315
  return done();
313
316
  } else {
314
- filename = (value || "").replace(/\t|\r|\n/g,'');
317
+ processMsg2(msg, nodeSend, (value || "").replace(/\t|\r|\n/g,''), nodeDone);
315
318
  }
316
319
  });
320
+ });
321
+
322
+ function processMsg2(msg, nodeSend, filename, nodeDone) {
317
323
  filename = filename || "";
318
324
  var fullFilename = filename;
319
325
  if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
@@ -434,7 +440,8 @@ module.exports = function(RED) {
434
440
  nodeDone();
435
441
  });
436
442
  }
437
- });
443
+ }
444
+
438
445
  this.on('close', function() {
439
446
  node.status({});
440
447
  });
@@ -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
  },
@@ -214,7 +216,8 @@
214
216
  "initialize": "Start",
215
217
  "finalize": "Stopp",
216
218
  "outputs": "Ausgänge",
217
- "modules": "Module"
219
+ "modules": "Module",
220
+ "timeout": "Timeout"
218
221
  },
219
222
  "text": {
220
223
  "initialize": "// Der Code hier wird ausgeführt,\n// wenn der Node gestartet wird\n",
@@ -362,6 +365,7 @@
362
365
  "port": "Port",
363
366
  "keepalive": "Keep-Alive",
364
367
  "cleansession": "Bereinigte Sitzung (clean session) verwenden",
368
+ "autoUnsubscribe": "Abonnement bei Verbindungsende automatisch beenden",
365
369
  "cleanstart": "Verwende bereinigten Start",
366
370
  "use-tls": "TLS",
367
371
  "tls-config": "TLS-Konfiguration",
@@ -36,5 +36,5 @@ greater than one day you should consider using a scheduler node that can cope wi
36
36
  <p><b>Note</b>: The <i>"Interval between times"</i> and <i>"at a specific time"</i> options use the standard cron system.
37
37
  This means that 20 minutes will be at the next hour, 20 minutes past and 40 minutes past - not in 20 minutes time.
38
38
  If you want every 20 minutes from now - use the <i>"interval"</i> option.</p>
39
- <p><b>Note</b>: To include a newline in a string you must use a Function node to create the payload.</p>
39
+ <p><b>Note</b>: To include a newline in a string you must use the Function or Template node to create the payload.</p>
40
40
  </script>
@@ -94,6 +94,7 @@
94
94
  },
95
95
  "catch": {
96
96
  "catch": "catch: all",
97
+ "catchGroup": "catch: group",
97
98
  "catchNodes": "catch: __number__",
98
99
  "catchUncaught": "catch: uncaught",
99
100
  "label": {
@@ -103,11 +104,13 @@
103
104
  },
104
105
  "scope": {
105
106
  "all": "all nodes",
107
+ "group": "in same group",
106
108
  "selected": "selected nodes"
107
109
  }
108
110
  },
109
111
  "status": {
110
112
  "status": "status: all",
113
+ "statusGroup": "status: group",
111
114
  "statusNodes": "status: __number__",
112
115
  "label": {
113
116
  "source": "Report status from",
@@ -115,6 +118,7 @@
115
118
  },
116
119
  "scope": {
117
120
  "all": "all nodes",
121
+ "group": "in same group",
118
122
  "selected": "selected nodes"
119
123
  }
120
124
  },
@@ -248,7 +252,8 @@
248
252
  "initialize": "On Start",
249
253
  "finalize": "On Stop",
250
254
  "outputs": "Outputs",
251
- "modules": "Modules"
255
+ "modules": "Modules",
256
+ "timeout": "Timeout"
252
257
  },
253
258
  "text": {
254
259
  "initialize": "// Code added here will be run once\n// whenever the node is started.\n",
@@ -414,6 +419,7 @@
414
419
  "port": "Port",
415
420
  "keepalive": "Keep Alive",
416
421
  "cleansession": "Use clean session",
422
+ "autoUnsubscribe": "Automatically unsubscribe when disconnecting",
417
423
  "cleanstart": "Use clean start",
418
424
  "use-tls": "Use TLS",
419
425
  "tls-config": "TLS Configuration",
@@ -103,6 +103,7 @@
103
103
  },
104
104
  "scope": {
105
105
  "all": "tous les noeuds",
106
+ "group": "dans le même groupe",
106
107
  "selected": "noeuds sélectionnés"
107
108
  }
108
109
  },
@@ -115,6 +116,7 @@
115
116
  },
116
117
  "scope": {
117
118
  "all": "tous les noeuds",
119
+ "group": "dans le même groupe",
118
120
  "selected": "noeuds sélectionnés"
119
121
  }
120
122
  },
@@ -414,6 +416,7 @@
414
416
  "port": "Port",
415
417
  "keepalive": "Rester en vie",
416
418
  "cleansession": "Utiliser une session propre",
419
+ "autoUnsubscribe": "Se désabonner automatiquement lors de la déconnexion",
417
420
  "cleanstart": "Utiliser un démarrage propre",
418
421
  "use-tls": "Utiliser TLS",
419
422
  "tls-config": "Configuration TLS",
@@ -30,5 +30,5 @@
30
30
  <p>また、フロー開始の際に一度だけメッセージを送出させることもできます。</p>
31
31
  <p>「<i>時間間隔</i>」に指定可能な値の最大値は、約596時間(もしくは24日)です。一日より長い間隔を扱いたい場合は、電源停止や再起動にも対応可能なスケジューラノードの利用を検討すると良いでしょう。</p>
32
32
  <p><b>注</b>:「<i>指定した時間間隔、日時</i>」と「<i>指定した日時</i>」オプションは標準的なcronシステムを内部で利用します。したがって「20分」という指定は、その時点から20分後ではなく、毎時きっかり、20分、40分を意味します。現時刻から20分毎を指定するには「<i>指定した時間間隔</i>」オプションを用います。</p>
33
- <p><b>注</b>: 文字列に改行を含めたい場合は、functionノードを使ってペイロードを設定してください。</p>
33
+ <p><b>注</b>: 文字列に改行を含めたい場合は、functionノードまたはtemplateノードを使ってペイロードを設定してください。</p>
34
34
  </script>
@@ -94,6 +94,7 @@
94
94
  },
95
95
  "catch": {
96
96
  "catch": "catch: 全て",
97
+ "catchGroup": "catch: グループ",
97
98
  "catchNodes": "catch: __number__",
98
99
  "catchUncaught": "catch: 未補足",
99
100
  "label": {
@@ -103,11 +104,13 @@
103
104
  },
104
105
  "scope": {
105
106
  "all": "全てのノード",
107
+ "group": "同一グループ内",
106
108
  "selected": "選択したノード"
107
109
  }
108
110
  },
109
111
  "status": {
110
112
  "status": "status: 全て",
113
+ "statusGroup": "status: グループ",
111
114
  "statusNodes": "status: __number__",
112
115
  "label": {
113
116
  "source": "ステータス取得元",
@@ -115,6 +118,7 @@
115
118
  },
116
119
  "scope": {
117
120
  "all": "全てのノード",
121
+ "group": "同一グループ内",
118
122
  "selected": "選択したノード"
119
123
  }
120
124
  },
@@ -248,7 +252,8 @@
248
252
  "initialize": "初期化処理",
249
253
  "finalize": "終了処理",
250
254
  "outputs": "出力数",
251
- "modules": "モジュール"
255
+ "modules": "モジュール",
256
+ "timeout": "タイムアウト"
252
257
  },
253
258
  "text": {
254
259
  "initialize": "// ここに記述したコードは、ノードをデプロイした時に\n// 一度だけ実行されます。\n",
@@ -414,6 +419,7 @@
414
419
  "port": "ポート",
415
420
  "keepalive": "キープアライブ時間",
416
421
  "cleansession": "セッションの初期化",
422
+ "autoUnsubscribe": "切断時に購読を自動解除",
417
423
  "cleanstart": "クリーンスタート",
418
424
  "use-tls": "TLSを使用",
419
425
  "tls-config": "TLS設定",
@@ -212,7 +212,8 @@
212
212
  "function": "Функция",
213
213
  "initialize": "Настройка",
214
214
  "finalize": "Закрытие",
215
- "outputs": "Выходы"
215
+ "outputs": "Выходы",
216
+ "timeout":"Время ожидания"
216
217
  },
217
218
  "text": {
218
219
  "initialize": "// Добавленный здесь код будет исполняться\n// однократно при развертывании узла.\n",
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.4",
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
  }