@node-red/nodes 2.2.2 → 3.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/99-sample.html.demo +1 -1
  2. package/core/common/05-junction.html +5 -0
  3. package/core/common/05-junction.js +12 -0
  4. package/core/common/20-inject.html +25 -13
  5. package/core/common/20-inject.js +3 -2
  6. package/core/common/21-debug.html +58 -5
  7. package/core/common/21-debug.js +57 -27
  8. package/core/common/60-link.html +65 -29
  9. package/core/common/60-link.js +169 -20
  10. package/core/common/lib/debug/debug-utils.js +34 -1
  11. package/core/function/10-function.html +58 -22
  12. package/core/function/10-switch.html +19 -12
  13. package/core/function/10-switch.js +1 -0
  14. package/core/function/15-change.html +40 -12
  15. package/core/function/16-range.html +14 -5
  16. package/core/function/80-template.html +16 -12
  17. package/core/function/89-delay.html +46 -6
  18. package/core/function/89-trigger.html +12 -4
  19. package/core/function/rbe.html +7 -3
  20. package/core/network/05-tls.html +10 -4
  21. package/core/network/06-httpproxy.html +10 -1
  22. package/core/network/10-mqtt.html +73 -17
  23. package/core/network/10-mqtt.js +239 -91
  24. package/core/network/21-httpin.html +10 -6
  25. package/core/network/21-httprequest.html +219 -12
  26. package/core/network/21-httprequest.js +98 -17
  27. package/core/network/22-websocket.html +19 -5
  28. package/core/network/22-websocket.js +16 -13
  29. package/core/network/31-tcpin.html +47 -10
  30. package/core/network/31-tcpin.js +23 -20
  31. package/core/network/32-udp.html +14 -2
  32. package/core/parsers/70-CSV.html +4 -1
  33. package/core/parsers/70-JSON.html +3 -2
  34. package/core/parsers/70-XML.html +2 -1
  35. package/core/parsers/70-YAML.html +2 -1
  36. package/core/sequence/17-split.html +6 -2
  37. package/core/sequence/19-batch.html +28 -4
  38. package/core/storage/10-file.html +66 -6
  39. package/core/storage/10-file.js +46 -3
  40. package/core/storage/23-watch.html +2 -1
  41. package/core/storage/23-watch.js +21 -43
  42. package/locales/de/messages.json +1 -0
  43. package/locales/en-US/common/60-link.html +19 -3
  44. package/locales/en-US/messages.json +136 -83
  45. package/locales/en-US/network/21-httprequest.html +1 -1
  46. package/locales/en-US/storage/10-file.html +6 -2
  47. package/locales/ja/common/60-link.html +13 -0
  48. package/locales/ja/messages.json +85 -32
  49. package/locales/ja/network/21-httprequest.html +1 -1
  50. package/locales/ja/storage/10-file.html +8 -6
  51. package/locales/ko/messages.json +1 -0
  52. package/locales/ru/messages.json +1 -0
  53. package/locales/zh-CN/messages.json +1 -0
  54. package/locales/zh-TW/messages.json +1 -0
  55. package/package.json +13 -13
@@ -20,7 +20,30 @@ module.exports = function(RED) {
20
20
  var isUtf8 = require('is-utf8');
21
21
  var HttpsProxyAgent = require('https-proxy-agent');
22
22
  var url = require('url');
23
-
23
+ const knownMediaTypes = {
24
+ "text/css":"string",
25
+ "text/html":"string",
26
+ "text/plain":"string",
27
+ "text/html":"string",
28
+ "application/json":"json",
29
+ "application/octet-stream":"buffer",
30
+ "application/pdf":"buffer",
31
+ "application/x-gtar":"buffer",
32
+ "application/x-gzip":"buffer",
33
+ "application/x-tar":"buffer",
34
+ "application/xml":"string",
35
+ "application/zip":"buffer",
36
+ "audio/aac":"buffer",
37
+ "audio/ac3":"buffer",
38
+ "audio/basic":"buffer",
39
+ "audio/mp4":"buffer",
40
+ "audio/ogg":"buffer",
41
+ "image/bmp":"buffer",
42
+ "image/gif":"buffer",
43
+ "image/jpeg":"buffer",
44
+ "image/tiff":"buffer",
45
+ "image/png":"buffer",
46
+ }
24
47
  //#region "Supporting functions"
25
48
  function matchTopic(ts,t) {
26
49
  if (ts == "#") {
@@ -68,12 +91,21 @@ module.exports = function(RED) {
68
91
  }
69
92
 
70
93
  /**
71
- * Test a topic string is valid
94
+ * Test a topic string is valid for subscription
72
95
  * @param {string} topic
73
96
  * @returns `true` if it is a valid topic
74
97
  */
75
98
  function isValidSubscriptionTopic(topic) {
76
- return /^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/.test(topic)
99
+ return /^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/.test(topic);
100
+ }
101
+
102
+ /**
103
+ * Test a topic string is valid for publishing
104
+ * @param {string} topic
105
+ * @returns `true` if it is a valid topic
106
+ */
107
+ function isValidPublishTopic(topic) {
108
+ return !/[\+#\b\f\n\r\t\v\0]/.test(topic);
77
109
  }
78
110
 
79
111
  /**
@@ -103,7 +135,7 @@ module.exports = function(RED) {
103
135
  if(src[propName] === "true" || src[propName] === true) {
104
136
  dst[propName] = true;
105
137
  } else if(src[propName] === "false" || src[propName] === false) {
106
- dst[propName] = true;
138
+ dst[propName] = false;
107
139
  }
108
140
  } else {
109
141
  if(def != undefined) dst[propName] = def;
@@ -188,6 +220,19 @@ module.exports = function(RED) {
188
220
  */
189
221
  function subscriptionHandler(node, datatype ,topic, payload, packet) {
190
222
  const v5 = node.brokerConn.options && node.brokerConn.options.protocolVersion == 5;
223
+ var msg = {topic:topic, payload:null, qos:packet.qos, retain:packet.retain};
224
+ if(v5 && packet.properties) {
225
+ setStrProp(packet.properties, msg, "responseTopic");
226
+ setBufferProp(packet.properties, msg, "correlationData");
227
+ setStrProp(packet.properties, msg, "contentType");
228
+ setIntProp(packet.properties, msg, "messageExpiryInterval", 0);
229
+ setBoolProp(packet.properties, msg, "payloadFormatIndicator");
230
+ setStrProp(packet.properties, msg, "reasonString");
231
+ setUserProperties(packet.properties.userProperties, msg);
232
+ }
233
+ const v5isUtf8 = v5 ? msg.payloadFormatIndicator === true : null;
234
+ const v5HasMediaType = v5 ? !!msg.contentType : null;
235
+ const v5MediaTypeLC = v5 ? (msg.contentType + "").toLowerCase() : null;
191
236
 
192
237
  if (datatype === "buffer") {
193
238
  // payload = payload;
@@ -196,25 +241,65 @@ module.exports = function(RED) {
196
241
  } else if (datatype === "utf8") {
197
242
  payload = payload.toString('utf8');
198
243
  } else if (datatype === "json") {
199
- if (isUtf8(payload)) {
200
- payload = payload.toString();
201
- try { payload = JSON.parse(payload); }
202
- catch(e) { node.error(RED._("mqtt.errors.invalid-json-parse"),{payload:payload, topic:topic, qos:packet.qos, retain:packet.retain}); return; }
244
+ if (v5isUtf8 || isUtf8(payload)) {
245
+ try {
246
+ payload = JSON.parse(payload.toString());
247
+ } catch (e) {
248
+ node.error(RED._("mqtt.errors.invalid-json-parse"), { payload: payload, topic: topic, qos: packet.qos, retain: packet.retain }); return;
249
+ }
250
+ } else {
251
+ node.error((RED._("mqtt.errors.invalid-json-string")), { payload: payload, topic: topic, qos: packet.qos, retain: packet.retain }); return;
203
252
  }
204
- else { node.error((RED._("mqtt.errors.invalid-json-string")),{payload:payload, topic:topic, qos:packet.qos, retain:packet.retain}); return; }
205
253
  } else {
206
- if (isUtf8(payload)) { payload = payload.toString(); }
207
- }
208
- var msg = {topic:topic, payload:payload, qos:packet.qos, retain:packet.retain};
209
- if(v5 && packet.properties) {
210
- setStrProp(packet.properties, msg, "responseTopic");
211
- setBufferProp(packet.properties, msg, "correlationData");
212
- setStrProp(packet.properties, msg, "contentType");
213
- setIntProp(packet.properties, msg, "messageExpiryInterval", 0);
214
- setBoolProp(packet.properties, msg, "payloadFormatIndicator");
215
- setStrProp(packet.properties, msg, "reasonString");
216
- setUserProperties(packet.properties.userProperties, msg);
254
+ //"auto" (legacy) or "auto-detect" (new default)
255
+ if (v5isUtf8 || v5HasMediaType) {
256
+ const outputType = knownMediaTypes[v5MediaTypeLC]
257
+ switch (outputType) {
258
+ case "string":
259
+ payload = payload.toString();
260
+ break;
261
+ case "buffer":
262
+ //no change
263
+ break;
264
+ case "json":
265
+ try {
266
+ //since v5 type states this should be JSON, parse it & error out if NOT JSON
267
+ payload = payload.toString()
268
+ const obj = JSON.parse(payload);
269
+ if (datatype === "auto-detect") {
270
+ payload = obj; //as mode is "auto-detect", return the parsed JSON
271
+ }
272
+ } catch (e) {
273
+ node.error(RED._("mqtt.errors.invalid-json-parse"), { payload: payload, topic: topic, qos: packet.qos, retain: packet.retain }); return;
274
+ }
275
+ break;
276
+ default:
277
+ if (v5isUtf8 || isUtf8(payload)) {
278
+ payload = payload.toString(); //auto String
279
+ if (datatype === "auto-detect") {
280
+ try {
281
+ payload = JSON.parse(payload); //auto to parsed object (attempt)
282
+ } catch (e) {
283
+ /* mute error - it simply isnt JSON, just leave payload as a string */
284
+ }
285
+ }
286
+ }
287
+ break;
288
+ }
289
+ } else if (isUtf8(payload)) {
290
+ payload = payload.toString(); //auto String
291
+ if (datatype === "auto-detect") {
292
+ try {
293
+ payload = JSON.parse(payload);
294
+ } catch (e) {
295
+ /* mute error - it simply isnt JSON, just leave payload as a string */
296
+ }
297
+ }
298
+ } //else {
299
+ //leave as buffer
300
+ //}
217
301
  }
302
+ msg.payload = payload;
218
303
  if ((node.brokerConn.broker === "localhost")||(node.brokerConn.broker === "127.0.0.1")) {
219
304
  msg._topic = topic;
220
305
  }
@@ -264,38 +349,15 @@ module.exports = function(RED) {
264
349
  msg.messageExpiryInterval = node.messageExpiryInterval;
265
350
  }
266
351
  }
267
- if (msg.userProperties && typeof msg.userProperties !== "object") {
268
- delete msg.userProperties;
269
- }
270
- if (hasProperty(msg, "topicAlias") && !isNaN(msg.topicAlias) && (msg.topicAlias === 0 || bsp.topicAliasMaximum === 0 || msg.topicAlias > bsp.topicAliasMaximum)) {
271
- delete msg.topicAlias;
272
- }
273
-
274
352
  if (hasProperty(msg, "payload")) {
275
-
276
- //check & sanitise topic
277
- let topicOK = hasProperty(msg, "topic") && (typeof msg.topic === "string") && (msg.topic !== "");
278
-
279
- if (!topicOK && v5) {
280
- //NOTE: A value of 0 (in server props topicAliasMaximum) indicates that the Server does not accept any Topic Aliases on this connection
281
- if (hasProperty(msg, "topicAlias") && !isNaN(msg.topicAlias) && msg.topicAlias >= 0 && bsp.topicAliasMaximum && bsp.topicAliasMaximum >= msg.topicAlias) {
282
- topicOK = true;
283
- msg.topic = ""; //must be empty string
284
- } else if (hasProperty(msg, "responseTopic") && (typeof msg.responseTopic === "string") && (msg.responseTopic !== "")) {
285
- //TODO: if topic is empty but responseTopic has a string value, use that instead. Is this desirable?
286
- topicOK = true;
287
- msg.topic = msg.responseTopic;
288
- //TODO: delete msg.responseTopic - to prevent it being resent?
353
+ // send the message
354
+ node.brokerConn.publish(msg, function(err) {
355
+ if(err && err.warn) {
356
+ node.warn(err);
357
+ return;
289
358
  }
290
- }
291
- topicOK = topicOK && !/[\+#\b\f\n\r\t\v\0]/.test(msg.topic);
292
-
293
- if (topicOK) {
294
- node.brokerConn.publish(msg, done); // send the message
295
- } else {
296
- node.warn(RED._("mqtt.errors.invalid-topic"));
297
- done();
298
- }
359
+ done(err);
360
+ });
299
361
  } else {
300
362
  done();
301
363
  }
@@ -391,6 +453,7 @@ module.exports = function(RED) {
391
453
  node.options = {};
392
454
  node.queue = [];
393
455
  node.subscriptions = {};
456
+ node.clientListeners = []
394
457
  /** @type {mqtt.MqttClient}*/ this.client;
395
458
  node.setOptions = function(opts, init) {
396
459
  if(!opts || typeof opts !== "object") {
@@ -415,8 +478,12 @@ module.exports = function(RED) {
415
478
  setIfHasProperty(opts, node, "topicAliasMaximum", init);
416
479
  setIfHasProperty(opts, node, "maximumPacketSize", init);
417
480
  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);
481
+ //https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901116
482
+ if (hasProperty(opts, "userProperties")) {
483
+ node.userProperties = opts.userProperties;
484
+ } else if (hasProperty(opts, "userProps")) {
485
+ node.userProperties = opts.userProps;
486
+ }
420
487
 
421
488
  function createLWT(topic, payload, qos, retain, v5opts, v5SubPropName) {
422
489
  let message = undefined;
@@ -465,7 +532,7 @@ module.exports = function(RED) {
465
532
  };
466
533
  if(hasProperty(opts, "willTopic")) {
467
534
  //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");
535
+ node.options.will = createLWT(opts.willTopic, opts.willPayload, opts.willQos, opts.willRetain, opts.willMsg, "properties");
469
536
  };
470
537
  } else {
471
538
  //update options
@@ -525,7 +592,7 @@ module.exports = function(RED) {
525
592
  // Only for ws or wss, check if proxy env var for additional configuration
526
593
  if (node.brokerurl.indexOf("wss://") > -1 || node.brokerurl.indexOf("ws://") > -1) {
527
594
  // check if proxy is set in env
528
- let prox, noprox;
595
+ let prox, noprox, noproxy;
529
596
  if (process.env.http_proxy) { prox = process.env.http_proxy; }
530
597
  if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
531
598
  if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
@@ -652,11 +719,16 @@ module.exports = function(RED) {
652
719
  setStatusConnecting(node, true);
653
720
  try {
654
721
  node.serverProperties = {};
722
+ if(node.client) {
723
+ //belt and braces to avoid left over clients
724
+ node.client.end(true);
725
+ node._clientRemoveListeners();
726
+ }
655
727
  node.client = mqtt.connect(node.brokerurl, node.options);
656
728
  node.client.setMaxListeners(0);
657
- let callbackDone = false; //prevent re-connects causing node.client.on('connect' firing callback multiple times
729
+ let callbackDone = false; //prevent re-connects causing node._clientOn('connect' firing callback multiple times
658
730
  // Register successful connect or reconnect handler
659
- node.client.on('connect', function (connack) {
731
+ node._clientOn('connect', function (connack) {
660
732
  node.closing = false;
661
733
  node.connecting = false;
662
734
  node.connected = true;
@@ -688,7 +760,7 @@ module.exports = function(RED) {
688
760
  }
689
761
  setStatusConnected(node, true);
690
762
  // Remove any existing listeners before resubscribing to avoid duplicates in the event of a re-connection
691
- node.client.removeAllListeners('message');
763
+ node._clientRemoveListeners('message');
692
764
 
693
765
  // Re-subscribe to stored topics
694
766
  for (var s in node.subscriptions) {
@@ -700,7 +772,7 @@ module.exports = function(RED) {
700
772
  if (node.subscriptions[s].hasOwnProperty(r)) {
701
773
  qos = Math.max(qos,node.subscriptions[s][r].qos);
702
774
  _options = node.subscriptions[s][r].options;
703
- node.client.on('message',node.subscriptions[s][r].handler);
775
+ node._clientOn('message',node.subscriptions[s][r].handler);
704
776
  }
705
777
  }
706
778
  _options.qos = _options.qos || qos;
@@ -713,11 +785,11 @@ module.exports = function(RED) {
713
785
  node.publish(node.birthMessage);
714
786
  }
715
787
  });
716
- node.client.on("reconnect", function() {
788
+ node._clientOn("reconnect", function() {
717
789
  setStatusConnecting(node, true);
718
790
  });
719
791
  //Broker Disconnect - V5 event
720
- node.client.on("disconnect", function(packet) {
792
+ node._clientOn("disconnect", function(packet) {
721
793
  //Emitted after receiving disconnect packet from broker. MQTT 5.0 feature.
722
794
  const rc = (packet && packet.properties && packet.reasonCode) || packet.reasonCode;
723
795
  const rs = packet && packet.properties && packet.properties.reasonString || "";
@@ -731,7 +803,7 @@ module.exports = function(RED) {
731
803
  setStatusDisconnected(node, true);
732
804
  });
733
805
  // Register disconnect handlers
734
- node.client.on('close', function () {
806
+ node._clientOn('close', function () {
735
807
  if (node.connected) {
736
808
  node.connected = false;
737
809
  node.log(RED._("mqtt.state.disconnected",{broker:(node.clientid?node.clientid+"@":"")+node.brokerurl}));
@@ -743,39 +815,59 @@ module.exports = function(RED) {
743
815
 
744
816
  // Register connect error handler
745
817
  // The client's own reconnect logic will take care of errors
746
- node.client.on('error', function (error) {
818
+ node._clientOn('error', function (error) {
747
819
  });
748
820
  }catch(err) {
749
821
  console.log(err);
750
822
  }
751
823
  }
752
824
  };
825
+
753
826
  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;
827
+ const _callback = function () {
828
+ if(node.connected || node.connecting) {
829
+ setStatusDisconnected(node, true);
760
830
  }
831
+ if(node.client) { node._clientRemoveListeners(); }
832
+ node.connecting = false;
833
+ node.connected = false;
761
834
  callback && typeof callback == "function" && callback();
762
835
  };
836
+ if(!node.client) { return _callback(); }
837
+ if(node.closing) { return _callback(); }
763
838
 
764
- if(node.closing) {
765
- return _callback(false);
766
- }
767
- var endCallBack = function endCallBack() {
768
- }
839
+ let waitEnd = (client, ms) => {
840
+ return new Promise( (resolve, reject) => {
841
+ node.closing = true;
842
+ if(!client) {
843
+ resolve();
844
+ } else {
845
+ const t = setTimeout(() => {
846
+ //clean end() has exceeded WAIT_END, lets force end!
847
+ client && client.end(true);
848
+ reject();
849
+ }, ms);
850
+ client.end(() => {
851
+ clearTimeout(t);
852
+ resolve()
853
+ });
854
+ }
855
+ });
856
+ };
769
857
  if(node.connected && node.closeMessage) {
770
858
  node.publish(node.closeMessage, function (err) {
771
- node.client.end(endCallBack);
772
- _callback(true);
859
+ waitEnd(node.client, 2000).then(() => {
860
+ _callback();
861
+ }).catch((e) => {
862
+ _callback();
863
+ })
773
864
  });
774
- } else if(node.connected) {
775
- node.client.end(endCallBack);
776
- _callback(true);
777
865
  } else {
778
- _callback(false);
866
+ waitEnd(node.client, 2000).then(() => {
867
+ _callback();
868
+ }).catch((e) => {
869
+ _callback();
870
+ })
779
871
  }
780
872
  }
781
873
  node.subscriptionIds = {};
@@ -812,7 +904,7 @@ module.exports = function(RED) {
812
904
  };
813
905
  node.subscriptions[topic][ref] = sub;
814
906
  if (node.connected) {
815
- node.client.on('message',sub.handler);
907
+ node._clientOn('message',sub.handler);
816
908
  node.client.subscribe(topic, options);
817
909
  }
818
910
  };
@@ -823,7 +915,7 @@ module.exports = function(RED) {
823
915
  if (sub) {
824
916
  if (sub[ref]) {
825
917
  if(node.client) {
826
- node.client.removeListener('message',sub[ref].handler);
918
+ node._clientRemoveListeners('message',sub[ref].handler);
827
919
  }
828
920
  delete sub[ref];
829
921
  }
@@ -857,8 +949,18 @@ module.exports = function(RED) {
857
949
  qos: msg.qos || 0,
858
950
  retain: msg.retain || false
859
951
  };
952
+ let topicOK = hasProperty(msg, "topic") && (typeof msg.topic === "string") && (isValidPublishTopic(msg.topic));
860
953
  //https://github.com/mqttjs/MQTT.js/blob/master/README.md#mqttclientpublishtopic-message-options-callback
861
954
  if(node.options.protocolVersion == 5) {
955
+ const bsp = node.serverProperties || {};
956
+ if (msg.userProperties && typeof msg.userProperties !== "object") {
957
+ delete msg.userProperties;
958
+ }
959
+ if (hasProperty(msg, "topicAlias") && !isNaN(Number(msg.topicAlias))) {
960
+ msg.topicAlias = parseInt(msg.topicAlias);
961
+ } else {
962
+ delete msg.topicAlias;
963
+ }
862
964
  options.properties = options.properties || {};
863
965
  setStrProp(msg, options.properties, "responseTopic");
864
966
  setBufferProp(msg, options.properties, "correlationData");
@@ -868,31 +970,75 @@ module.exports = function(RED) {
868
970
  setIntProp(msg, options.properties, "topicAlias", 1, node.serverProperties.topicAliasMaximum || 0);
869
971
  setBoolProp(msg, options.properties, "payloadFormatIndicator");
870
972
  //FUTURE setIntProp(msg, options.properties, "subscriptionIdentifier", 1, 268435455);
871
- if (options.properties.topicAlias) {
872
- if (!node.topicAliases.hasOwnProperty(options.properties.topicAlias) && msg.topic == "") {
973
+
974
+ //check & sanitise topic
975
+ if (topicOK && options.properties.topicAlias) {
976
+ let aliasValid = (bsp.topicAliasMaximum && bsp.topicAliasMaximum >= options.properties.topicAlias);
977
+ if (!aliasValid) {
873
978
  done("Invalid topicAlias");
874
979
  return
875
980
  }
876
981
  if (node.topicAliases[options.properties.topicAlias] === msg.topic) {
877
- msg.topic = ""
982
+ msg.topic = "";
878
983
  } else {
879
- node.topicAliases[options.properties.topicAlias] = msg.topic
984
+ node.topicAliases[options.properties.topicAlias] = msg.topic;
880
985
  }
986
+ } else if (!msg.topic && options.properties.responseTopic) {
987
+ msg.topic = msg.responseTopic;
988
+ topicOK = isValidPublishTopic(msg.topic);
989
+ delete msg.responseTopic; //prevent responseTopic being resent?
881
990
  }
882
991
  }
883
992
 
884
- node.client.publish(msg.topic, msg.payload, options, function(err) {
885
- done && done(err);
886
- return
887
- });
993
+ if (topicOK) {
994
+ node.client.publish(msg.topic, msg.payload, options, function(err) {
995
+ done && done(err);
996
+ return
997
+ });
998
+ } else {
999
+ const error = new Error(RED._("mqtt.errors.invalid-topic"));
1000
+ error.warn = true;
1001
+ done(error);
1002
+ }
888
1003
  }
889
1004
  };
890
1005
 
891
1006
  node.on('close', function(done) {
892
- node.closing = true;
893
- node.disconnect(done);
1007
+ node.disconnect(function() {
1008
+ done();
1009
+ });
894
1010
  });
895
1011
 
1012
+ /**
1013
+ * Add event handlers to the MQTT.js client and track them so that
1014
+ * we do not remove any handlers that the MQTT client uses internally.
1015
+ * Use {@link node._clientRemoveListeners `node._clientRemoveListeners`} to remove handlers
1016
+ * @param {string} event The name of the event
1017
+ * @param {function} handler The handler for this event
1018
+ */
1019
+ node._clientOn = function(event, handler) {
1020
+ node.clientListeners.push({event, handler})
1021
+ node.client.on(event, handler)
1022
+ }
1023
+
1024
+ /**
1025
+ * Remove event handlers from the MQTT.js client & only the events
1026
+ * that we attached in {@link node._clientOn `node._clientOn`}.
1027
+ * * If `event` is omitted, then all events matching `handler` are removed
1028
+ * * If `handler` is omitted, then all events named `event` are removed
1029
+ * * If both parameters are omitted, then all events are removed
1030
+ * @param {string} [event] The name of the event (optional)
1031
+ * @param {function} [handler] The handler for this event (optional)
1032
+ */
1033
+ node._clientRemoveListeners = function(event, handler) {
1034
+ node.clientListeners = node.clientListeners.filter((l) => {
1035
+ if (event && event !== l.event) { return true; }
1036
+ if (handler && handler !== l.handler) { return true; }
1037
+ node.client.removeListener(l.event, l.handler)
1038
+ return false; //found and removed, filter out this one
1039
+ })
1040
+ }
1041
+
896
1042
  }
897
1043
 
898
1044
  RED.nodes.registerType("mqtt-broker",MQTTBrokerNode,{
@@ -1067,6 +1213,7 @@ module.exports = function(RED) {
1067
1213
  node.brokerConn.unsubscribe(node.topic,node.id, removed);
1068
1214
  }
1069
1215
  node.brokerConn.deregister(node, done);
1216
+ node.brokerConn = null;
1070
1217
  } else {
1071
1218
  done();
1072
1219
  }
@@ -1131,6 +1278,7 @@ module.exports = function(RED) {
1131
1278
  node.on('close', function(done) {
1132
1279
  if (node.brokerConn) {
1133
1280
  node.brokerConn.deregister(node,done);
1281
+ node.brokerConn = null;
1134
1282
  } else {
1135
1283
  done();
1136
1284
  }
@@ -70,7 +70,8 @@
70
70
  color:"rgb(231, 231, 174)",
71
71
  defaults: {
72
72
  name: {value:""},
73
- url: {value:"",required:true},
73
+ url: {value:"", required:true,
74
+ label:RED._("node-red:httpin.label.url")},
74
75
  method: {value:"get",required:true},
75
76
  upload: {value:false},
76
77
  swaggerDoc: {type:"swagger-doc", required:false}
@@ -128,7 +129,7 @@
128
129
  var headerTypes = [
129
130
  {value:"content-type",label:"Content-Type",hasValue: false},
130
131
  {value:"location",label:"Location",hasValue: false},
131
- {value:"other",label:RED._("node-red:httpin.label.other"),icon:"red/images/typedInput/az.png"}
132
+ {value:"other",label:RED._("node-red:httpin.label.other"),icon:"red/images/typedInput/az.svg"}
132
133
  ]
133
134
  var contentTypes = [
134
135
  {value:"application/json",label:"application/json",hasValue: false},
@@ -138,7 +139,7 @@
138
139
  {value:"text/plain",label:"text/plain",hasValue: false},
139
140
  {value:"image/gif",label:"image/gif",hasValue: false},
140
141
  {value:"image/png",label:"image/png",hasValue: false},
141
- {value:"other",label:RED._("node-red:httpin.label.other"),icon:"red/images/typedInput/az.png"}
142
+ {value:"other",label:RED._("node-red:httpin.label.other"),icon:"red/images/typedInput/az.svg"}
142
143
  ];
143
144
 
144
145
  RED.nodes.registerType('http response',{
@@ -146,7 +147,10 @@
146
147
  color:"rgb(231, 231, 174)",
147
148
  defaults: {
148
149
  name: {value:""},
149
- statusCode: {value:"",validate: RED.validators.number(true)},
150
+ statusCode: {
151
+ value:"",
152
+ label: RED._("node-red:httpin.label.status"),
153
+ validate: RED.validators.number(true)},
150
154
  headers: {value:{}}
151
155
  },
152
156
  inputs:1,
@@ -176,7 +180,7 @@
176
180
  var propertyValue = $('<input/>',{class:"node-input-header-value",type:"text",style:"width: 100%"})
177
181
  .appendTo(propertyValueCell)
178
182
  .typedInput({types:
179
- header.h === 'content-type'?contentTypes:[{value:"other",label:"other",icon:"red/images/typedInput/az.png"}]
183
+ header.h === 'content-type'?contentTypes:[{value:"other",label:"other",icon:"red/images/typedInput/az.svg"}]
180
184
  });
181
185
 
182
186
  var matchedType = headerTypes.filter(function(ht) {
@@ -219,7 +223,7 @@
219
223
  if (type === 'content-type') {
220
224
  propertyValue.typedInput('types',contentTypes);
221
225
  } else {
222
- propertyValue.typedInput('types',[{value:"other",label:"other",icon:"red/images/typedInput/az.png"}]);
226
+ propertyValue.typedInput('types',[{value:"other",label:"other",icon:"red/images/typedInput/az.svg"}]);
223
227
  }
224
228
  });
225
229
  },