@node-red/nodes 2.2.0 → 2.2.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.
@@ -109,9 +109,10 @@ module.exports = function(RED) {
109
109
  if (!property) return;
110
110
 
111
111
  if (valueType === "jsonata") {
112
- if (p.exp) {
112
+ if (p.v) {
113
113
  try {
114
- var val = RED.util.evaluateJSONataExpression(p.exp, msg);
114
+ var exp = RED.util.prepareJSONataExpression(p.v, node);
115
+ var val = RED.util.evaluateJSONataExpression(exp, msg);
115
116
  RED.util.setMessageProperty(msg, property, val, true);
116
117
  }
117
118
  catch (err) {
@@ -7,6 +7,7 @@ module.exports = function(RED) {
7
7
  var debuglength = RED.settings.debugMaxLength || 1000;
8
8
  var useColors = RED.settings.debugUseColors || false;
9
9
  util.inspect.styles.boolean = "red";
10
+ const { hasOwnProperty } = Object.prototype;
10
11
 
11
12
  function DebugNode(n) {
12
13
  var hasEditExpression = (n.targetType === "jsonata");
@@ -107,7 +108,9 @@ module.exports = function(RED) {
107
108
  }
108
109
  })
109
110
  this.on("input", function(msg, send, done) {
110
- if (msg.hasOwnProperty("status") && msg.status.hasOwnProperty("source") && msg.status.source.hasOwnProperty("id") && (msg.status.source.id === node.id)) {
111
+ if (hasOwnProperty.call(msg, "status") && msg.status &&
112
+ hasOwnProperty.call(msg.status, "source") && msg.status.source &&
113
+ hasOwnProperty.call(msg.status.source, "id") && (msg.status.source.id === node.id)) {
111
114
  done();
112
115
  return;
113
116
  }
@@ -118,17 +121,18 @@ module.exports = function(RED) {
118
121
  var st = (typeof output === 'string') ? output : util.inspect(output);
119
122
  var fill = "grey";
120
123
  var shape = "dot";
121
- if (typeof output === 'object' && output.hasOwnProperty("fill") && output.hasOwnProperty("shape") && output.hasOwnProperty("text")) {
124
+ if (typeof output === 'object' && hasOwnProperty.call(output, "fill") && hasOwnProperty.call(output, "shape") && hasOwnProperty.call(output, "text")) {
122
125
  fill = output.fill;
123
126
  shape = output.shape;
124
127
  st = output.text;
125
128
  }
126
129
  if (node.statusType === "auto") {
127
- if (msg.hasOwnProperty("error")) {
130
+ if (hasOwnProperty.call(msg, "error")) {
128
131
  fill = "red";
129
132
  st = msg.error.message;
130
133
  }
131
- if (msg.hasOwnProperty("status")) {
134
+ if (hasOwnProperty.call(msg, "status") &&
135
+ msg.status) {
132
136
  fill = msg.status.fill || "grey";
133
137
  shape = msg.status.shape || "ring";
134
138
  st = msg.status.text || "";
@@ -194,7 +198,7 @@ module.exports = function(RED) {
194
198
 
195
199
  function sendDebug(msg) {
196
200
  // don't put blank errors in sidebar (but do add to logs)
197
- //if ((msg.msg === "") && (msg.hasOwnProperty("level")) && (msg.level === 20)) { return; }
201
+ //if ((msg.msg === "") && (hasOwnProperty.call(msg, "level")) && (msg.level === 20)) { return; }
198
202
  msg = RED.util.encodeObject(msg,{maxLength:debuglength});
199
203
  RED.comms.publish("debug",msg);
200
204
  }
@@ -399,7 +399,7 @@
399
399
  $("#func-tabs-content").children().hide();
400
400
  $("#" + tab.id).show();
401
401
  let editor = $("#" + tab.id).find('.monaco-editor').first();
402
- if(editor.length) {
402
+ if(editor.length) {
403
403
  if(that.editor.nodered && that.editor.type == "monaco") {
404
404
  that.editor.nodered.refreshModuleLibs(getLibsList());
405
405
  }
@@ -168,9 +168,9 @@ module.exports = function(RED) {
168
168
  return getFromValueType(RED.util.getMessageProperty(msg,rule.from),done);
169
169
  } else if (rule.fromt === 'flow' || rule.fromt === 'global') {
170
170
  var contextKey = RED.util.parseContextStore(rule.from);
171
- if (/\[msg\./.test(context.key)) {
171
+ if (/\[msg\./.test(contextKey.key)) {
172
172
  // The key has a nest msg. reference to evaluate first
173
- context.key = RED.util.normalisePropertyExpression(contextKey.key,msg,true);
173
+ contextKey.key = RED.util.normalisePropertyExpression(contextKey.key,msg,true);
174
174
  }
175
175
  node.context()[rule.fromt].get(contextKey.key, contextKey.store, (err,fromValue) => {
176
176
  if (err) {
@@ -115,7 +115,7 @@
115
115
  timeoutUnits: {value:"seconds"},
116
116
  rate: {value:"1", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
117
117
  nbRateUnits: {value:"1", required:false,
118
- validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
118
+ validate:function(v) { return v === undefined || (RED.validators.number(v) && (v >= 0)); }},
119
119
  rateUnits: {value: "second"},
120
120
  randomFirst: {value:"1", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
121
121
  randomLast: {value:"5", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
@@ -275,18 +275,22 @@ module.exports = function(RED) {
275
275
  if (msg.hasOwnProperty("flush")) {
276
276
  var len = node.buffer.length;
277
277
  if (typeof(msg.flush) == 'number') { len = Math.min(Math.floor(msg.flush),len); }
278
- while (len > 0) {
279
- const msgInfo = node.buffer.shift();
280
- if (Object.keys(msgInfo.msg).length > 1) {
281
- node.send(msgInfo.msg);
282
- msgInfo.done();
283
- }
284
- len = len - 1;
285
- }
286
- if (node.buffer.length === 0) {
278
+ if (len === 0) {
287
279
  clearInterval(node.intervalID);
288
280
  node.intervalID = -1;
289
281
  }
282
+ else {
283
+ while (len > 0) {
284
+ const msgInfo = node.buffer.shift();
285
+ if (Object.keys(msgInfo.msg).length > 1) {
286
+ node.send(msgInfo.msg);
287
+ msgInfo.done();
288
+ }
289
+ len = len - 1;
290
+ }
291
+ clearInterval(node.intervalID);
292
+ node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
293
+ }
290
294
  node.status({fill:"blue",shape:"dot",text:node.buffer.length});
291
295
  done();
292
296
  }
@@ -442,7 +442,17 @@
442
442
  }
443
443
  return defaultContentType || 'none'
444
444
  }
445
-
445
+ /**
446
+ * Test a topic string is valid for publishing
447
+ * @param {string} topic
448
+ * @returns `true` if it is a valid topic
449
+ */
450
+ function validateMQTTPublishTopic(topic, opts) {
451
+ if(!topic || topic == "" || !/[\+#\b\f\n\r\t\v\0]/.test(topic)) {
452
+ return true;
453
+ }
454
+ return RED._("node-red:mqtt.errors.invalid-topic");
455
+ }
446
456
  RED.nodes.registerType('mqtt-broker',{
447
457
  category: 'config',
448
458
  defaults: {
@@ -480,6 +490,7 @@
480
490
  willRetain: {value:false},
481
491
  willPayload: {value:""},
482
492
  willMsg: { value: {}},
493
+ userProps: { value: ""},
483
494
  sessionExpiry: {value:0}
484
495
  },
485
496
  credentials: {
@@ -609,6 +620,7 @@
609
620
  default: !this.userProps ? 'none':'json',
610
621
  types: [typedInputNoneOpt, 'json']
611
622
  });
623
+ $("#node-config-input-userProps").typedInput('value',this.userProps);
612
624
  if (typeof this.keepalive === 'undefined') {
613
625
  this.keepalive = 15;
614
626
  $("#node-config-input-keepalive").val(this.keepalive);
@@ -718,6 +730,14 @@
718
730
  }
719
731
 
720
732
  if (v5) {
733
+ this.userProps = "";
734
+ const userPropsType = $("#node-config-input-userProps").typedInput("type");
735
+ if(userPropsType == "json") {
736
+ const userProps = $("#node-config-input-userProps").val();
737
+ if (userProps && typeof userProps === "string") {
738
+ this.userProps = userProps.trim();
739
+ }
740
+ }
721
741
  this.birthMsg = saveV5Message("birth");
722
742
  this.closeMsg = saveV5Message("close");
723
743
  this.willMsg = saveV5Message("will");
@@ -68,12 +68,21 @@ module.exports = function(RED) {
68
68
  }
69
69
 
70
70
  /**
71
- * Test a topic string is valid
71
+ * Test a topic string is valid for subscription
72
72
  * @param {string} topic
73
73
  * @returns `true` if it is a valid topic
74
74
  */
75
75
  function isValidSubscriptionTopic(topic) {
76
- return /^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/.test(topic)
76
+ return /^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/.test(topic);
77
+ }
78
+
79
+ /**
80
+ * Test a topic string is valid for publishing
81
+ * @param {string} topic
82
+ * @returns `true` if it is a valid topic
83
+ */
84
+ function isValidPublishTopic(topic) {
85
+ return !/[\+#\b\f\n\r\t\v\0]/.test(topic);
77
86
  }
78
87
 
79
88
  /**
@@ -103,7 +112,7 @@ module.exports = function(RED) {
103
112
  if(src[propName] === "true" || src[propName] === true) {
104
113
  dst[propName] = true;
105
114
  } else if(src[propName] === "false" || src[propName] === false) {
106
- dst[propName] = true;
115
+ dst[propName] = false;
107
116
  }
108
117
  } else {
109
118
  if(def != undefined) dst[propName] = def;
@@ -288,7 +297,7 @@ module.exports = function(RED) {
288
297
  //TODO: delete msg.responseTopic - to prevent it being resent?
289
298
  }
290
299
  }
291
- topicOK = topicOK && !/[\+#\b\f\n\r\t\v\0]/.test(msg.topic);
300
+ topicOK = topicOK && isValidPublishTopic(msg.topic);
292
301
 
293
302
  if (topicOK) {
294
303
  node.brokerConn.publish(msg, done); // send the message
@@ -391,6 +400,7 @@ module.exports = function(RED) {
391
400
  node.options = {};
392
401
  node.queue = [];
393
402
  node.subscriptions = {};
403
+ node.clientListeners = []
394
404
  /** @type {mqtt.MqttClient}*/ this.client;
395
405
  node.setOptions = function(opts, init) {
396
406
  if(!opts || typeof opts !== "object") {
@@ -415,8 +425,12 @@ module.exports = function(RED) {
415
425
  setIfHasProperty(opts, node, "topicAliasMaximum", init);
416
426
  setIfHasProperty(opts, node, "maximumPacketSize", init);
417
427
  setIfHasProperty(opts, node, "receiveMaximum", init);
418
- setIfHasProperty(opts, node, "userProperties", init);//https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901116
419
- setIfHasProperty(opts, node, "userPropertiesType", init);
428
+ //https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901116
429
+ if (hasProperty(opts, "userProperties")) {
430
+ node.userProperties = opts.userProperties;
431
+ } else if (hasProperty(opts, "userProps")) {
432
+ node.userProperties = opts.userProps;
433
+ }
420
434
 
421
435
  function createLWT(topic, payload, qos, retain, v5opts, v5SubPropName) {
422
436
  let message = undefined;
@@ -465,7 +479,7 @@ module.exports = function(RED) {
465
479
  };
466
480
  if(hasProperty(opts, "willTopic")) {
467
481
  //will v5 properties must be set in the "properties" sub object
468
- node.options.will = createLWT(opts.willTopic, opts.willPayload, opts.willQos, opts.willRetain, opts.willMsg, "properies");
482
+ node.options.will = createLWT(opts.willTopic, opts.willPayload, opts.willQos, opts.willRetain, opts.willMsg, "properties");
469
483
  };
470
484
  } else {
471
485
  //update options
@@ -525,7 +539,7 @@ module.exports = function(RED) {
525
539
  // Only for ws or wss, check if proxy env var for additional configuration
526
540
  if (node.brokerurl.indexOf("wss://") > -1 || node.brokerurl.indexOf("ws://") > -1) {
527
541
  // check if proxy is set in env
528
- let prox, noprox;
542
+ let prox, noprox, noproxy;
529
543
  if (process.env.http_proxy) { prox = process.env.http_proxy; }
530
544
  if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
531
545
  if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
@@ -637,24 +651,8 @@ module.exports = function(RED) {
637
651
 
638
652
  node.deregister = function(mqttNode,done) {
639
653
  delete node.users[mqttNode.id];
640
- if (node.closing) {
641
- return done();
642
- }
643
- if (Object.keys(node.users).length === 0) {
644
- if (node.client && node.client.connected) {
645
- // Send close message
646
- if (node.closeMessage) {
647
- node.publish(node.closeMessage,function(err) {
648
- node.client.end(done);
649
- });
650
- } else {
651
- node.client.end(done);
652
- }
653
- return;
654
- } else {
655
- if (node.client) { node.client.end(); }
656
- return done();
657
- }
654
+ if (!node.closing && node.connected && Object.keys(node.users).length === 0) {
655
+ node.disconnect();
658
656
  }
659
657
  done();
660
658
  };
@@ -663,15 +661,22 @@ module.exports = function(RED) {
663
661
  }
664
662
  node.connect = function (callback) {
665
663
  if (node.canConnect()) {
664
+ node.closing = false;
666
665
  node.connecting = true;
667
666
  setStatusConnecting(node, true);
668
667
  try {
669
668
  node.serverProperties = {};
669
+ if(node.client) {
670
+ //belt and braces to avoid left over clients
671
+ node.client.end(true);
672
+ node._clientRemoveListeners();
673
+ }
670
674
  node.client = mqtt.connect(node.brokerurl, node.options);
671
675
  node.client.setMaxListeners(0);
672
- let callbackDone = false; //prevent re-connects causing node.client.on('connect' firing callback multiple times
676
+ let callbackDone = false; //prevent re-connects causing node._clientOn('connect' firing callback multiple times
673
677
  // Register successful connect or reconnect handler
674
- node.client.on('connect', function (connack) {
678
+ node._clientOn('connect', function (connack) {
679
+ node.closing = false;
675
680
  node.connecting = false;
676
681
  node.connected = true;
677
682
  if(!callbackDone && typeof callback == "function") {
@@ -702,7 +707,7 @@ module.exports = function(RED) {
702
707
  }
703
708
  setStatusConnected(node, true);
704
709
  // Remove any existing listeners before resubscribing to avoid duplicates in the event of a re-connection
705
- node.client.removeAllListeners('message');
710
+ node._clientRemoveListeners('message');
706
711
 
707
712
  // Re-subscribe to stored topics
708
713
  for (var s in node.subscriptions) {
@@ -714,7 +719,7 @@ module.exports = function(RED) {
714
719
  if (node.subscriptions[s].hasOwnProperty(r)) {
715
720
  qos = Math.max(qos,node.subscriptions[s][r].qos);
716
721
  _options = node.subscriptions[s][r].options;
717
- node.client.on('message',node.subscriptions[s][r].handler);
722
+ node._clientOn('message',node.subscriptions[s][r].handler);
718
723
  }
719
724
  }
720
725
  _options.qos = _options.qos || qos;
@@ -727,11 +732,11 @@ module.exports = function(RED) {
727
732
  node.publish(node.birthMessage);
728
733
  }
729
734
  });
730
- node.client.on("reconnect", function() {
735
+ node._clientOn("reconnect", function() {
731
736
  setStatusConnecting(node, true);
732
737
  });
733
738
  //Broker Disconnect - V5 event
734
- node.client.on("disconnect", function(packet) {
739
+ node._clientOn("disconnect", function(packet) {
735
740
  //Emitted after receiving disconnect packet from broker. MQTT 5.0 feature.
736
741
  const rc = (packet && packet.properties && packet.reasonCode) || packet.reasonCode;
737
742
  const rs = packet && packet.properties && packet.properties.reasonString || "";
@@ -740,11 +745,12 @@ module.exports = function(RED) {
740
745
  reasonCode: rc,
741
746
  reasonString: rs
742
747
  }
748
+ node.connected = false;
743
749
  node.log(RED._("mqtt.state.broker-disconnected", details));
744
750
  setStatusDisconnected(node, true);
745
751
  });
746
752
  // Register disconnect handlers
747
- node.client.on('close', function () {
753
+ node._clientOn('close', function () {
748
754
  if (node.connected) {
749
755
  node.connected = false;
750
756
  node.log(RED._("mqtt.state.disconnected",{broker:(node.clientid?node.clientid+"@":"")+node.brokerurl}));
@@ -756,33 +762,59 @@ module.exports = function(RED) {
756
762
 
757
763
  // Register connect error handler
758
764
  // The client's own reconnect logic will take care of errors
759
- node.client.on('error', function (error) {
765
+ node._clientOn('error', function (error) {
760
766
  });
761
767
  }catch(err) {
762
768
  console.log(err);
763
769
  }
764
770
  }
765
771
  };
772
+
766
773
  node.disconnect = function (callback) {
767
774
  const _callback = function () {
768
- setStatusDisconnected(node, true);
775
+ if(node.connected || node.connecting) {
776
+ setStatusDisconnected(node, true);
777
+ }
778
+ if(node.client) { node._clientRemoveListeners(); }
769
779
  node.connecting = false;
770
780
  node.connected = false;
771
781
  callback && typeof callback == "function" && callback();
772
782
  };
773
-
774
- if(node.client) {
775
- if(node.client.connected && node.closeMessage) {
776
- node.publish(node.closeMessage, function (err) {
777
- node.client.end(_callback);
778
- });
779
- } else if(node.client.connected || node.client.reconnecting) {
780
- node.client.end(_callback);
781
- } else if(node.client.disconnecting || node.client.connected === false) {
782
- _callback();
783
- }
783
+ if(!node.client) { return _callback(); }
784
+ if(node.closing) { return _callback(); }
785
+
786
+ let waitEnd = (client, ms) => {
787
+ return new Promise( (resolve, reject) => {
788
+ node.closing = true;
789
+ if(!client) {
790
+ resolve();
791
+ } else {
792
+ const t = setTimeout(() => {
793
+ //clean end() has exceeded WAIT_END, lets force end!
794
+ client && client.end(true);
795
+ reject();
796
+ }, ms);
797
+ client.end(() => {
798
+ clearTimeout(t);
799
+ resolve()
800
+ });
801
+ }
802
+ });
803
+ };
804
+ if(node.connected && node.closeMessage) {
805
+ node.publish(node.closeMessage, function (err) {
806
+ waitEnd(node.client, 2000).then(() => {
807
+ _callback();
808
+ }).catch((e) => {
809
+ _callback();
810
+ })
811
+ });
784
812
  } else {
785
- _callback();
813
+ waitEnd(node.client, 2000).then(() => {
814
+ _callback();
815
+ }).catch((e) => {
816
+ _callback();
817
+ })
786
818
  }
787
819
  }
788
820
  node.subscriptionIds = {};
@@ -819,7 +851,7 @@ module.exports = function(RED) {
819
851
  };
820
852
  node.subscriptions[topic][ref] = sub;
821
853
  if (node.connected) {
822
- node.client.on('message',sub.handler);
854
+ node._clientOn('message',sub.handler);
823
855
  node.client.subscribe(topic, options);
824
856
  }
825
857
  };
@@ -830,7 +862,7 @@ module.exports = function(RED) {
830
862
  if (sub) {
831
863
  if (sub[ref]) {
832
864
  if(node.client) {
833
- node.client.removeListener('message',sub[ref].handler);
865
+ node._clientRemoveListeners('message',sub[ref].handler);
834
866
  }
835
867
  delete sub[ref];
836
868
  }
@@ -864,8 +896,18 @@ module.exports = function(RED) {
864
896
  qos: msg.qos || 0,
865
897
  retain: msg.retain || false
866
898
  };
899
+ let topicOK = hasProperty(msg, "topic") && (typeof msg.topic === "string") && (isValidPublishTopic(msg.topic));
867
900
  //https://github.com/mqttjs/MQTT.js/blob/master/README.md#mqttclientpublishtopic-message-options-callback
868
901
  if(node.options.protocolVersion == 5) {
902
+ const bsp = node.serverProperties || {};
903
+ if (msg.userProperties && typeof msg.userProperties !== "object") {
904
+ delete msg.userProperties;
905
+ }
906
+ if (hasProperty(msg, "topicAlias") && !isNaN(Number(msg.topicAlias))) {
907
+ msg.topicAlias = parseInt(msg.topicAlias);
908
+ } else {
909
+ delete msg.topicAlias;
910
+ }
869
911
  options.properties = options.properties || {};
870
912
  setStrProp(msg, options.properties, "responseTopic");
871
913
  setBufferProp(msg, options.properties, "correlationData");
@@ -875,31 +917,75 @@ module.exports = function(RED) {
875
917
  setIntProp(msg, options.properties, "topicAlias", 1, node.serverProperties.topicAliasMaximum || 0);
876
918
  setBoolProp(msg, options.properties, "payloadFormatIndicator");
877
919
  //FUTURE setIntProp(msg, options.properties, "subscriptionIdentifier", 1, 268435455);
878
- if (options.properties.topicAlias) {
879
- if (!node.topicAliases.hasOwnProperty(options.properties.topicAlias) && msg.topic == "") {
920
+
921
+ //check & sanitise topic
922
+ if (topicOK && options.properties.topicAlias) {
923
+ let aliasValid = (bsp.topicAliasMaximum && bsp.topicAliasMaximum >= options.properties.topicAlias);
924
+ if (!aliasValid) {
880
925
  done("Invalid topicAlias");
881
926
  return
882
927
  }
883
928
  if (node.topicAliases[options.properties.topicAlias] === msg.topic) {
884
- msg.topic = ""
929
+ msg.topic = "";
885
930
  } else {
886
- node.topicAliases[options.properties.topicAlias] = msg.topic
931
+ node.topicAliases[options.properties.topicAlias] = msg.topic;
887
932
  }
933
+ } else if (!msg.topic && options.properties.responseTopic) {
934
+ msg.topic = msg.responseTopic;
935
+ topicOK = isValidPublishTopic(msg.topic);
936
+ delete msg.responseTopic; //prevent responseTopic being resent?
888
937
  }
889
938
  }
890
939
 
891
- node.client.publish(msg.topic, msg.payload, options, function(err) {
892
- done && done(err);
893
- return
894
- });
940
+ if (topicOK) {
941
+ node.client.publish(msg.topic, msg.payload, options, function(err) {
942
+ done && done(err);
943
+ return
944
+ });
945
+ } else {
946
+ const error = new Error(RED._("mqtt.errors.invalid-topic"));
947
+ error.warn = true;
948
+ done(error);
949
+ }
895
950
  }
896
951
  };
897
952
 
898
953
  node.on('close', function(done) {
899
- node.closing = true;
900
- node.disconnect(done);
954
+ node.disconnect(function() {
955
+ done();
956
+ });
901
957
  });
902
958
 
959
+ /**
960
+ * Add event handlers to the MQTT.js client and track them so that
961
+ * we do not remove any handlers that the MQTT client uses internally.
962
+ * Use {@link node._clientRemoveListeners `node._clientRemoveListeners`} to remove handlers
963
+ * @param {string} event The name of the event
964
+ * @param {function} handler The handler for this event
965
+ */
966
+ node._clientOn = function(event, handler) {
967
+ node.clientListeners.push({event, handler})
968
+ node.client.on(event, handler)
969
+ }
970
+
971
+ /**
972
+ * Remove event handlers from the MQTT.js client & only the events
973
+ * that we attached in {@link node._clientOn `node._clientOn`}.
974
+ * * If `event` is omitted, then all events matching `handler` are removed
975
+ * * If `handler` is omitted, then all events named `event` are removed
976
+ * * If both parameters are omitted, then all events are removed
977
+ * @param {string} [event] The name of the event (optional)
978
+ * @param {function} [handler] The handler for this event (optional)
979
+ */
980
+ node._clientRemoveListeners = function(event, handler) {
981
+ node.clientListeners = node.clientListeners.filter((l) => {
982
+ if (event && event !== l.event) { return true; }
983
+ if (handler && handler !== l.handler) { return true; }
984
+ node.client.removeListener(l.event, l.handler)
985
+ return false; //found and removed, filter out this one
986
+ })
987
+ }
988
+
903
989
  }
904
990
 
905
991
  RED.nodes.registerType("mqtt-broker",MQTTBrokerNode,{
@@ -1074,6 +1160,9 @@ module.exports = function(RED) {
1074
1160
  node.brokerConn.unsubscribe(node.topic,node.id, removed);
1075
1161
  }
1076
1162
  node.brokerConn.deregister(node, done);
1163
+ node.brokerConn = null;
1164
+ } else {
1165
+ done();
1077
1166
  }
1078
1167
  });
1079
1168
  } else {
@@ -1134,7 +1223,12 @@ module.exports = function(RED) {
1134
1223
  }
1135
1224
  node.brokerConn.register(node);
1136
1225
  node.on('close', function(done) {
1137
- node.brokerConn.deregister(node,done);
1226
+ if (node.brokerConn) {
1227
+ node.brokerConn.deregister(node,done);
1228
+ node.brokerConn = null;
1229
+ } else {
1230
+ done();
1231
+ }
1138
1232
  });
1139
1233
  } else {
1140
1234
  node.error(RED._("mqtt.errors.missing-config"));
@@ -91,6 +91,11 @@
91
91
  <label for="node-input-senderr" style="width: auto" data-i18n="httpin.senderr"></label>
92
92
  </div>
93
93
 
94
+ <div class="form-row">
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>
97
+ </div>
98
+
94
99
 
95
100
  <div class="form-row">
96
101
  <label for="node-input-ret"><i class="fa fa-arrow-left"></i> <span data-i18n="httpin.label.return"></span></label>
@@ -120,6 +125,7 @@
120
125
  tls: {type:"tls-config",required: false},
121
126
  persist: {value:false},
122
127
  proxy: {type:"http proxy",required: false},
128
+ insecureHTTPParser: {value: false},
123
129
  authType: {value: ""},
124
130
  senderr: {value: false}
125
131
  },
@@ -224,6 +230,12 @@
224
230
  } else {
225
231
  $("#node-input-useProxy").prop("checked", false);
226
232
  }
233
+
234
+ if (node.insecureHTTPParser) {
235
+ $("node-intput-insecureHTTPParser").prop("checked", true)
236
+ } else {
237
+ $("node-intput-insecureHTTPParser").prop("checked", false)
238
+ }
227
239
  updateProxyOptions();
228
240
  $("#node-input-useProxy").on("click", function() {
229
241
  updateProxyOptions();
@@ -214,6 +214,10 @@ in your Node-RED user directory (${RED.settings.userDir}).
214
214
  delete options.headers[h];
215
215
  }
216
216
  })
217
+
218
+ if (node.insecureHTTPParser) {
219
+ options.insecureHTTPParser = true
220
+ }
217
221
  }
218
222
  ],
219
223
  beforeRedirect: [
@@ -35,8 +35,6 @@ module.exports = function(RED) {
35
35
  }
36
36
  }
37
37
  var listenerNodes = {};
38
- var activeListenerNodes = 0;
39
-
40
38
 
41
39
  // A node red node that sets up a local websocket server
42
40
  function WebSocketListenerNode(n) {
@@ -166,7 +164,6 @@ module.exports = function(RED) {
166
164
  }
167
165
 
168
166
  if (node.isServer) {
169
- activeListenerNodes++;
170
167
  if (!serverUpgradeAdded) {
171
168
  RED.server.on('upgrade', handleServerUpgrade);
172
169
  serverUpgradeAdded = true
@@ -210,7 +207,7 @@ module.exports = function(RED) {
210
207
  startconn(); // start outbound connection
211
208
  }
212
209
 
213
- node.on("close", function() {
210
+ node.on("close", function(done) {
214
211
  if (node.heartbeatInterval) {
215
212
  clearInterval(node.heartbeatInterval);
216
213
  }
@@ -218,19 +215,25 @@ module.exports = function(RED) {
218
215
  delete listenerNodes[node.fullPath];
219
216
  node.server.close();
220
217
  node._inputNodes = [];
221
- activeListenerNodes--;
222
- // if (activeListenerNodes === 0 && serverUpgradeAdded) {
223
- // RED.server.removeListener('upgrade', handleServerUpgrade);
224
- // serverUpgradeAdded = false;
225
- // }
226
218
  }
227
219
  else {
228
220
  node.closing = true;
229
221
  node.server.close();
230
- if (node.tout) {
231
- clearTimeout(node.tout);
232
- node.tout = null;
233
- }
222
+ //wait 20*50 (1000ms max) for ws to close.
223
+ //call done when readyState === ws.CLOSED (or 1000ms, whichever comes fist)
224
+ const closeMonitorInterval = 20;
225
+ let closeMonitorCount = 50;
226
+ let si = setInterval(() => {
227
+ if(node.server.readyState === ws.CLOSED || closeMonitorCount <= 0) {
228
+ if (node.tout) {
229
+ clearTimeout(node.tout);
230
+ node.tout = null;
231
+ }
232
+ clearInterval(si);
233
+ return done();
234
+ }
235
+ closeMonitorCount--;
236
+ }, closeMonitorInterval);
234
237
  }
235
238
  });
236
239
  }
@@ -135,7 +135,7 @@ module.exports = function(RED) {
135
135
  buffer = buffer+data;
136
136
  var parts = buffer.split(node.newline);
137
137
  for (var i = 0; i<parts.length-1; i+=1) {
138
- msg = {topic:node.topic, payload:parts[i] + node.newline.trimEnd()};
138
+ msg = {topic:node.topic, payload:parts[i]};
139
139
  msg._session = {type:"tcp",id:id};
140
140
  node.send(msg);
141
141
  }
@@ -229,7 +229,7 @@ module.exports = function(RED) {
229
229
  buffer = buffer+data;
230
230
  var parts = buffer.split(node.newline);
231
231
  for (var i = 0; i<parts.length-1; i+=1) {
232
- msg = {topic:node.topic, payload:parts[i] + node.newline.trimEnd(), ip:socket.remoteAddress, port:socket.remotePort};
232
+ msg = {topic:node.topic, payload:parts[i], ip:socket.remoteAddress, port:socket.remotePort};
233
233
  msg._session = {type:"tcp",id:id};
234
234
  node.send(msg);
235
235
  }
@@ -432,7 +432,7 @@ module.exports = function(RED) {
432
432
  });
433
433
  }
434
434
  else {
435
- var connectedSockets = [];
435
+ const connectedSockets = new Set();
436
436
  node.status({text:RED._("tcpin.status.connections",{count:0})});
437
437
  let srv = net;
438
438
  let connOpts;
@@ -453,16 +453,16 @@ module.exports = function(RED) {
453
453
  });
454
454
  socket.on('close',function() {
455
455
  node.log(RED._("tcpin.status.connection-closed",{host:socket.remoteAddress, port:socket.remotePort}));
456
- connectedSockets.splice(connectedSockets.indexOf(socket),1);
457
- node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
456
+ connectedSockets.delete(socket);
457
+ node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.size})});
458
458
  });
459
459
  socket.on('error',function() {
460
460
  node.log(RED._("tcpin.errors.socket-error",{host:socket.remoteAddress, port:socket.remotePort}));
461
- connectedSockets.splice(connectedSockets.indexOf(socket),1);
462
- node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
461
+ connectedSockets.delete(socket);
462
+ node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.size})});
463
463
  });
464
- connectedSockets.push(socket);
465
- node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
464
+ connectedSockets.add(socket);
465
+ node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.size})});
466
466
  });
467
467
 
468
468
  node.on("input", function(msg, nodeSend, nodeDone) {
@@ -475,10 +475,10 @@ module.exports = function(RED) {
475
475
  } else {
476
476
  buffer = Buffer.from(""+msg.payload);
477
477
  }
478
- for (var i = 0; i < connectedSockets.length; i += 1) {
479
- if (node.doend === true) { connectedSockets[i].end(buffer); }
480
- else { connectedSockets[i].write(buffer); }
481
- }
478
+ connectedSockets.forEach(soc => {
479
+ if (node.doend === true) { soc.end(buffer); }
480
+ else { soc.write(buffer); }
481
+ })
482
482
  }
483
483
  nodeDone();
484
484
  });
@@ -495,12 +495,10 @@ module.exports = function(RED) {
495
495
  } else {
496
496
  node.log(RED._("tcpin.status.listening-port",{port:node.port}));
497
497
  node.on('close', function() {
498
- for (var c in connectedSockets) {
499
- if (connectedSockets.hasOwnProperty(c)) {
500
- connectedSockets[c].end();
501
- connectedSockets[c].unref();
502
- }
503
- }
498
+ connectedSockets.forEach(soc => {
499
+ soc.end();
500
+ soc.unref();
501
+ })
504
502
  server.close();
505
503
  node.log(RED._("tcpin.status.stopped-listening",{port:node.port}));
506
504
  });
@@ -89,6 +89,9 @@ module.exports = function(RED) {
89
89
  else if (msg.payload[s][t].toString().indexOf(node.sep) !== -1) { // add quotes if any "commas"
90
90
  msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
91
91
  }
92
+ else if (msg.payload[s][t].toString().indexOf("\n") !== -1) { // add quotes if any "\n"
93
+ msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
94
+ }
92
95
  }
93
96
  ou += msg.payload[s].join(node.sep) + node.ret;
94
97
  }
@@ -112,7 +115,7 @@ module.exports = function(RED) {
112
115
  q = q.replace(/"/g, '""');
113
116
  ou += node.quo + q + node.quo + node.sep;
114
117
  }
115
- else if (q.indexOf(node.sep) !== -1) { // add quotes if any "commas"
118
+ else if (q.indexOf(node.sep) !== -1 || p.indexOf("\n") !== -1) { // add quotes if any "commas" or "\n"
116
119
  ou += node.quo + q + node.quo + node.sep;
117
120
  }
118
121
  else { ou += q + node.sep; } // otherwise just add
@@ -134,7 +137,7 @@ module.exports = function(RED) {
134
137
  p = p.replace(/"/g, '""');
135
138
  ou += node.quo + p + node.quo + node.sep;
136
139
  }
137
- else if (p.indexOf(node.sep) !== -1) { // add quotes if any "commas"
140
+ else if (p.indexOf(node.sep) !== -1 || p.indexOf("\n") !== -1) { // add quotes if any "commas" or "\n"
138
141
  ou += node.quo + p + node.quo + node.sep;
139
142
  }
140
143
  else { ou += p + node.sep; } // otherwise just add
@@ -21,7 +21,7 @@
21
21
  <label style="width:100%;"><span data-i18n="json.label.o2j"></span></label>
22
22
  </div>
23
23
  <div class="form-row node-json-to-json-options" style="padding-left: 20px;">
24
- <input style="width:20px; vertical-align:top; margin-right: 5px;" type="checkbox" id="node-input-pretty"><label style="width: auto;" for="node-input-pretty" data-i18n="json.label.pretty"></span>
24
+ <input style="width:20px; vertical-align:top; margin-right: 5px;" type="checkbox" id="node-input-pretty"><label style="width: auto;" for="node-input-pretty" data-i18n="json.label.pretty"></label>
25
25
  </div>
26
26
  </script>
27
27
 
@@ -314,11 +314,13 @@ module.exports = function(RED) {
314
314
  if (err) {
315
315
  return done(err);
316
316
  }
317
- msgInfo.send({payload: result});
317
+ msgInfo.msg.payload = result;
318
+ msgInfo.send(msgInfo.msg);
318
319
  done();
319
320
  });
320
321
  } else {
321
- msgInfo.send({payload: result});
322
+ msgInfo.msg.payload = result;
323
+ msgInfo.send(msgInfo.msg);
322
324
  done();
323
325
  }
324
326
  } else {
@@ -47,7 +47,7 @@
47
47
  <div class="form-row">
48
48
  <input type="checkbox" id="node-input-allowEmptySequence" style="margin-left:20px; margin-right: 10px; vertical-align:top; width:auto;">
49
49
  <label for="node-input-allowEmptySequence" style="width:auto;" data-i18n="batch.interval.empty"></label>
50
- </div>
50
+ </div>
51
51
  </div>
52
52
 
53
53
  <div class="node-row-msg-concat">
@@ -0,0 +1,149 @@
1
+ [
2
+ {
3
+ "id": "48d660b3a4109400",
4
+ "type": "inject",
5
+ "z": "9e5f48c16729e4f0",
6
+ "name": "inject",
7
+ "props": [
8
+ {
9
+ "p": "payload"
10
+ }
11
+ ],
12
+ "repeat": "",
13
+ "crontab": "",
14
+ "once": false,
15
+ "onceDelay": 0.1,
16
+ "topic": "",
17
+ "payload": "",
18
+ "payloadType": "date",
19
+ "x": 185,
20
+ "y": 795,
21
+ "wires": [
22
+ [
23
+ "e0f9e206681f3504"
24
+ ]
25
+ ]
26
+ },
27
+ {
28
+ "id": "e0f9e206681f3504",
29
+ "type": "delay",
30
+ "z": "9e5f48c16729e4f0",
31
+ "name": "",
32
+ "pauseType": "rate",
33
+ "timeout": "5",
34
+ "timeoutUnits": "seconds",
35
+ "rate": "1",
36
+ "nbRateUnits": "30",
37
+ "rateUnits": "second",
38
+ "randomFirst": "1",
39
+ "randomLast": "5",
40
+ "randomUnits": "seconds",
41
+ "drop": false,
42
+ "allowrate": false,
43
+ "outputs": 1,
44
+ "x": 430,
45
+ "y": 795,
46
+ "wires": [
47
+ [
48
+ "e470f1d794e1bef9",
49
+ "af7cea1dfb797a75"
50
+ ]
51
+ ]
52
+ },
53
+ {
54
+ "id": "943543cf7a1958e4",
55
+ "type": "change",
56
+ "z": "9e5f48c16729e4f0",
57
+ "name": "set flush to 1",
58
+ "rules": [
59
+ {
60
+ "t": "set",
61
+ "p": "flush",
62
+ "pt": "msg",
63
+ "to": "1",
64
+ "tot": "num"
65
+ },
66
+ {
67
+ "t": "delete",
68
+ "p": "payload",
69
+ "pt": "msg"
70
+ }
71
+ ],
72
+ "action": "",
73
+ "property": "",
74
+ "from": "",
75
+ "to": "",
76
+ "reg": false,
77
+ "x": 510,
78
+ "y": 915,
79
+ "wires": [
80
+ [
81
+ "e0f9e206681f3504"
82
+ ]
83
+ ]
84
+ },
85
+ {
86
+ "id": "e470f1d794e1bef9",
87
+ "type": "function",
88
+ "z": "9e5f48c16729e4f0",
89
+ "name": "Do something that takes a few seconds",
90
+ "func": "\n//send on the message between 3 and 6 seconds later\nsetTimeout(\n function() { \n node.send(msg) \n }, \n Math.random() * 3000 + 3000\n);\nreturn null;",
91
+ "outputs": 1,
92
+ "noerr": 0,
93
+ "initialize": "",
94
+ "finalize": "",
95
+ "libs": [],
96
+ "x": 760,
97
+ "y": 795,
98
+ "wires": [
99
+ [
100
+ "943543cf7a1958e4",
101
+ "859258551b8389b7"
102
+ ]
103
+ ]
104
+ },
105
+ {
106
+ "id": "af7cea1dfb797a75",
107
+ "type": "debug",
108
+ "z": "9e5f48c16729e4f0",
109
+ "name": "IN",
110
+ "active": true,
111
+ "tosidebar": true,
112
+ "console": false,
113
+ "tostatus": false,
114
+ "complete": "payload",
115
+ "targetType": "msg",
116
+ "statusVal": "",
117
+ "statusType": "auto",
118
+ "x": 710,
119
+ "y": 735,
120
+ "wires": []
121
+ },
122
+ {
123
+ "id": "859258551b8389b7",
124
+ "type": "debug",
125
+ "z": "9e5f48c16729e4f0",
126
+ "name": "OUT",
127
+ "active": true,
128
+ "tosidebar": true,
129
+ "console": false,
130
+ "tostatus": false,
131
+ "complete": "payload",
132
+ "targetType": "msg",
133
+ "statusVal": "",
134
+ "statusType": "auto",
135
+ "x": 895,
136
+ "y": 735,
137
+ "wires": []
138
+ },
139
+ {
140
+ "id": "ecaaf26326da10ee",
141
+ "type": "comment",
142
+ "z": "9e5f48c16729e4f0",
143
+ "name": "Simple Queue with release",
144
+ "info": "This example shows how to use a delay node set to rate limit mode as a simple queue to feed a\nprocess that may take some time to complete. Once that process completes the feedback is then\nset to flush out the next message - thus running the \"loop\" as fast as possible with no overlaps.\n\n**Note**: only the `msg.flush` property msut be set - otherwise the other properties that are fed \nback will be added as another new message to the queue.",
145
+ "x": 235,
146
+ "y": 915,
147
+ "wires": []
148
+ }
149
+ ]
@@ -525,7 +525,8 @@
525
525
  },
526
526
  "status": {
527
527
  "requesting": "requesting"
528
- }
528
+ },
529
+ "insecureHTTPParser": "Disable strict HTTP parsing"
529
530
  },
530
531
  "websocket": {
531
532
  "label": {
@@ -28,7 +28,7 @@
28
28
  <p>返却/sendの対象は次のとおりです:</p>
29
29
  <ul>
30
30
  <li>単一メッセージオブジェクト - 最初の出力に接続されたノードに渡されます</li>
31
- <li>メッセージオブジェクトの配列 - 対応する出力に接続されたノードに渡されます</li>
31
+ <li>メッセージオブジェクトの配列 - 対応する出力に接続されたノードに渡されます</li>
32
32
  </ul>
33
33
  <p>注: 初期化処理の実行はノードの初期化中に行われます。そのため、初期化処理タブにsendを記述した場合に後続ノードでメッセージを受け取れないことがあります。</p>
34
34
  <p>配列要素が配列の場合には、複数のメッセージを対応する出力に送出します。</p>
@@ -291,8 +291,8 @@
291
291
  "hour": "時間",
292
292
  "days": "日",
293
293
  "day": "日",
294
- "between": "頻度",
295
- "and": "回/",
294
+ "between": "範囲",
295
+ "and": "",
296
296
  "rate": "流量",
297
297
  "msgper": "メッセージ/",
298
298
  "queuemsg": "中間メッセージをキューに追加",
@@ -89,7 +89,7 @@
89
89
  <dt class="optional">userProperties <span class="property-type">オブジェクト</span></dt>
90
90
  <dd><b>MQTTv5</b>: メッセージのユーザプロパティ</dd>
91
91
  <dt class="optional">messageExpiryInterval <span class="property-type">数値</span></dt>
92
- <dd><b>MQTTv5</b>: 秒単位のメッセージの有効期限</dd>
92
+ <dd><b>MQTTv5</b>: 秒単位のメッセージの有効期限</dd>
93
93
  <dt class="optional">topicAlias <span class="property-type">数値</span></dt>
94
94
  <dd><b>MQTTv5</b>: 使用するMQTTトピックエイリアス</dd>
95
95
  </dl>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-red/nodes",
3
- "version": "2.2.0",
3
+ "version": "2.2.3",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,12 +17,12 @@
17
17
  "dependencies": {
18
18
  "acorn": "8.7.0",
19
19
  "acorn-walk": "8.2.0",
20
- "ajv": "8.9.0",
20
+ "ajv": "8.10.0",
21
21
  "body-parser": "1.19.1",
22
22
  "cheerio": "1.0.0-rc.10",
23
23
  "content-type": "1.0.4",
24
24
  "cookie-parser": "1.4.6",
25
- "cookie": "0.4.1",
25
+ "cookie": "0.4.2",
26
26
  "cors": "2.8.5",
27
27
  "cronosjs": "1.7.1",
28
28
  "denque": "2.0.1",
@@ -36,11 +36,11 @@
36
36
  "is-utf8": "0.2.1",
37
37
  "js-yaml": "3.14.1",
38
38
  "media-typer": "1.1.0",
39
- "mqtt": "4.3.4",
39
+ "mqtt": "4.3.5",
40
40
  "multer": "1.4.4",
41
41
  "mustache": "4.2.0",
42
42
  "on-headers": "1.0.2",
43
- "raw-body": "2.4.2",
43
+ "raw-body": "2.4.3",
44
44
  "tough-cookie": "4.0.0",
45
45
  "uuid": "8.3.2",
46
46
  "ws": "7.5.6",