@node-red/nodes 2.2.2 → 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) {
@@ -108,7 +108,9 @@ module.exports = function(RED) {
108
108
  }
109
109
  })
110
110
  this.on("input", function(msg, send, done) {
111
- if (hasOwnProperty.call(msg, "status") && hasOwnProperty.call(msg.status, "source") && hasOwnProperty.call(msg.status.source, "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)) {
112
114
  done();
113
115
  return;
114
116
  }
@@ -129,7 +131,8 @@ module.exports = function(RED) {
129
131
  fill = "red";
130
132
  st = msg.error.message;
131
133
  }
132
- if (hasOwnProperty.call(msg, "status")) {
134
+ if (hasOwnProperty.call(msg, "status") &&
135
+ msg.status) {
133
136
  fill = msg.status.fill || "grey";
134
137
  shape = msg.status.shape || "ring";
135
138
  st = msg.status.text || "";
@@ -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) {
@@ -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(","); }
@@ -652,11 +666,16 @@ module.exports = function(RED) {
652
666
  setStatusConnecting(node, true);
653
667
  try {
654
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
+ }
655
674
  node.client = mqtt.connect(node.brokerurl, node.options);
656
675
  node.client.setMaxListeners(0);
657
- 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
658
677
  // Register successful connect or reconnect handler
659
- node.client.on('connect', function (connack) {
678
+ node._clientOn('connect', function (connack) {
660
679
  node.closing = false;
661
680
  node.connecting = false;
662
681
  node.connected = true;
@@ -688,7 +707,7 @@ module.exports = function(RED) {
688
707
  }
689
708
  setStatusConnected(node, true);
690
709
  // Remove any existing listeners before resubscribing to avoid duplicates in the event of a re-connection
691
- node.client.removeAllListeners('message');
710
+ node._clientRemoveListeners('message');
692
711
 
693
712
  // Re-subscribe to stored topics
694
713
  for (var s in node.subscriptions) {
@@ -700,7 +719,7 @@ module.exports = function(RED) {
700
719
  if (node.subscriptions[s].hasOwnProperty(r)) {
701
720
  qos = Math.max(qos,node.subscriptions[s][r].qos);
702
721
  _options = node.subscriptions[s][r].options;
703
- node.client.on('message',node.subscriptions[s][r].handler);
722
+ node._clientOn('message',node.subscriptions[s][r].handler);
704
723
  }
705
724
  }
706
725
  _options.qos = _options.qos || qos;
@@ -713,11 +732,11 @@ module.exports = function(RED) {
713
732
  node.publish(node.birthMessage);
714
733
  }
715
734
  });
716
- node.client.on("reconnect", function() {
735
+ node._clientOn("reconnect", function() {
717
736
  setStatusConnecting(node, true);
718
737
  });
719
738
  //Broker Disconnect - V5 event
720
- node.client.on("disconnect", function(packet) {
739
+ node._clientOn("disconnect", function(packet) {
721
740
  //Emitted after receiving disconnect packet from broker. MQTT 5.0 feature.
722
741
  const rc = (packet && packet.properties && packet.reasonCode) || packet.reasonCode;
723
742
  const rs = packet && packet.properties && packet.properties.reasonString || "";
@@ -731,7 +750,7 @@ module.exports = function(RED) {
731
750
  setStatusDisconnected(node, true);
732
751
  });
733
752
  // Register disconnect handlers
734
- node.client.on('close', function () {
753
+ node._clientOn('close', function () {
735
754
  if (node.connected) {
736
755
  node.connected = false;
737
756
  node.log(RED._("mqtt.state.disconnected",{broker:(node.clientid?node.clientid+"@":"")+node.brokerurl}));
@@ -743,39 +762,59 @@ module.exports = function(RED) {
743
762
 
744
763
  // Register connect error handler
745
764
  // The client's own reconnect logic will take care of errors
746
- node.client.on('error', function (error) {
765
+ node._clientOn('error', function (error) {
747
766
  });
748
767
  }catch(err) {
749
768
  console.log(err);
750
769
  }
751
770
  }
752
771
  };
772
+
753
773
  node.disconnect = function (callback) {
754
- const _callback = function (resetNodeConnectedState) {
755
- setStatusDisconnected(node, true);
756
- if(resetNodeConnectedState) {
757
- node.closing = true;
758
- node.connecting = false;
759
- node.connected = false;
774
+ const _callback = function () {
775
+ if(node.connected || node.connecting) {
776
+ setStatusDisconnected(node, true);
760
777
  }
778
+ if(node.client) { node._clientRemoveListeners(); }
779
+ node.connecting = false;
780
+ node.connected = false;
761
781
  callback && typeof callback == "function" && callback();
762
782
  };
783
+ if(!node.client) { return _callback(); }
784
+ if(node.closing) { return _callback(); }
763
785
 
764
- if(node.closing) {
765
- return _callback(false);
766
- }
767
- var endCallBack = function endCallBack() {
768
- }
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
+ };
769
804
  if(node.connected && node.closeMessage) {
770
805
  node.publish(node.closeMessage, function (err) {
771
- node.client.end(endCallBack);
772
- _callback(true);
806
+ waitEnd(node.client, 2000).then(() => {
807
+ _callback();
808
+ }).catch((e) => {
809
+ _callback();
810
+ })
773
811
  });
774
- } else if(node.connected) {
775
- node.client.end(endCallBack);
776
- _callback(true);
777
812
  } else {
778
- _callback(false);
813
+ waitEnd(node.client, 2000).then(() => {
814
+ _callback();
815
+ }).catch((e) => {
816
+ _callback();
817
+ })
779
818
  }
780
819
  }
781
820
  node.subscriptionIds = {};
@@ -812,7 +851,7 @@ module.exports = function(RED) {
812
851
  };
813
852
  node.subscriptions[topic][ref] = sub;
814
853
  if (node.connected) {
815
- node.client.on('message',sub.handler);
854
+ node._clientOn('message',sub.handler);
816
855
  node.client.subscribe(topic, options);
817
856
  }
818
857
  };
@@ -823,7 +862,7 @@ module.exports = function(RED) {
823
862
  if (sub) {
824
863
  if (sub[ref]) {
825
864
  if(node.client) {
826
- node.client.removeListener('message',sub[ref].handler);
865
+ node._clientRemoveListeners('message',sub[ref].handler);
827
866
  }
828
867
  delete sub[ref];
829
868
  }
@@ -857,8 +896,18 @@ module.exports = function(RED) {
857
896
  qos: msg.qos || 0,
858
897
  retain: msg.retain || false
859
898
  };
899
+ let topicOK = hasProperty(msg, "topic") && (typeof msg.topic === "string") && (isValidPublishTopic(msg.topic));
860
900
  //https://github.com/mqttjs/MQTT.js/blob/master/README.md#mqttclientpublishtopic-message-options-callback
861
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
+ }
862
911
  options.properties = options.properties || {};
863
912
  setStrProp(msg, options.properties, "responseTopic");
864
913
  setBufferProp(msg, options.properties, "correlationData");
@@ -868,31 +917,75 @@ module.exports = function(RED) {
868
917
  setIntProp(msg, options.properties, "topicAlias", 1, node.serverProperties.topicAliasMaximum || 0);
869
918
  setBoolProp(msg, options.properties, "payloadFormatIndicator");
870
919
  //FUTURE setIntProp(msg, options.properties, "subscriptionIdentifier", 1, 268435455);
871
- if (options.properties.topicAlias) {
872
- 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) {
873
925
  done("Invalid topicAlias");
874
926
  return
875
927
  }
876
928
  if (node.topicAliases[options.properties.topicAlias] === msg.topic) {
877
- msg.topic = ""
929
+ msg.topic = "";
878
930
  } else {
879
- node.topicAliases[options.properties.topicAlias] = msg.topic
931
+ node.topicAliases[options.properties.topicAlias] = msg.topic;
880
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?
881
937
  }
882
938
  }
883
939
 
884
- node.client.publish(msg.topic, msg.payload, options, function(err) {
885
- done && done(err);
886
- return
887
- });
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
+ }
888
950
  }
889
951
  };
890
952
 
891
953
  node.on('close', function(done) {
892
- node.closing = true;
893
- node.disconnect(done);
954
+ node.disconnect(function() {
955
+ done();
956
+ });
894
957
  });
895
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
+
896
989
  }
897
990
 
898
991
  RED.nodes.registerType("mqtt-broker",MQTTBrokerNode,{
@@ -1067,6 +1160,7 @@ module.exports = function(RED) {
1067
1160
  node.brokerConn.unsubscribe(node.topic,node.id, removed);
1068
1161
  }
1069
1162
  node.brokerConn.deregister(node, done);
1163
+ node.brokerConn = null;
1070
1164
  } else {
1071
1165
  done();
1072
1166
  }
@@ -1131,6 +1225,7 @@ module.exports = function(RED) {
1131
1225
  node.on('close', function(done) {
1132
1226
  if (node.brokerConn) {
1133
1227
  node.brokerConn.deregister(node,done);
1228
+ node.brokerConn = null;
1134
1229
  } else {
1135
1230
  done();
1136
1231
  }
@@ -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>
@@ -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.2",
3
+ "version": "2.2.3",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",