@node-red/nodes 4.1.0-beta.1 → 4.1.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.
@@ -536,10 +536,22 @@ RED.debug = (function() {
536
536
  e.stopPropagation();
537
537
  showMessageMenu(filterMessage,debugMessage,sourceNode&&sourceNode.id);
538
538
  });
539
- $('<span class="red-ui-debug-msg-topic">'+
540
- (o.topic?topic+' : ':'')+
541
- (o.property?'msg.'+property:'msg')+" : "+format+
542
- '</span>').appendTo(metaRow);
539
+ const msgInfos = `${o.topic ? " : " : ""}msg${o.property ? "." + property : ""} : ${format}`
540
+ const topicTruncated = RED.utils.truncateString((o.topic ? topic : ""), 120 - msgInfos.length);
541
+ const topicElem = $('<span class="red-ui-debug-msg-topic">' + topicTruncated + msgInfos + '</span>').appendTo(metaRow);
542
+ if (topic !== topicTruncated) {
543
+ RED.popover.create({
544
+ content: topic,
545
+ delay: { show: 750, hide: 50 },
546
+ direction: "bottom",
547
+ interactive: true,
548
+ maxWidth: 300,
549
+ target: topicElem,
550
+ trigger: "hover",
551
+ tooltip: true,
552
+ size: "small"
553
+ });
554
+ }
543
555
  }
544
556
 
545
557
  var atBottom = (sbc.scrollHeight-messageList.height()-sbc.scrollTop) < 5;
@@ -477,6 +477,16 @@
477
477
  }
478
478
  return RED._("node-red:mqtt.errors.invalid-topic");
479
479
  }
480
+ function setupTopicField(node, input) {
481
+ input.autoComplete({
482
+ minLength: 0,
483
+ completionPluginType: 'node-red-mqtt-topic-autocomplete-source',
484
+ context: {
485
+ node
486
+ }
487
+ })
488
+ }
489
+
480
490
  RED.nodes.registerType('mqtt-broker',{
481
491
  category: 'config',
482
492
  defaults: {
@@ -852,6 +862,7 @@
852
862
  },
853
863
  oneditprepare: function() {
854
864
  const node = this;
865
+ setupTopicField(this, $("#node-input-topic"));
855
866
  const isV5Broker = function() {
856
867
  var confNode = RED.nodes.node($("#node-input-broker").val());
857
868
  return confNode && confNode.protocolVersion === "5";
@@ -930,6 +941,7 @@
930
941
  },
931
942
  oneditprepare: function() {
932
943
  var that = this;
944
+ setupTopicField(this, $("#node-input-topic"));
933
945
 
934
946
  function showHideDynamicFields() {
935
947
  var confNode = RED.nodes.node($("#node-input-broker").val());
@@ -25,11 +25,22 @@
25
25
  <option value="patch">PATCH</option>
26
26
  </select>
27
27
  </div>
28
- <div class="form-row form-row-http-in-upload hide">
28
+
29
+ <div id="form-reqBody-http-in-controller" class="form-row hide" style="display: flex;">
29
30
  <label>&nbsp;</label>
30
- <input type="checkbox" id="node-input-upload" style="display: inline-block; width: auto; vertical-align: top;">
31
- <label for="node-input-upload" style="width: 70%;" data-i18n="httpin.label.upload"></label>
31
+ <div style="display: flex; margin-left: 5px; flex-direction: column-reverse; gap: 10px; justify-content: center;">
32
+ <div id="form-row-http-in-upload" class="hide" style="display: flex; gap: 10px">
33
+ <input type="checkbox" id="node-input-upload" style="margin: 0px;">
34
+ <label for="node-input-upload" style="text-wrap: nowrap; margin-bottom: 0;" data-i18n="httpin.label.upload"></label>
35
+ </div>
36
+ <div id="form-row-http-in-parsing" class="hide" style="display: flex; gap: 10px">
37
+ <input type="checkbox" id="node-input-skipBodyParsing" style="margin: 0px;">
38
+ <label for="node-input-skipBodyParsing" style="text-wrap: nowrap; margin-bottom: 0;" data-i18n="httpin.label.parsing"></label>
39
+ </div>
40
+ </div>
32
41
  </div>
42
+
43
+
33
44
  <div class="form-row">
34
45
  <label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
35
46
  <input id="node-input-url" type="text" placeholder="/url">
@@ -74,6 +85,7 @@
74
85
  label:RED._("node-red:httpin.label.url")},
75
86
  method: {value:"get",required:true},
76
87
  upload: {value:false},
88
+ skipBodyParsing: {value:false},
77
89
  swaggerDoc: {type:"swagger-doc", required:false}
78
90
  },
79
91
  inputs:0,
@@ -115,16 +127,22 @@
115
127
  $('.row-swagger-doc').hide();
116
128
  }
117
129
  $("#node-input-method").on("change", function() {
118
- if ($(this).val() === "post") {
119
- $(".form-row-http-in-upload").show();
130
+ var method = $(this).val();
131
+ if(["post", "put", "patch","delete"].includes(method)){
132
+ $("#form-reqBody-http-in-controller").show();
133
+ $("#form-row-http-in-parsing").show();
134
+ if (method === "post") {
135
+ $("#form-row-http-in-upload").show();
136
+ } else {
137
+ $("#form-row-http-in-upload").hide();
138
+ }
120
139
  } else {
121
- $(".form-row-http-in-upload").hide();
140
+ $("#form-row-http-in-parsing").hide();
141
+ $("#form-row-http-in-upload").hide();
142
+ $("#form-reqBody-http-in-controller").hide();
122
143
  }
123
144
  }).change();
124
-
125
-
126
145
  }
127
-
128
146
  });
129
147
  var headerTypes = [
130
148
  {value:"content-type",label:"Content-Type",hasValue: false},
@@ -16,6 +16,7 @@
16
16
 
17
17
  module.exports = function(RED) {
18
18
  "use strict";
19
+ var rootApp;
19
20
  var bodyParser = require("body-parser");
20
21
  var multer = require("multer");
21
22
  var cookieParser = require("cookie-parser");
@@ -26,6 +27,7 @@ module.exports = function(RED) {
26
27
  var mediaTyper = require('media-typer');
27
28
  var isUtf8 = require('is-utf8');
28
29
  var hashSum = require("hash-sum");
30
+ var rawDataRoutes = new Set();
29
31
 
30
32
  function rawBodyParser(req, res, next) {
31
33
  if (req.skipRawBodyParser) { next(); } // don't parse this if told to skip
@@ -71,7 +73,65 @@ module.exports = function(RED) {
71
73
  });
72
74
  }
73
75
 
74
- var corsSetup = false;
76
+ /**
77
+ * This method retrieves the root app by traversing the parent hierarchy.
78
+ * @param {import('express').Application} app
79
+ * @returns {import('express').Application}
80
+ */
81
+ function getRootApp(app) {
82
+ if (typeof app.parent === 'undefined') {
83
+ return app;
84
+ }
85
+ return getRootApp(app.parent);
86
+ }
87
+
88
+ /**
89
+ * It provide the unique route key
90
+ * @param {{method: string, url: string}} obj
91
+ * @returns
92
+ */
93
+ function getRouteKey(obj) {
94
+ var method = obj.method.toUpperCase();
95
+ // Normalize the URL by replacing double slashes with a single slash and removing the trailing slash
96
+ var url = obj.url.replace(/\/{2,}/g, '/').replace(/\/$/, '');
97
+ return `${method}:${url}`;
98
+ }
99
+
100
+ /**
101
+ * This middleware is for capture raw body
102
+ * @param {import('express').Request} req
103
+ * @param {import('express').Response} _res
104
+ * @param {import('express').NextFunction} next
105
+ * @returns
106
+ */
107
+ function rawBodyCapture(req, _res, next) {
108
+ var routeKey = getRouteKey({ method: req.method, url: req._parsedUrl.pathname });
109
+ // Check if routeKey exist in rawDataRoutes
110
+ if (rawDataRoutes.has(routeKey)) {
111
+ // Convert the request stream to buffer
112
+ getBody(req, {
113
+ length: req.headers['content-length'],
114
+ }, function (err, buf) {
115
+ if (err) {
116
+ return next(err);
117
+ }
118
+ req.body = buf;
119
+ // Skip the body parsing
120
+ req.skipRawBodyParser = true;
121
+ req._body = true;
122
+ next();
123
+ })
124
+ } else {
125
+ next();
126
+ }
127
+ }
128
+
129
+ if(typeof RED.httpNode === 'function' && (rootApp = getRootApp(RED.httpNode))) {
130
+ // Add middleware to the stack
131
+ rootApp.use(rawBodyCapture);
132
+ // Move the middleware to top of the stack
133
+ rootApp._router.stack.unshift(rootApp._router.stack.pop());
134
+ }
75
135
 
76
136
  function createRequestWrapper(node,req) {
77
137
  // This misses a bunch of properties (eg headers). Before we use this function
@@ -125,6 +185,7 @@ module.exports = function(RED) {
125
185
 
126
186
  return wrapper;
127
187
  }
188
+
128
189
  function createResponseWrapper(node,res) {
129
190
  var wrapper = {
130
191
  _res: res
@@ -188,9 +249,16 @@ module.exports = function(RED) {
188
249
  }
189
250
  this.method = n.method;
190
251
  this.upload = n.upload;
252
+ this.skipBodyParsing = n.skipBodyParsing;
191
253
  this.swaggerDoc = n.swaggerDoc;
192
254
 
193
255
  var node = this;
256
+ var routeKey = getRouteKey({method: this.method, url: RED.httpNode.path() + this.url});
257
+
258
+ // If the user enables raw body, add it to the raw data routes.
259
+ if(this.skipBodyParsing) {
260
+ rawDataRoutes.add(routeKey);
261
+ }
194
262
 
195
263
  this.errorHandler = function(err,req,res,next) {
196
264
  node.warn(err);
@@ -227,7 +295,9 @@ module.exports = function(RED) {
227
295
  }
228
296
 
229
297
  var maxApiRequestSize = RED.settings.apiMaxLength || '5mb';
298
+
230
299
  var jsonParser = bodyParser.json({limit:maxApiRequestSize});
300
+
231
301
  var urlencParser = bodyParser.urlencoded({limit:maxApiRequestSize,extended:true});
232
302
 
233
303
  var metricsHandler = function(req,res,next) { next(); }
@@ -254,25 +324,27 @@ module.exports = function(RED) {
254
324
  var mp = multer({ storage: multer.memoryStorage() }).any();
255
325
  multipartParser = function(req,res,next) {
256
326
  mp(req,res,function(err) {
257
- req._body = true;
258
327
  next(err);
259
328
  })
260
329
  };
261
330
  }
262
331
 
263
332
  if (this.method == "get") {
264
- RED.httpNode.get(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,this.callback,this.errorHandler);
333
+ RED.httpNode.get(this.url, cookieParser(), httpMiddleware, corsHandler, metricsHandler, this.callback, this.errorHandler);
265
334
  } else if (this.method == "post") {
266
- RED.httpNode.post(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,multipartParser,rawBodyParser,this.callback,this.errorHandler);
335
+ RED.httpNode.post(this.url,cookieParser(), httpMiddleware, corsHandler, metricsHandler, jsonParser, urlencParser, multipartParser, rawBodyParser, this.callback, this.errorHandler);
267
336
  } else if (this.method == "put") {
268
- RED.httpNode.put(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
337
+ RED.httpNode.put(this.url, cookieParser(), httpMiddleware, corsHandler, metricsHandler, jsonParser, urlencParser, rawBodyParser, this.callback, this.errorHandler);
269
338
  } else if (this.method == "patch") {
270
- RED.httpNode.patch(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
339
+ RED.httpNode.patch(this.url, cookieParser(), httpMiddleware, corsHandler, metricsHandler, jsonParser, urlencParser, rawBodyParser, this.callback, this.errorHandler);
271
340
  } else if (this.method == "delete") {
272
- RED.httpNode.delete(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
341
+ RED.httpNode.delete(this.url, cookieParser(), httpMiddleware, corsHandler, metricsHandler, jsonParser, urlencParser, rawBodyParser, this.callback, this.errorHandler);
273
342
  }
274
-
343
+
344
+
275
345
  this.on("close",function() {
346
+ // Remove it from the raw data routes if the node is closed
347
+ rawDataRoutes.delete(routeKey);
276
348
  var node = this;
277
349
  RED.httpNode._router.stack.forEach(function(route,i,routes) {
278
350
  if (route.route && route.route.path === node.url && route.route.methods[node.method]) {
@@ -284,9 +356,9 @@ module.exports = function(RED) {
284
356
  this.warn(RED._("httpin.errors.not-created"));
285
357
  }
286
358
  }
359
+
287
360
  RED.nodes.registerType("http in",HTTPIn);
288
361
 
289
-
290
362
  function HTTPOut(n) {
291
363
  RED.nodes.createNode(this,n);
292
364
  var node = this;
@@ -361,5 +433,6 @@ module.exports = function(RED) {
361
433
  done();
362
434
  });
363
435
  }
436
+
364
437
  RED.nodes.registerType("http response",HTTPOut);
365
438
  }
@@ -599,7 +599,18 @@ in your Node-RED user directory (${RED.settings.userDir}).
599
599
  }
600
600
  } else {
601
601
  if (msg.hasOwnProperty('rejectUnauthorized')) {
602
- opts.https = { rejectUnauthorized: msg.rejectUnauthorized };
602
+ if (typeof msg.rejectUnauthorized === 'boolean') {
603
+ opts.https = { rejectUnauthorized: msg.rejectUnauthorized }
604
+ } else if (typeof msg.rejectUnauthorized === 'string') {
605
+ if (msg.rejectUnauthorized.toLowerCase() === 'true' || msg.rejectUnauthorized.toLowerCase() === 'false') {
606
+ opts.https = { rejectUnauthorized: (msg.rejectUnauthorized.toLowerCase() === 'true') }
607
+ } else {
608
+ node.warn(RED._("httpin.errors.rejectunauthorized-invalid"))
609
+ }
610
+ } else {
611
+ node.warn(RED._("httpin.errors.rejectunauthorized-invalid"))
612
+ }
613
+
603
614
  }
604
615
  }
605
616
 
@@ -515,7 +515,8 @@
515
515
  "url": "URL",
516
516
  "doc": "Docs",
517
517
  "return": "Return",
518
- "upload": "Accept file uploads?",
518
+ "upload": "Accept file uploads",
519
+ "parsing": "Do not parse request body",
519
520
  "status": "Status code",
520
521
  "headers": "Headers",
521
522
  "other": "other",
@@ -563,7 +564,8 @@
563
564
  "timeout-isnan": "Timeout value is not a valid number, ignoring",
564
565
  "timeout-isnegative": "Timeout value is negative, ignoring",
565
566
  "invalid-payload": "Invalid payload",
566
- "invalid-url": "Invalid url"
567
+ "invalid-url": "Invalid url",
568
+ "rejectunauthorized-invalid": "msg.rejectUnauthorized should be a boolean"
567
569
  },
568
570
  "status": {
569
571
  "requesting": "requesting"
@@ -50,6 +50,7 @@
50
50
  <p>If the content type of the request can be determined, the body will be parsed to
51
51
  any appropriate type. For example, <code>application/json</code> will be parsed to
52
52
  its JavaScript object representation.</p>
53
+ <p>The node can be configured to not parse the body, in which case it will be provided as a Buffer object.</p>
53
54
  <p><b>Note:</b> this node does not send any response to the request. The flow
54
55
  must include an HTTP Response node to complete the request.</p>
55
56
  </script>
@@ -406,6 +406,7 @@
406
406
  "label": {
407
407
  "unknown": "unknown"
408
408
  },
409
+ "manageModules": "モジュールを管理",
409
410
  "tip": "<p>現在のNode-RED環境では、本ノードの型が不明です。</p><p><i>現在の状態で本ノードをデプロイすると設定は保存されますが、不明なノードがインストールされるまでフローは実行されません。</i></p><p>詳細はノードの「情報」を参照してください。</p>"
410
411
  },
411
412
  "mqtt": {
@@ -47,7 +47,7 @@
47
47
  <p>これらは、動的購読が設定されている場合のみ適用されます。</p>
48
48
  <dl class="message-properties">
49
49
  <dt>action <span class="property-type">文字列</span></dt>
50
- <dd>本ノードが行う動作の名前。利用可能な動作は<code>"connect"</code>、<code>"disconnect"</code>、<code>"subscribe"</code>、<code>"unsubscribe"</code>です。</dd>
50
+ <dd>本ノードが行う動作の名前。利用可能な動作は<code>"connect"</code>、<code>"disconnect"</code>、<code>"getSubscriptions"</code>、<code>"subscribe"</code>、<code>"unsubscribe"</code>です。</dd>
51
51
  <dt class="optional">topic <span class="property-type">文字列|オブジェクト|配列</span></dt>
52
52
  <dd><code>"subscribe"</code>と<code>"unsubscribe"</code>の動作に対して、本プロパティはトピックを提供します。次のいずれかを設定できます:<ul>
53
53
  <li>トピックフィルターを含む文字列</li>
@@ -105,7 +105,7 @@
105
105
  <h3>入力</h3>
106
106
  <dl class="message-properties">
107
107
  <dt>action <span class="property-type">文字列</span></dt>
108
- <dd>本ノードが行う動作の名前。利用可能な動作は<code>"connect"</code>、<code>"disconnect"</code>、<code>"subscribe"</code>、<code>"unsubscribe"</code>です。</dd>
108
+ <dd>本ノードが行う動作の名前。利用可能な動作は<code>"connect"</code>と<code>"disconnect"</code>です。</dd>
109
109
  <dt class="optional">broker <span class="property-type">broker</span> </dt>
110
110
  <dd><code>"connect"</code>の動作に対して、本プロパティは次の様な個々のブローカ設定を上書きします: <ul>
111
111
  <li><code>broker</code></li>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-red/nodes",
3
- "version": "4.1.0-beta.1",
3
+ "version": "4.1.0-beta.2",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",