@node-red/nodes 5.0.0-beta.1 → 5.0.0-beta.2

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.
@@ -20,13 +20,15 @@ module.exports = function(RED) {
20
20
  function StatusNode(n) {
21
21
  RED.nodes.createNode(this,n);
22
22
  var node = this;
23
- this.scope = n.scope || [];
23
+ this.scope = n.scope;
24
24
 
25
25
  // auto-filter out any directly connected nodes to avoid simple loopback
26
- const w = this.wires.flat();
27
- for (let i=0; i < this.scope.length; i++) {
28
- if (w.includes(this.scope[i])) {
29
- this.scope.splice(i, 1);
26
+ if (Array.isArray(this.scope)) {
27
+ const w = this.wires.flat();
28
+ for (let i = 0; i < this.scope.length; i++) {
29
+ if (w.includes(this.scope[i])) {
30
+ this.scope.splice(i, 1);
31
+ }
30
32
  }
31
33
  }
32
34
 
@@ -32,12 +32,18 @@ RED.debug = (function() {
32
32
  var numMessages = 100; // Hardcoded number of message to show in debug window scrollback
33
33
 
34
34
  var debugNodeTreeList;
35
+ var debugPaused = false;
36
+ let debugPausedMessage
37
+ let debugPausedMessageCount = 0
35
38
 
36
39
  function init(_config) {
37
40
  config = _config;
38
41
 
39
42
  var content = $("<div>").css({"position":"relative","height":"100%"});
40
43
  var toolbar = $('<div class="red-ui-sidebar-header">'+
44
+ '<span class="button-group">'+
45
+ '<a id="red-ui-sidebar-debug-pause" class="red-ui-sidebar-header-button" href="#"><i class="fa fa-pause"></i></a>'+
46
+ '</span>'+
41
47
  '<span class="button-group">'+
42
48
  '<a id="red-ui-sidebar-debug-filter" style="padding-right: 5px" class="red-ui-sidebar-header-button" href="#"><i class="fa fa-filter"></i> <span></span> <i style="padding-left: 5px;" class="fa fa-caret-down"></i></a>'+
43
49
  '</span>'+
@@ -47,6 +53,7 @@ RED.debug = (function() {
47
53
  '</span></div>').appendTo(content);
48
54
 
49
55
  var footerToolbar = $('<div>'+
56
+ '<span id="red-ui-sidebar-debug-info" style="float: left;"></span>' +
50
57
  '<span class="button-group"><a id="red-ui-sidebar-debug-open" class="red-ui-footer-button" href="#"><i class="fa fa-desktop"></i></a></span> ' +
51
58
  '</div>');
52
59
 
@@ -185,6 +192,39 @@ RED.debug = (function() {
185
192
  });
186
193
  RED.popover.tooltip(toolbar.find('#red-ui-sidebar-debug-filter'),RED._('node-red:debug.sidebar.filterLog'));
187
194
 
195
+ toolbar.find("#red-ui-sidebar-debug-pause").on("click", function(e) {
196
+ e.preventDefault();
197
+ toggleDebugflow();
198
+ });
199
+ var pauseTooltip = RED.popover.tooltip(toolbar.find("#red-ui-sidebar-debug-pause"),RED._('node-red:debug.sidebar.pause'),"core:pause-debug-messages");
200
+ function toggleDebugflow() {
201
+ debugPaused = !debugPaused;
202
+ if (debugPaused) {
203
+ debugPausedMessageCount = 0
204
+ debugPausedMessage = $('<div class="red-ui-debug-msg red-ui-debug-msg-paused">')
205
+ $(`<div class="red-ui-debug-msg-meta">
206
+ <span class="red-ui-debug-msg-date">${getTimestamp()}</span>
207
+ <span class="red-ui-debug-msg-name">${RED._('node-red:debug.sidebar.paused')}</span>
208
+ </div>`).appendTo(debugPausedMessage);
209
+ $('<div class="red-ui-debug-msg-meta"><span class="red-ui-debug-msg-name">&nbsp;</span></div>').appendTo(debugPausedMessage);
210
+ const atBottom = (sbc.scrollHeight-messageList.height()-sbc.scrollTop) < 5;
211
+ messageList.append(debugPausedMessage);
212
+ if (atBottom) {
213
+ messageList.scrollTop(sbc.scrollHeight);
214
+ }
215
+
216
+ $("#red-ui-sidebar-debug-pause i").removeClass("fa-pause").addClass("fa-play");
217
+ pauseTooltip.setAction("core:resume-debug-messages");
218
+ pauseTooltip.setContent(RED._('node-red:debug.sidebar.resume'))
219
+ $("#red-ui-sidebar-debug-info").text(RED._('node-red:debug.sidebar.paused'));
220
+ } else {
221
+ $("#red-ui-sidebar-debug-pause i").removeClass("fa-play").addClass("fa-pause");
222
+ pauseTooltip.setAction("core:pause-debug-messages");
223
+ pauseTooltip.setContent(RED._('node-red:debug.sidebar.pause'))
224
+ $("#red-ui-sidebar-debug-info").text("");
225
+ }
226
+ }
227
+
188
228
  toolbar.find("#red-ui-sidebar-debug-clear").on("click", function(e) {
189
229
  e.preventDefault();
190
230
  var action = RED.settings.get("debug.clearType","all")
@@ -389,15 +429,20 @@ RED.debug = (function() {
389
429
  var stack = [];
390
430
  var busy = false;
391
431
  function handleDebugMessage(o) {
392
- if (o) { stack.push(o); }
393
- if (!busy && (stack.length > 0)) {
394
- busy = true;
395
- processDebugMessage(stack.shift());
396
- setTimeout(function() {
397
- busy = false;
398
- handleDebugMessage();
399
- }, 15); // every 15mS = 66 times a second
400
- if (stack.length > numMessages) { stack = stack.splice(-numMessages); }
432
+ if (!debugPaused) {
433
+ if (o) { stack.push(o); }
434
+ if (!busy && (stack.length > 0)) {
435
+ busy = true;
436
+ processDebugMessage(stack.shift());
437
+ setTimeout(function() {
438
+ busy = false;
439
+ handleDebugMessage();
440
+ }, 15); // every 15mS = 66 times a second
441
+ if (stack.length > numMessages) { stack = stack.splice(-numMessages); }
442
+ }
443
+ } else {
444
+ debugPausedMessageCount++
445
+ debugPausedMessage.find('.red-ui-debug-msg-name').last().text(RED._("node-red:debug.sidebar.messagesDropped",{count:debugPausedMessageCount}));
401
446
  }
402
447
  }
403
448
 
@@ -598,6 +643,9 @@ RED.debug = (function() {
598
643
  } else {
599
644
  $(".red-ui-debug-msg:not(.hide)").remove();
600
645
  }
646
+ if (debugPaused) {
647
+ messageList.append(debugPausedMessage);
648
+ }
601
649
  config.clear();
602
650
  if (!!clearFilter) {
603
651
  clearFilterSettings();
@@ -63,6 +63,7 @@
63
63
  <label></label>
64
64
  <select id="node-input-rate-type" style="width:270px !important">
65
65
  <option value="all" data-i18n="delay.limitall"></option>
66
+ <option value="burst" data-i18n="delay.limitburst"></option>
66
67
  <option value="topic" data-i18n="delay.limittopic"></option>
67
68
  </select>
68
69
  </div>
@@ -185,6 +186,8 @@
185
186
  return this._("delay.label.limit")+" "+rate;
186
187
  } else if (this.pauseType == "timed") {
187
188
  return this._("delay.label.limitTopic")+" "+rate;
189
+ } else if (this.pauseType == "burst") {
190
+ return this._("delay.label.burst")+" "+rate;
188
191
  } else {
189
192
  return this._("delay.label.limitTopic")+" "+rate;
190
193
  }
@@ -245,6 +248,9 @@
245
248
  $("#node-input-delay-action").val('rate');
246
249
  $("#node-input-rate-type").val('topic');
247
250
  $("#node-input-rate-topic-type").val('timed');
251
+ } else if (this.pauseType == "burst") {
252
+ $("#node-input-delay-action").val('rate');
253
+ $("#node-input-rate-type").val('burst');
248
254
  }
249
255
 
250
256
  if (!this.timeoutUnits) {
@@ -294,12 +300,17 @@
294
300
  if (this.value === "all") {
295
301
  $("#rate-details-per-topic").hide();
296
302
  $("#node-input-drop-select-queue").attr('disabled', false);
303
+ $("#rate-override").show();
304
+ } else if (this.value === "burst") {
305
+ $("#rate-details-per-topic").hide();
306
+ $("#node-input-drop-select-queue").attr('disabled', true);
307
+ $("#rate-override").hide();
297
308
  } else if (this.value === "topic") {
298
309
  if ($("#node-input-drop-select").val() === "queue") {
299
- $("#node-input-drop-select").val("drop");
300
310
  }
301
311
  $("#node-input-drop-select-queue").attr('disabled', true);
302
312
  $("#rate-details-per-topic").show();
313
+ $("#rate-override").show();
303
314
  }
304
315
  }).trigger("change");
305
316
  },
@@ -312,6 +323,8 @@
312
323
  action = $("#node-input-rate-type").val();
313
324
  if (action === "all") {
314
325
  this.pauseType = "rate";
326
+ } else if (action === "burst") {
327
+ this.pauseType = "burst";
315
328
  } else {
316
329
  this.pauseType = $("#node-input-rate-topic-type").val();
317
330
  }
@@ -42,6 +42,7 @@ module.exports = function(RED) {
42
42
  this.timeoutUnits = n.timeoutUnits;
43
43
  this.randomUnits = n.randomUnits;
44
44
  this.rateUnits = n.rateUnits;
45
+ this.burst = n.rate;
45
46
 
46
47
  if (n.timeoutUnits === "milliseconds") {
47
48
  this.timeout = n.timeout;
@@ -57,12 +58,16 @@ module.exports = function(RED) {
57
58
 
58
59
  if (n.rateUnits === "minute") {
59
60
  this.rate = (60 * 1000)/n.rate;
61
+ this.timer = n.nbRateUnits * (60 * 1000);
60
62
  } else if (n.rateUnits === "hour") {
61
63
  this.rate = (60 * 60 * 1000)/n.rate;
64
+ this.timer = n.nbRateUnits * (60 * 60 * 1000);
62
65
  } else if (n.rateUnits === "day") {
63
66
  this.rate = (24 * 60 * 60 * 1000)/n.rate;
67
+ this.timer = n.nbRateUnits * (24 * 60 * 60 * 1000);
64
68
  } else { // Default to seconds
65
69
  this.rate = 1000/n.rate;
70
+ this.timer = n.nbRateUnits * 1000;
66
71
  }
67
72
 
68
73
  this.rate *= (n.nbRateUnits > 0 ? n.nbRateUnits : 1);
@@ -192,7 +197,8 @@ module.exports = function(RED) {
192
197
  }
193
198
  done();
194
199
  }, delayvar, () => done());
195
- node.idList.push(id);
200
+ if (Object.keys(msg).length === 2 && msg.hasOwnProperty("flush")) { id.clear(); }
201
+ else { node.idList.push(id); }
196
202
  if (msg.hasOwnProperty("reset")) { clearDelayList(true); }
197
203
  if (msg.hasOwnProperty("flush")) { flushDelayList(msg.flush); done(); }
198
204
  if (delayvar >= 0) {
@@ -336,6 +342,42 @@ module.exports = function(RED) {
336
342
  });
337
343
  }
338
344
 
345
+ // Handle the burst mode
346
+ else if (node.pauseType === "burst") {
347
+ node.timers = [];
348
+ node.inflight = 0;
349
+ node.status({ fill: "green", shape: "ring", text: "" })
350
+ node.on("input", function(msg, send, done) {
351
+ if (msg.hasOwnProperty("reset")) {
352
+ node.timers.forEach((t) => clearTimeout(t));
353
+ node.timers = [];
354
+ node.inflight = 0;
355
+ node.status({ fill: "green", shape: "ring", text: "" });
356
+ done();
357
+ return;
358
+ }
359
+ if (node.inflight < node.burst) {
360
+ node.inflight += 1;
361
+ send(msg);
362
+ node.timers[node.inflight-1] = setTimeout(() => {
363
+ if (node.inflight == node.burst) {
364
+ node.status({ fill: "green", shape: "ring", text: "" });
365
+ }
366
+ node.inflight -= 1;
367
+ }, node.timer);
368
+ }
369
+ else {
370
+ if (node.outputs === 2) { send([null,msg]); }
371
+ node.status({ fill: "red", shape: "dot", text: "" });
372
+ }
373
+ done();
374
+ });
375
+ node.on("close", function() {
376
+ node.timers.forEach((t) => clearTimeout(t));
377
+ node.status({});
378
+ });
379
+ }
380
+
339
381
  // The topic based fair queue and last arrived on all topics queue
340
382
  else if ((node.pauseType === "queue") || (node.pauseType === "timed")) {
341
383
  node.intervalID = setInterval(function() {
@@ -15,17 +15,18 @@
15
15
  -->
16
16
 
17
17
  <script type="text/html" data-template-name="tls-config">
18
- <div class="form-row" class="hide" id="node-config-row-uselocalfiles">
19
- <input type="checkbox" id="node-config-input-uselocalfiles" style="display: inline-block; width: auto; vertical-align: top;">
20
- <label for="node-config-input-uselocalfiles" style="width: 70%;"><span data-i18n="tls.label.use-local-files"></label>
21
- </div>
22
18
  <div class="form-row">
23
19
  <label style="width: 120px;" for="node-config-input-certType"><i class="fa fa-bars"></i> <span data-i18n="tls.label.certtype"></label>
24
20
  <select id="node-config-input-certType">
25
21
  <option value="files" data-i18n="tls.label.files"></option>
26
22
  <option value="pfx" data-i18n="tls.label.pfx"></option>
23
+ <option value="env" data-i18n="tls.label.env"></option>
27
24
  </select>
28
25
  </div>
26
+ <div class="form-row" class="hide" id="node-config-row-uselocalfiles">
27
+ <input type="checkbox" id="node-config-input-uselocalfiles" style="display: inline-block; width: auto; vertical-align: top;">
28
+ <label for="node-config-input-uselocalfiles" style="width: 70%;"><span data-i18n="tls.label.use-local-files"></label>
29
+ </div>
29
30
  <div class="form-row" id="node-tls-conf-cer">
30
31
  <label style="width: 120px;"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.cert"></span></label>
31
32
  <span class="tls-config-input-data">
@@ -38,6 +39,10 @@
38
39
  <input type="hidden" id="node-config-input-certdata">
39
40
  <input class="hide tls-config-input-path" style="width: calc(100% - 170px);" type="text" id="node-config-input-cert" data-i18n="[placeholder]tls.placeholder.cert">
40
41
  </div>
42
+ <div class="form-row" id="node-tls-conf-cer-env">
43
+ <label style="width: 120px;"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.cert"></span></label>
44
+ <input type="text" id="node-config-input-certEnv">
45
+ </div>
41
46
  <div class="form-row" id="node-tls-conf-key">
42
47
  <label style="width: 120px;" for="node-config-input-key"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.key"></span></label>
43
48
  <span class="tls-config-input-data">
@@ -50,6 +55,10 @@
50
55
  <input type="hidden" id="node-config-input-keydata">
51
56
  <input class="hide tls-config-input-path" style="width: calc(100% - 170px);" type="text" id="node-config-input-key" data-i18n="[placeholder]tls.placeholder.key">
52
57
  </div>
58
+ <div class="form-row" id="node-tls-conf-key-env">
59
+ <label style="width: 120px;"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.key"></span></label>
60
+ <input type="text" id="node-config-input-keyEnv">
61
+ </div>
53
62
  <div class="form-row" id="node-tls-conf-p12">
54
63
  <label style="width: 120px;" for="node-config-input-p12"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.p12"></span></label>
55
64
  <span class="tls-config-input-data">
@@ -78,6 +87,10 @@
78
87
  <input type="hidden" id="node-config-input-cadata">
79
88
  <input class="hide tls-config-input-path" style="width: calc(100% - 170px);" type="text" id="node-config-input-ca" data-i18n="[placeholder]tls.placeholder.ca">
80
89
  </div>
90
+ <div class="form-row" id="node-tls-conf-ca-env">
91
+ <label style="width: 120px;"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.ca"></span></label>
92
+ <input type="text" id="node-config-input-caEnv">
93
+ </div>
81
94
  <div class="form-row">
82
95
  <input type="checkbox" id="node-config-input-verifyservercert" style="display: inline-block; width: auto; vertical-align: top;">
83
96
  <label for="node-config-input-verifyservercert" style="width: calc(100% - 170px);" data-i18n="tls.label.verify-server-cert"></label>
@@ -131,7 +144,10 @@
131
144
  caname: {value:""},
132
145
  servername: {value:""},
133
146
  verifyservercert: {value: true},
134
- alpnprotocol: {value: ""}
147
+ alpnprotocol: {value: ""},
148
+ certEnv: {value: ""},
149
+ keyEnv: {value: ""},
150
+ caEnv: {value: ""}
135
151
  },
136
152
  credentials: {
137
153
  certdata: {type:"text"},
@@ -152,15 +168,45 @@
152
168
  $("#node-tls-conf-cer").hide();
153
169
  $("#node-tls-conf-key").hide();
154
170
  $("#node-tls-conf-ca").hide();
171
+ $("#node-tls-conf-cer-env").hide();
172
+ $("#node-tls-conf-key-env").hide();
173
+ $("#node-tls-conf-ca-env").hide();
155
174
  $("#node-tls-conf-p12").show();
175
+ $("#node-config-row-uselocalfiles").show();
156
176
  }
157
- else {
177
+ else if ($("#node-config-input-certType").val() === "env") {
178
+ $("#node-tls-conf-cer").hide();
179
+ $("#node-tls-conf-key").hide();
180
+ $("#node-tls-conf-ca").hide();
181
+ $("#node-tls-conf-cer-env").show();
182
+ $("#node-tls-conf-key-env").show();
183
+ $("#node-tls-conf-ca-env").show();
184
+ $("#node-tls-conf-p12").hide();
185
+ $("#node-config-row-uselocalfiles").hide();
186
+ } else {
158
187
  $("#node-tls-conf-cer").show();
159
188
  $("#node-tls-conf-key").show();
160
189
  $("#node-tls-conf-ca").show();
190
+ $("#node-tls-conf-cer-env").hide();
191
+ $("#node-tls-conf-key-env").hide();
192
+ $("#node-tls-conf-ca-env").hide();
161
193
  $("#node-tls-conf-p12").hide();
194
+ $("#node-config-row-uselocalfiles").show();
162
195
  }
163
196
  });
197
+
198
+ $("#node-config-input-certEnv").typedInput({
199
+ type:"env",
200
+ types:["env"]
201
+ });
202
+ $("#node-config-input-keyEnv").typedInput({
203
+ type:"env",
204
+ types:["env"]
205
+ });
206
+ $("#node-config-input-caEnv").typedInput({
207
+ type:"env",
208
+ types:["env"]
209
+ });
164
210
 
165
211
  function updateFileUpload() {
166
212
  if ($("#node-config-input-uselocalfiles").is(':checked')) {
@@ -218,15 +264,19 @@
218
264
  $("#tls-config-button-cert-clear").on("click", function() {
219
265
  clearNameData("cert");
220
266
  });
267
+ RED.popover.tooltip($("#tls-config-button-cert-clear"), RED._("common.label.delete"));
221
268
  $("#tls-config-button-key-clear").on("click", function() {
222
269
  clearNameData("key");
223
270
  });
271
+ RED.popover.tooltip($("#tls-config-button-key-clear"), RED._("common.label.delete"));
224
272
  $("#tls-config-button-ca-clear").on("click", function() {
225
273
  clearNameData("ca");
226
274
  });
275
+ RED.popover.tooltip($("#tls-config-button-ca-clear"), RED._("common.label.delete"));
227
276
  $("#tls-config-button-p12-clear").on("click", function() {
228
277
  clearNameData("p12");
229
278
  });
279
+ RED.popover.tooltip($("#tls-config-button-p12-clear"), RED._("common.label.delete"));
230
280
 
231
281
  if (RED.settings.tlsConfigDisableLocalFiles) {
232
282
  $("#node-config-row-uselocalfiles").hide();
@@ -23,10 +23,14 @@ module.exports = function(RED) {
23
23
  this.valid = true;
24
24
  this.verifyservercert = n.verifyservercert;
25
25
  var certPath, keyPath, caPath, p12Path;
26
+ var certEnv, keyEnv, caEnv;
26
27
  if (n.cert) { certPath = n.cert.trim(); }
27
28
  if (n.key) { keyPath = n.key.trim(); }
28
29
  if (n.ca) { caPath = n.ca.trim(); }
29
30
  if (n.p12) { p12Path = n.p12.trim(); }
31
+ if (n.certEnv) { certEnv = n.certEnv }
32
+ if (n.keyEnv) { keyEnv = n.keyEnv }
33
+ if (n.caEnv) { caEnv = n.caEnv }
30
34
  this.certType = n.certType || "files";
31
35
  this.servername = (n.servername||"").trim();
32
36
  this.alpnprotocol = (n.alpnprotocol||"").trim();
@@ -41,13 +45,23 @@ module.exports = function(RED) {
41
45
  return;
42
46
  }
43
47
  }
48
+ else if (this.certType === "env") {
49
+ if (certEnv) {
50
+ this.certEnv = Buffer.from(RED.util.evaluateNodeProperty(certEnv, 'env', this))
51
+ }
52
+ if (keyEnv) {
53
+ this.keyEnv = Buffer.from(RED.util.evaluateNodeProperty(keyEnv, 'env', this))
54
+ }
55
+ if (caEnv) {
56
+ this.caEnv = Buffer.from(RED.util.evaluateNodeProperty(caEnv, 'env', this))
57
+ }
58
+ }
44
59
  else if ((certPath && certPath.length > 0) || (keyPath && keyPath.length > 0) || (caPath && caPath.length > 0)) {
45
60
  if ( (certPath && certPath.length > 0) !== (keyPath && keyPath.length > 0)) {
46
61
  this.valid = false;
47
62
  this.error(RED._("tls.error.missing-file"));
48
63
  return;
49
64
  }
50
-
51
65
  try {
52
66
  if (certPath) {
53
67
  this.cert = fs.readFileSync(certPath);
@@ -122,6 +136,17 @@ module.exports = function(RED) {
122
136
  opts.ca = this.ca;
123
137
  }
124
138
  }
139
+ else if (this.certType === "env") {
140
+ if (this.keyEnv) {
141
+ opts.key = this.keyEnv
142
+ }
143
+ if (this.certEnv) {
144
+ opts.cert= this.certEnv
145
+ }
146
+ if (this.caEnv) {
147
+ opts.ca = this.caEnv
148
+ }
149
+ }
125
150
  else {
126
151
  if (this.pfx) {
127
152
  opts.pfx = this.pfx;
@@ -143,15 +143,6 @@ in your Node-RED user directory (${RED.settings.userDir}).
143
143
  });
144
144
  }
145
145
  }
146
- /**
147
- * @param {Object} headersObject
148
- * @param {string} name
149
- * @return {any} value
150
- */
151
- const getHeaderValue = (headersObject, name) => {
152
- const asLowercase = name.toLowercase();
153
- return headersObject[Object.keys(headersObject).find(k => k.toLowerCase() === asLowercase)];
154
- }
155
146
  this.count = 0;
156
147
  this.on("input",function(msg,nodeSend,nodeDone) {
157
148
  node.count++;
@@ -256,34 +247,42 @@ in your Node-RED user directory (${RED.settings.userDir}).
256
247
  opts.hooks = {
257
248
  beforeRequest: [
258
249
  options => {
259
- // Whilst HTTP headers are meant to be case-insensitive,
260
- // in the real world, there are servers that aren't so compliant.
261
- // GOT will lower case all headers given a chance, so we need
262
- // to restore the case of any headers the user has set.
263
- Object.keys(options.headers).forEach(h => {
264
- if (originalHeaderMap[h] && originalHeaderMap[h] !== h) {
265
- options.headers[originalHeaderMap[h]] = options.headers[h];
266
- delete options.headers[h];
250
+ try {
251
+ // Whilst HTTP headers are meant to be case-insensitive,
252
+ // in the real world, there are servers that aren't so compliant.
253
+ // GOT will lower case all headers given a chance, so we need
254
+ // to restore the case of any headers the user has set.
255
+ Object.keys(options.headers).forEach(h => {
256
+ if (originalHeaderMap[h] && originalHeaderMap[h] !== h) {
257
+ options.headers[originalHeaderMap[h]] = options.headers[h];
258
+ delete options.headers[h];
259
+ }
260
+ })
261
+ if (node.insecureHTTPParser) {
262
+ // Setting the property under _unixOptions as pretty
263
+ // much the only hack available to get got to apply
264
+ // a core http option it doesn't think we should be
265
+ // allowed to set
266
+ options._unixOptions = { ...options.unixOptions, insecureHTTPParser: true }
267
267
  }
268
- })
269
- if (node.insecureHTTPParser) {
270
- // Setting the property under _unixOptions as pretty
271
- // much the only hack available to get got to apply
272
- // a core http option it doesn't think we should be
273
- // allowed to set
274
- options._unixOptions = { ...options.unixOptions, insecureHTTPParser: true }
268
+ } catch (err) {
269
+ node.warn("Error in beforeRequest hook: " + err.message);
275
270
  }
276
271
  }
277
272
  ],
278
273
  beforeRedirect: [
279
274
  (options, response) => {
280
- let redirectInfo = {
281
- location: response.headers.location
282
- }
283
- if (response.headers.hasOwnProperty('set-cookie')) {
284
- redirectInfo.cookies = extractCookies(response.headers['set-cookie']);
275
+ try {
276
+ let redirectInfo = {
277
+ location: response.headers.location
278
+ }
279
+ if (response.headers.hasOwnProperty('set-cookie')) {
280
+ redirectInfo.cookies = extractCookies(response.headers['set-cookie']);
281
+ }
282
+ redirectList.push(redirectInfo)
283
+ } catch (err) {
284
+ node.warn("Error processing redirect: " + err.message);
285
285
  }
286
- redirectList.push(redirectInfo)
287
286
  }
288
287
  ]
289
288
  }
@@ -422,25 +421,30 @@ in your Node-RED user directory (${RED.settings.userDir}).
422
421
  let digestCreds = this.credentials;
423
422
  let sentCreds = false;
424
423
  opts.hooks.afterResponse = [(response, retry) => {
425
- if (response.statusCode === 401) {
426
- if (sentCreds) {
427
- return response
428
- }
429
- const requestUrl = new URL(response.request.requestUrl);
430
- const options = { headers: {} }
431
- const normalisedHeaders = {};
432
- Object.keys(response.headers).forEach(k => {
433
- normalisedHeaders[k.toLowerCase()] = response.headers[k]
434
- })
435
- if (normalisedHeaders['www-authenticate']) {
436
- let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, response.request.options.method, requestUrl.pathname + requestUrl.search, normalisedHeaders['www-authenticate'])
437
- options.headers.Authorization = authHeader;
424
+ try {
425
+ if (response.statusCode === 401) {
426
+ if (sentCreds) {
427
+ return response
428
+ }
429
+ const requestUrl = new URL(response.request.requestUrl);
430
+ const options = { headers: {} }
431
+ const normalisedHeaders = {};
432
+ Object.keys(response.headers).forEach(k => {
433
+ normalisedHeaders[k.toLowerCase()] = response.headers[k]
434
+ })
435
+ if (normalisedHeaders['www-authenticate']) {
436
+ let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, response.request.options.method, requestUrl.pathname + requestUrl.search, normalisedHeaders['www-authenticate'])
437
+ options.headers.Authorization = authHeader;
438
+ }
439
+ // response.request.options.merge(options)
440
+ sentCreds = true;
441
+ return retry(options);
438
442
  }
439
- // response.request.options.merge(options)
440
- sentCreds = true;
441
- return retry(options);
443
+ return response
444
+ } catch (err) {
445
+ node.warn("Digest authentication failed: " + err.message);
446
+ return response;
442
447
  }
443
- return response
444
448
  }];
445
449
  } else if (this.authType === "bearer") {
446
450
  opts.headers.Authorization = `Bearer ${this.credentials.password||""}`
@@ -688,6 +692,7 @@ in your Node-RED user directory (${RED.settings.userDir}).
688
692
  if (!sendErrorsToCatch) {
689
693
  nodeSend(msg);
690
694
  }
695
+ node.count--;
691
696
  nodeDone();
692
697
  });
693
698
  });
@@ -720,13 +725,29 @@ in your Node-RED user directory (${RED.settings.userDir}).
720
725
 
721
726
  function extractCookies(setCookie) {
722
727
  var cookies = {};
728
+ if (!Array.isArray(setCookie)) {
729
+ return cookies;
730
+ }
723
731
  setCookie.forEach(function(c) {
724
- var parsedCookie = cookie.parse(c);
725
- var eq_idx = c.indexOf('=');
726
- var key = c.substr(0, eq_idx).trim()
727
- parsedCookie.value = parsedCookie[key];
728
- delete parsedCookie[key];
729
- cookies[key] = parsedCookie;
732
+ try {
733
+ if (typeof c !== 'string') {
734
+ return;
735
+ }
736
+ var parsedCookie = cookie.parse(c);
737
+ var eq_idx = c.indexOf('=');
738
+ if (eq_idx === -1) {
739
+ return;
740
+ }
741
+ var key = c.substr(0, eq_idx).trim()
742
+ if (!key) {
743
+ return;
744
+ }
745
+ parsedCookie.value = parsedCookie[key];
746
+ delete parsedCookie[key];
747
+ cookies[key] = parsedCookie;
748
+ } catch (err) {
749
+ // Skip malformed cookies
750
+ }
730
751
  });
731
752
  return cookies;
732
753
  }
@@ -778,6 +799,9 @@ in your Node-RED user directory (${RED.settings.userDir}).
778
799
  }
779
800
 
780
801
  function buildDigestHeader(user, pass, method, path, authHeader) {
802
+ if (!authHeader || typeof authHeader !== 'string') {
803
+ throw new Error("Invalid or missing WWW-Authenticate header");
804
+ }
781
805
  var challenge = {}
782
806
  var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
783
807
  for (;;) {
@@ -787,6 +811,12 @@ in your Node-RED user directory (${RED.settings.userDir}).
787
811
  }
788
812
  challenge[match[1]] = match[2] || match[3]
789
813
  }
814
+ if (!challenge.nonce) {
815
+ throw new Error("Invalid digest challenge: missing nonce");
816
+ }
817
+ if (!challenge.realm) {
818
+ throw new Error("Invalid digest challenge: missing realm");
819
+ }
790
820
  var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth'
791
821
  var nc = qop && '00000001'
792
822
  var cnonce = qop && uuid().replace(/-/g, '')
@@ -2,7 +2,7 @@
2
2
  <script type="text/html" data-template-name="file">
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
- <input id="node-input-filename" type="text">
5
+ <input id="node-input-filename" type="text" style="width: 100% !important;">
6
6
  <input type="hidden" id="node-input-filenameType">
7
7
  </div>
8
8
  <div class="form-row">
@@ -38,7 +38,7 @@
38
38
  <script type="text/html" data-template-name="file in">
39
39
  <div class="form-row">
40
40
  <label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
41
- <input id="node-input-filename" type="text">
41
+ <input id="node-input-filename" type="text" style="width: 100% !important;">
42
42
  <input type="hidden" id="node-input-filenameType">
43
43
  </div>
44
44
  <div class="form-row">
@@ -149,7 +149,10 @@
149
149
  "openWindow": "In neuem Fenster öffnen",
150
150
  "copyPath": "Pfad kopieren",
151
151
  "copyPayload": "Wert kopieren",
152
- "pinPath": "Angezeigt lassen (pinnen)"
152
+ "pinPath": "Angezeigt lassen (pinnen)",
153
+ "pause": "pause",
154
+ "paused": "Pausiert",
155
+ "resume": "weiter"
153
156
  },
154
157
  "messageMenu": {
155
158
  "collapseAll": "Alle Pfade ausblenden",
@@ -16,6 +16,7 @@
16
16
 
17
17
  <script type="text/html" data-help-name="delay">
18
18
  <p>Delays each message passing through the node or limits the rate at which they can pass.</p>
19
+ <p>Not all input parameters apply to all modes.</p>
19
20
  <h3>Inputs</h3>
20
21
  <dl class="message-properties">
21
22
  <dt class="optional">delay <span class="property-type">number</span></dt>
@@ -47,9 +48,11 @@
47
48
  Each message is delayed independently of any other message, based on
48
49
  the time of its arrival.
49
50
  </p>
50
- <p>When configured to rate limit messages, their delivery is spread across
51
- the configured time period. The status shows the number of messages currently in the queue.
52
- It can optionally discard intermediate messages as they arrive.</p>
51
+ <p>When configured to rate limit messages, the node can operate in several modes.
52
+ By default the messages are delivered evenly spread across
53
+ the configured time period. e.g. 3 msgs in 12 seconds means 1 message every 4 seconds.
54
+ The status shows the number of messages currently in the queue.
55
+ It can optionally discard intermediate messages as they arrive.
53
56
  </p>
54
57
  <p>If set to allow override of the rate, the new rate will be applied immediately,
55
58
  and will remain in effect until changed again, the node is reset, or the flow is restarted.</p>
@@ -59,6 +62,12 @@
59
62
  the most recent message for all topics, or release the most recent message
60
63
  for the next topic.
61
64
  </p>
65
+ <p>In burst mode, messages are passed through up to the configured limit immediately,
66
+ any further messages will be dropped or sent to the second output until the time
67
+ period has elapsed. At that point the next burst of messages can be sent.
68
+ The status shows green when messages can be sent immediately, and red when
69
+ messages are blocked.
70
+ </p>
62
71
  <p><b>Note</b>: In rate limit mode the maximum queue depth can be set by a property in your
63
72
  <i>settings.js</i> file. For example <code>nodeMessageBufferMaxLength: 1000,</code></p>
64
73
  </script>
@@ -166,7 +166,12 @@
166
166
  "selectAll": "select all",
167
167
  "selectNone": "select none",
168
168
  "all": "all",
169
- "filtered": "filtered"
169
+ "filtered": "filtered",
170
+ "pause": "pause",
171
+ "paused": "Paused",
172
+ "resume": "resume",
173
+ "messagesDropped": "__count__ message dropped",
174
+ "messagesDropped_plural": "__count__ messages dropped"
170
175
  },
171
176
  "messageMenu": {
172
177
  "collapseAll": "Collapse all paths",
@@ -208,7 +213,8 @@
208
213
  "certtype": "Cert Type",
209
214
  "files": "Individual files",
210
215
  "p12": "pfx or p12",
211
- "pfx": "pfx or p12 file"
216
+ "pfx": "pfx or p12 file",
217
+ "env": "Environment Variable"
212
218
  },
213
219
  "placeholder": {
214
220
  "cert": "path to certificate (PEM format)",
@@ -308,7 +314,8 @@
308
314
  "delayvarmsg": "Override delay with msg.delay",
309
315
  "randomdelay": "Random delay",
310
316
  "limitrate": "Rate Limit",
311
- "limitall": "All messages",
317
+ "limitall": "All messages - even spacing",
318
+ "limitburst": "All messages - burst mode",
312
319
  "limittopic": "For each msg.topic",
313
320
  "fairqueue": "Send each topic in turn",
314
321
  "timedqueue": "Send all topics",
@@ -332,6 +339,7 @@
332
339
  "label": {
333
340
  "delay": "delay",
334
341
  "variable": "variable",
342
+ "burst": "burst",
335
343
  "limit": "limit",
336
344
  "limitTopic": "limit topic",
337
345
  "random": "random",
@@ -166,7 +166,10 @@
166
166
  "selectAll": "seleccionar todo",
167
167
  "selectNone": "seleccionar ninguno",
168
168
  "all": "todo",
169
- "filtered": "filtrado"
169
+ "filtered": "filtrado",
170
+ "pause": "pausa",
171
+ "paused": "Pausado",
172
+ "resume": "reanudar"
170
173
  },
171
174
  "messageMenu": {
172
175
  "collapseAll": "Colapsar todas las rutas",
@@ -166,7 +166,10 @@
166
166
  "selectAll": "Tout sélectionner",
167
167
  "selectNone": "Ne rien sélectionner",
168
168
  "all": "Tout",
169
- "filtered": "Filtrés"
169
+ "filtered": "Filtrés",
170
+ "pause": "pause",
171
+ "paused": "Interrompue",
172
+ "resume": "continuer"
170
173
  },
171
174
  "messageMenu": {
172
175
  "collapseAll": "Réduire tous les chemins",
@@ -166,7 +166,10 @@
166
166
  "selectAll": "全てを選択",
167
167
  "selectNone": "選択を外す",
168
168
  "all": "全て",
169
- "filtered": "選択したメッセージ"
169
+ "filtered": "選択したメッセージ",
170
+ "pause": "一時停止",
171
+ "paused": "一時停止",
172
+ "resume": "再開"
170
173
  },
171
174
  "messageMenu": {
172
175
  "collapseAll": "全パスを折りたたむ",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-red/nodes",
3
- "version": "5.0.0-beta.1",
3
+ "version": "5.0.0-beta.2",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,7 +18,7 @@
18
18
  "acorn": "8.15.0",
19
19
  "acorn-walk": "8.3.4",
20
20
  "ajv": "8.17.1",
21
- "body-parser": "1.20.3",
21
+ "body-parser": "1.20.4",
22
22
  "cheerio": "1.0.0-rc.10",
23
23
  "content-type": "1.0.5",
24
24
  "cookie-parser": "1.4.7",