@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
@@ -50,7 +50,8 @@
50
50
  </div>
51
51
 
52
52
  <div id="node-row-newline" class="form-row hidden" style="padding-left:110px;">
53
- <span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional">
53
+ <span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional"><br/>
54
+ <input type="checkbox" id="node-input-trim" style="display:inline-block; width:auto; vertical-align:top;"> <span data-i18n="tcpin.label.reattach"></span>
54
55
  </div>
55
56
 
56
57
  <div class="form-row">
@@ -70,14 +71,27 @@
70
71
  defaults: {
71
72
  name: {value:""},
72
73
  server: {value:"server", required:true},
73
- host: {value:"", validate:function(v) { return (this.server == "server")||v.length > 0;} },
74
- port: {value:"", required:true, validate:RED.validators.number()},
74
+ host: {
75
+ value:"",
76
+ validate:function(v, opt) {
77
+ if ((this.server == "server")||v.length > 0) {
78
+ return true;
79
+ }
80
+ return RED._("node-red:tcpin.errors.invalid-host");
81
+ }
82
+ },
83
+ port: {
84
+ value:"", required:true,
85
+ label:RED._("node-red:tcpin.label.port"),
86
+ validate:RED.validators.number(false)},
75
87
  datamode:{value:"stream"},
76
88
  datatype:{value:"buffer"},
77
89
  newline:{value:""},
78
90
  topic: {value:""},
91
+ trim: {value:false},
79
92
  base64: {/*deprecated*/ value:false, required:true},
80
- tls: {type:"tls-config", value:'', required:false}
93
+ tls: {type:"tls-config", value:'', required:false,
94
+ label:RED._("node-red:httpin.tls-config") }
81
95
  },
82
96
  inputs:0,
83
97
  outputs:1,
@@ -186,12 +200,29 @@
186
200
  color: "Silver",
187
201
  defaults: {
188
202
  name: {value:""},
189
- host: {value:"",validate:function(v) { return (this.beserver != "client")||v.length > 0;} },
190
- port: {value:"",validate:function(v) { return (this.beserver == "reply")||RED.validators.number()(v); } },
203
+ host: {
204
+ value:"",
205
+ validate:function(v, opt) {
206
+ if ((this.beserver != "client")||v.length > 0) {
207
+ return true;
208
+ }
209
+ return RED._("node-red:tcpin.errors.invalid-host");
210
+ }
211
+ },
212
+ port: {
213
+ value:"",
214
+ validate:function(v) {
215
+ if ((this.beserver == "reply")||RED.validators.number()(v)) {
216
+ return true;
217
+ }
218
+ return RED._("node-red:tcpin.errors.invalid-port");
219
+ }
220
+ },
191
221
  beserver: {value:"client", required:true},
192
222
  base64: {value:false, required:true},
193
223
  end: {value:false, required:true},
194
- tls: {type:"tls-config", value:'', required:false}
224
+ tls: {type:"tls-config", value:'', required:false,
225
+ label:RED._("node-red:httpin.tls-config") }
195
226
  },
196
227
  inputs:1,
197
228
  outputs:0,
@@ -286,7 +317,8 @@
286
317
  <span id="node-units"></span>
287
318
  </div>
288
319
  <div id="node-row-newline" class="form-row hidden" style="padding-left:162px;">
289
- <span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional">
320
+ <span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional"><br/>
321
+ <input type="checkbox" id="node-input-trim" style="display:inline-block; width:auto; vertical-align:top;"> <span data-i18n="tcpin.label.reattach"></span>
290
322
  </div>
291
323
  <div class="form-row">
292
324
  <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
@@ -301,12 +333,17 @@
301
333
  defaults: {
302
334
  name: {value:""},
303
335
  server: {value:""},
304
- port: {value:"", validate:RED.validators.regex(/^(\d*|)$/)},
336
+ port: {
337
+ value:"",
338
+ label: RED._("node-red:tcpin.label.port"),
339
+ validate:RED.validators.regex(/^(\d*|)$/)
340
+ },
305
341
  out: {value:"time", required:true},
306
342
  ret: {value:"buffer"},
307
343
  splitc: {value:"0", required:true},
308
344
  newline: {value:""},
309
- tls: {type:"tls-config", value:'', required:false}
345
+ trim: {value:false},
346
+ tls: {type:"tls-config", value:'', required:false, label:RED._("node-red:httpin.tls-config")}
310
347
  },
311
348
  inputs:1,
312
349
  outputs:1,
@@ -88,6 +88,7 @@ module.exports = function(RED) {
88
88
  this.datatype = n.datatype||'buffer'; /* buffer,utf8,base64 */
89
89
  this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r").replace("\\t","\t");
90
90
  this.base64 = n.base64;
91
+ this.trim = n.trim || false;
91
92
  this.server = (typeof n.server == 'boolean')?n.server:(n.server == "server");
92
93
  this.closing = false;
93
94
  this.connected = false;
@@ -135,7 +136,8 @@ module.exports = function(RED) {
135
136
  buffer = buffer+data;
136
137
  var parts = buffer.split(node.newline);
137
138
  for (var i = 0; i<parts.length-1; i+=1) {
138
- msg = {topic:node.topic, payload:parts[i] + node.newline.trimEnd()};
139
+ msg = {topic:node.topic, payload:parts[i]};
140
+ if (node.trim == true) { msg.payload += node.newline; }
139
141
  msg._session = {type:"tcp",id:id};
140
142
  node.send(msg);
141
143
  }
@@ -229,7 +231,8 @@ module.exports = function(RED) {
229
231
  buffer = buffer+data;
230
232
  var parts = buffer.split(node.newline);
231
233
  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};
234
+ msg = {topic:node.topic, payload:parts[i], ip:socket.remoteAddress, port:socket.remotePort};
235
+ if (node.trim == true) { msg.payload += node.newline; }
233
236
  msg._session = {type:"tcp",id:id};
234
237
  node.send(msg);
235
238
  }
@@ -432,7 +435,7 @@ module.exports = function(RED) {
432
435
  });
433
436
  }
434
437
  else {
435
- var connectedSockets = [];
438
+ const connectedSockets = new Set();
436
439
  node.status({text:RED._("tcpin.status.connections",{count:0})});
437
440
  let srv = net;
438
441
  let connOpts;
@@ -453,16 +456,16 @@ module.exports = function(RED) {
453
456
  });
454
457
  socket.on('close',function() {
455
458
  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})});
459
+ connectedSockets.delete(socket);
460
+ node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.size})});
458
461
  });
459
462
  socket.on('error',function() {
460
463
  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})});
464
+ connectedSockets.delete(socket);
465
+ node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.size})});
463
466
  });
464
- connectedSockets.push(socket);
465
- node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
467
+ connectedSockets.add(socket);
468
+ node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.size})});
466
469
  });
467
470
 
468
471
  node.on("input", function(msg, nodeSend, nodeDone) {
@@ -475,10 +478,10 @@ module.exports = function(RED) {
475
478
  } else {
476
479
  buffer = Buffer.from(""+msg.payload);
477
480
  }
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
- }
481
+ connectedSockets.forEach(soc => {
482
+ if (node.doend === true) { soc.end(buffer); }
483
+ else { soc.write(buffer); }
484
+ })
482
485
  }
483
486
  nodeDone();
484
487
  });
@@ -495,12 +498,10 @@ module.exports = function(RED) {
495
498
  } else {
496
499
  node.log(RED._("tcpin.status.listening-port",{port:node.port}));
497
500
  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
- }
501
+ connectedSockets.forEach(soc => {
502
+ soc.end();
503
+ soc.unref();
504
+ })
504
505
  server.close();
505
506
  node.log(RED._("tcpin.status.stopped-listening",{port:node.port}));
506
507
  });
@@ -518,6 +519,7 @@ module.exports = function(RED) {
518
519
  this.out = n.out;
519
520
  this.ret = n.ret || "buffer";
520
521
  this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r").replace("\\t","\t");
522
+ this.trim = n.trim || false;
521
523
  this.splitc = n.splitc;
522
524
  if (n.tls) {
523
525
  var tlsNode = RED.nodes.getNode(n.tls);
@@ -653,7 +655,8 @@ module.exports = function(RED) {
653
655
  let parts = chunk.split(node.newline);
654
656
  for (var p=0; p<parts.length-1; p+=1) {
655
657
  let m = RED.util.cloneMessage(msg);
656
- m.payload = parts[p] + node.newline.trimEnd();
658
+ m.payload = parts[p];
659
+ if (node.trim == true) { m.payload += node.newline; }
657
660
  nodeSend(m);
658
661
  }
659
662
  chunk = parts[parts.length-1];
@@ -62,10 +62,22 @@
62
62
  defaults: {
63
63
  name: {value:""},
64
64
  iface: {value:""},
65
- port: {value:"",required:true,validate:RED.validators.number()},
65
+ port: {
66
+ value:"", required:true,
67
+ label:RED._("node-red:udp.label.port"),
68
+ validate:RED.validators.number(false)
69
+ },
66
70
  ipv: {value:"udp4"},
67
71
  multicast: {value:"false"},
68
- group: {value:"",validate:function(v) { return (this.multicast !== "true")||v.length > 0;} },
72
+ group: {
73
+ value:"",
74
+ validate:function(v,opt) {
75
+ if ((this.multicast !== "true")||v.length > 0) {
76
+ return true;
77
+ }
78
+ return RED._("node-red:udp.errors.invalid-group");
79
+ }
80
+ },
69
81
  datatype: {value:"buffer",required:true}
70
82
  },
71
83
  inputs:0,
@@ -75,7 +75,10 @@
75
75
  color:"#DEBD5C",
76
76
  defaults: {
77
77
  name: {value:""},
78
- sep: {value:',',required:true,validate:RED.validators.regex(/^.{1,2}$/)},
78
+ sep: {
79
+ value:',', required:true,
80
+ label:RED._("node-red:csv.label.separator"),
81
+ validate:RED.validators.regex(/^.{1,2}$/)},
79
82
  //quo: {value:'"',required:true},
80
83
  hdrin: {value:""},
81
84
  hdrout: {value:"none"},
@@ -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
 
@@ -31,7 +31,8 @@
31
31
  color:"#DEBD5C",
32
32
  defaults: {
33
33
  name: {value:""},
34
- property: {value:"payload",required:true},
34
+ property: {value:"payload",required:true,
35
+ label:RED._("node-red:json.label.property")},
35
36
  action: {value:""},
36
37
  pretty: {value:false}
37
38
  },
@@ -26,7 +26,8 @@
26
26
  color:"#DEBD5C",
27
27
  defaults: {
28
28
  name: {value:""},
29
- property: {value:"payload",required:true},
29
+ property: {value:"payload",required:true,
30
+ label:RED._("node-red:common.label.property")},
30
31
  attr: {value:""},
31
32
  chr: {value:""}
32
33
  },
@@ -15,7 +15,8 @@
15
15
  category: 'parser',
16
16
  color:"#DEBD5C",
17
17
  defaults: {
18
- property: {value:"payload",required:true},
18
+ property: {value:"payload",required:true,
19
+ label:RED._("node-red:common.label.property")},
19
20
  name: {value:""}
20
21
  },
21
22
  inputs:1,
@@ -202,7 +202,11 @@
202
202
  name: {value:""},
203
203
  mode: {value:"auto"},
204
204
  build: { value:"object"},
205
- property: { value:"payload", validate:RED.validators.typedInput("propertyType")},
205
+ property: {
206
+ value:"payload",
207
+ label: RED._("node-red:join.message-prop"),
208
+ validate:RED.validators.typedInput("propertyType", false)
209
+ },
206
210
  propertyType: { value:"msg"},
207
211
  key: {value:"topic"},
208
212
  joiner: { value:"\\n"},
@@ -243,7 +247,7 @@
243
247
  var jsonata_or_empty = {
244
248
  value: "jsonata",
245
249
  label: "expression",
246
- icon: "red/images/typedInput/expr.png",
250
+ icon: "red/images/typedInput/expr.svg",
247
251
  validate: function(v) {
248
252
  try{
249
253
  if(v !== "") {
@@ -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">
@@ -73,9 +73,33 @@
73
73
  defaults: {
74
74
  name: {value:""},
75
75
  mode: {value:"count"},
76
- count: {value:10,validate:function(v) { return RED.validators.number(v) && (v >= 1); }},
77
- overlap: {value:0,validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
78
- interval: {value:10,validate:function(v) { return RED.validators.number(v) && (v >= 1); }},
76
+ count: {
77
+ value:10,
78
+ validate:function(v, opt) {
79
+ if (RED.validators.number(v) && (v >= 1)) {
80
+ return true;
81
+ }
82
+ return RED._("node-red:batch.error.invalid-count");
83
+ }
84
+ },
85
+ overlap: {
86
+ value:0,
87
+ validate:function(v, opt) {
88
+ if (RED.validators.number(v) && (v >= 0)) {
89
+ return true;
90
+ }
91
+ return RED._("node-red:batch.error.invalid-overlap");
92
+ }
93
+ },
94
+ interval: {
95
+ value:10,
96
+ validate:function(v, opt) {
97
+ if (RED.validators.number(v) && (v >= 1)) {
98
+ return true;
99
+ }
100
+ return RED._("node-red:batch.error.invalid-interval");
101
+ }
102
+ },
79
103
  allowEmptySequence: {value:false},
80
104
  topics: {value:[{topic:""}]}
81
105
  },
@@ -3,6 +3,7 @@
3
3
  <div class="form-row node-input-filename">
4
4
  <label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
5
5
  <input id="node-input-filename" type="text">
6
+ <input type="hidden" id="node-input-filenameType">
6
7
  </div>
7
8
  <div class="form-row">
8
9
  <label for="node-input-overwriteFile"><i class="fa fa-random"></i> <span data-i18n="file.label.action"></span></label>
@@ -29,7 +30,7 @@
29
30
  </div>
30
31
  <div class="form-row">
31
32
  <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
32
- <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
33
+ <input type="text" id="node-input-name">
33
34
  </div>
34
35
  <div class="form-tips"><span data-i18n="file.tip"></span></div>
35
36
  </script>
@@ -37,7 +38,8 @@
37
38
  <script type="text/html" data-template-name="file in">
38
39
  <div class="form-row">
39
40
  <label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
40
- <input id="node-input-filename" type="text" data-i18n="[placeholder]file.label.filename">
41
+ <input id="node-input-filename" type="text">
42
+ <input type="hidden" id="node-input-filenameType">
41
43
  </div>
42
44
  <div class="form-row">
43
45
  <label for="node-input-format"><i class="fa fa-sign-out"></i> <span data-i18n="file.label.outputas"></span></label>
@@ -60,7 +62,7 @@
60
62
  </div>
61
63
  <div class="form-row">
62
64
  <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
63
- <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
65
+ <input type="text" id="node-input-name">
64
66
  </div>
65
67
  <div class="form-tips"><span data-i18n="file.tip"></span></div>
66
68
  </script>
@@ -197,6 +199,7 @@
197
199
  defaults: {
198
200
  name: {value:""},
199
201
  filename: {value:""},
202
+ filenameType: {value:"str"},
200
203
  appendNewline: {value:true},
201
204
  createDir: {value:false},
202
205
  overwriteFile: {value:"false"},
@@ -207,10 +210,13 @@
207
210
  outputs:1,
208
211
  icon: "file-out.svg",
209
212
  label: function() {
213
+ var fn = this.filename;
214
+ if(this.filenameType != "str" && this.filenameType != "env" ) { fn = ""; }
215
+ if(this.filenameType === "env") { fn = "env."+fn; }
210
216
  if (this.overwriteFile === "delete") {
211
- return this.name||this._("file.label.deletelabel",{file:this.filename});
217
+ return this.name||this._("file.label.deletelabel",{file:fn});
212
218
  } else {
213
- return this.name||this.filename||this._("file.label.write");
219
+ return this.name||fn||this._("file.label.write");
214
220
  }
215
221
  },
216
222
  paletteLabel: RED._("node-red:file.label.write"),
@@ -229,6 +235,31 @@
229
235
  value: "setbymsg",
230
236
  label: node._("file.encoding.setbymsg")
231
237
  }).text(label).appendTo(encSel);
238
+ $("#node-input-filename").typedInput({
239
+ default: "str",
240
+ types: [{label:RED._("node-red:file.label.path"), value:"str", icon:""}, "msg", "jsonata", "env"],
241
+ typeField: $("#node-input-filenameType")
242
+ });
243
+ if(typeof node.filenameType == 'undefined') {
244
+ //existing node AND filenameType is not set - inplace (compatible) upgrade to new typedInput
245
+ if(node.filename == "") { //was using empty value to denote msg.filename - set typedInput to match
246
+ node.filename = "filename";
247
+ node.filenameType = "msg";
248
+ $("#node-input-filename").typedInput("type", node.filenameType);
249
+ $("#node-input-filename").typedInput("value", node.filename);
250
+ } else if(/^\${[^}]+}$/.test(node.filename)) { //was using an ${ENV_VAR}
251
+ node.filenameType = "env";
252
+ node.filename = node.filename.replace(/\${([^}]+)}/g, function(match, name) {
253
+ return (name === undefined)?"":name;
254
+ });
255
+ $("#node-input-filename").typedInput("type", node.filenameType);
256
+ $("#node-input-filename").typedInput("value", node.filename);
257
+ } else { //was using a static filename - set typedInput type to str
258
+ node.filenameType = "str";
259
+ $("#node-input-filename").typedInput("type", node.filenameType);
260
+ $("#node-input-filename").typedInput("value", node.filename);
261
+ }
262
+ }
232
263
  encodings.forEach(function(item) {
233
264
  if(Array.isArray(item)) {
234
265
  var group = $("<optgroup/>", {
@@ -267,6 +298,7 @@
267
298
  defaults: {
268
299
  name: {value:""},
269
300
  filename: {value:""},
301
+ filenameType: {value:"str"},
270
302
  format: {value:"utf8"},
271
303
  chunk: {value:false},
272
304
  sendError: {value: false},
@@ -291,7 +323,10 @@
291
323
  },
292
324
  icon: "file-in.svg",
293
325
  label: function() {
294
- return this.name||this.filename||this._("file.label.read");
326
+ var fn = this.filename;
327
+ if(this.filenameType != "str" && this.filenameType != "env" ) { fn = ""; }
328
+ if(this.filenameType === "env") { fn = "env."+fn; }
329
+ return this.name||fn||this._("file.label.read");
295
330
  },
296
331
  paletteLabel: RED._("node-red:file.label.read"),
297
332
  labelStyle: function() {
@@ -305,6 +340,31 @@
305
340
  value: "none",
306
341
  label: label
307
342
  }).text(label).appendTo(encSel);
343
+ $("#node-input-filename").typedInput({
344
+ default: "str",
345
+ types: [{label:RED._("node-red:file.label.path"), value:"str", icon:""}, "msg", "jsonata", "env"],
346
+ typeField: $("#node-input-filenameType")
347
+ });
348
+ if(typeof node.filenameType == 'undefined') {
349
+ //existing node AND filenameType is not set - inplace (compatible) upgrade to new typedInput
350
+ if(node.filename == "") { //was using empty value to denote msg.filename - set typedInput to match
351
+ node.filename = "filename";
352
+ node.filenameType = "msg";
353
+ $("#node-input-filename").typedInput("type", node.filenameType);
354
+ $("#node-input-filename").typedInput("value", node.filename);
355
+ } else if(/^\${[^}]+}$/.test(node.filename)) { //was using an ${ENV_VAR}
356
+ node.filenameType = "env";
357
+ node.filename = node.filename.replace(/\${([^}]+)}/g, function(match, name) {
358
+ return (name === undefined)?"":name;
359
+ });
360
+ $("#node-input-filename").typedInput("type", node.filenameType);
361
+ $("#node-input-filename").typedInput("value", node.filename);
362
+ } else { //was using a static filename - set typedInput type to str
363
+ node.filenameType = "str";
364
+ $("#node-input-filename").typedInput("type", node.filenameType);
365
+ $("#node-input-filename").typedInput("value", node.filename);
366
+ }
367
+ }
308
368
  encodings.forEach(function(item) {
309
369
  if(Array.isArray(item)) {
310
370
  var group = $("<optgroup/>", {
@@ -39,6 +39,7 @@ module.exports = function(RED) {
39
39
  // Write/delete a file
40
40
  RED.nodes.createNode(this,n);
41
41
  this.filename = n.filename;
42
+ this.filenameType = n.filenameType;
42
43
  this.appendNewline = n.appendNewline;
43
44
  this.overwriteFile = n.overwriteFile.toString();
44
45
  this.createDir = n.createDir || false;
@@ -50,7 +51,28 @@ module.exports = function(RED) {
50
51
  node.closeCallback = null;
51
52
 
52
53
  function processMsg(msg,nodeSend, done) {
53
- var filename = node.filename || msg.filename || "";
54
+ var filename = node.filename || "";
55
+ //Pre V3 compatibility - if filenameType is empty, do in place upgrade
56
+ if(typeof node.filenameType == 'undefined' || node.filenameType == "") {
57
+ //existing node AND filenameType is not set - inplace (compatible) upgrade
58
+ if(filename == "") { //was using empty value to denote msg.filename
59
+ node.filename = "filename";
60
+ node.filenameType = "msg";
61
+ } else { //was using a static filename - set typedInput type to str
62
+ node.filenameType = "str";
63
+ }
64
+ }
65
+
66
+ RED.util.evaluateNodeProperty(node.filename,node.filenameType,node,msg,(err,value) => {
67
+ if (err) {
68
+ node.error(err,msg);
69
+ return done();
70
+ } else {
71
+ filename = value;
72
+ }
73
+ });
74
+ filename = filename || "";
75
+ msg.filename = filename;
54
76
  var fullFilename = filename;
55
77
  if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
56
78
  fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename));
@@ -158,7 +180,7 @@ module.exports = function(RED) {
158
180
  done();
159
181
  });
160
182
  }
161
- if (node.filename) {
183
+ if (node.filenameType === "str" || node.filenameType === "env") {
162
184
  // Static filename - write and reuse the stream next time
163
185
  node.wstream.write(buf, function() {
164
186
  nodeSend(msg);
@@ -256,6 +278,7 @@ module.exports = function(RED) {
256
278
  // Read a file
257
279
  RED.nodes.createNode(this,n);
258
280
  this.filename = n.filename;
281
+ this.filenameType = n.filenameType;
259
282
  this.format = n.format;
260
283
  this.chunk = false;
261
284
  this.encoding = n.encoding || "none";
@@ -270,8 +293,28 @@ module.exports = function(RED) {
270
293
  var node = this;
271
294
 
272
295
  this.on("input",function(msg, nodeSend, nodeDone) {
273
- var filename = (node.filename || msg.filename || "").replace(/\t|\r|\n/g,'');
296
+ var filename = node.filename || "";
297
+ //Pre V3 compatibility - if filenameType is empty, do in place upgrade
298
+ if(typeof node.filenameType == 'undefined' || node.filenameType == "") {
299
+ //existing node AND filenameType is not set - inplace (compatible) upgrade
300
+ if(filename == "") { //was using empty value to denote msg.filename
301
+ node.filename = "filename";
302
+ node.filenameType = "msg";
303
+ } else { //was using a static filename - set typedInput type to str
304
+ node.filenameType = "str";
305
+ }
306
+ }
307
+ RED.util.evaluateNodeProperty(node.filename,node.filenameType,node,msg,(err,value) => {
308
+ if (err) {
309
+ node.error(err,msg);
310
+ return done();
311
+ } else {
312
+ filename = (value || "").replace(/\t|\r|\n/g,'');
313
+ }
314
+ });
315
+ filename = filename || "";
274
316
  var fullFilename = filename;
317
+ var filePath = "";
275
318
  if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
276
319
  fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename));
277
320
  }
@@ -36,7 +36,8 @@
36
36
  category: 'storage',
37
37
  defaults: {
38
38
  name: {value:""},
39
- files: {value:"",required:true},
39
+ files: {value:"",required:true,
40
+ label:RED._("node-red:watch.label.files")},
40
41
  recursive: {value:""}
41
42
  },
42
43
  color:"BurlyWood",