From 356c0c241634370f1461229d89fc602621985325 Mon Sep 17 00:00:00 2001 From: Debadutta Panda Date: Mon, 3 Feb 2025 14:11:29 +0530 Subject: [PATCH 01/21] Add include raw data option in request body. --- .../nodes/core/network/21-httpin.html | 36 +++-- .../@node-red/nodes/core/network/21-httpin.js | 138 +++++++++++++++--- .../@node-red/nodes/locales/de/messages.json | 1 + .../nodes/locales/en-US/messages.json | 1 + .../nodes/locales/es-ES/messages.json | 1 + .../@node-red/nodes/locales/fr/messages.json | 1 + .../@node-red/nodes/locales/ja/messages.json | 1 + .../@node-red/nodes/locales/ko/messages.json | 3 +- .../nodes/locales/pt-BR/messages.json | 1 + .../@node-red/nodes/locales/ru/messages.json | 1 + .../nodes/locales/zh-CN/messages.json | 1 + .../nodes/locales/zh-TW/messages.json | 3 +- packages/node_modules/node-red/red.js | 2 +- 13 files changed, 154 insertions(+), 36 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.html b/packages/node_modules/@node-red/nodes/core/network/21-httpin.html index f828077a1..c7724d9b4 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.html +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.html @@ -25,11 +25,22 @@ -
+ +
- - +
+
+ + +
+
+ + +
+
+ +
@@ -74,6 +85,7 @@ label:RED._("node-red:httpin.label.url")}, method: {value:"get",required:true}, upload: {value:false}, + includeRawBody: {value:false}, swaggerDoc: {type:"swagger-doc", required:false} }, inputs:0, @@ -115,16 +127,22 @@ $('.row-swagger-doc').hide(); } $("#node-input-method").on("change", function() { - if ($(this).val() === "post") { - $(".form-row-http-in-upload").show(); + var method = $(this).val(); + if(["post", "put", "patch","delete"].includes(method)){ + $("#form-reqBody-http-in-controller").show(); + $("#form-row-http-in-rawdata").show(); + if (method === "post") { + $("#form-row-http-in-upload").show(); + } else { + $("#form-row-http-in-upload").hide(); + } } else { - $(".form-row-http-in-upload").hide(); + $("#form-row-http-in-rawdata").hide(); + $("#form-row-http-in-upload").hide(); + $("#form-reqBody-http-in-controller").hide(); } }).change(); - - } - }); var headerTypes = [ {value:"content-type",label:"Content-Type",hasValue: false}, diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js index 22e83b411..28e90771e 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js @@ -26,16 +26,13 @@ module.exports = function(RED) { var mediaTyper = require('media-typer'); var isUtf8 = require('is-utf8'); var hashSum = require("hash-sum"); - - function rawBodyParser(req, res, next) { - if (req.skipRawBodyParser) { next(); } // don't parse this if told to skip - if (req._body) { return next(); } - req.body = ""; - req._body = true; - - var isText = true; - var checkUTF = false; - + var PassThrough = require('stream').PassThrough + + function rawBodyParser(req, _res, next) { + if(!req._nodeRedReqStream) { + return next(); + } + var isText = true, checkUTF = false; if (req.headers['content-type']) { var contentType = typer.parse(req.headers['content-type']) if (contentType.type) { @@ -51,27 +48,105 @@ module.exports = function(RED) { && (parsedType.subtype !== "x-protobuf")) { checkUTF = true; } else { - // application/octet-stream or application/cbor isText = false; } - } } - getBody(req, { + getBody(req._nodeRedReqStream, { length: req.headers['content-length'], encoding: isText ? "utf8" : null }, function (err, buf) { - if (err) { return next(err); } + if (err) { + return next(err); + } if (!isText && checkUTF && isUtf8(buf)) { buf = buf.toString() } - req.body = buf; - next(); + Object.defineProperty(req, "rawRequestBody", { + value: buf, + enumerable: true + }) + return next(); }); } - var corsSetup = false; + function getRootApp(app) { + if (typeof app.parent === 'undefined') { + return app; + } + return getRootApp(app.parent) + } + + function createRouteId(req) { + var method = req.method.toLowerCase(), url = req.url; + return `${method}:${url}`; + } + + var rootApp = getRootApp(RED.httpNode) + + // Check if middleware already exists + var isMiddlewarePresent = rootApp._router.stack.some(layer => layer.name === 'setupRawBodyCapture') + + if (!isMiddlewarePresent) { + // Initialize HTTP node settings storage + var httpInNodes = new Map() + + rootApp.set('httpInNodes', httpInNodes); + + // This middleware must always be at the root to handle the raw request body correctly + function setupRawBodyCapture(req, _res, next) { + var httpInNodeId = createRouteId(req) + + // Check if settings for this ID exist + if (httpInNodes.has(httpInNodeId)) { + var httpInNode = httpInNodes.get(httpInNodeId); + // If raw body inclusion is disabled, skip the processing + if (!httpInNode.includeRawBody) { + return next(); + } + // Create a PassThrough stream to capture the request body + var passThrough = new PassThrough(); + + // Function to handle 'data' event + function onData(chunk) { + passThrough.write(chunk); + } + + // Function to handle 'end' or 'error' events + function onEnd(err) { + if (err) { + passThrough.destroy(err); + } else { + passThrough.end(); + } + } + + // Attach event listeners to the request stream + req.on('data', onData); + req.once('end', onEnd); + req.once('error', onEnd); + + // Remove listeners once the request is closed + req.once('close', function () { + req.removeListener('data', onData); + }); + + // Attach the passThrough stream to the request + Object.defineProperty(req, "_nodeRedReqStream", { + value: passThrough + }); + + return next(); + } + // Proceed to the next middleware if no settings found + return next(); + } + // Add middleware to the stack + rootApp.use(setupRawBodyCapture) + // Move the router to top of the stack + rootApp._router.stack.unshift(rootApp._router.stack.pop()) + } function createRequestWrapper(node,req) { // This misses a bunch of properties (eg headers). Before we use this function @@ -125,6 +200,7 @@ module.exports = function(RED) { return wrapper; } + function createResponseWrapper(node,res) { var wrapper = { _res: res @@ -188,6 +264,7 @@ module.exports = function(RED) { } this.method = n.method; this.upload = n.upload; + this.includeRawBody = n.includeRawBody; this.swaggerDoc = n.swaggerDoc; var node = this; @@ -227,7 +304,9 @@ module.exports = function(RED) { } var maxApiRequestSize = RED.settings.apiMaxLength || '5mb'; + var jsonParser = bodyParser.json({limit:maxApiRequestSize}); + var urlencParser = bodyParser.urlencoded({limit:maxApiRequestSize,extended:true}); var metricsHandler = function(req,res,next) { next(); } @@ -254,25 +333,35 @@ module.exports = function(RED) { var mp = multer({ storage: multer.memoryStorage() }).any(); multipartParser = function(req,res,next) { mp(req,res,function(err) { - req._body = true; next(err); }) }; } if (this.method == "get") { - RED.httpNode.get(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,this.callback,this.errorHandler); + RED.httpNode.get(this.url, cookieParser(), httpMiddleware, corsHandler, metricsHandler, this.callback, this.errorHandler); } else if (this.method == "post") { - RED.httpNode.post(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,multipartParser,rawBodyParser,this.callback,this.errorHandler); + RED.httpNode.post(this.url,cookieParser(), httpMiddleware, corsHandler, metricsHandler, jsonParser, urlencParser, multipartParser, rawBodyParser, this.callback, this.errorHandler); } else if (this.method == "put") { - RED.httpNode.put(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler); + RED.httpNode.put(this.url, cookieParser(), httpMiddleware, corsHandler, metricsHandler, jsonParser, urlencParser, rawBodyParser, this.callback, this.errorHandler); } else if (this.method == "patch") { - RED.httpNode.patch(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler); + RED.httpNode.patch(this.url, cookieParser(), httpMiddleware, corsHandler, metricsHandler, jsonParser, urlencParser, rawBodyParser, this.callback, this.errorHandler); } else if (this.method == "delete") { - RED.httpNode.delete(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler); + RED.httpNode.delete(this.url, cookieParser(), httpMiddleware, corsHandler, metricsHandler, jsonParser, urlencParser, rawBodyParser, this.callback, this.errorHandler); } + // unique id for httpInNode based on method name and url path + var httpInNodeId = createRouteId({url: this.url, method: this.method}) + + // get httpInNode from RED.httpNode + var httpInNodes = rootApp.get('httpInNodes') + + // Add httpInNode to RED.httpNode + httpInNodes.set(httpInNodeId, this); + this.on("close",function() { + // remove httpInNodeSettings from RED.httpNode + httpInNodes.remove(httpInNodeId) var node = this; RED.httpNode._router.stack.forEach(function(route,i,routes) { if (route.route && route.route.path === node.url && route.route.methods[node.method]) { @@ -284,9 +373,9 @@ module.exports = function(RED) { this.warn(RED._("httpin.errors.not-created")); } } + RED.nodes.registerType("http in",HTTPIn); - function HTTPOut(n) { RED.nodes.createNode(this,n); var node = this; @@ -361,5 +450,6 @@ module.exports = function(RED) { done(); }); } + RED.nodes.registerType("http response",HTTPOut); } diff --git a/packages/node_modules/@node-red/nodes/locales/de/messages.json b/packages/node_modules/@node-red/nodes/locales/de/messages.json index a51e504cf..332712902 100644 --- a/packages/node_modules/@node-red/nodes/locales/de/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/de/messages.json @@ -451,6 +451,7 @@ "upload": "Dateiuploads akzeptieren", "status": "Statuscode", "headers": "Kopfzeilen", + "rawBody": "Rohdaten einbeziehen?", "other": "andere", "paytoqs": { "ignore": "Ignorieren", diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index bc89992e2..76ff2394b 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -515,6 +515,7 @@ "doc": "Docs", "return": "Return", "upload": "Accept file uploads?", + "rawBody": "Include Raw Data?", "status": "Status code", "headers": "Headers", "other": "other", diff --git a/packages/node_modules/@node-red/nodes/locales/es-ES/messages.json b/packages/node_modules/@node-red/nodes/locales/es-ES/messages.json index b8ac84f1c..56465e451 100644 --- a/packages/node_modules/@node-red/nodes/locales/es-ES/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/es-ES/messages.json @@ -518,6 +518,7 @@ "status": "Status code", "headers": "Headers", "other": "otro", + "rawBody": "¿Incluir datos sin procesar?", "paytoqs": { "ignore": "Ignore", "query": "Append to query-string parameters", diff --git a/packages/node_modules/@node-red/nodes/locales/fr/messages.json b/packages/node_modules/@node-red/nodes/locales/fr/messages.json index 99002f48f..b675efcb5 100644 --- a/packages/node_modules/@node-red/nodes/locales/fr/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/fr/messages.json @@ -518,6 +518,7 @@ "status": "Code d'état", "headers": "En-têtes", "other": "Autre", + "rawBody": "Inclure les données brutes ?", "paytoqs": { "ignore": "Ignorer", "query": "Joindre aux paramètres de chaîne de requête", diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index 8d38ac077..eefdf5762 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -518,6 +518,7 @@ "status": "ステータスコード", "headers": "ヘッダ", "other": "その他", + "rawBody": "生データを含める?", "paytoqs": { "ignore": "無視", "query": "クエリパラメータに追加", diff --git a/packages/node_modules/@node-red/nodes/locales/ko/messages.json b/packages/node_modules/@node-red/nodes/locales/ko/messages.json index c82e0f51b..abef1c89c 100644 --- a/packages/node_modules/@node-red/nodes/locales/ko/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ko/messages.json @@ -397,7 +397,8 @@ "binaryBuffer": "바이너리 버퍼", "jsonObject": "JSON오브젝트", "authType": "종류별", - "bearerToken": "토큰" + "bearerToken": "토큰", + "rawBody": "원시 데이터를 포함할까요?" }, "setby": "- msg.method에 정의 -", "basicauth": "인증을 사용", diff --git a/packages/node_modules/@node-red/nodes/locales/pt-BR/messages.json b/packages/node_modules/@node-red/nodes/locales/pt-BR/messages.json index 51e1fd897..003c484af 100644 --- a/packages/node_modules/@node-red/nodes/locales/pt-BR/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/pt-BR/messages.json @@ -506,6 +506,7 @@ "status": "Código de estado", "headers": "Cabeçalhos", "other": "outro", + "rawBody": "Incluir dados brutos?", "paytoqs" : { "ignore": "Ignorar", "query": "Anexar aos parâmetros da cadeia de caracteres de consulta", diff --git a/packages/node_modules/@node-red/nodes/locales/ru/messages.json b/packages/node_modules/@node-red/nodes/locales/ru/messages.json index 2694ac6a5..49ed92239 100644 --- a/packages/node_modules/@node-red/nodes/locales/ru/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ru/messages.json @@ -411,6 +411,7 @@ "status": "Код состояния", "headers": "Заголовки", "other": "другое", + "rawBody": "Включить необработанные данные?", "paytoqs": { "ignore": "Игнорировать", "query": "Добавлять к параметрам строки запроса", diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json index 7d5616a8f..23b6b07ee 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json @@ -508,6 +508,7 @@ "status": "状态码", "headers": "头", "other": "其他", + "rawBody": "包含原始数据?", "paytoqs": { "ignore": "忽略", "query": "附加到查询字符串参数", diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json index 7d16c5817..2f9f3b560 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json @@ -416,7 +416,8 @@ "binaryBuffer": "二進制buffer", "jsonObject": "解析的JSON對象", "authType": "類型", - "bearerToken": "Token" + "bearerToken": "Token", + "rawBody": "包含原始數據?" }, "setby": "- 用 msg.method 設定 -", "basicauth": "基本認證", diff --git a/packages/node_modules/node-red/red.js b/packages/node_modules/node-red/red.js index 5f3c9da25..fa5c840bc 100755 --- a/packages/node_modules/node-red/red.js +++ b/packages/node_modules/node-red/red.js @@ -65,6 +65,7 @@ var knownOpts = { "version": Boolean, "define": [String, Array] }; + var shortHands = { "?":["--help"], "p":["--port"], @@ -286,7 +287,6 @@ httpsPromise.then(function(startupHttps) { server = http.createServer(function(req,res) {app(req,res);}); } server.setMaxListeners(0); - function formatRoot(root) { if (root[0] != "/") { root = "/" + root; From 90328ef75e64d95f006d401c1da0506bf0282a60 Mon Sep 17 00:00:00 2001 From: Debadutta Panda Date: Fri, 7 Feb 2025 00:09:13 +0530 Subject: [PATCH 02/21] Replace httpInNodes.remove with httpInNodes.delete to fix timeout error --- packages/node_modules/@node-red/nodes/core/network/21-httpin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js index 28e90771e..5c6ea0010 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js @@ -361,7 +361,7 @@ module.exports = function(RED) { this.on("close",function() { // remove httpInNodeSettings from RED.httpNode - httpInNodes.remove(httpInNodeId) + httpInNodes.delete(httpInNodeId) var node = this; RED.httpNode._router.stack.forEach(function(route,i,routes) { if (route.route && route.route.path === node.url && route.route.methods[node.method]) { From 7321cd20db631cc76b35f40af3e89702d2888f58 Mon Sep 17 00:00:00 2001 From: Debadutta Panda Date: Sat, 8 Feb 2025 10:59:37 +0530 Subject: [PATCH 03/21] Rename createRouteId to getRouteId and update related references; improve raw body capture stream handling --- .../@node-red/nodes/core/network/21-httpin.js | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js index 5c6ea0010..4d7786838 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js @@ -78,7 +78,7 @@ module.exports = function(RED) { return getRootApp(app.parent) } - function createRouteId(req) { + function getRouteId(req) { var method = req.method.toLowerCase(), url = req.url; return `${method}:${url}`; } @@ -94,49 +94,63 @@ module.exports = function(RED) { rootApp.set('httpInNodes', httpInNodes); - // This middleware must always be at the root to handle the raw request body correctly + /** + * This middleware must always be at the root to handle the raw request body correctly + * @param {import('express').Request} req + * @param {import('express').Response} _res + * @param {import('express').NextFunction} next + * @returns + */ function setupRawBodyCapture(req, _res, next) { - var httpInNodeId = createRouteId(req) + var httpInNodeId = getRouteId(req) // Check if settings for this ID exist if (httpInNodes.has(httpInNodeId)) { + // Get the httpInNode by routeId var httpInNode = httpInNodes.get(httpInNodeId); + // If raw body inclusion is disabled, skip the processing if (!httpInNode.includeRawBody) { return next(); } + // Create a PassThrough stream to capture the request body - var passThrough = new PassThrough(); + var cloneStream = new PassThrough(); // Function to handle 'data' event function onData(chunk) { - passThrough.write(chunk); + // Continue pushing chunks into clone stream if it is writable. + if (!cloneStream.writable) { + cloneStream.write(chunk); + } } // Function to handle 'end' or 'error' events function onEnd(err) { if (err) { - passThrough.destroy(err); + // If clone stream is already destroyed don't call destory function again + if (!cloneStream.destroyed) { + cloneStream.destroy(err); + } } else { - passThrough.end(); + cloneStream.end(); } } // Attach event listeners to the request stream - req.on('data', onData); - req.once('end', onEnd); - req.once('error', onEnd); + req.on('data', onData) + .once('end', onEnd) + .once('error', onEnd) + // Remove listeners once the request is closed + .once('close', () => { + req.removeListener('data', onData); + }) - // Remove listeners once the request is closed - req.once('close', function () { - req.removeListener('data', onData); - }); - - // Attach the passThrough stream to the request + // Attach the clone stream to the request Object.defineProperty(req, "_nodeRedReqStream", { - value: passThrough + value: cloneStream }); - + // Proceed to the next middleware if no settings found return next(); } // Proceed to the next middleware if no settings found @@ -351,7 +365,7 @@ module.exports = function(RED) { } // unique id for httpInNode based on method name and url path - var httpInNodeId = createRouteId({url: this.url, method: this.method}) + var httpInNodeId = getRouteId({url: this.url, method: this.method}) // get httpInNode from RED.httpNode var httpInNodes = rootApp.get('httpInNodes') From 9d811c7bce3a0e6a0aa0590ae1313881d0dc6639 Mon Sep 17 00:00:00 2001 From: Debadutta Panda Date: Sat, 8 Feb 2025 11:13:04 +0530 Subject: [PATCH 04/21] Improve clone stream handling by adding safety checks for writable and destroyed states --- .../@node-red/nodes/core/network/21-httpin.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js index 4d7786838..e8f99d2dc 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js @@ -119,8 +119,8 @@ module.exports = function(RED) { // Function to handle 'data' event function onData(chunk) { - // Continue pushing chunks into clone stream if it is writable. - if (!cloneStream.writable) { + // Safely call clone stream write + if (cloneStream.writable) { cloneStream.write(chunk); } } @@ -128,12 +128,15 @@ module.exports = function(RED) { // Function to handle 'end' or 'error' events function onEnd(err) { if (err) { - // If clone stream is already destroyed don't call destory function again + // Safely call clone stream destroy method if (!cloneStream.destroyed) { cloneStream.destroy(err); } } else { - cloneStream.end(); + // Safely call clone stream end method + if(cloneStream.writable) { + cloneStream.end(); + } } } From 9a26ee30522893a6278462d3069e89733c3c97c0 Mon Sep 17 00:00:00 2001 From: Debadutta Panda Date: Sun, 9 Feb 2025 13:26:05 +0530 Subject: [PATCH 05/21] Refactor raw body capture middleware and improve route key generation for HTTP nodes --- .../@node-red/nodes/core/network/21-httpin.js | 180 ++++++++++-------- 1 file changed, 96 insertions(+), 84 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js index e8f99d2dc..81d1f4e9b 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js @@ -27,9 +27,20 @@ module.exports = function(RED) { var isUtf8 = require('is-utf8'); var hashSum = require("hash-sum"); var PassThrough = require('stream').PassThrough - + var rootApp = getRootApp(RED.httpNode) + + // Check if middleware already exists + var isMiddlewareExists = rootApp._router.stack.some(layer => layer.name === 'setupRawBodyCapture') + + /** + * This middleware parses the raw body if the user enables it. + * @param {import('express').Request} req + * @param {import('express').Response} _res + * @param {import('express').NextFunction} next + * @returns + */ function rawBodyParser(req, _res, next) { - if(!req._nodeRedReqStream) { + if (!req._nodeRedReqStream) { return next(); } var isText = true, checkUTF = false; @@ -57,8 +68,8 @@ module.exports = function(RED) { length: req.headers['content-length'], encoding: isText ? "utf8" : null }, function (err, buf) { - if (err) { - return next(err); + if (err) { + return next(err) } if (!isText && checkUTF && isUtf8(buf)) { buf = buf.toString() @@ -71,6 +82,11 @@ module.exports = function(RED) { }); } + /** + * This method retrieves the root app by traversing the parent hierarchy. + * @param {import('express').Application} app + * @returns {import('express').Application} + */ function getRootApp(app) { if (typeof app.parent === 'undefined') { return app; @@ -78,87 +94,87 @@ module.exports = function(RED) { return getRootApp(app.parent) } - function getRouteId(req) { - var method = req.method.toLowerCase(), url = req.url; + /** + * It provide the unique route key + * @param {{method: string, url: string}} obj + * @returns + */ + function getRouteKey(obj) { + var method = obj.method.toUpperCase() + // Normalize the URL by replacing double slashes with a single slash and removing the trailing slash + var url = obj.url.replace(/\/{2,}/g, '/').replace(/\/$/, '') return `${method}:${url}`; } - var rootApp = getRootApp(RED.httpNode) + /** + * This middleware is for clone the request stream + * @param {import('express').Request} req + * @param {import('express').Response} _res + * @param {import('express').NextFunction} next + * @returns + */ + function setupRawBodyCapture(req, _res, next) { + var routeKey = getRouteKey({method: req.method, url: req._parsedUrl.pathname}) + var httpInNodes = rootApp.get('httpInNodes') + // Check if settings for this ID exist + if (httpInNodes.has(routeKey)) { + // Get the httpInNode by routeId + var httpInNode = httpInNodes.get(routeKey); - // Check if middleware already exists - var isMiddlewarePresent = rootApp._router.stack.some(layer => layer.name === 'setupRawBodyCapture') - - if (!isMiddlewarePresent) { - // Initialize HTTP node settings storage - var httpInNodes = new Map() - - rootApp.set('httpInNodes', httpInNodes); - - /** - * This middleware must always be at the root to handle the raw request body correctly - * @param {import('express').Request} req - * @param {import('express').Response} _res - * @param {import('express').NextFunction} next - * @returns - */ - function setupRawBodyCapture(req, _res, next) { - var httpInNodeId = getRouteId(req) - - // Check if settings for this ID exist - if (httpInNodes.has(httpInNodeId)) { - // Get the httpInNode by routeId - var httpInNode = httpInNodes.get(httpInNodeId); - - // If raw body inclusion is disabled, skip the processing - if (!httpInNode.includeRawBody) { - return next(); - } - - // Create a PassThrough stream to capture the request body - var cloneStream = new PassThrough(); - - // Function to handle 'data' event - function onData(chunk) { - // Safely call clone stream write - if (cloneStream.writable) { - cloneStream.write(chunk); - } - } - - // Function to handle 'end' or 'error' events - function onEnd(err) { - if (err) { - // Safely call clone stream destroy method - if (!cloneStream.destroyed) { - cloneStream.destroy(err); - } - } else { - // Safely call clone stream end method - if(cloneStream.writable) { - cloneStream.end(); - } - } - } - - // Attach event listeners to the request stream - req.on('data', onData) - .once('end', onEnd) - .once('error', onEnd) - // Remove listeners once the request is closed - .once('close', () => { - req.removeListener('data', onData); - }) - - // Attach the clone stream to the request - Object.defineProperty(req, "_nodeRedReqStream", { - value: cloneStream - }); - // Proceed to the next middleware if no settings found + // If raw body inclusion is disabled, skip the processing + if (!httpInNode.includeRawBody) { return next(); } + + // Create a PassThrough stream to capture the request body + var cloneStream = new PassThrough(); + + // Function to handle 'data' event + function onData(chunk) { + // Safely call clone stream write + if (cloneStream.writable) { + cloneStream.write(chunk); + } + } + + // Function to handle 'end' or 'error' events + function onEnd(err) { + if (err) { + // Safely call clone stream destroy method + if (!cloneStream.destroyed) { + cloneStream.destroy(err); + } + } else { + // Safely call clone stream end method + if (cloneStream.writable) { + cloneStream.end(); + } + } + } + + // Attach event listeners to the request stream + req.on('data', onData) + .once('end', onEnd) + .once('error', onEnd) + // Remove listeners once the request is closed + .once('close', () => { + req.removeListener('data', onData); + }) + + // Attach the clone stream to the request + Object.defineProperty(req, "_nodeRedReqStream", { + value: cloneStream + }); // Proceed to the next middleware if no settings found return next(); } + // Proceed to the next middleware if no settings found + return next(); + } + + if (!isMiddlewareExists) { + // Initialize HTTP node storage + rootApp.set('httpInNodes', new Map()); // Add middleware to the stack rootApp.use(setupRawBodyCapture) // Move the router to top of the stack @@ -366,19 +382,15 @@ module.exports = function(RED) { } else if (this.method == "delete") { RED.httpNode.delete(this.url, cookieParser(), httpMiddleware, corsHandler, metricsHandler, jsonParser, urlencParser, rawBodyParser, this.callback, this.errorHandler); } - - // unique id for httpInNode based on method name and url path - var httpInNodeId = getRouteId({url: this.url, method: this.method}) - // get httpInNode from RED.httpNode + var routeKey = getRouteKey({method: this.method, url: RED.httpNode.path() + this.url}) + var httpInNodes = rootApp.get('httpInNodes') - // Add httpInNode to RED.httpNode - httpInNodes.set(httpInNodeId, this); + httpInNodes.set(routeKey, this); this.on("close",function() { - // remove httpInNodeSettings from RED.httpNode - httpInNodes.delete(httpInNodeId) + httpInNodes.delete(routeKey) var node = this; RED.httpNode._router.stack.forEach(function(route,i,routes) { if (route.route && route.route.path === node.url && route.route.methods[node.method]) { From caeb5d3538dfc96aa3d5e04d9ae9aa2bf6ef773f Mon Sep 17 00:00:00 2001 From: Debadutta Panda Date: Sun, 9 Feb 2025 21:09:30 +0530 Subject: [PATCH 06/21] Refactor raw body capture middleware to use a Set for route management and improve code consistency --- .../@node-red/nodes/core/network/21-httpin.js | 58 ++++++++----------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js index 81d1f4e9b..34030f13d 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js @@ -26,11 +26,9 @@ module.exports = function(RED) { var mediaTyper = require('media-typer'); var isUtf8 = require('is-utf8'); var hashSum = require("hash-sum"); - var PassThrough = require('stream').PassThrough - var rootApp = getRootApp(RED.httpNode) - - // Check if middleware already exists - var isMiddlewareExists = rootApp._router.stack.some(layer => layer.name === 'setupRawBodyCapture') + var PassThrough = require('stream').PassThrough; + var rootApp = getRootApp(RED.httpNode); + var rawDataRoutes = new Set(); /** * This middleware parses the raw body if the user enables it. @@ -69,7 +67,7 @@ module.exports = function(RED) { encoding: isText ? "utf8" : null }, function (err, buf) { if (err) { - return next(err) + return next(err); } if (!isText && checkUTF && isUtf8(buf)) { buf = buf.toString() @@ -77,7 +75,7 @@ module.exports = function(RED) { Object.defineProperty(req, "rawRequestBody", { value: buf, enumerable: true - }) + }); return next(); }); } @@ -91,7 +89,7 @@ module.exports = function(RED) { if (typeof app.parent === 'undefined') { return app; } - return getRootApp(app.parent) + return getRootApp(app.parent); } /** @@ -100,9 +98,9 @@ module.exports = function(RED) { * @returns */ function getRouteKey(obj) { - var method = obj.method.toUpperCase() + var method = obj.method.toUpperCase(); // Normalize the URL by replacing double slashes with a single slash and removing the trailing slash - var url = obj.url.replace(/\/{2,}/g, '/').replace(/\/$/, '') + var url = obj.url.replace(/\/{2,}/g, '/').replace(/\/$/, ''); return `${method}:${url}`; } @@ -114,17 +112,9 @@ module.exports = function(RED) { * @returns */ function setupRawBodyCapture(req, _res, next) { - var routeKey = getRouteKey({method: req.method, url: req._parsedUrl.pathname}) - var httpInNodes = rootApp.get('httpInNodes') + var routeKey = getRouteKey({ method: req.method, url: req._parsedUrl.pathname }); // Check if settings for this ID exist - if (httpInNodes.has(routeKey)) { - // Get the httpInNode by routeId - var httpInNode = httpInNodes.get(routeKey); - - // If raw body inclusion is disabled, skip the processing - if (!httpInNode.includeRawBody) { - return next(); - } + if (rawDataRoutes.has(routeKey)) { // Create a PassThrough stream to capture the request body var cloneStream = new PassThrough(); @@ -159,7 +149,7 @@ module.exports = function(RED) { // Remove listeners once the request is closed .once('close', () => { req.removeListener('data', onData); - }) + }); // Attach the clone stream to the request Object.defineProperty(req, "_nodeRedReqStream", { @@ -172,14 +162,10 @@ module.exports = function(RED) { return next(); } - if (!isMiddlewareExists) { - // Initialize HTTP node storage - rootApp.set('httpInNodes', new Map()); - // Add middleware to the stack - rootApp.use(setupRawBodyCapture) - // Move the router to top of the stack - rootApp._router.stack.unshift(rootApp._router.stack.pop()) - } + // Add middleware to the stack + rootApp.use(setupRawBodyCapture); + // Move the router to top of the stack + rootApp._router.stack.unshift(rootApp._router.stack.pop()); function createRequestWrapper(node,req) { // This misses a bunch of properties (eg headers). Before we use this function @@ -301,6 +287,12 @@ module.exports = function(RED) { this.swaggerDoc = n.swaggerDoc; var node = this; + var routeKey = getRouteKey({method: this.method, url: RED.httpNode.path() + this.url}); + + // If the user enables raw body, add it to the raw data routes. + if(this.includeRawBody) { + rawDataRoutes.add(routeKey); + } this.errorHandler = function(err,req,res,next) { node.warn(err); @@ -383,14 +375,10 @@ module.exports = function(RED) { RED.httpNode.delete(this.url, cookieParser(), httpMiddleware, corsHandler, metricsHandler, jsonParser, urlencParser, rawBodyParser, this.callback, this.errorHandler); } - var routeKey = getRouteKey({method: this.method, url: RED.httpNode.path() + this.url}) - var httpInNodes = rootApp.get('httpInNodes') - - httpInNodes.set(routeKey, this); - this.on("close",function() { - httpInNodes.delete(routeKey) + // Remove it from the raw data routes if the node is closed + rawDataRoutes.delete(routeKey); var node = this; RED.httpNode._router.stack.forEach(function(route,i,routes) { if (route.route && route.route.path === node.url && route.route.methods[node.method]) { From bb43d63b54cd6376e27195c2b79987e9360f3397 Mon Sep 17 00:00:00 2001 From: Debadutta Panda Date: Mon, 10 Feb 2025 19:40:49 +0530 Subject: [PATCH 07/21] Fix condition check for RED.httpNode to ensure proper middleware setup --- .../@node-red/nodes/core/network/21-httpin.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js index 34030f13d..0083ad315 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js @@ -16,6 +16,7 @@ module.exports = function(RED) { "use strict"; + var rootApp; var bodyParser = require("body-parser"); var multer = require("multer"); var cookieParser = require("cookie-parser"); @@ -27,7 +28,6 @@ module.exports = function(RED) { var isUtf8 = require('is-utf8'); var hashSum = require("hash-sum"); var PassThrough = require('stream').PassThrough; - var rootApp = getRootApp(RED.httpNode); var rawDataRoutes = new Set(); /** @@ -162,10 +162,12 @@ module.exports = function(RED) { return next(); } - // Add middleware to the stack - rootApp.use(setupRawBodyCapture); - // Move the router to top of the stack - rootApp._router.stack.unshift(rootApp._router.stack.pop()); + if(typeof RED.httpNode === 'function' && (rootApp = getRootApp(RED.httpNode))) { + // Add middleware to the stack + rootApp.use(setupRawBodyCapture); + // Move the router to top of the stack + rootApp._router.stack.unshift(rootApp._router.stack.pop()); + } function createRequestWrapper(node,req) { // This misses a bunch of properties (eg headers). Before we use this function From 3fc94e7b99957d662eec35501b1dfc1fdbed3fc7 Mon Sep 17 00:00:00 2001 From: Debadutta Panda Date: Tue, 11 Feb 2025 00:34:32 +0530 Subject: [PATCH 08/21] Clarify comment for route key existence check in raw body capture middleware --- .../node_modules/@node-red/nodes/core/network/21-httpin.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js index 0083ad315..14bc4d465 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js @@ -113,7 +113,7 @@ module.exports = function(RED) { */ function setupRawBodyCapture(req, _res, next) { var routeKey = getRouteKey({ method: req.method, url: req._parsedUrl.pathname }); - // Check if settings for this ID exist + // Check if routeKey exist in rawDataRoutes if (rawDataRoutes.has(routeKey)) { // Create a PassThrough stream to capture the request body @@ -155,10 +155,8 @@ module.exports = function(RED) { Object.defineProperty(req, "_nodeRedReqStream", { value: cloneStream }); - // Proceed to the next middleware if no settings found - return next(); } - // Proceed to the next middleware if no settings found + // Proceed to the next middleware return next(); } From 3a29330021d3f11cb3df403168783deff3c179af Mon Sep 17 00:00:00 2001 From: Debadutta Panda Date: Fri, 14 Feb 2025 13:50:06 +0530 Subject: [PATCH 09/21] Update comment to clarify middleware positioning in HTTP node setup --- packages/node_modules/@node-red/nodes/core/network/21-httpin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js index 14bc4d465..45c980e2a 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js @@ -163,7 +163,7 @@ module.exports = function(RED) { if(typeof RED.httpNode === 'function' && (rootApp = getRootApp(RED.httpNode))) { // Add middleware to the stack rootApp.use(setupRawBodyCapture); - // Move the router to top of the stack + // Move the middleware to top of the stack rootApp._router.stack.unshift(rootApp._router.stack.pop()); } From 16d095397c8d11f194b86af20e2566f5fbd7cdea Mon Sep 17 00:00:00 2001 From: Debadutta Panda Date: Tue, 18 Mar 2025 18:42:27 +0530 Subject: [PATCH 10/21] refactor: update rawBody label to skipBodyParsing and adjust related UI elements --- .../nodes/core/network/21-httpin.html | 12 +-- .../@node-red/nodes/core/network/21-httpin.js | 87 +++++++------------ .../@node-red/nodes/locales/de/messages.json | 1 - .../nodes/locales/en-US/messages.json | 2 +- .../nodes/locales/es-ES/messages.json | 1 - .../@node-red/nodes/locales/fr/messages.json | 1 - .../@node-red/nodes/locales/ja/messages.json | 1 - .../@node-red/nodes/locales/ko/messages.json | 3 +- .../nodes/locales/pt-BR/messages.json | 1 - .../@node-red/nodes/locales/ru/messages.json | 1 - .../nodes/locales/zh-CN/messages.json | 1 - .../nodes/locales/zh-TW/messages.json | 3 +- 12 files changed, 40 insertions(+), 74 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.html b/packages/node_modules/@node-red/nodes/core/network/21-httpin.html index c7724d9b4..59c2bfb6f 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.html +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.html @@ -33,9 +33,9 @@
-
- - +
+ +
@@ -85,7 +85,7 @@ label:RED._("node-red:httpin.label.url")}, method: {value:"get",required:true}, upload: {value:false}, - includeRawBody: {value:false}, + skipBodyParsing: {value:false}, swaggerDoc: {type:"swagger-doc", required:false} }, inputs:0, @@ -130,14 +130,14 @@ var method = $(this).val(); if(["post", "put", "patch","delete"].includes(method)){ $("#form-reqBody-http-in-controller").show(); - $("#form-row-http-in-rawdata").show(); + $("#form-row-http-in-parsing").show(); if (method === "post") { $("#form-row-http-in-upload").show(); } else { $("#form-row-http-in-upload").hide(); } } else { - $("#form-row-http-in-rawdata").hide(); + $("#form-row-http-in-parsing").hide(); $("#form-row-http-in-upload").hide(); $("#form-reqBody-http-in-controller").hide(); } diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js index 45c980e2a..b188e9627 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js @@ -37,11 +37,15 @@ module.exports = function(RED) { * @param {import('express').NextFunction} next * @returns */ - function rawBodyParser(req, _res, next) { - if (!req._nodeRedReqStream) { - return next(); - } - var isText = true, checkUTF = false; + function rawBodyParser(req, res, next) { + if (req.skipRawBodyParser) { next(); } // don't parse this if told to skip + if (req._body) { return next(); } + req.body = ""; + req._body = true; + + var isText = true; + var checkUTF = false; + if (req.headers['content-type']) { var contentType = typer.parse(req.headers['content-type']) if (contentType.type) { @@ -57,26 +61,23 @@ module.exports = function(RED) { && (parsedType.subtype !== "x-protobuf")) { checkUTF = true; } else { + // application/octet-stream or application/cbor isText = false; } + } } - getBody(req._nodeRedReqStream, { + getBody(req, { length: req.headers['content-length'], encoding: isText ? "utf8" : null }, function (err, buf) { - if (err) { - return next(err); - } + if (err) { return next(err); } if (!isText && checkUTF && isUtf8(buf)) { buf = buf.toString() } - Object.defineProperty(req, "rawRequestBody", { - value: buf, - enumerable: true - }); - return next(); + req.body = buf; + next(); }); } @@ -115,49 +116,23 @@ module.exports = function(RED) { var routeKey = getRouteKey({ method: req.method, url: req._parsedUrl.pathname }); // Check if routeKey exist in rawDataRoutes if (rawDataRoutes.has(routeKey)) { - - // Create a PassThrough stream to capture the request body - var cloneStream = new PassThrough(); - - // Function to handle 'data' event - function onData(chunk) { - // Safely call clone stream write - if (cloneStream.writable) { - cloneStream.write(chunk); - } - } - - // Function to handle 'end' or 'error' events - function onEnd(err) { + // Convert the request stream to buffer + getBody(req, { + length: req.headers['content-length'], + encoding: typer.parse(req).parameters.charset + }, function (err, buf) { if (err) { - // Safely call clone stream destroy method - if (!cloneStream.destroyed) { - cloneStream.destroy(err); - } - } else { - // Safely call clone stream end method - if (cloneStream.writable) { - cloneStream.end(); - } + return next(err); } - } - - // Attach event listeners to the request stream - req.on('data', onData) - .once('end', onEnd) - .once('error', onEnd) - // Remove listeners once the request is closed - .once('close', () => { - req.removeListener('data', onData); - }); - - // Attach the clone stream to the request - Object.defineProperty(req, "_nodeRedReqStream", { - value: cloneStream - }); + req.body = buf; + // Skip the body parsing + req.skipRawBodyParser = true; + req._body = true; + next(); + }) + } else { + next(); } - // Proceed to the next middleware - return next(); } if(typeof RED.httpNode === 'function' && (rootApp = getRootApp(RED.httpNode))) { @@ -283,14 +258,14 @@ module.exports = function(RED) { } this.method = n.method; this.upload = n.upload; - this.includeRawBody = n.includeRawBody; + this.skipBodyParsing = n.skipBodyParsing; this.swaggerDoc = n.swaggerDoc; var node = this; var routeKey = getRouteKey({method: this.method, url: RED.httpNode.path() + this.url}); // If the user enables raw body, add it to the raw data routes. - if(this.includeRawBody) { + if(this.skipBodyParsing) { rawDataRoutes.add(routeKey); } diff --git a/packages/node_modules/@node-red/nodes/locales/de/messages.json b/packages/node_modules/@node-red/nodes/locales/de/messages.json index 332712902..a51e504cf 100644 --- a/packages/node_modules/@node-red/nodes/locales/de/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/de/messages.json @@ -451,7 +451,6 @@ "upload": "Dateiuploads akzeptieren", "status": "Statuscode", "headers": "Kopfzeilen", - "rawBody": "Rohdaten einbeziehen?", "other": "andere", "paytoqs": { "ignore": "Ignorieren", diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index 57223a861..4b52db3f8 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -515,7 +515,7 @@ "doc": "Docs", "return": "Return", "upload": "Accept file uploads?", - "rawBody": "Include Raw Data?", + "parsing": "Skip body parsing?", "status": "Status code", "headers": "Headers", "other": "other", diff --git a/packages/node_modules/@node-red/nodes/locales/es-ES/messages.json b/packages/node_modules/@node-red/nodes/locales/es-ES/messages.json index 666300bcc..19427e2fc 100644 --- a/packages/node_modules/@node-red/nodes/locales/es-ES/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/es-ES/messages.json @@ -518,7 +518,6 @@ "status": "Código de estado", "headers": "Encabezados", "other": "otro", - "rawBody": "¿Incluir datos sin procesar?", "paytoqs": { "ignore": "Ignorar", "query": "Agregar a los parámetros de la cadena de consulta", diff --git a/packages/node_modules/@node-red/nodes/locales/fr/messages.json b/packages/node_modules/@node-red/nodes/locales/fr/messages.json index b9362003f..238763e3b 100644 --- a/packages/node_modules/@node-red/nodes/locales/fr/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/fr/messages.json @@ -518,7 +518,6 @@ "status": "Code d'état", "headers": "En-têtes", "other": "Autre", - "rawBody": "Inclure les données brutes ?", "paytoqs": { "ignore": "Ignorer", "query": "Joindre aux paramètres de chaîne de requête", diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index ec9af3351..1693f879e 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -518,7 +518,6 @@ "status": "ステータスコード", "headers": "ヘッダ", "other": "その他", - "rawBody": "生データを含める?", "paytoqs": { "ignore": "無視", "query": "クエリパラメータに追加", diff --git a/packages/node_modules/@node-red/nodes/locales/ko/messages.json b/packages/node_modules/@node-red/nodes/locales/ko/messages.json index abef1c89c..c82e0f51b 100644 --- a/packages/node_modules/@node-red/nodes/locales/ko/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ko/messages.json @@ -397,8 +397,7 @@ "binaryBuffer": "바이너리 버퍼", "jsonObject": "JSON오브젝트", "authType": "종류별", - "bearerToken": "토큰", - "rawBody": "원시 데이터를 포함할까요?" + "bearerToken": "토큰" }, "setby": "- msg.method에 정의 -", "basicauth": "인증을 사용", diff --git a/packages/node_modules/@node-red/nodes/locales/pt-BR/messages.json b/packages/node_modules/@node-red/nodes/locales/pt-BR/messages.json index 003c484af..51e1fd897 100644 --- a/packages/node_modules/@node-red/nodes/locales/pt-BR/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/pt-BR/messages.json @@ -506,7 +506,6 @@ "status": "Código de estado", "headers": "Cabeçalhos", "other": "outro", - "rawBody": "Incluir dados brutos?", "paytoqs" : { "ignore": "Ignorar", "query": "Anexar aos parâmetros da cadeia de caracteres de consulta", diff --git a/packages/node_modules/@node-red/nodes/locales/ru/messages.json b/packages/node_modules/@node-red/nodes/locales/ru/messages.json index 49ed92239..2694ac6a5 100644 --- a/packages/node_modules/@node-red/nodes/locales/ru/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ru/messages.json @@ -411,7 +411,6 @@ "status": "Код состояния", "headers": "Заголовки", "other": "другое", - "rawBody": "Включить необработанные данные?", "paytoqs": { "ignore": "Игнорировать", "query": "Добавлять к параметрам строки запроса", diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json index 23b6b07ee..7d5616a8f 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json @@ -508,7 +508,6 @@ "status": "状态码", "headers": "头", "other": "其他", - "rawBody": "包含原始数据?", "paytoqs": { "ignore": "忽略", "query": "附加到查询字符串参数", diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json index 2f9f3b560..7d16c5817 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json @@ -416,8 +416,7 @@ "binaryBuffer": "二進制buffer", "jsonObject": "解析的JSON對象", "authType": "類型", - "bearerToken": "Token", - "rawBody": "包含原始數據?" + "bearerToken": "Token" }, "setby": "- 用 msg.method 設定 -", "basicauth": "基本認證", From b081e66806006ac94f78886e693a7d973b2d7fb4 Mon Sep 17 00:00:00 2001 From: Debadutta Panda Date: Wed, 19 Mar 2025 00:50:41 +0530 Subject: [PATCH 11/21] refactor: remove unused PassThrough stream import from httpin.js --- packages/node_modules/@node-red/nodes/core/network/21-httpin.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js index b188e9627..ce9577925 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js @@ -27,7 +27,6 @@ module.exports = function(RED) { var mediaTyper = require('media-typer'); var isUtf8 = require('is-utf8'); var hashSum = require("hash-sum"); - var PassThrough = require('stream').PassThrough; var rawDataRoutes = new Set(); /** @@ -119,7 +118,6 @@ module.exports = function(RED) { // Convert the request stream to buffer getBody(req, { length: req.headers['content-length'], - encoding: typer.parse(req).parameters.charset }, function (err, buf) { if (err) { return next(err); From 9b3246075dca3e91c17fa21b713fbcf592b596bc Mon Sep 17 00:00:00 2001 From: Debadutta Panda Date: Sat, 7 Jun 2025 11:30:02 +0530 Subject: [PATCH 12/21] Update 21-httpin.js - change the function name - remove and change some functions description --- .../@node-red/nodes/core/network/21-httpin.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js index ce9577925..060d479b6 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js @@ -29,13 +29,6 @@ module.exports = function(RED) { var hashSum = require("hash-sum"); var rawDataRoutes = new Set(); - /** - * This middleware parses the raw body if the user enables it. - * @param {import('express').Request} req - * @param {import('express').Response} _res - * @param {import('express').NextFunction} next - * @returns - */ function rawBodyParser(req, res, next) { if (req.skipRawBodyParser) { next(); } // don't parse this if told to skip if (req._body) { return next(); } @@ -105,13 +98,13 @@ module.exports = function(RED) { } /** - * This middleware is for clone the request stream + * This middleware is for capture raw body * @param {import('express').Request} req * @param {import('express').Response} _res * @param {import('express').NextFunction} next * @returns */ - function setupRawBodyCapture(req, _res, next) { + function rawBodyCapture(req, _res, next) { var routeKey = getRouteKey({ method: req.method, url: req._parsedUrl.pathname }); // Check if routeKey exist in rawDataRoutes if (rawDataRoutes.has(routeKey)) { @@ -135,7 +128,7 @@ module.exports = function(RED) { if(typeof RED.httpNode === 'function' && (rootApp = getRootApp(RED.httpNode))) { // Add middleware to the stack - rootApp.use(setupRawBodyCapture); + rootApp.use(rawBodyCapture); // Move the middleware to top of the stack rootApp._router.stack.unshift(rootApp._router.stack.pop()); } From e5f2e8783abfd01d9fe79492c9ee3afe6057a391 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Wed, 11 Jun 2025 16:51:11 +0200 Subject: [PATCH 13/21] Fix pending_version not setted after module update --- .../@node-red/editor-client/src/js/ui/palette-editor.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js index dae41e8b4..5cc1faddc 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js @@ -769,6 +769,11 @@ RED.palette.editor = (function() { }); RED.events.on('registry:module-updated', function(ns) { + if (nodeEntries[ns.module]) { + // Set the node/plugin as updated + nodeEntries[ns.module].info.pending_version = ns.version; + } + refreshNodeModule(ns.module); refreshUpdateStatus(); }); From 410e5b8faf0a1ed51d7af08cdccb647100c3cbd6 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Fri, 13 Jun 2025 13:33:42 +0100 Subject: [PATCH 14/21] Allow limited Strings for msg.rejectUnauthorized fixes #5171 If `msg.rejectUnauthorized` is a string allow "true", "false" (and upper case versions) otherwise show a warning and use default behaviour. Boolean values used as is, any other types also ignored. --- .../@node-red/nodes/core/network/21-httprequest.js | 13 ++++++++++++- .../@node-red/nodes/locales/en-US/messages.json | 3 ++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js index 194d06175..b3aa9cfd7 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js @@ -599,7 +599,18 @@ in your Node-RED user directory (${RED.settings.userDir}). } } else { if (msg.hasOwnProperty('rejectUnauthorized')) { - opts.https = { rejectUnauthorized: msg.rejectUnauthorized }; + if (typeof msg.rejectUnauthorized === 'boolean') { + opts.https = { rejectUnauthorized: msg.rejectUnauthorized } + } else if (typeof msg.rejectUnauthorized === 'string') { + if (msg.rejectUnauthorized.toLowerCase() === 'true' || msg.rejectUnauthorized.toLowerCase() === 'false') { + opts.https = { rejectUnauthorized: (msg.rejectUnauthorized.toLowerCase() === 'true') } + } else { + node.warn(RED._("httpin.errors.rejectunauthorized-invalid")) + } + } else { + node.warn(RED._("httpin.errors.rejectunauthorized-invalid")) + } + } } diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index 7cde427f4..6d33e78aa 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -563,7 +563,8 @@ "timeout-isnan": "Timeout value is not a valid number, ignoring", "timeout-isnegative": "Timeout value is negative, ignoring", "invalid-payload": "Invalid payload", - "invalid-url": "Invalid url" + "invalid-url": "Invalid url", + "rejectunauthorized-invalid": "msg.rejectUnauthorized should be a boolean" }, "status": { "requesting": "requesting" From 922229221f4f13063659a7db033bcf67a2dbef46 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 15 Jun 2025 00:53:35 +0900 Subject: [PATCH 15/21] Fix image URLs in welcome tour for Node-RED v4.0 --- .../editor-client/src/tours/4.0/welcome.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/tours/4.0/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/4.0/welcome.js index 02a559136..a55763189 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/4.0/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/4.0/welcome.js @@ -20,7 +20,7 @@ export default { "ja": "複数ユーザ同時利用モード", "fr": "Mode Multi-utilisateur" }, - image: 'images/nr4-multiplayer-location.png', + image: '4.0/images/nr4-multiplayer-location.png', description: { "en-US": `

This release includes the first small steps towards making Node-RED easier to work with when you have multiple people editing flows at the same time.

@@ -44,7 +44,7 @@ export default { "ja": "バックグラウンドのデプロイ処理の改善", "fr": "Meilleure gestion du déploiement en arrière-plan" }, - image: 'images/nr4-background-deploy.png', + image: '4.0/images/nr4-background-deploy.png', description: { "en-US": `

If another user deploys changes whilst you are editing, we now use a more discrete notification that doesn't stop you continuing your work - especially if they are being very productive and deploying lots @@ -60,7 +60,7 @@ export default { "ja": "フローの差分表示の改善", "fr": "Amélioration des différences de flux" }, - image: 'images/nr4-diff-update.png', + image: '4.0/images/nr4-diff-update.png', description: { "en-US": `

When viewing changes made to a flow, Node-RED now distinguishes between nodes that have had configuration changes and those that have only been moved.

@@ -79,7 +79,7 @@ export default { "ja": "設定ノードのUXが向上", "fr": "Meilleure expérience utilisateur du noeud de configuration" }, - image: 'images/nr4-config-select.png', + image: '4.0/images/nr4-config-select.png', description: { "en-US": `

The Configuration node selection UI has had a small update to have a dedicated 'add' button next to the select box.

@@ -97,7 +97,7 @@ export default { "ja": "タイムスタンプの形式の項目", "fr": "Options de formatage de l'horodatage" }, - image: 'images/nr4-timestamp-formatting.png', + image: '4.0/images/nr4-timestamp-formatting.png', description: { "en-US": `

Nodes that let you set a timestamp now have options on what format that timestamp should be in.

We're keeping it simple to begin with by providing three options:

@@ -128,7 +128,7 @@ export default { "ja": "フロー/グローバル、環境変数の型の自動補完", "fr": "Saisie automatique des types de flux/global et env" }, - image: 'images/nr4-auto-complete.png', + image: '4.0/images/nr4-auto-complete.png', description: { "en-US": `

The flow/global context inputs and the env input now all include auto-complete suggestions based on the live state of your flows.

@@ -146,7 +146,7 @@ export default { "ja": "サブフローでの設定ノードのカスタマイズ", "fr": "Personnalisation du noeud de configuration dans les sous-flux" }, - image: 'images/nr4-sf-config.png', + image: '4.0/images/nr4-sf-config.png', description: { "en-US": `

Subflows can now be customised to allow each instance to use a different config node of a selected type.

@@ -183,7 +183,7 @@ export default { "ja": "パレット管理にプラグインを表示", "fr": "Affichage des Plugins dans le gestionnaire de palettes" }, - image: 'images/nr4-plugins.png', + image: '4.0/images/nr4-plugins.png', description: { "en-US": `

The palette manager now shows any plugin modules you have installed, such as node-red-debugger. Previously they would only be shown if the plugins include From 09a539a710170ebb6565707897310525dc2caf9d Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 15 Jun 2025 00:58:20 +0900 Subject: [PATCH 16/21] Make explanation of update notifications more specific --- .../@node-red/editor-client/locales/en-US/editor.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index bade10e6f..1aa92c370 100644 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -1290,7 +1290,7 @@ "label": "Update Notifications", "settingsTitle": "Enable Update Notifications", "settingsDescription": "

Node-RED can notify you when there is a new version available. This ensures you keep up to date with the latest features and fixes.

This requires sending anonymised data back to the Node-RED team. It does not include any details of your flows or users.

For full information on what information is collected and how it is used, please see the documentation.

", - "settingsDescription2": "

You can change this setting at any time in the editor settings.

", + "settingsDescription2": "

You can change this setting at any time in the User Settings.

", "enableLabel": "Yes, enable notifications", "disableLabel": "No, do not enable notifications", "updateAvailable": "Update available", From 2886fc326cee8dd3cf30f5a65c59a9e8b74bec55 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 15 Jun 2025 01:01:55 +0900 Subject: [PATCH 17/21] Add Japanese translations for 4.1.0-beta.1 --- .../editor-client/locales/ja/editor.json | 26 ++++++++++++++++++- .../editor-client/src/tours/welcome.js | 26 ++++++++++++++++--- .../@node-red/nodes/locales/ja/messages.json | 1 + 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index e6c02590b..b65730c52 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -111,6 +111,7 @@ "userSettings": "ユーザ設定", "nodes": "ノード", "displayStatus": "ノードのステータスを表示", + "displayInfoIcon": "ノード情報のアイコンを表示", "displayConfig": "設定ノード", "import": "読み込み", "importExample": "フロー例を読み込み", @@ -264,6 +265,8 @@ "download": "ダウンロード", "importUnrecognised": "認識できない型が読み込まれました:", "importUnrecognised_plural": "認識できない型が読み込まれました:", + "importWithModuleInfo": "必要なモジュールが不足", + "importWithModuleInfoDesc": "以下のノードは現在パレットにインストールされていませんが、読み込んだフローには必要なノードです:", "importDuplicate": "重複したノードを読み込みました:", "importDuplicate_plural": "重複したノードを読み込みました:", "nodesExported": "クリップボードへフローを書き出しました", @@ -623,12 +626,15 @@ "yearsMonthsV": "__y__ 年 __count__ ヵ月前", "yearsMonthsV_plural": "__y__ 年 __count__ ヵ月前" }, + "manageModules": "モジュールを管理", "nodeCount": "__label__ 個のノード", "nodeCount_plural": "__label__ 個のノード", "pluginCount": "__count__ 個のプラグイン", "pluginCount_plural": "__count__ 個のプラグイン", "moduleCount": "__count__ 個のモジュール", "moduleCount_plural": "__count__ 個のモジュール", + "updateCount": "__count__ 個の更新が存在", + "updateCount_plural": "__count__ 個の更新が存在", "inuse": "使用中", "enableall": "全て有効化", "disableall": "全て無効化", @@ -638,9 +644,12 @@ "update": "__version__ へ更新", "updated": "更新済", "install": "ノードを追加", + "installAll": "全てインストール", "installed": "追加しました", + "installing": "モジュールのインストールが進行中: __module__", "conflict": "競合", "conflictTip": "

インストール済みのノードの種別と競合しているため
ノードをインストールできません

競合: __module__

", + "majorVersion": "

これはノードのメジャーバージョンの更新です。更新内容の詳細については、ドキュメントを確認してください。

", "loading": "カタログを読み込み中", "tab-nodes": "現在のノード", "tab-install": "ノードを追加", @@ -648,9 +657,12 @@ "sortRelevance": "関連順", "sortAZ": "辞書順", "sortRecent": "日付順", + "successfulInstall": "モジュールのインストールが成功", "more": "+ さらに __count__ 個", "upload": "モジュールのtgzファイルをアップロード", "refresh": "モジュールリスト更新", + "deprecated": "非推奨", + "deprecatedTip": "本モジュールは非推奨です", "errors": { "catalogLoadFailed": "

ノードのカタログの読み込みに失敗しました。

詳細はブラウザのコンソールを確認してください。

", "installFailed": "

追加処理が失敗しました: __module__

__message__

詳細はログを確認してください。

", @@ -1262,6 +1274,16 @@ "header": "グローバル環境変数", "revert": "破棄" }, + "telemetry": { + "label": "更新の通知", + "settingsTitle": "更新の通知を有効化", + "settingsDescription": "

新バージョンのNode-REDが存在した時に、通知を受けることができます。この機能によって最新機能の提供や修正があることを把握できます。

この通知を受け取るには、匿名化されたデータをNode-REDチームに送る必要があります。このデータには、フローやユーザの詳細は含まれません。

収集される情報と利用方法の詳細については、ドキュメントを参照してください。

", + "settingsDescription2": "

この設定はユーザ設定からいつでも変更できます。

", + "enableLabel": "はい、通知を有効にします", + "disableLabel": "いいえ、通知を有効にしません", + "updateAvailable": "更新を利用可能", + "updateAvailableDesc": "現在、Node-RED __version__ が利用可能" + }, "action-list": { "toggle-show-tips": "ヒント表示切替", "show-about": "Node-REDの説明を表示", @@ -1302,6 +1324,7 @@ "toggle-show-grid": "グリッド表示切替", "toggle-snap-grid": "ノードの配置補助切替", "toggle-status": "ステータス表示切替", + "toggle-node-info-icon": "ノード情報のアイコン表示切替", "show-selected-node-labels": "選択したノードのラベルを表示", "hide-selected-node-labels": "選択したノードのラベルを非表示", "scroll-view-up": "上スクロール", @@ -1414,6 +1437,7 @@ "show-global-env": "グローバル環境変数を表示", "lock-flow": "フローを固定", "unlock-flow": "フローの固定を解除", - "show-node-help": "ノードのヘルプを表示" + "show-node-help": "ノードのヘルプを表示", + "trigger-selected-nodes-action": "選択したノードのアクションを実行" } } diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index 627f542be..f77c7c60a 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -17,23 +17,28 @@ export default { { title: { "en-US": "Update notifications", + "ja": "更新の通知", "fr": "Notifications de mise à jour" }, image: 'images/update-notification.png', description: { "en-US": `

Stay up to date with notifications when there is a new Node-RED version available, or updates to the nodes you have installed

`, + "en-US": `

新バージョンのNode-REDの提供や、インストールしたノードの更新があった時に、通知を受け取ることができます。

`, "fr": `

Désormais vous recevrez une notification lorsqu'une nouvelle version de Node-RED ou une nouvelle version relative à un des noeuds que vous avez installés est disponible

` } }, { title: { "en-US": "Flow documentation", + "ja": "フローのドキュメント", "fr": "Documentation des flux" }, image: 'images/node-docs.png', description: { "en-US": `

Quickly see which nodes have additional documentation with the new documentation icon.

Clicking on the icon opens up the Description tab of the node edit dialog.

`, + "ja": `

ドキュメントアイコンによって、どのノードにドキュメントが追加されているかをすぐに確認できます。

+

アイコンをクリックすると、ノード編集ダイアログの説明タブが開きます。

`, "fr": `

Voyez rapidement quels noeuds ont une documentation supplémentaire avec la nouvelle icône de documentation.

Cliquer sur l'icône ouvre l'onglet Description de la boîte de dialogue d'édition du noeud.

` } @@ -41,6 +46,7 @@ export default { { title: { "en-US": "Palette Manager Improvements", + "ja": "パレットの管理の改善", "fr": "Améliorations du Gestionnaire de Palettes" }, description: { @@ -50,6 +56,12 @@ export default {
  • See which nodes have been deprecated by their author and are no longer recommended for use
  • Links to node documentation for the nodes you already have installed
  • `, + "ja": `

    パレットの管理に多くの改善が加えられました:

    +
      +
    • 検索結果はダウンロード数順で並べられ、最も人気のあるノードを見つけやすくなりました。
    • +
    • 作者によって非推奨とされ、利用が推奨されなくなったノードかを確認できるようになりました。
    • +
    • 既にインストールされているノードに、ノードのドキュメントへのリンクが追加されました。
    • +
    `, "fr": `

    Le Gestionnaire de Palettes a bénéficié de nombreuses améliorations :

    • Les résultats de recherche sont triés par téléchargement pour vous aider à trouver les noeuds les plus populaires.
    • @@ -61,6 +73,7 @@ export default { { title: { "en-US": "Installing missing modules", + "ja": "不足モジュールのインストール", "fr": "Installation des modules manquants" }, image: 'images/missing-modules.png', @@ -68,6 +81,9 @@ export default { "en-US": `

      Flows exported from Node-RED 4.1 now include information on what additional modules need to be installed.

      When importing a flow with this information, the editor will let you know what is missing and help to get them installed.

      `, + "ja": `

      Node-RED 4.1から書き出したフローには、インストールが必要な追加モジュールの情報が含まれる様になりました。

      +

      この情報を含むフローを読み込むと、エディタは不足しているモジュールを通知し、インストールを支援します。

      + `, "fr": `

      Les flux exportés depuis Node-RED 4.1 incluent désormais des informations sur les modules supplémentaires à installer.

      Lors de l'importation d'un flux contenant ces informations, l'éditeur vous indiquera les modules manquants et vous aidera à les installer.

      ` @@ -89,9 +105,13 @@ export default {
    • Better display of error objects in the Debug sidebar
    • and lots more...
    `, - // "ja": `

    コアノードには沢山の軽微な修正、ドキュメント更新、小さな機能拡張が入っています。全リストはヘルプサイドバーにある変更履歴を参照してください。

    - //
      - //
    `, + "ja": `

    コアノードには沢山の軽微な修正、ドキュメント更新、小さな機能拡張が入っています。全リストはヘルプサイドバーにある変更履歴を参照してください。

    +
      +
    • Functionノードでnode:のプレフィックスモジュールをサポート
    • +
    • ランタイム設定からFunctionノードのグローバルタイムアウトを設定可能
    • +
    • デバッグサイドバーでのエラーオブジェクトの表示を改善
    • +
    • その他、多数...
    • +
    `, "fr": `

    Les noeuds principaux ont bénéficié de nombreux correctifs mineurs, de mises à jour de documentation et d'améliorations mineures. Consultez le journal complet des modifications dans la barre latérale d'aide pour une liste complète.

      diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index 1693f879e..118d6af2c 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -406,6 +406,7 @@ "label": { "unknown": "unknown" }, + "manageModules": "モジュールを管理", "tip": "

      現在のNode-RED環境では、本ノードの型が不明です。

      現在の状態で本ノードをデプロイすると設定は保存されますが、不明なノードがインストールされるまでフローは実行されません。

      詳細はノードの「情報」を参照してください。

      " }, "mqtt": { From 51e0e18045f8704b3e569dfca2c55cc76084cca3 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 15 Jun 2025 01:29:39 +0900 Subject: [PATCH 18/21] Fix typo in welcome tour --- .../node_modules/@node-red/editor-client/src/tours/welcome.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index f77c7c60a..8041db469 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -23,7 +23,7 @@ export default { image: 'images/update-notification.png', description: { "en-US": `

      Stay up to date with notifications when there is a new Node-RED version available, or updates to the nodes you have installed

      `, - "en-US": `

      新バージョンのNode-REDの提供や、インストールしたノードの更新があった時に、通知を受け取ることができます。

      `, + "ja": `

      新バージョンのNode-REDの提供や、インストールしたノードの更新があった時に、通知を受け取ることができます。

      `, "fr": `

      Désormais vous recevrez une notification lorsqu'une nouvelle version de Node-RED ou une nouvelle version relative à un des noeuds que vous avez installés est disponible

      ` } }, From f24bf3549c9f83365eb02a61e2c520aa1f96a6b4 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 16 Jun 2025 11:32:43 +0100 Subject: [PATCH 19/21] Do not use css display when counting filtered palette nodes --- .../node_modules/@node-red/editor-client/src/js/ui/palette.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 5c9c9e5d2..89337e7c9 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -80,7 +80,7 @@ RED.palette = (function() { getNodeCount: function (visibleOnly) { const nodes = catDiv.find(".red-ui-palette-node") if (visibleOnly) { - return nodes.filter(function() { return $(this).css('display') !== 'none'}).length + return nodes.filter(function() { return $(this).attr("data-filter") !== "true"}).length } else { return nodes.length } @@ -572,8 +572,10 @@ RED.palette = (function() { var currentLabel = $(el).attr("data-palette-label"); var type = $(el).attr("data-palette-type"); if (val === "" || re.test(type) || re.test(currentLabel)) { + $(el).attr("data-filter", null) $(this).show(); } else { + $(el).attr("data-filter", "true") $(this).hide(); } }); From 1a873b8389b3772585842e051bac8a6df9930c1e Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 19 Jun 2025 15:17:56 +0100 Subject: [PATCH 20/21] Add event-log widget to status bar --- .../editor-client/src/js/ui/event-log.js | 34 ++++++++++++++++--- .../node_modules/@node-red/util/lib/exec.js | 2 +- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js b/packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js index 54ed0a27b..a641740f6 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js @@ -15,11 +15,14 @@ **/ RED.eventLog = (function() { - var template = ''; + const template = ''; + + let eventLogEditor; + let backlog = []; + let shown = false; + + const activeLogs = new Set() - var eventLogEditor; - var backlog = []; - var shown = false; function appendLogLine(line) { backlog.push(line); @@ -38,6 +41,18 @@ RED.eventLog = (function() { init: function() { $(template).appendTo("#red-ui-editor-node-configs"); RED.actions.add("core:show-event-log",RED.eventLog.show); + + const statusWidget = $(''); + statusWidget.on("click", function(evt) { + RED.actions.invoke("core:show-event-log"); + }) + RED.statusBar.add({ + id: "red-ui-event-log-status", + align: "right", + element: statusWidget + }); + // RED.statusBar.hide("red-ui-event-log-status"); + }, show: function() { if (shown) { @@ -98,6 +113,12 @@ RED.eventLog = (function() { }, log: function(id,payload) { var ts = (new Date(payload.ts)).toISOString()+" "; + if (!payload.end) { + activeLogs.add(id) + } else { + activeLogs.delete(id); + } + if (payload.type) { ts += "["+payload.type+"] " } @@ -111,6 +132,11 @@ RED.eventLog = (function() { appendLogLine(ts+line); }) } + if (activeLogs.size > 0) { + RED.statusBar.show("red-ui-event-log-status"); + } else { + RED.statusBar.hide("red-ui-event-log-status"); + } }, startEvent: function(name) { backlog.push(""); diff --git a/packages/node_modules/@node-red/util/lib/exec.js b/packages/node_modules/@node-red/util/lib/exec.js index c7197ef65..15b81aa89 100644 --- a/packages/node_modules/@node-red/util/lib/exec.js +++ b/packages/node_modules/@node-red/util/lib/exec.js @@ -78,7 +78,7 @@ module.exports = { stdout: stdout, stderr: stderr } - emit && events.emit("event-log", {id:invocationId,payload:{ts: Date.now(),data:"rc="+code}}); + emit && events.emit("event-log", {id:invocationId,payload:{ts: Date.now(), data:"rc="+code, end: true}}); if (code === 0) { resolve(result) From 1036805b117b95a934beec53afb56f8a9e953de5 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Wed, 25 Jun 2025 11:52:55 +0100 Subject: [PATCH 21/21] Prevent library leaking full local paths --- .../@node-red/runtime/lib/storage/localfilesystem/library.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js index fbcb44e2f..d8d770677 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js @@ -135,7 +135,7 @@ function getLibraryEntry(type,path) { throw err; }); } else { - throw err; + throw new Error(`Library Entry not found ${path}`, { cause: err}); } }); }