From dc1ab7e3319cc9fc747c62caeb2d7fbde6031c52 Mon Sep 17 00:00:00 2001 From: Kunihiko Toumura Date: Mon, 26 Oct 2020 16:52:18 +0900 Subject: [PATCH 001/114] Add support for Messaging API to delay node --- .../@node-red/nodes/core/function/89-delay.js | 81 +++++++++++------ test/nodes/core/function/89-delay_spec.js | 91 +++++++++++++++++++ 2 files changed, 146 insertions(+), 26 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-delay.js b/packages/node_modules/@node-red/nodes/core/function/89-delay.js index b700913cc..b9f76ef9e 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-delay.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-delay.js @@ -80,10 +80,10 @@ module.exports = function(RED) { this.drop = n.drop; var node = this; - function ourTimeout(handler, delay) { + function ourTimeout(handler, delay, clearHandler) { var toutID = setTimeout(handler, delay); return { - clear: function() { clearTimeout(toutID); }, + clear: function() { clearTimeout(toutID); clearHandler(); }, trigger: function() { clearTimeout(toutID); return handler(); } }; } @@ -113,14 +113,15 @@ module.exports = function(RED) { } if (node.pauseType === "delay") { - node.on("input", function(msg) { - if (msg.hasOwnProperty("flush")) { flushDelayList(); } + node.on("input", function(msg, send, done) { + if (msg.hasOwnProperty("flush")) { flushDelayList(); done(); } else { var id = ourTimeout(function() { node.idList.splice(node.idList.indexOf(id),1); if (node.idList.length === 0) { node.status({}); } - node.send(msg); - }, node.timeout); + send(msg); + done(); + }, node.timeout, () => done()); node.idList.push(id); if ((node.timeout > 1000) && (node.idList.length !== 0)) { node.status({fill:"blue",shape:"dot",text:" "}); @@ -131,7 +132,7 @@ module.exports = function(RED) { node.on("close", function() { clearDelayList(); }); } else if (node.pauseType === "delayv") { - node.on("input", function(msg) { + node.on("input", function(msg, send, done) { var delayvar = Number(node.timeout); if (msg.hasOwnProperty("delay") && !isNaN(parseFloat(msg.delay))) { delayvar = parseFloat(msg.delay); @@ -140,8 +141,9 @@ module.exports = function(RED) { var id = ourTimeout(function() { node.idList.splice(node.idList.indexOf(id),1); if (node.idList.length === 0) { node.status({}); } - node.send(msg); - }, delayvar); + send(msg); + done(); + }, delayvar, () => done()); node.idList.push(id); if ((delayvar >= 0) && (node.idList.length !== 0)) { node.status({fill:"blue",shape:"dot",text:delayvar/1000+"s"}); @@ -152,7 +154,7 @@ module.exports = function(RED) { node.on("close", function() { clearDelayList(); }); } else if (node.pauseType === "rate") { - node.on("input", function(msg) { + node.on("input", function(msg, send, done) { if (msg.hasOwnProperty("reset")) { if (node.intervalID !== -1 ) { clearInterval(node.intervalID); @@ -161,17 +163,18 @@ module.exports = function(RED) { delete node.lastSent; node.buffer = []; node.status({text:"reset"}); + done(); return; } if (!node.drop) { var m = RED.util.cloneMessage(msg); delete m.flush; if (node.intervalID !== -1) { - node.buffer.push(m); + node.buffer.push({msg: m, send: send, done: done}); node.reportDepth(); } else { - node.send(m); + send(m); node.reportDepth(); node.intervalID = setInterval(function() { if (node.buffer.length === 0) { @@ -179,16 +182,22 @@ module.exports = function(RED) { node.intervalID = -1; } if (node.buffer.length > 0) { - node.send(node.buffer.shift()); + const msgInfo = node.buffer.shift(); + msgInfo.send(msgInfo.msg); + msgInfo.done(); } node.reportDepth(); }, node.rate); + done(); } if (msg.hasOwnProperty("flush")) { while (node.buffer.length > 0) { - node.send(node.buffer.shift()); + const msgInfo = node.buffer.shift(); + msgInfo.send(msgInfo.msg); + msgInfo.done(); } node.status({}); + done(); } } else { @@ -198,17 +207,19 @@ module.exports = function(RED) { } if (!node.lastSent) { // ensuring that we always send the first message node.lastSent = process.hrtime(); - node.send(msg); + send(msg); } else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) { node.lastSent = process.hrtime(); - node.send(msg); + send(msg); } + done(); } }); node.on("close", function() { clearInterval(node.intervalID); clearTimeout(node.busy); + node.buffer.forEach((msgInfo) => msgInfo.done()); node.buffer = []; node.status({}); }); @@ -217,57 +228,75 @@ module.exports = function(RED) { node.intervalID = setInterval(function() { if (node.pauseType === "queue") { if (node.buffer.length > 0) { - node.send(node.buffer.shift()); // send the first on the queue + const msgInfo = node.buffer.shift(); + msgInfo.send(msgInfo.msg); // send the first on the queue + msgInfo.done(); } } else { while (node.buffer.length > 0) { // send the whole queue - node.send(node.buffer.shift()); + const msgInfo = node.buffer.shift(); + msgInfo.send(msgInfo.msg); + msgInfo.done(); } } node.reportDepth(); },node.rate); var hit; - node.on("input", function(msg) { + node.on("input", function(msg, send, done) { if (!msg.hasOwnProperty("topic")) { msg.topic = "_none_"; } hit = false; for (var b in node.buffer) { // check if already in queue - if (msg.topic === node.buffer[b].topic) { - node.buffer[b] = msg; // if so - replace existing entry + if (msg.topic === node.buffer[b].msg.topic) { + node.buffer[b].done(); + node.buffer[b] = {msg, send, done}; // if so - replace existing entry hit = true; break; } } if (!hit) { - node.buffer.push(msg); // if not add to end of queue + node.buffer.push({msg, send, done}); // if not add to end of queue node.reportDepth(); } if (msg.hasOwnProperty("reset")) { + while (node.buffer.length > 0) { + const msgInfo = node.buffer.shift(); + msgInfo.done(); + } node.buffer = []; node.status({text:"reset"}); + done(); } if (msg.hasOwnProperty("flush")) { while (node.buffer.length > 0) { - node.send(node.buffer.shift()); + const msgInfo = node.buffer.shift(); + msgInfo.send(msgInfo.msg); + msgInfo.done(); } node.status({}); + done(); } }); node.on("close", function() { clearInterval(node.intervalID); + while (node.buffer.length > 0) { + const msgInfo = node.buffer.shift(); + msgInfo.done(); + } node.buffer = []; node.status({}); }); } else if (node.pauseType === "random") { - node.on("input", function(msg) { + node.on("input", function(msg, send, done) { var wait = node.randomFirst + (node.diff * Math.random()); var id = ourTimeout(function() { node.idList.splice(node.idList.indexOf(id),1); - node.send(msg); + send(msg); node.status({}); - }, wait); + done(); + }, wait, () => done()); node.idList.push(id); if ((node.timeout >= 1000) && (node.idList.length !== 0)) { node.status({fill:"blue",shape:"dot",text:parseInt(wait/10)/100+"s"}); diff --git a/test/nodes/core/function/89-delay_spec.js b/test/nodes/core/function/89-delay_spec.js index 51583d8a4..6a5a3c25f 100644 --- a/test/nodes/core/function/89-delay_spec.js +++ b/test/nodes/core/function/89-delay_spec.js @@ -719,4 +719,95 @@ describe('delay Node', function() { setImmediate( function() { delayNode1.receive({reset:true}); }); // reset the queue }); }); + + /* Messaging API support */ + function mapiDoneTestHelper(done, pauseType, drop, msgAndTimings) { + const completeNode = require("nr-test-utils").require("@node-red/nodes/core/common/24-complete.js"); + const flow = [{id:"delayNode1",type:"delay",name:"delayNode", pauseType:pauseType, timeout:"1", timeoutUnits: "seconds", + rate: "1", nbRateUnits: "1", rateUnits: "second", randomFirst:"950", randomLast:"1050",randomUnits:"milliseconds", + drop: drop, wires: [[]]}, + {id:"completeNode1",type:"complete",scope: ["delayNode1"],uncaught:false,wires:[["helperNode1"]]}, + {id:"helperNode1",type:"helper", wires:[[]]}]; + const numMsgs = msgAndTimings.length; + helper.load([delayNode, completeNode], flow, function () { + const delayNode1 = helper.getNode("delayNode1"); + const helperNode1 = helper.getNode("helperNode1"); + const t = Date.now(); + let c = 0; + helperNode1.on("input", function (msg) { + msg.should.have.a.property('payload', msgAndTimings[c].msg.payload); + (Date.now() - t).should.be.approximately(msgAndTimings[c].avr, msgAndTimings[c].var); + c += 1; + if ( c === numMsgs) { + done(); + } + }); + for (let i = 0; i < numMsgs; i++) { + setImmediate( function() { delayNode1.receive(msgAndTimings[i].msg); } ); + } + }); + } + + it('calls done when queued message is emitted (type: delay)', function(done) { + mapiDoneTestHelper(done, "delay", false, [{msg:{payload:1}, avr:1000, var:100}]); + }); + it('calls done when queued message is emitted (type: delayv)', function(done) { + mapiDoneTestHelper(done, "delayv", false, [{msg:{payload:1, delay:1000}, avr:1000, var:100}]); + }); + it('calls done when queued message is emitted (type: delay)', function(done) { + mapiDoneTestHelper(done, "random", false, [{msg:{payload:1}, avr:1000, var:100}]); + }); + it('calls done when queued message is cleared (type: delay)', function(done) { + mapiDoneTestHelper(done, "delay", false, [{msg:{payload:1}, avr:100, var:100}, + {msg:{payload:2,reset:true}, avr:100, var:100}]); + }); + it('calls done when queued message is cleared (type: delayv)', function(done) { + mapiDoneTestHelper(done, "delayv", false, [{msg:{payload:1, delay:1000}, avr:100, var:100}, + {msg:{payload:2, reset:true}, avr:100, var:100}]); + }); + it('calls done when queued message is cleared (type: random)', function(done) { + mapiDoneTestHelper(done, "random", false, [{msg:{payload:1}, avr:100, var:100}, + {msg:{payload:2,reset:true}, avr:100, var:100}]); + }); + it('calls done when queued message is flushed (type: delay)', function(done) { + mapiDoneTestHelper(done, "delay", false, [{msg:{payload:1}, avr:100, var:100}, + {msg:{payload:2,flush:true}, avr:100, var:100}]); + }); + it('calls done when queued message is flushed (type: delayv)', function(done) { + mapiDoneTestHelper(done, "delayv", false, [{msg:{payload:1, delay:1000}, avr:100, var:100}, + {msg:{payload:2, flush:true}, avr:100, var:100}]); + }); + it('calls done when queued message is flushed (type: random)', function(done) { + mapiDoneTestHelper(done, "random", false, [{msg:{payload:1}, avr:100, var:100}, + {msg:{payload:2,flush:true}, avr:100, var:100}]); + }); + it('calls done when rated message is emitted (drop: false)', function(done) { + mapiDoneTestHelper(done, "rate", false, [{msg:{payload:1}, avr:0, var:100}, + {msg:{payload:2}, avr:1000, var:100}]); + }); + it('calls done when rated message is emitted (drop: true)', function(done) { + mapiDoneTestHelper(done, "rate", true, [{msg:{payload:1}, avr:0, var:100}, + {msg:{payload:2}, avr:0, var:100}]); + }); + it('calls done when rated message is flushed', function(done) { + mapiDoneTestHelper(done, "rate", false, [{msg:{payload:1}, avr:0, var:100}, + {msg:{payload:2}, avr:0, var:100}, + {msg:{payload:3,flush:true}, avr:0, var:100}]); + }); + it('calls done when queued messages are sent (queue)', function(done) { + mapiDoneTestHelper(done, "queue", false, [{msg:{payload:1,topic:"a"}, avr:500, var:700}, + {msg:{payload:2, topic:"b"}, avr:1500, var:700}]); + }); + it('calls done when queued messages are sent (timed)', function(done) { + mapiDoneTestHelper(done, "timed", false, [{msg:{payload:1,topic:"a"}, avr:500, var:700}, + {msg:{payload:2,topic:"b"}, avr:500, var:700}]); + }); + it('calls done when queue is reset (queue/timed)', function(done) { + mapiDoneTestHelper(done, "timed", false, [{msg:{payload:1,topic:"a"}, avr:0, var:500}, + {msg:{payload:2,reset:true}, avr:0, var:500}]); + }); + it('calls done when queue is flushed (queue/timed)', function(done) { + mapiDoneTestHelper(done, "timed", false, [{msg:{payload:1,topic:"a"}, avr:0, var:500}, + {msg:{payload:2,flush:true}, avr:0, var:500}]); + }); }); From 8007bea7db71e12da1d51ea6fc319a697c2fa0e8 Mon Sep 17 00:00:00 2001 From: Kunihiko Toumura Date: Mon, 26 Oct 2020 20:25:52 +0900 Subject: [PATCH 002/114] Messaging API support in CSV node --- .../@node-red/nodes/core/parsers/70-CSV.js | 21 +++++----- test/nodes/core/parsers/70-CSV_spec.js | 39 +++++++++++++++++++ 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js index 15c16da13..b2fe9a4b5 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js @@ -55,7 +55,7 @@ module.exports = function(RED) { node.template = clean(node.template); node.hdrSent = false; - this.on("input", function(msg) { + this.on("input", function(msg, send, done) { if (msg.hasOwnProperty("reset")) { node.hdrSent = false; } @@ -140,9 +140,10 @@ module.exports = function(RED) { } msg.payload = ou; msg.columns = node.template.join(','); - if (msg.payload !== '') { node.send(msg); } + if (msg.payload !== '') { send(msg); } + done(); } - catch(e) { node.error(e,msg); } + catch(e) { done(e); } } else if (typeof msg.payload == "string") { // convert CSV string to object try { @@ -251,13 +252,13 @@ module.exports = function(RED) { msg.payload = node.store; msg.columns = node.template.filter(val => val).join(','); delete msg.parts; - node.send(msg); + send(msg); node.store = []; } } else { msg.columns = node.template.filter(val => val).join(','); - node.send(msg); // finally send the array + send(msg); // finally send the array } } else { @@ -281,19 +282,21 @@ module.exports = function(RED) { newMessage.parts.count -= 1; } } - node.send(newMessage); + send(newMessage); } } node.linecount = 0; + done(); } - catch(e) { node.error(e,msg); } + catch(e) { done(e); } } - else { node.warn(RED._("csv.errors.csv_js")); } + else { node.warn(RED._("csv.errors.csv_js")); done(); } } else { if (!msg.hasOwnProperty("reset")) { - node.send(msg); // If no payload and not reset - just pass it on. + send(msg); // If no payload and not reset - just pass it on. } + done(); } }); } diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js index 10e167b6d..656f9faa6 100644 --- a/test/nodes/core/parsers/70-CSV_spec.js +++ b/test/nodes/core/parsers/70-CSV_spec.js @@ -719,4 +719,43 @@ describe('CSV node', function() { }); }); + it('should call done when message processing is completed', function(done) { + const completeNode = require("nr-test-utils").require("@node-red/nodes/core/common/24-complete.js"); + const flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[[]]}, + { id:"c1", type:"complete", scope: ["n1"], uncaught:false, wires:[["h1"]]}, + { id:"h1", type:"helper", wires:[[]]} ]; + helper.load([csvNode,completeNode], flow, function() { + const n1 = helper.getNode("n1"); + const h1 = helper.getNode("h1"); + h1.on("input", function(msg) { + try { + msg.should.have.a.property('payload', "1,2,3,4"); + done(); + } catch (e) { + done(e); + } + }); + n1.receive({payload:"1,2,3,4"}); + }); + }); + + it('should call done when input causes an error', function(done) { + const completeNode = require("nr-test-utils").require("@node-red/nodes/core/common/24-complete.js"); + const flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[[]]}, + { id:"c1", type:"complete", scope: ["n1"], uncaught:false, wires:[["h1"]]}, + { id:"h1", type:"helper", wires:[[]]} ]; + helper.load([csvNode,completeNode], flow, function() { + const n1 = helper.getNode("n1"); + const h1 = helper.getNode("h1"); + h1.on("input", function(msg) { + try { + msg.should.have.a.property('payload', 1); + done(); + } catch (e) { + done(e); + } + }); + n1.receive({payload:1}); // neither object nor string + }); + }); }); From dbfbd54e1f66e68d341d91669f95b6bdfef8ab1e Mon Sep 17 00:00:00 2001 From: Kunihiko Toumura Date: Thu, 29 Oct 2020 16:16:03 +0900 Subject: [PATCH 003/114] Messaging API support in Batch node --- .../@node-red/nodes/core/sequence/19-batch.js | 96 +++++++++++++------ test/nodes/core/sequence/19-batch_spec.js | 88 +++++++++++++++++ 2 files changed, 155 insertions(+), 29 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js b/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js index 77574f222..f3f29df6a 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js +++ b/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js @@ -32,11 +32,11 @@ module.exports = function(RED) { return _max_kept_msgs_count; } - function send_msgs(node, msgs, clone_msg) { - var count = msgs.length; - var msg_id = msgs[0]._msgid; + function send_msgs(node, msgInfos, clone_msg) { + var count = msgInfos.length; + var msg_id = msgInfos[0].msg._msgid; for (var i = 0; i < count; i++) { - var msg = clone_msg ? RED.util.cloneMessage(msgs[i]) : msgs[i]; + var msg = clone_msg ? RED.util.cloneMessage(msgInfos[i].msg) : msgInfos[i].msg; if (!msg.hasOwnProperty("parts")) { msg.parts = {}; } @@ -44,14 +44,16 @@ module.exports = function(RED) { parts.id = msg_id; parts.index = i; parts.count = count; - node.send(msg); + msgInfos[i].send(msg); + //msgInfos[i].done(); } } function send_interval(node, allow_empty_seq) { - let msgs = node.pending; - if (msgs.length > 0) { - send_msgs(node, msgs, false); + let msgInfos = node.pending; + if (msgInfos.length > 0) { + send_msgs(node, msgInfos, false); + msgInfos.forEach(e => e.done()); node.pending = []; } else { @@ -108,19 +110,20 @@ module.exports = function(RED) { return; } } - var msgs = []; + var msgInfos = []; for (var topic of topics) { - var t_msgs = get_msgs_of_topic(pending, topic); - msgs = msgs.concat(t_msgs); + var t_msgInfos = get_msgs_of_topic(pending, topic); + msgInfos = msgInfos.concat(t_msgInfos); } for (var topic of topics) { remove_topic(pending, topic); } - send_msgs(node, msgs, true); - node.pending_count -= msgs.length; + send_msgs(node, msgInfos, true); + msgInfos.forEach(e => e.done() ); + node.pending_count -= msgInfos.length; } - function add_to_topic_group(pending, topic, gid, msg) { + function add_to_topic_group(pending, topic, gid, msgInfo) { if (!pending.hasOwnProperty(topic)) { pending[topic] = { groups: {}, gids: [] }; } @@ -132,32 +135,43 @@ module.exports = function(RED) { gids.push(gid); } var group = groups[gid]; - group.msgs.push(msg); + group.msgs.push(msgInfo); if ((group.count === undefined) && - msg.parts.hasOwnProperty('count')) { - group.count = msg.parts.count; + msgInfo.msg.parts.hasOwnProperty('count')) { + group.count = msgInfo.msg.parts.count; } } - function concat_msg(node, msg) { + function concat_msg(node, msg, send, done) { var topic = msg.topic; if(node.topics.indexOf(topic) >= 0) { if (!msg.hasOwnProperty("parts") || !msg.parts.hasOwnProperty("id") || !msg.parts.hasOwnProperty("index") || !msg.parts.hasOwnProperty("count")) { - node.error(RED._("batch.no-parts"), msg); + done(RED._("batch.no-parts")); return; } var gid = msg.parts.id; var pending = node.pending; - add_to_topic_group(pending, topic, gid, msg); + add_to_topic_group(pending, topic, gid, {msg, send, done}); node.pending_count++; var max_msgs = max_kept_msgs_count(node); if ((max_msgs > 0) && (node.pending_count > max_msgs)) { + Object.values(node.pending).forEach(p_topic => { + Object.values(p_topic.groups).forEach(group => { + group.msgs.forEach(msgInfo => { + if (msgInfo.msg.id === msg.id) { + // the message that caused the overflow + msgInfo.done(RED._("batch.too-many")); + } else { + msgInfo.done(); + } + }) + }) + }); node.pending = {}; node.pending_count = 0; - node.error(RED._("batch.too-many"), msg); } try_concat(node, pending); } @@ -178,29 +192,37 @@ module.exports = function(RED) { return; } node.pending = []; - this.on("input", function(msg) { + this.on("input", function(msg, send, done) { if (msg.hasOwnProperty("reset")) { + node.pending.forEach(e => e.done()); node.pending = []; node.pending_count = 0; + done(); return; } var queue = node.pending; - queue.push(msg); + queue.push({msg, send, done}); node.pending_count++; if (queue.length === count) { send_msgs(node, queue, is_overlap); + for (let i = 0; i < queue.length-overlap; i++) { + queue[i].done(); + } node.pending = (overlap === 0) ? [] : queue.slice(-overlap); node.pending_count = 0; } var max_msgs = max_kept_msgs_count(node); if ((max_msgs > 0) && (node.pending_count > max_msgs)) { + let lastMInfo = node.pending.pop(); + lastMInfo.done(RED._("batch.too-many")); + node.pending.forEach(e => e.done()); node.pending = []; node.pending_count = 0; - node.error(RED._("batch.too-many"), msg); } }); this.on("close", function() { + node.pending.forEach(e=> e.done()); node.pending_count = 0; node.pending = []; }); @@ -217,31 +239,36 @@ module.exports = function(RED) { if (interval > 0) { timer = setInterval(msgHandler, interval); } - this.on("input", function(msg) { + this.on("input", function(msg, send, done) { if (msg.hasOwnProperty("reset")) { if (timer !== undefined) { clearInterval(timer); } + node.pending.forEach(e => e.done()); node.pending = []; node.pending_count = 0; + done(); if (interval > 0) { timer = setInterval(msgHandler, interval); } return; } - node.pending.push(msg); + node.pending.push({msg, send, done}); node.pending_count++; var max_msgs = max_kept_msgs_count(node); if ((max_msgs > 0) && (node.pending_count > max_msgs)) { + let lastMInfo = node.pending.pop(); + lastMInfo.done(RED._("batch.too-many")); + node.pending.forEach(e => e.done()); node.pending = []; node.pending_count = 0; - node.error(RED._("batch.too-many"), msg); } }); this.on("close", function() { if (timer !== undefined) { clearInterval(timer); } + node.pending.forEach(e => e.done()); node.pending = []; node.pending_count = 0; }); @@ -251,15 +278,26 @@ module.exports = function(RED) { return x.topic; }); node.pending = {}; - this.on("input", function(msg) { + this.on("input", function(msg, send, done) { if (msg.hasOwnProperty("reset")) { + Object.values(node.pending).forEach(p_topic => { + Object.values(p_topic.groups).forEach(group => { + group.msgs.forEach(e => e.done()); + }); + }); node.pending = {}; node.pending_count = 0; + done(); return; } - concat_msg(node, msg); + concat_msg(node, msg, send, done); }); this.on("close", function() { + Object.values(node.pending).forEach(p_topic => { + Object.values(p_topic.groups).forEach(group => { + group.msgs.forEach(e => e.done()); + }); + }); node.pending = {}; node.pending_count = 0; }); diff --git a/test/nodes/core/sequence/19-batch_spec.js b/test/nodes/core/sequence/19-batch_spec.js index b2e1e6de2..2ebcb8d4d 100644 --- a/test/nodes/core/sequence/19-batch_spec.js +++ b/test/nodes/core/sequence/19-batch_spec.js @@ -451,4 +451,92 @@ describe('BATCH node', function() { }); }); + describe('messaging API', function() { + function mapiDoneTestHelper(done, mode, count, overlap, interval, allowEmptySequence, msgAndTimings) { + const completeNode = require("nr-test-utils").require("@node-red/nodes/core/common/24-complete.js"); + const catchNode = require("nr-test-utils").require("@node-red/nodes/core/common/25-catch.js"); + const flow = [{id:"batchNode1", type:"batch", name: "BatchNode", mode, count, overlap, interval, + allowEmptySequence, topics: [{topic: "TA"}], wires:[[]]}, + {id:"completeNode1",type:"complete",scope: ["batchNode1"],uncaught:false,wires:[["helperNode1"]]}, + {id:"catchNode1", type:"catch",scope: ["batchNode1"],uncaught:false,wires:[["helperNode1"]]}, + {id:"helperNode1",type:"helper", wires:[[]]}]; + const numMsgs = msgAndTimings.length; + helper.load([batchNode, completeNode, catchNode], flow, function () { + const batchNode1 = helper.getNode("batchNode1"); + const helperNode1 = helper.getNode("helperNode1"); + RED.settings.nodeMessageBufferMaxLength = 2; + const t = Date.now(); + let c = 0; + helperNode1.on("input", function (msg) { + msg.should.have.a.property('payload'); + (Date.now() - t).should.be.approximately(msgAndTimings[msg.payload].avr, msgAndTimings[msg.payload].var); + c += 1; + if ( c === numMsgs) { + done(); + } + }); + for (let i = 0; i < numMsgs; i++) { + setTimeout( function() { batchNode1.receive(msgAndTimings[i].msg); }, msgAndTimings[i].delay); + } + }); + } + + it('should call done() when message is sent (mode: count)', function(done) { + mapiDoneTestHelper(done, "count", 2, 0, 2, false, [ + { msg: {payload: 0}, delay: 0, avr: 0, var: 100}, + { msg: {payload: 1}, delay: 0, avr: 0, var: 100} + ]); + }); + it('should call done() when reset (mode: count)', function(done) { + mapiDoneTestHelper(done, "count", 2, 0, 2, false, [ + { msg: {payload: 0}, delay: 0, avr: 200, var: 100}, + { msg: {payload: 1, reset:true}, delay: 200, avr: 200, var: 100} + ]); + }); + it('should call done() regardless of buffer overflow (mode: count)', function(done) { + mapiDoneTestHelper(done, "count", 10, 0, 2, false, [ + { msg: {payload: 0}, delay: 0, avr: 500, var: 100}, + { msg: {payload: 1}, delay: 100, avr: 500, var: 100}, + { msg: {payload: 2}, delay: 500, avr: 500, var: 100} + ]); + }); + it('should call done() when message is sent (mode: interval)', function(done) { + mapiDoneTestHelper(done, "interval", 2, 0, 2, false, [ + { msg: {payload: 0}, delay: 0, avr: 2000, var: 100}, + { msg: {payload: 1}, delay: 500, avr: 2000, var: 100} + ]); + }); + it('should call done() when reset (mode: interval)', function(done) { + mapiDoneTestHelper(done, "interval", 2, 0, 2, false, [ + { msg: {payload: 0}, delay: 0, avr: 200, var: 100}, + { msg: {payload: 1, reset:true}, delay: 200, avr: 200, var: 100} + ]); + }); + it('should call done() regardless of buffer overflow (mode: interval)', function(done) { + mapiDoneTestHelper(done, "interval", 2, 0, 2, false, [ + { msg: {payload: 0}, delay: 0, avr: 500, var: 100}, + { msg: {payload: 1}, delay: 100, avr: 500, var: 100}, + { msg: {payload: 2}, delay: 500, avr: 500, var: 100} + ]); + }); + it('should call done() when message is sent (mode: concat)', function(done) { + mapiDoneTestHelper(done, "concat", 2, 0, 2, false, [ + { msg: {topic:"TA", payload: 0, parts: {id: "TA", index: 0, count: 2}}, delay: 0, avr: 1000, var: 100}, + { msg: {topic:"TA", payload: 1, parts: {id: "TA", index: 1, count: 2}}, delay: 1000, avr: 1000, var: 100}, + ]); + }); + it('should call done() when reset (mode: concat)', function(done) { + mapiDoneTestHelper(done, "concat", 2, 0, 2, false, [ + { msg: {topic:"TA", payload: 0, parts: {id: "TA", index: 0, count: 2}}, delay: 0, avr: 1000, var: 100}, + { msg: {payload: 1, reset:true}, delay: 1000, avr: 1000, var: 100}, + ]); + }); + it('should call done() regardless of buffer overflow (mode: concat)', function(done) { + mapiDoneTestHelper(done, "concat", 2, 0, 2, false, [ + { msg: {topic:"TA", payload: 0, parts: {id: "TA", index: 0, count: 3}}, delay: 0, avr: 1000, var: 100}, + { msg: {topic:"TA", payload: 0, parts: {id: "TA", index: 1, count: 3}}, delay: 500, avr: 1000, var: 100}, + { msg: {topic:"TA", payload: 0, parts: {id: "TA", index: 2, count: 3}}, delay: 1000, avr: 1000, var: 100} + ]); + }); + }); }); From d7dfeaf0c1ad69638f1ed4f168b9db96f0027650 Mon Sep 17 00:00:00 2001 From: Kunihiko Toumura Date: Mon, 2 Nov 2020 13:31:27 +0900 Subject: [PATCH 004/114] Messaging API support in Sort node --- .../@node-red/nodes/core/sequence/18-sort.js | 65 ++++++++++++------- test/nodes/core/sequence/18-sort_spec.js | 58 +++++++++++++++++ 2 files changed, 98 insertions(+), 25 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/sequence/18-sort.js b/packages/node_modules/@node-red/nodes/core/sequence/18-sort.js index c330801e2..3bcdfb105 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/18-sort.js +++ b/packages/node_modules/@node-red/nodes/core/sequence/18-sort.js @@ -81,16 +81,16 @@ module.exports = function(RED) { function sortMessageGroup(group) { var promise; - var msgs = group.msgs; + var msgInfos = group.msgInfos; if (key_is_exp) { - var evaluatedDataPromises = msgs.map(msg => { + var evaluatedDataPromises = msgInfos.map(mInfo => { return new Promise((resolve,reject) => { - RED.util.evaluateJSONataExpression(key_exp, msg, (err, result) => { + RED.util.evaluateJSONataExpression(key_exp, mInfo.msg, (err, result) => { if (err) { reject(RED._("sort.invalid-exp",{message:err.toString()})); } else { resolve({ - item: msg, + item: mInfo, sortValue: result }) } @@ -106,20 +106,21 @@ module.exports = function(RED) { var key = function(msg) { return ; } - var comp = generateComparisonFunction(msg => RED.util.getMessageProperty(msg, key_prop)); + var comp = generateComparisonFunction(mInfo => RED.util.getMessageProperty(mInfo.msg, key_prop)); try { - msgs.sort(comp); + msgInfos.sort(comp); } catch (e) { return; // not send when error } - promise = Promise.resolve(msgs); + promise = Promise.resolve(msgInfos); } - return promise.then(msgs => { - for (var i = 0; i < msgs.length; i++) { - var msg = msgs[i]; + return promise.then(msgInfos => { + for (let i = 0; i < msgInfos.length; i++) { + const msg = msgInfos[i].msg; msg.parts.index = i; - node.send(msg); + msgInfos[i].send(msg); + msgInfos[i].done(); } }); } @@ -181,65 +182,79 @@ module.exports = function(RED) { } } if(oldest !== undefined) { + oldest.msgInfos[oldest.msgInfos.length - 1].done(RED._("sort.too-many")); + for (let i = 0; i < oldest.msgInfos.length - 1; i++) { + oldest.msgInfos[i].done(); + } delete pending[oldest_key]; - return oldest.msgs.length; + return oldest.msgInfos.length; } return 0; } - function processMessage(msg) { + function processMessage(msgInfo) { + const msg = msgInfo.msg; if (target_is_prop) { sortMessageProperty(msg).then(send => { if (send) { - node.send(msg); + msgInfo.send(msg); } + msgInfo.done(); }).catch(err => { - node.error(err,msg); + msgInfo.done(err); }); return; } var parts = msg.parts; if (!parts || !parts.hasOwnProperty("id") || !parts.hasOwnProperty("index")) { + msgInfo.done(); return; } var gid = parts.id; if (!pending.hasOwnProperty(gid)) { pending[gid] = { count: undefined, - msgs: [], + msgInfos: [], seq_no: pending_id++ }; } var group = pending[gid]; - var msgs = group.msgs; - msgs.push(msg); + var msgInfos = group.msgInfos; + msgInfos.push(msgInfo); if (parts.hasOwnProperty("count")) { group.count = parts.count; } pending_count++; - if (group.count === msgs.length) { + if (group.count === msgInfos.length) { delete pending[gid] sortMessageGroup(group).catch(err => { - node.error(err,msg); + // throw an error for last message, and just call done() for remaining messages + msgInfos[msgInfos.length-1].done(err); + for (let i = 0; i < msgInfos.length - 1; i++) { + msgInfos[i].done() + }; }); - pending_count -= msgs.length; + pending_count -= msgInfos.length; } else { var max_msgs = max_kept_msgs_count(node); if ((max_msgs > 0) && (pending_count > max_msgs)) { pending_count -= removeOldestPending(); - node.error(RED._("sort.too-many"), msg); } } } - this.on("input", function(msg) { - processMessage(msg); + this.on("input", function(msg, send, done) { + processMessage({msg, send, done}); }); this.on("close", function() { for(var key in pending) { if (pending.hasOwnProperty(key)) { - node.log(RED._("sort.clear"), pending[key].msgs[0]); + node.log(RED._("sort.clear"), pending[key].msgInfos[0]); + const group = pending[key]; + group.msgInfos.forEach(mInfo => { + mInfo.done(); + }); delete pending[key]; } } diff --git a/test/nodes/core/sequence/18-sort_spec.js b/test/nodes/core/sequence/18-sort_spec.js index ac220216c..955038bfb 100644 --- a/test/nodes/core/sequence/18-sort_spec.js +++ b/test/nodes/core/sequence/18-sort_spec.js @@ -493,4 +493,62 @@ describe('SORT node', function() { }); }); + describe('messaging API', function() { + function mapiDoneTestHelper(done, targetType, msgAndTimings) { + const completeNode = require("nr-test-utils").require("@node-red/nodes/core/common/24-complete.js"); + const catchNode = require("nr-test-utils").require("@node-red/nodes/core/common/25-catch.js"); + const flow = [ + {id: "sortNode1", type: "sort", order: "ascending", as_num: false, target: "payload", targetType, + seqKey: "payload", seqKeyType: "msg", wires: [[]]}, + { id: "completeNode1", type: "complete", scope: ["sortNode1"], uncaught: false, wires: [["helperNode1"]] }, + { id: "catchNode1", type: "catch", scope: ["sortNode1"], uncaught: false, wires: [["helperNode1"]] }, + { id: "helperNode1", type: "helper", wires: [[]] }]; + const numMsgs = msgAndTimings.length; + helper.load([sortNode, completeNode, catchNode], flow, function () { + const sortNode1 = helper.getNode("sortNode1"); + const helperNode1 = helper.getNode("helperNode1"); + RED.settings.nodeMessageBufferMaxLength = 2; + const t = Date.now(); + let c = 0; + helperNode1.on("input", function (msg) { + msg.should.have.a.property('payload'); + (Date.now() - t).should.be.approximately(msgAndTimings[msg.seq].avr, msgAndTimings[msg.seq].var); + c += 1; + if (c === numMsgs) { + done(); + } + }); + for (let i = 0; i < numMsgs; i++) { + setTimeout(function () { sortNode1.receive(msgAndTimings[i].msg); }, msgAndTimings[i].delay); + } + }); + } + it('should call done() when message is sent (payload)', function (done) { + mapiDoneTestHelper(done, "msg", [ + { msg: { seq: 0, payload: [1, 3, 2] }, delay: 0, avr: 0, var: 100 }, + ]); + }); + it('should call done() when message is sent (sequence)', function (done) { + mapiDoneTestHelper(done, "seq", [ + { msg: { seq: 0, payload: 3, parts: {id:"A", index: 0, count: 2}}, delay: 0, avr: 500, var: 100 }, + { msg: { seq: 1, payload: 2, parts: {id:"A", index: 1, count: 2}}, delay: 500, avr: 500, var: 100} + ]); + }); + it('should call done() regardless of buffer overflow (same group)', function (done) { + mapiDoneTestHelper(done, "seq", [ + { msg: { seq: 0, payload: 1, parts: {id:"A", index: 0, count: 3}}, delay: 0, avr: 1000, var: 100 }, + { msg: { seq: 1, payload: 3, parts: {id:"A", index: 1, count: 3}}, delay: 500, avr: 1000, var: 100 }, + { msg: { seq: 2, payload: 2, parts: {id:"A", index: 2, count: 3}}, delay: 1000, avr: 1000, var: 100 }, + ]); + }); + it('should call done() regardless of buffer overflow (different group)', function (done) { + mapiDoneTestHelper(done, "seq", [ + { msg: { seq: 0, payload: 1, parts: {id:"A", index: 0, count: 2}}, delay: 0, avr: 1000, var: 100 }, + { msg: { seq: 1, payload: 3, parts: {id:"B", index: 0, count: 2}}, delay: 500, avr: 1200, var: 100 }, + { msg: { seq: 2, payload: 5, parts: {id:"C", index: 0, count: 2}}, delay: 1000, avr: 1500, var: 100 }, + { msg: { seq: 3, payload: 2, parts: {id:"B", index: 1, count: 2}}, delay: 1200, avr: 1200, var: 100 }, + { msg: { seq: 4, payload: 4, parts: {id:"C", index: 1, count: 2}}, delay: 1500, avr: 1500, var: 100 }, + ]); + }); + }); }); From 725c9622364b36130d654a706e98599066567aa7 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 3 Nov 2020 14:34:34 +0900 Subject: [PATCH 005/114] add examples for CSV node --- ...fault column name as message sequence.json | 99 +++++++++ ...CSV with default column name as array.json | 99 +++++++++ ...ified column name as message sequence.json | 99 +++++++++ ...name in first row as message sequence.json | 99 +++++++++ ...05 - Convert JavaScript object to CSV.json | 99 +++++++++ ...06 - Convert JavaScript object to CSV.json | 99 +++++++++ ...bjects to CSV with column name header.json | 99 +++++++++ ...Specify column names in input message.json | 99 +++++++++ ...d column name when reset property set.json | 200 ++++++++++++++++++ ... CSV message sequence using join node.json | 150 +++++++++++++ 10 files changed, 1142 insertions(+) create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/csv/01 - Parse CSV with default column name as message sequence.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/csv/02 - Parse CSV with default column name as array.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/csv/03 - Parse CSV with specified column name as message sequence.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/csv/04 - Parse CSV with column name in first row as message sequence.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/csv/05 - Convert JavaScript object to CSV.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/csv/06 - Convert JavaScript object to CSV.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/csv/07 - Convert array of JavaScript objects to CSV with column name header.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/csv/08 - Specify column names in input message.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/csv/09 - Send column name when reset property set.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/csv/10 - Join parsed CSV message sequence using join node.json diff --git a/packages/node_modules/@node-red/nodes/examples/parser/csv/01 - Parse CSV with default column name as message sequence.json b/packages/node_modules/@node-red/nodes/examples/parser/csv/01 - Parse CSV with default column name as message sequence.json new file mode 100644 index 000000000..bcf37bed8 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/csv/01 - Parse CSV with default column name as message sequence.json @@ -0,0 +1,99 @@ +[ + { + "id": "330f4888.cccb28", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 260, + "y": 180, + "wires": [ + [ + "ed11f8d6.5e3c88" + ] + ] + }, + { + "id": "a0288b44.71d488", + "type": "csv", + "z": "4b63452d.672afc", + "name": "", + "sep": ",", + "hdrin": "", + "hdrout": "none", + "multi": "one", + "ret": "\\n", + "temp": "", + "skip": "0", + "strings": true, + "include_empty_strings": "", + "include_null_values": "", + "x": 600, + "y": 180, + "wires": [ + [ + "369cbe42.4af9f2" + ] + ] + }, + { + "id": "ed11f8d6.5e3c88", + "type": "template", + "z": "4b63452d.672afc", + "name": "CSV data", + "field": "payload", + "fieldType": "msg", + "format": "text", + "syntax": "mustache", + "template": "Apple,100,Canada\nOrange,120,USA\nBanana,80,Philippines", + "output": "str", + "x": 430, + "y": 180, + "wires": [ + [ + "a0288b44.71d488" + ] + ] + }, + { + "id": "369cbe42.4af9f2", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 780, + "y": 180, + "wires": [] + }, + { + "id": "783cfaa6.52fbe4", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Partse CSV with default column name as messages", + "info": "CSV node can parse input CSV data.\nParsed CSV record can be send as a message sequence.\nEach message payload point to an object with `col`*N* as a key and CSV value as a value.\n", + "x": 330, + "y": 120, + "wires": [] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/csv/02 - Parse CSV with default column name as array.json b/packages/node_modules/@node-red/nodes/examples/parser/csv/02 - Parse CSV with default column name as array.json new file mode 100644 index 000000000..bfaab885c --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/csv/02 - Parse CSV with default column name as array.json @@ -0,0 +1,99 @@ +[ + { + "id": "98c9d44d.4457b8", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 260, + "y": 360, + "wires": [ + [ + "65476517.3d760c" + ] + ] + }, + { + "id": "76df98f7.0dcd08", + "type": "csv", + "z": "4b63452d.672afc", + "name": "", + "sep": ",", + "hdrin": "", + "hdrout": "none", + "multi": "mult", + "ret": "\\n", + "temp": "", + "skip": "0", + "strings": true, + "include_empty_strings": "", + "include_null_values": "", + "x": 600, + "y": 360, + "wires": [ + [ + "557979e0.e6b588" + ] + ] + }, + { + "id": "65476517.3d760c", + "type": "template", + "z": "4b63452d.672afc", + "name": "CSV data", + "field": "payload", + "fieldType": "msg", + "format": "text", + "syntax": "mustache", + "template": "Apple,100,Canada\nOrange,120,USA\nBanana,80,Philippines", + "output": "str", + "x": 430, + "y": 360, + "wires": [ + [ + "76df98f7.0dcd08" + ] + ] + }, + { + "id": "557979e0.e6b588", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 780, + "y": 360, + "wires": [] + }, + { + "id": "187f4ab3.4c9ab5", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Partse CSV with default column name as array", + "info": "CSV node can send single message with array of parsed CSV records.\nEach element of the array consists of objects with key-value pair.", + "x": 320, + "y": 300, + "wires": [] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/csv/03 - Parse CSV with specified column name as message sequence.json b/packages/node_modules/@node-red/nodes/examples/parser/csv/03 - Parse CSV with specified column name as message sequence.json new file mode 100644 index 000000000..abb1fccda --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/csv/03 - Parse CSV with specified column name as message sequence.json @@ -0,0 +1,99 @@ +[ + { + "id": "1216e95b.1b1e87", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 260, + "y": 560, + "wires": [ + [ + "e41ffbbc.de2ed8" + ] + ] + }, + { + "id": "286828bc.9233c8", + "type": "csv", + "z": "4b63452d.672afc", + "name": "", + "sep": ",", + "hdrin": "", + "hdrout": "none", + "multi": "one", + "ret": "\\n", + "temp": "kind,price,origin", + "skip": "0", + "strings": true, + "include_empty_strings": "", + "include_null_values": "", + "x": 600, + "y": 560, + "wires": [ + [ + "9d8218c.5550ee8" + ] + ] + }, + { + "id": "e41ffbbc.de2ed8", + "type": "template", + "z": "4b63452d.672afc", + "name": "CSV data", + "field": "payload", + "fieldType": "msg", + "format": "text", + "syntax": "mustache", + "template": "Apple,100,Canada\nOrange,120,USA\nBanana,80,Philippines", + "output": "str", + "x": 430, + "y": 560, + "wires": [ + [ + "286828bc.9233c8" + ] + ] + }, + { + "id": "9d8218c.5550ee8", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 780, + "y": 560, + "wires": [] + }, + { + "id": "aaa1ee8f.21e2c", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Partse CSV with specified column name as messages", + "info": "CSV node can specify column name of parsed objects in its settings panel.", + "x": 340, + "y": 500, + "wires": [] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/csv/04 - Parse CSV with column name in first row as message sequence.json b/packages/node_modules/@node-red/nodes/examples/parser/csv/04 - Parse CSV with column name in first row as message sequence.json new file mode 100644 index 000000000..580849567 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/csv/04 - Parse CSV with column name in first row as message sequence.json @@ -0,0 +1,99 @@ +[ + { + "id": "24093558.0315aa", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 260, + "y": 740, + "wires": [ + [ + "80abaee1.5fa7f" + ] + ] + }, + { + "id": "d4d2ca3f.1d9488", + "type": "csv", + "z": "4b63452d.672afc", + "name": "", + "sep": ",", + "hdrin": true, + "hdrout": "none", + "multi": "one", + "ret": "\\n", + "temp": "", + "skip": "0", + "strings": true, + "include_empty_strings": "", + "include_null_values": "", + "x": 600, + "y": 740, + "wires": [ + [ + "b52791c3.08967" + ] + ] + }, + { + "id": "80abaee1.5fa7f", + "type": "template", + "z": "4b63452d.672afc", + "name": "CSV data", + "field": "payload", + "fieldType": "msg", + "format": "text", + "syntax": "mustache", + "template": "kind,price,origin\nApple,100,Canada\nOrange,120,USA\nBanana,80,Philippines", + "output": "str", + "x": 430, + "y": 740, + "wires": [ + [ + "d4d2ca3f.1d9488" + ] + ] + }, + { + "id": "b52791c3.08967", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 780, + "y": 740, + "wires": [] + }, + { + "id": "85091361.85644", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Partse CSV with column name in first row as messages", + "info": "CSV node can use first row of input CSV text as a column name of each record object.\n", + "x": 340, + "y": 680, + "wires": [] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/csv/05 - Convert JavaScript object to CSV.json b/packages/node_modules/@node-red/nodes/examples/parser/csv/05 - Convert JavaScript object to CSV.json new file mode 100644 index 000000000..a97656085 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/csv/05 - Convert JavaScript object to CSV.json @@ -0,0 +1,99 @@ +[ + { + "id": "9e93169c.b763a8", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Convert JavaScript object to CSV", + "info": "CSV node can convert a JavaScript object to CSV text.\nEach object contains key-value pair of specified properties.\n", + "x": 270, + "y": 860, + "wires": [] + }, + { + "id": "8ca41fee.3303d", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 260, + "y": 920, + "wires": [ + [ + "c466905b.e8c61" + ] + ] + }, + { + "id": "65146d20.d78204", + "type": "csv", + "z": "4b63452d.672afc", + "name": "", + "sep": ",", + "hdrin": false, + "hdrout": "none", + "multi": "one", + "ret": "\\n", + "temp": "kind,price", + "skip": "0", + "strings": true, + "include_empty_strings": "", + "include_null_values": "", + "x": 600, + "y": 920, + "wires": [ + [ + "92e99e67.a37d8" + ] + ] + }, + { + "id": "c466905b.e8c61", + "type": "template", + "z": "4b63452d.672afc", + "name": "JS object", + "field": "payload", + "fieldType": "msg", + "format": "json", + "syntax": "plain", + "template": "{\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n}", + "output": "json", + "x": 430, + "y": 920, + "wires": [ + [ + "65146d20.d78204" + ] + ] + }, + { + "id": "92e99e67.a37d8", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 780, + "y": 920, + "wires": [] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/csv/06 - Convert JavaScript object to CSV.json b/packages/node_modules/@node-red/nodes/examples/parser/csv/06 - Convert JavaScript object to CSV.json new file mode 100644 index 000000000..0266dbe49 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/csv/06 - Convert JavaScript object to CSV.json @@ -0,0 +1,99 @@ +[ + { + "id": "e89019c5.70ae78", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Convert array of JavaScript objects to CSV", + "info": "CSV node can convert an array of JavaScript objects to multi-line CSV text.", + "x": 300, + "y": 1020, + "wires": [] + }, + { + "id": "bd0d82ed.7b28", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 260, + "y": 1080, + "wires": [ + [ + "1d857b8d.3a4014" + ] + ] + }, + { + "id": "66a37667.16ebd8", + "type": "csv", + "z": "4b63452d.672afc", + "name": "", + "sep": ",", + "hdrin": false, + "hdrout": "none", + "multi": "one", + "ret": "\\n", + "temp": "kind,price", + "skip": "0", + "strings": true, + "include_empty_strings": "", + "include_null_values": "", + "x": 600, + "y": 1080, + "wires": [ + [ + "859725fd.dc93d8" + ] + ] + }, + { + "id": "1d857b8d.3a4014", + "type": "template", + "z": "4b63452d.672afc", + "name": "JS object", + "field": "payload", + "fieldType": "msg", + "format": "json", + "syntax": "plain", + "template": "[\n {\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n },\n {\n \"kind\": \"Orange\",\n \"price\": 120,\n \"origin\": \"USA\"\n },\n {\n \"kind\": \"Banana\",\n \"price\": 80,\n \"origin\": \"Philippines\"\n }\n]", + "output": "json", + "x": 430, + "y": 1080, + "wires": [ + [ + "66a37667.16ebd8" + ] + ] + }, + { + "id": "859725fd.dc93d8", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 780, + "y": 1080, + "wires": [] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/csv/07 - Convert array of JavaScript objects to CSV with column name header.json b/packages/node_modules/@node-red/nodes/examples/parser/csv/07 - Convert array of JavaScript objects to CSV with column name header.json new file mode 100644 index 000000000..b1b337a06 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/csv/07 - Convert array of JavaScript objects to CSV with column name header.json @@ -0,0 +1,99 @@ +[ + { + "id": "2ebdd51e.c5d17a", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Convert array of JavaScript objects to CSV with column name header", + "info": "CSV node can convert an array of JavaScript objects to multi-line CSV text with column name header at first line.", + "x": 390, + "y": 1200, + "wires": [] + }, + { + "id": "2b4d538d.ada07c", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 260, + "y": 1260, + "wires": [ + [ + "3e5c9e8.5065b62" + ] + ] + }, + { + "id": "db02c7be.0984e8", + "type": "csv", + "z": "4b63452d.672afc", + "name": "", + "sep": ",", + "hdrin": false, + "hdrout": "all", + "multi": "one", + "ret": "\\n", + "temp": "kind,price", + "skip": "0", + "strings": true, + "include_empty_strings": "", + "include_null_values": "", + "x": 600, + "y": 1260, + "wires": [ + [ + "61f8b772.ddb1f8" + ] + ] + }, + { + "id": "3e5c9e8.5065b62", + "type": "template", + "z": "4b63452d.672afc", + "name": "JS object", + "field": "payload", + "fieldType": "msg", + "format": "json", + "syntax": "plain", + "template": "[\n {\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n },\n {\n \"kind\": \"Orange\",\n \"price\": 120,\n \"origin\": \"USA\"\n },\n {\n \"kind\": \"Banana\",\n \"price\": 80,\n \"origin\": \"Philippines\"\n }\n]", + "output": "json", + "x": 430, + "y": 1260, + "wires": [ + [ + "db02c7be.0984e8" + ] + ] + }, + { + "id": "61f8b772.ddb1f8", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 780, + "y": 1260, + "wires": [] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/csv/08 - Specify column names in input message.json b/packages/node_modules/@node-red/nodes/examples/parser/csv/08 - Specify column names in input message.json new file mode 100644 index 000000000..b1b337a06 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/csv/08 - Specify column names in input message.json @@ -0,0 +1,99 @@ +[ + { + "id": "2ebdd51e.c5d17a", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Convert array of JavaScript objects to CSV with column name header", + "info": "CSV node can convert an array of JavaScript objects to multi-line CSV text with column name header at first line.", + "x": 390, + "y": 1200, + "wires": [] + }, + { + "id": "2b4d538d.ada07c", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 260, + "y": 1260, + "wires": [ + [ + "3e5c9e8.5065b62" + ] + ] + }, + { + "id": "db02c7be.0984e8", + "type": "csv", + "z": "4b63452d.672afc", + "name": "", + "sep": ",", + "hdrin": false, + "hdrout": "all", + "multi": "one", + "ret": "\\n", + "temp": "kind,price", + "skip": "0", + "strings": true, + "include_empty_strings": "", + "include_null_values": "", + "x": 600, + "y": 1260, + "wires": [ + [ + "61f8b772.ddb1f8" + ] + ] + }, + { + "id": "3e5c9e8.5065b62", + "type": "template", + "z": "4b63452d.672afc", + "name": "JS object", + "field": "payload", + "fieldType": "msg", + "format": "json", + "syntax": "plain", + "template": "[\n {\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n },\n {\n \"kind\": \"Orange\",\n \"price\": 120,\n \"origin\": \"USA\"\n },\n {\n \"kind\": \"Banana\",\n \"price\": 80,\n \"origin\": \"Philippines\"\n }\n]", + "output": "json", + "x": 430, + "y": 1260, + "wires": [ + [ + "db02c7be.0984e8" + ] + ] + }, + { + "id": "61f8b772.ddb1f8", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 780, + "y": 1260, + "wires": [] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/csv/09 - Send column name when reset property set.json b/packages/node_modules/@node-red/nodes/examples/parser/csv/09 - Send column name when reset property set.json new file mode 100644 index 000000000..81d9b4730 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/csv/09 - Send column name when reset property set.json @@ -0,0 +1,200 @@ +[ + { + "id": "1ae28939.9f5fc7", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Send column name when reset property set", + "info": "CSV node can send column names at first or `reset` property exists in input message.", + "x": 310, + "y": 1540, + "wires": [] + }, + { + "id": "c16ad95b.4f9ac8", + "type": "inject", + "z": "4b63452d.672afc", + "name": "Apple", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 250, + "y": 1600, + "wires": [ + [ + "7f7bfc72.aed104" + ] + ] + }, + { + "id": "870620b9.95343", + "type": "csv", + "z": "4b63452d.672afc", + "name": "", + "sep": ",", + "hdrin": false, + "hdrout": "once", + "multi": "one", + "ret": "\\n", + "temp": "kind,price", + "skip": "0", + "strings": true, + "include_empty_strings": "", + "include_null_values": "", + "x": 650, + "y": 1720, + "wires": [ + [ + "d960de42.619c7" + ] + ] + }, + { + "id": "7f7bfc72.aed104", + "type": "template", + "z": "4b63452d.672afc", + "name": "JS object", + "field": "payload", + "fieldType": "msg", + "format": "json", + "syntax": "plain", + "template": "{\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n}", + "output": "json", + "x": 470, + "y": 1600, + "wires": [ + [ + "870620b9.95343" + ] + ] + }, + { + "id": "d960de42.619c7", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 830, + "y": 1720, + "wires": [] + }, + { + "id": "6f8296e.f95ca68", + "type": "inject", + "z": "4b63452d.672afc", + "name": "Orange", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 250, + "y": 1660, + "wires": [ + [ + "c37d0dfa.ec1ab" + ] + ] + }, + { + "id": "c37d0dfa.ec1ab", + "type": "template", + "z": "4b63452d.672afc", + "name": "JS object", + "field": "payload", + "fieldType": "msg", + "format": "json", + "syntax": "plain", + "template": "{\n \"kind\": \"Orange\",\n \"price\": 120,\n \"origin\": \"USA\"\n}\n", + "output": "json", + "x": 470, + "y": 1660, + "wires": [ + [ + "870620b9.95343" + ] + ] + }, + { + "id": "35209fe2.16926", + "type": "inject", + "z": "4b63452d.672afc", + "name": "Banana & reset", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + }, + { + "p": "reset", + "v": "", + "vt": "date" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 280, + "y": 1720, + "wires": [ + [ + "afd4e6b3.624a28" + ] + ] + }, + { + "id": "afd4e6b3.624a28", + "type": "template", + "z": "4b63452d.672afc", + "name": "JS object", + "field": "payload", + "fieldType": "msg", + "format": "json", + "syntax": "plain", + "template": "{\n \"kind\": \"Banana\",\n \"price\": 80,\n \"origin\": \"Philippines\"\n}", + "output": "json", + "x": 470, + "y": 1720, + "wires": [ + [ + "870620b9.95343" + ] + ] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/csv/10 - Join parsed CSV message sequence using join node.json b/packages/node_modules/@node-red/nodes/examples/parser/csv/10 - Join parsed CSV message sequence using join node.json new file mode 100644 index 000000000..87036d8ee --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/csv/10 - Join parsed CSV message sequence using join node.json @@ -0,0 +1,150 @@ +[ + { + "id": "195c168c.44f149", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 260, + "y": 1900, + "wires": [ + [ + "b270564c.171908" + ] + ] + }, + { + "id": "8ec8cf9e.103fa", + "type": "csv", + "z": "4b63452d.672afc", + "name": "", + "sep": ",", + "hdrin": true, + "hdrout": "none", + "multi": "one", + "ret": "\\n", + "temp": "", + "skip": "0", + "strings": true, + "include_empty_strings": "", + "include_null_values": "", + "x": 600, + "y": 1900, + "wires": [ + [ + "5c5254a8.bc562c" + ] + ] + }, + { + "id": "b270564c.171908", + "type": "template", + "z": "4b63452d.672afc", + "name": "CSV data", + "field": "payload", + "fieldType": "msg", + "format": "text", + "syntax": "mustache", + "template": "kind,price,origin\nApple,100,Canada\nOrange,120,USA\nBanana,80,Philippines", + "output": "str", + "x": 430, + "y": 1900, + "wires": [ + [ + "8ec8cf9e.103fa" + ] + ] + }, + { + "id": "1c7be442.6a4bdc", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1090, + "y": 1900, + "wires": [] + }, + { + "id": "d3da7cfb.cf596", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Join parsed CSV message sequence using join node", + "info": "Parset CSV message sequence can be joined by join node.", + "x": 330, + "y": 1840, + "wires": [] + }, + { + "id": "a07c9e26.c84fd", + "type": "csv", + "z": "4b63452d.672afc", + "name": "", + "sep": ",", + "hdrin": "", + "hdrout": "none", + "multi": "one", + "ret": "\\n", + "temp": "kind,price", + "skip": "0", + "strings": true, + "include_empty_strings": "", + "include_null_values": "", + "x": 910, + "y": 1900, + "wires": [ + [ + "1c7be442.6a4bdc" + ] + ] + }, + { + "id": "5c5254a8.bc562c", + "type": "join", + "z": "4b63452d.672afc", + "name": "", + "mode": "auto", + "build": "string", + "property": "payload", + "propertyType": "msg", + "key": "topic", + "joiner": "\\n", + "joinerType": "str", + "accumulate": false, + "timeout": "", + "count": "", + "reduceRight": false, + "reduceExp": "", + "reduceInit": "", + "reduceInitType": "", + "reduceFixup": "", + "x": 750, + "y": 1900, + "wires": [ + [ + "a07c9e26.c84fd" + ] + ] + } +] \ No newline at end of file From bc82ca21060eac6b4dbd7dc46b559c7021b765a6 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 3 Nov 2020 17:32:53 +0900 Subject: [PATCH 006/114] add examples for HTML node --- ...array of HTML element by CSS selector.json | 94 ++++++++++++++ ...uence of HTML element by CSS selector.json | 94 ++++++++++++++ ... by CSS selector specified in message.json | 121 +++++++++++++++++ ...uence of HTML element using join node.json | 122 ++++++++++++++++++ 4 files changed, 431 insertions(+) create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/html/01 - Extract array of HTML element by CSS selector.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/html/02 - Extract sequence of HTML element by CSS selector.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/html/03 - Extract array of HTML element by CSS selector specified in message.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/html/04 - Join extracedt sequence of HTML element using join node.json diff --git a/packages/node_modules/@node-red/nodes/examples/parser/html/01 - Extract array of HTML element by CSS selector.json b/packages/node_modules/@node-red/nodes/examples/parser/html/01 - Extract array of HTML element by CSS selector.json new file mode 100644 index 000000000..14ef21214 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/html/01 - Extract array of HTML element by CSS selector.json @@ -0,0 +1,94 @@ +[ + { + "id": "8c5224a6.201b88", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 220, + "y": 180, + "wires": [ + [ + "d6c67e51.0d709" + ] + ] + }, + { + "id": "d6c67e51.0d709", + "type": "template", + "z": "4b63452d.672afc", + "name": "HTML text", + "field": "payload", + "fieldType": "msg", + "format": "handlebars", + "syntax": "plain", + "template": "\n \n List of Fruits\n \n \n
    \n
  • Apple
  • \n
  • Orange
  • \n
  • Banana
  • \n
\n \n\n", + "output": "str", + "x": 390, + "y": 180, + "wires": [ + [ + "599a1155.61a5c" + ] + ] + }, + { + "id": "b0d5cd89.338df", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Extract array of HTML element by CSS selector", + "info": "HTML node can be used to extract elements in HTML document as an array using CSS selector.", + "x": 280, + "y": 120, + "wires": [] + }, + { + "id": "599a1155.61a5c", + "type": "html", + "z": "4b63452d.672afc", + "name": "", + "property": "payload", + "outproperty": "payload", + "tag": ".Item", + "ret": "html", + "as": "single", + "x": 550, + "y": 180, + "wires": [ + [ + "942b23d1.cce09" + ] + ] + }, + { + "id": "942b23d1.cce09", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 710, + "y": 180, + "wires": [] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/html/02 - Extract sequence of HTML element by CSS selector.json b/packages/node_modules/@node-red/nodes/examples/parser/html/02 - Extract sequence of HTML element by CSS selector.json new file mode 100644 index 000000000..ccd3bc782 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/html/02 - Extract sequence of HTML element by CSS selector.json @@ -0,0 +1,94 @@ +[ + { + "id": "a44973e8.6319b", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 220, + "y": 360, + "wires": [ + [ + "de1b012e.96ec3" + ] + ] + }, + { + "id": "de1b012e.96ec3", + "type": "template", + "z": "4b63452d.672afc", + "name": "HTML text", + "field": "payload", + "fieldType": "msg", + "format": "handlebars", + "syntax": "plain", + "template": "\n \n List of Fruits\n \n \n
    \n
  • Apple
  • \n
  • Orange
  • \n
  • Banana
  • \n
\n \n\n", + "output": "str", + "x": 390, + "y": 360, + "wires": [ + [ + "cee70712.6f3538" + ] + ] + }, + { + "id": "99e32bc7.c8e508", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Extract sequence of HTML element by CSS selector", + "info": "HTML node can be used to extract elements in HTML document as a messege sequence using CSS selector.", + "x": 290, + "y": 300, + "wires": [] + }, + { + "id": "cee70712.6f3538", + "type": "html", + "z": "4b63452d.672afc", + "name": "", + "property": "payload", + "outproperty": "payload", + "tag": ".Item", + "ret": "html", + "as": "multi", + "x": 550, + "y": 360, + "wires": [ + [ + "17f25482.d4b56b" + ] + ] + }, + { + "id": "17f25482.d4b56b", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 710, + "y": 360, + "wires": [] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/html/03 - Extract array of HTML element by CSS selector specified in message.json b/packages/node_modules/@node-red/nodes/examples/parser/html/03 - Extract array of HTML element by CSS selector specified in message.json new file mode 100644 index 000000000..ad2400ef6 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/html/03 - Extract array of HTML element by CSS selector specified in message.json @@ -0,0 +1,121 @@ +[ + { + "id": "653ce9aa.b6a1c8", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 220, + "y": 560, + "wires": [ + [ + "52a16f7f.447d8" + ] + ] + }, + { + "id": "52a16f7f.447d8", + "type": "template", + "z": "4b63452d.672afc", + "name": "HTML text", + "field": "payload", + "fieldType": "msg", + "format": "handlebars", + "syntax": "plain", + "template": "\n \n List of Fruits\n \n \n
    \n
  • Apple
  • \n
  • Orange
  • \n
  • Banana
  • \n
\n \n\n", + "output": "str", + "x": 390, + "y": 560, + "wires": [ + [ + "a52319c3.89b008" + ] + ] + }, + { + "id": "8bc35379.31d99", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Extract array of HTML element by CSS selector specified in message", + "info": "CSS selector for HTML node can be specified by `select` property of input message.", + "x": 350, + "y": 500, + "wires": [] + }, + { + "id": "9c49de8a.bad25", + "type": "html", + "z": "4b63452d.672afc", + "name": "", + "property": "payload", + "outproperty": "payload", + "tag": "", + "ret": "html", + "as": "single", + "x": 730, + "y": 560, + "wires": [ + [ + "d4f4b987.278a68" + ] + ] + }, + { + "id": "d4f4b987.278a68", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 890, + "y": 560, + "wires": [] + }, + { + "id": "a52319c3.89b008", + "type": "change", + "z": "4b63452d.672afc", + "name": "", + "rules": [ + { + "t": "set", + "p": "select", + "pt": "msg", + "to": ".Item", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 560, + "y": 560, + "wires": [ + [ + "9c49de8a.bad25" + ] + ] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/html/04 - Join extracedt sequence of HTML element using join node.json b/packages/node_modules/@node-red/nodes/examples/parser/html/04 - Join extracedt sequence of HTML element using join node.json new file mode 100644 index 000000000..919953284 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/html/04 - Join extracedt sequence of HTML element using join node.json @@ -0,0 +1,122 @@ +[ + { + "id": "66cff4ee.f2761c", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 220, + "y": 760, + "wires": [ + [ + "2baaf6bf.0a02ca" + ] + ] + }, + { + "id": "2baaf6bf.0a02ca", + "type": "template", + "z": "4b63452d.672afc", + "name": "HTML text", + "field": "payload", + "fieldType": "msg", + "format": "handlebars", + "syntax": "plain", + "template": "\n \n List of Fruits\n \n \n
    \n
  • Apple
  • \n
  • Orange
  • \n
  • Banana
  • \n
\n \n\n", + "output": "str", + "x": 390, + "y": 760, + "wires": [ + [ + "bbb22e6b.0fa25" + ] + ] + }, + { + "id": "a57d35d0.8aa538", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Join extracedt sequence of HTML element using join node", + "info": "Message sequence extracted by HTML node can be combined using join node.", + "x": 310, + "y": 700, + "wires": [] + }, + { + "id": "bbb22e6b.0fa25", + "type": "html", + "z": "4b63452d.672afc", + "name": "", + "property": "payload", + "outproperty": "payload", + "tag": ".Item", + "ret": "html", + "as": "multi", + "x": 550, + "y": 760, + "wires": [ + [ + "bd01ca4.966ad38" + ] + ] + }, + { + "id": "4d2616a8.84de88", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 850, + "y": 760, + "wires": [] + }, + { + "id": "bd01ca4.966ad38", + "type": "join", + "z": "4b63452d.672afc", + "name": "", + "mode": "custom", + "build": "string", + "property": "payload", + "propertyType": "msg", + "key": "topic", + "joiner": ",", + "joinerType": "str", + "accumulate": false, + "timeout": "", + "count": "", + "reduceRight": false, + "reduceExp": "", + "reduceInit": "", + "reduceInitType": "", + "reduceFixup": "", + "x": 690, + "y": 760, + "wires": [ + [ + "4d2616a8.84de88" + ] + ] + } +] \ No newline at end of file From 468cfeffb6758d0397a3e6e5ca86f9eed27c115f Mon Sep 17 00:00:00 2001 From: martinb Date: Tue, 3 Nov 2020 09:35:21 +0100 Subject: [PATCH 007/114] make split node work with out of order messages as long as one of the messages has msg.parts.count set to the proper value --- .../node_modules/@node-red/nodes/core/sequence/17-split.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js index 3f774d68c..fbbae8048 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js +++ b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js @@ -677,7 +677,10 @@ module.exports = function(RED) { } group.msg = Object.assign(group.msg, msg); var tcnt = group.targetCount; - if (msg.hasOwnProperty("parts")) { tcnt = group.targetCount || msg.parts.count; } + if (msg.hasOwnProperty("parts")) { + tcnt = group.targetCount || msg.parts.count; + group.targetCount = tcnt; + } if ((tcnt > 0 && group.currentCount >= tcnt) || msg.hasOwnProperty('complete')) { completeSend(partId); } @@ -697,3 +700,4 @@ module.exports = function(RED) { } RED.nodes.registerType("join",JoinNode); } + From dac7830bd4b6f9a50a15b1a8fb11d341be5cb8cd Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 3 Nov 2020 20:06:18 +0900 Subject: [PATCH 008/114] add example for JSON node --- ...vert JSON string to JavaScript object.json | 92 ++++++++++ ...vert JavaScript object to JSON string.json | 92 ++++++++++ .../json/03 - Validate input JSON string.json | 160 ++++++++++++++++++ 3 files changed, 344 insertions(+) create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/json/01 - Convert JSON string to JavaScript object.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/json/02 - Convert JavaScript object to JSON string.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/json/03 - Validate input JSON string.json diff --git a/packages/node_modules/@node-red/nodes/examples/parser/json/01 - Convert JSON string to JavaScript object.json b/packages/node_modules/@node-red/nodes/examples/parser/json/01 - Convert JSON string to JavaScript object.json new file mode 100644 index 000000000..c95e71a7d --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/json/01 - Convert JSON string to JavaScript object.json @@ -0,0 +1,92 @@ +[ + { + "id": "9976e95d.2f8398", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 240, + "y": 180, + "wires": [ + [ + "d94fc083.49d87" + ] + ] + }, + { + "id": "6684abb1.8eb454", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Convert JSON string to JS object", + "info": "JSON node can convert JSON string to JavaScript object.", + "x": 250, + "y": 120, + "wires": [] + }, + { + "id": "d94fc083.49d87", + "type": "template", + "z": "4b63452d.672afc", + "name": "JSON string", + "field": "payload", + "fieldType": "msg", + "format": "json", + "syntax": "plain", + "template": "{\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n}", + "output": "str", + "x": 410, + "y": 180, + "wires": [ + [ + "1a3dc54a.78598b" + ] + ] + }, + { + "id": "8950a55d.023988", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 730, + "y": 180, + "wires": [] + }, + { + "id": "1a3dc54a.78598b", + "type": "json", + "z": "4b63452d.672afc", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 570, + "y": 180, + "wires": [ + [ + "8950a55d.023988" + ] + ] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/json/02 - Convert JavaScript object to JSON string.json b/packages/node_modules/@node-red/nodes/examples/parser/json/02 - Convert JavaScript object to JSON string.json new file mode 100644 index 000000000..a10cc75b9 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/json/02 - Convert JavaScript object to JSON string.json @@ -0,0 +1,92 @@ +[ + { + "id": "cb13761f.56c328", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 240, + "y": 380, + "wires": [ + [ + "c607642a.78c3c8" + ] + ] + }, + { + "id": "180b1e22.0074e2", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Convert JS object to JSON string", + "info": "JSON node can convert JavaScript object to JSON string.", + "x": 250, + "y": 320, + "wires": [] + }, + { + "id": "c607642a.78c3c8", + "type": "template", + "z": "4b63452d.672afc", + "name": "JS object", + "field": "payload", + "fieldType": "msg", + "format": "json", + "syntax": "plain", + "template": "{\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n}", + "output": "json", + "x": 400, + "y": 380, + "wires": [ + [ + "bf309844.fa12e8" + ] + ] + }, + { + "id": "5b6b130b.72a14c", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 730, + "y": 380, + "wires": [] + }, + { + "id": "bf309844.fa12e8", + "type": "json", + "z": "4b63452d.672afc", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 570, + "y": 380, + "wires": [ + [ + "5b6b130b.72a14c" + ] + ] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/json/03 - Validate input JSON string.json b/packages/node_modules/@node-red/nodes/examples/parser/json/03 - Validate input JSON string.json new file mode 100644 index 000000000..6271f00c6 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/json/03 - Validate input JSON string.json @@ -0,0 +1,160 @@ +[ + { + "id": "2b18621b.e2670e", + "type": "inject", + "z": "4b63452d.672afc", + "name": "OK", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 230, + "y": 580, + "wires": [ + [ + "5986faee.aef954" + ] + ] + }, + { + "id": "59acf99.9a92308", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Validate input JSON string", + "info": "JSON node can validate input JSON string using [JSON schema](https://json-schema.org/) when converting to JavaScript object.", + "x": 230, + "y": 520, + "wires": [] + }, + { + "id": "5986faee.aef954", + "type": "template", + "z": "4b63452d.672afc", + "name": "JSON string", + "field": "payload", + "fieldType": "msg", + "format": "json", + "syntax": "plain", + "template": "{\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n}", + "output": "str", + "x": 410, + "y": 580, + "wires": [ + [ + "f8a67c6d.4f1f1" + ] + ] + }, + { + "id": "ca27c92c.ad7cb8", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 910, + "y": 580, + "wires": [] + }, + { + "id": "2fad9978.ea1916", + "type": "json", + "z": "4b63452d.672afc", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 750, + "y": 580, + "wires": [ + [ + "ca27c92c.ad7cb8" + ] + ] + }, + { + "id": "f8a67c6d.4f1f1", + "type": "template", + "z": "4b63452d.672afc", + "name": "Schema", + "field": "schema", + "fieldType": "msg", + "format": "json", + "syntax": "plain", + "template": "{\n \"type\": \"object\",\n \"properties\": {\n \"kind\": {\n \"type\": \"string\"\n },\n \"price\": {\n \"type\": \"number\"\n },\n \"origin\": {\n \"type\": \"string\"\n }\n }\n}", + "output": "json", + "x": 590, + "y": 580, + "wires": [ + [ + "2fad9978.ea1916" + ] + ] + }, + { + "id": "8337e847.ac18d8", + "type": "inject", + "z": "4b63452d.672afc", + "name": "NG", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 230, + "y": 660, + "wires": [ + [ + "fa14d8bf.1ac938" + ] + ] + }, + { + "id": "fa14d8bf.1ac938", + "type": "template", + "z": "4b63452d.672afc", + "name": "JSON string", + "field": "payload", + "fieldType": "msg", + "format": "json", + "syntax": "plain", + "template": "{\n \"kind\": \"Apple\",\n \"price\": \"100\",\n \"origin\": \"Canada\"\n}", + "output": "str", + "x": 410, + "y": 660, + "wires": [ + [ + "f8a67c6d.4f1f1" + ] + ] + } +] \ No newline at end of file From ccbd179f23b5e42b631b71855eda10028e848914 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 3 Nov 2020 20:06:27 +0900 Subject: [PATCH 009/114] add example for XML node --- ...01 - Convert JavaScript object to XML.json | 92 ++++++++++++++ ...02 - Convert XML to JavaScript object.json | 92 ++++++++++++++ ...rol conversion using options property.json | 119 ++++++++++++++++++ 3 files changed, 303 insertions(+) create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/xml/01 - Convert JavaScript object to XML.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/xml/02 - Convert XML to JavaScript object.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/xml/03 - Control conversion using options property.json diff --git a/packages/node_modules/@node-red/nodes/examples/parser/xml/01 - Convert JavaScript object to XML.json b/packages/node_modules/@node-red/nodes/examples/parser/xml/01 - Convert JavaScript object to XML.json new file mode 100644 index 000000000..3a97c8498 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/xml/01 - Convert JavaScript object to XML.json @@ -0,0 +1,92 @@ +[ + { + "id": "82f1bd0b.43474", + "type": "xml", + "z": "4b63452d.672afc", + "name": "", + "property": "payload", + "attr": "", + "chr": "", + "x": 530, + "y": 180, + "wires": [ + [ + "1cd4ad02.9a5423" + ] + ] + }, + { + "id": "84222b92.d65d18", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 200, + "y": 180, + "wires": [ + [ + "cdd1c154.3a655" + ] + ] + }, + { + "id": "7b014430.dfd94c", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Convert JavaScript object to XML", + "info": "XML node can convert JavaScript object to XML string.", + "x": 240, + "y": 120, + "wires": [] + }, + { + "id": "1cd4ad02.9a5423", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 690, + "y": 180, + "wires": [] + }, + { + "id": "cdd1c154.3a655", + "type": "template", + "z": "4b63452d.672afc", + "name": "JS object", + "field": "payload", + "fieldType": "msg", + "format": "json", + "syntax": "plain", + "template": "{\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n}", + "output": "json", + "x": 360, + "y": 180, + "wires": [ + [ + "82f1bd0b.43474" + ] + ] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/xml/02 - Convert XML to JavaScript object.json b/packages/node_modules/@node-red/nodes/examples/parser/xml/02 - Convert XML to JavaScript object.json new file mode 100644 index 000000000..5e46428ef --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/xml/02 - Convert XML to JavaScript object.json @@ -0,0 +1,92 @@ +[ + { + "id": "93e423a9.a407d", + "type": "xml", + "z": "4b63452d.672afc", + "name": "", + "property": "payload", + "attr": "", + "chr": "", + "x": 530, + "y": 360, + "wires": [ + [ + "2d0dde7e.a50082" + ] + ] + }, + { + "id": "ba1dab90.8d1da8", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 200, + "y": 360, + "wires": [ + [ + "16617f26.14ced1" + ] + ] + }, + { + "id": "a9f97b00.57d658", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Convert XML to JavaScript object", + "info": "XML node can convert XML string to JavaScript object.", + "x": 240, + "y": 300, + "wires": [] + }, + { + "id": "2d0dde7e.a50082", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 690, + "y": 360, + "wires": [] + }, + { + "id": "16617f26.14ced1", + "type": "template", + "z": "4b63452d.672afc", + "name": "XML string", + "field": "payload", + "fieldType": "msg", + "format": "html", + "syntax": "plain", + "template": "\n\n Apple\n 100\n Canada\n", + "output": "str", + "x": 370, + "y": 360, + "wires": [ + [ + "93e423a9.a407d" + ] + ] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/xml/03 - Control conversion using options property.json b/packages/node_modules/@node-red/nodes/examples/parser/xml/03 - Control conversion using options property.json new file mode 100644 index 000000000..7a3c39f7e --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/xml/03 - Control conversion using options property.json @@ -0,0 +1,119 @@ +[ + { + "id": "581bd648.636628", + "type": "xml", + "z": "4b63452d.672afc", + "name": "", + "property": "payload", + "attr": "", + "chr": "", + "x": 710, + "y": 540, + "wires": [ + [ + "b74237dc.1e5028" + ] + ] + }, + { + "id": "d0899f9b.f1ac6", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 200, + "y": 540, + "wires": [ + [ + "f04ffb9a.68edb8" + ] + ] + }, + { + "id": "8a214c05.dc61f", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Control conversion using options property", + "info": "XML node can control conversion by setting `options` property (defined by [xml2js](https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md#options)) in input message.", + "x": 260, + "y": 480, + "wires": [] + }, + { + "id": "b74237dc.1e5028", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 870, + "y": 540, + "wires": [] + }, + { + "id": "f04ffb9a.68edb8", + "type": "template", + "z": "4b63452d.672afc", + "name": "XML string", + "field": "payload", + "fieldType": "msg", + "format": "html", + "syntax": "plain", + "template": "\n\n Apple\n 100\n Canada\n", + "output": "str", + "x": 370, + "y": 540, + "wires": [ + [ + "fedf79.5889c088" + ] + ] + }, + { + "id": "fedf79.5889c088", + "type": "change", + "z": "4b63452d.672afc", + "name": "set options", + "rules": [ + { + "t": "set", + "p": "options", + "pt": "msg", + "to": "{\"explicitArray\":false}", + "tot": "json" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 550, + "y": 540, + "wires": [ + [ + "581bd648.636628" + ] + ] + } +] \ No newline at end of file From b8bc62ee11296ae03f0bbd0f8635697f08205094 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 3 Nov 2020 20:54:04 +0900 Subject: [PATCH 010/114] add example for YAML node --- ...1 - Convert JavaScript object to YAML.json | 90 +++++++++++++++++++ ...2 - Convert YAML to JavaScript object.json | 90 +++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/yaml/01 - Convert JavaScript object to YAML.json create mode 100644 packages/node_modules/@node-red/nodes/examples/parser/yaml/02 - Convert YAML to JavaScript object.json diff --git a/packages/node_modules/@node-red/nodes/examples/parser/yaml/01 - Convert JavaScript object to YAML.json b/packages/node_modules/@node-red/nodes/examples/parser/yaml/01 - Convert JavaScript object to YAML.json new file mode 100644 index 000000000..d751a8731 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/yaml/01 - Convert JavaScript object to YAML.json @@ -0,0 +1,90 @@ +[ + { + "id": "84222b92.d65d18", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 200, + "y": 180, + "wires": [ + [ + "cdd1c154.3a655" + ] + ] + }, + { + "id": "7b014430.dfd94c", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Convert JavaScript object to YAML", + "info": "YAML node can convert JavaScript object to YAML string.", + "x": 240, + "y": 120, + "wires": [] + }, + { + "id": "1cd4ad02.9a5423", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 670, + "y": 180, + "wires": [] + }, + { + "id": "cdd1c154.3a655", + "type": "template", + "z": "4b63452d.672afc", + "name": "JS object", + "field": "payload", + "fieldType": "msg", + "format": "json", + "syntax": "plain", + "template": "{\n \"fruits\" : {\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n }\n}", + "output": "json", + "x": 360, + "y": 180, + "wires": [ + [ + "aaf0100b.16628" + ] + ] + }, + { + "id": "aaf0100b.16628", + "type": "yaml", + "z": "4b63452d.672afc", + "property": "payload", + "name": "", + "x": 510, + "y": 180, + "wires": [ + [ + "1cd4ad02.9a5423" + ] + ] + } +] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/parser/yaml/02 - Convert YAML to JavaScript object.json b/packages/node_modules/@node-red/nodes/examples/parser/yaml/02 - Convert YAML to JavaScript object.json new file mode 100644 index 000000000..d80e0d144 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/parser/yaml/02 - Convert YAML to JavaScript object.json @@ -0,0 +1,90 @@ +[ + { + "id": "ba1dab90.8d1da8", + "type": "inject", + "z": "4b63452d.672afc", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 200, + "y": 360, + "wires": [ + [ + "16617f26.14ced1" + ] + ] + }, + { + "id": "a9f97b00.57d658", + "type": "comment", + "z": "4b63452d.672afc", + "name": "Convert YAML to JavaScript object", + "info": "YAML node can convert YAML string to JavaScript object.", + "x": 240, + "y": 300, + "wires": [] + }, + { + "id": "2d0dde7e.a50082", + "type": "debug", + "z": "4b63452d.672afc", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 690, + "y": 360, + "wires": [] + }, + { + "id": "16617f26.14ced1", + "type": "template", + "z": "4b63452d.672afc", + "name": "YAML string", + "field": "payload", + "fieldType": "msg", + "format": "yaml", + "syntax": "plain", + "template": "fruits:\n kind: Apple\n price: 100\n origin: Canada", + "output": "str", + "x": 370, + "y": 360, + "wires": [ + [ + "e2e4f862.f9d7d8" + ] + ] + }, + { + "id": "e2e4f862.f9d7d8", + "type": "yaml", + "z": "4b63452d.672afc", + "property": "payload", + "name": "", + "x": 530, + "y": 360, + "wires": [ + [ + "2d0dde7e.a50082" + ] + ] + } +] \ No newline at end of file From 407cb3e7d5f34121c7dea1e668e7c4443f97576f Mon Sep 17 00:00:00 2001 From: Kunihiko Toumura Date: Wed, 4 Nov 2020 21:43:20 +0900 Subject: [PATCH 011/114] Messaging API support in Split/Join nodes --- .../@node-red/nodes/core/sequence/17-split.js | 152 ++++++++++++------ test/nodes/core/sequence/17-split_spec.js | 139 ++++++++++++++++ 2 files changed, 246 insertions(+), 45 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js index 3f774d68c..dc422f3da 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js +++ b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js @@ -17,18 +17,18 @@ module.exports = function(RED) { "use strict"; - function sendArray(node,msg,array) { + function sendArray(node,msg,array,send) { for (var i = 0; i < array.length-1; i++) { msg.payload = array[i]; msg.parts.index = node.c++; if (node.stream !== true) { msg.parts.count = array.length; } - node.send(RED.util.cloneMessage(msg)); + send(RED.util.cloneMessage(msg)); } if (node.stream !== true) { msg.payload = array[i]; msg.parts.index = node.c++; msg.parts.count = array.length; - node.send(RED.util.cloneMessage(msg)); + send(RED.util.cloneMessage(msg)); node.c = 0; } else { node.remainder = array[i]; } @@ -67,7 +67,8 @@ module.exports = function(RED) { } node.c = 0; node.buffer = Buffer.from([]); - this.on("input", function(msg) { + node.pendingDones = []; + this.on("input", function(msg, send, done) { if (msg.hasOwnProperty("payload")) { if (msg.hasOwnProperty("parts")) { msg.parts = { parts:msg.parts }; } // push existing parts to a stack else { msg.parts = {}; } @@ -93,14 +94,23 @@ module.exports = function(RED) { msg.payload = data.substring(pos,pos+node.splt); msg.parts.index = node.c++; pos += node.splt; - node.send(RED.util.cloneMessage(msg)); + send(RED.util.cloneMessage(msg)); + } + if (count > 1) { + node.pendingDones.forEach(d => d()); + node.pendingDones = []; } node.remainder = data.substring(pos); if ((node.stream !== true) || (node.remainder.length === node.splt)) { msg.payload = node.remainder; msg.parts.index = node.c++; - node.send(RED.util.cloneMessage(msg)); + send(RED.util.cloneMessage(msg)); + node.pendingDones.forEach(d => d()); + node.pendingDones = []; + done(); node.remainder = ""; + } else { + node.pendingDones.push(done); } } else { @@ -115,7 +125,8 @@ module.exports = function(RED) { a = msg.payload.split(node.splt); msg.parts.ch = node.splt; // pass the split char to other end for rejoin } - sendArray(node,msg,a); + sendArray(node,msg,a,send); + done(); } } else if (Array.isArray(msg.payload)) { // then split array into messages @@ -135,8 +146,9 @@ module.exports = function(RED) { } msg.parts.index = i; pos += node.arraySplt; - node.send(RED.util.cloneMessage(msg)); + send(RED.util.cloneMessage(msg)); } + done(); } else if ((typeof msg.payload === "object") && !Buffer.isBuffer(msg.payload)) { var j = 0; @@ -152,10 +164,11 @@ module.exports = function(RED) { msg.parts.key = p; msg.parts.index = j; msg.parts.count = l; - node.send(RED.util.cloneMessage(msg)); + send(RED.util.cloneMessage(msg)); j += 1; } } + done(); } else if (Buffer.isBuffer(msg.payload)) { var len = node.buffer.length + msg.payload.length; @@ -176,14 +189,23 @@ module.exports = function(RED) { msg.payload = buff.slice(pos,pos+node.splt); msg.parts.index = node.c++; pos += node.splt; - node.send(RED.util.cloneMessage(msg)); + send(RED.util.cloneMessage(msg)); + } + if (count > 1) { + node.pendingDones.forEach(d => d()); + node.pendingDones = []; } node.buffer = buff.slice(pos); if ((node.stream !== true) || (node.buffer.length === node.splt)) { msg.payload = node.buffer; msg.parts.index = node.c++; - node.send(RED.util.cloneMessage(msg)); + send(RED.util.cloneMessage(msg)); + node.pendingDones.forEach(d => d()); + node.pendingDones = []; + done(); node.buffer = Buffer.from([]); + } else { + node.pendingDones.push(done); } } else { @@ -210,23 +232,34 @@ module.exports = function(RED) { while (pos > -1) { msg.payload = buff.slice(p,pos); msg.parts.index = node.c++; - node.send(RED.util.cloneMessage(msg)); + send(RED.util.cloneMessage(msg)); i++; p = pos+node.splt.length; pos = buff.indexOf(node.splt,p); } + if (count > 1) { + node.pendingDones.forEach(d => d()); + node.pendingDones = []; + } if ((node.stream !== true) && (p < buff.length)) { msg.payload = buff.slice(p,buff.length); msg.parts.index = node.c++; msg.parts.count = node.c++; - node.send(RED.util.cloneMessage(msg)); + send(RED.util.cloneMessage(msg)); + node.pendingDones.forEach(d => d()); + node.pendingDones = []; } else { node.buffer = buff.slice(p,buff.length); + node.pendingDones.push(done); + } + if (node.buffer.length == 0) { + done(); } } - } - //else { } // otherwise drop the message. + } else { // otherwise drop the message. + done(); + } } }); } @@ -264,16 +297,16 @@ module.exports = function(RED) { } - function reduceMessageGroup(node,msgs,exp,fixup,count,accumulator,done) { - var msg = msgs.shift(); - exp.assign("I", msg.parts.index); + function reduceMessageGroup(node,msgInfos,exp,fixup,count,accumulator,done) { + var msgInfo = msgInfos.shift(); + exp.assign("I", msgInfo.msg.parts.index); exp.assign("N", count); exp.assign("A", accumulator); - RED.util.evaluateJSONataExpression(exp, msg, (err,result) => { + RED.util.evaluateJSONataExpression(exp, msgInfo.msg, (err,result) => { if (err) { return done(err); } - if (msgs.length === 0) { + if (msgInfos.length === 0) { if (fixup) { fixup.assign("N", count); fixup.assign("A", result); @@ -281,39 +314,43 @@ module.exports = function(RED) { if (err) { return done(err); } - node.send({payload: result}); + msgInfo.send({payload: result}); done(); }); } else { - node.send({payload: result}); + msgInfo.send({payload: result}); done(); } } else { - reduceMessageGroup(node,msgs,exp,fixup,count,result,done); + reduceMessageGroup(node,msgInfos,exp,fixup,count,result,done); } }); } function reduceAndSendGroup(node, group, done) { var is_right = node.reduce_right; var flag = is_right ? -1 : 1; - var msgs = group.msgs; + var msgInfos = group.msgs; + const preservedMsgInfos = [...msgInfos]; try { RED.util.evaluateNodeProperty(node.exp_init, node.exp_init_type, node, {}, (err,accum) => { var reduceExpression = node.reduceExpression; var fixupExpression = node.fixupExpression; var count = group.count; - msgs.sort(function(x,y) { - var ix = x.parts.index; - var iy = y.parts.index; + msgInfos.sort(function(x,y) { + var ix = x.msg.parts.index; + var iy = y.msg.parts.index; if (ix < iy) {return -flag;} if (ix > iy) {return flag;} return 0; }); - reduceMessageGroup(node, msgs,reduceExpression,fixupExpression,count,accum,(err,result) => { + reduceMessageGroup(node, msgInfos,reduceExpression,fixupExpression,count,accum,(err,result) => { if (err) { + preservedMsgInfos.pop(); // omit last message to emit error message + preservedMsgInfos.forEach(mInfo => mInfo.done()); done(err); return; } else { + preservedMsgInfos.forEach(mInfo => mInfo.done()); done(); } }) @@ -323,7 +360,8 @@ module.exports = function(RED) { } } - function reduceMessage(node, msg, done) { + function reduceMessage(node, msgInfo, done) { + let msg = msgInfo.msg; if (msg.hasOwnProperty('parts')) { var parts = msg.parts; var pending = node.pending; @@ -344,7 +382,7 @@ module.exports = function(RED) { if (parts.hasOwnProperty('count') && (group.count === undefined)) { group.count = parts.count; } - msgs.push(msg); + msgs.push(msgInfo); pending_count++; var completeProcess = function(err) { if (err) { @@ -353,6 +391,13 @@ module.exports = function(RED) { node.pending_count = pending_count; var max_msgs = maxKeptMsgsCount(node); if ((max_msgs > 0) && (pending_count > max_msgs)) { + Object.values(node.pending).forEach(group => { + group.msgs.forEach(mInfo => { + if (mInfo.msg._msgid !== msgInfo.msg._msgid) { + mInfo.done(); + } + }); + }); node.pending = {}; node.pending_count = 0; done(RED._("join.too-many")); @@ -368,7 +413,8 @@ module.exports = function(RED) { completeProcess(); } } else { - node.send(msg); + msgInfo.send(msg); + msgInfo.done(); done(); } } @@ -480,7 +526,9 @@ module.exports = function(RED) { delete group.msg.parts; } delete group.msg.complete; - node.send(RED.util.cloneMessage(group.msg)); + group.send(RED.util.cloneMessage(group.msg)); + group.dones.forEach(f => f()); + group.dones = []; } var pendingMessages = []; @@ -489,10 +537,10 @@ module.exports = function(RED) { // groups may overlap and cause unexpected results. The use of JSONata // means some async processing *might* occur if flow/global context is // accessed. - var processReduceMessageQueue = function(msg) { - if (msg) { + var processReduceMessageQueue = function(msgInfo) { + if (msgInfo) { // A new message has arrived - add it to the message queue - pendingMessages.push(msg); + pendingMessages.push(msgInfo); if (activeMessage !== null) { // The node is currently processing a message, so do nothing // more with this message @@ -508,22 +556,23 @@ module.exports = function(RED) { // There are more messages to process. Get the next message and // start processing it. Recurse back in to check for any more - var nextMsg = pendingMessages.shift(); + var nextMsgInfo = pendingMessages.shift(); activeMessage = true; - reduceMessage(node, nextMsg, err => { + reduceMessage(node, nextMsgInfo, err => { if (err) { - node.error(err,nextMsg); - } + nextMsgInfo.done(err);//.error(err,nextMsg); + } activeMessage = null; processReduceMessageQueue(); }) } - this.on("input", function(msg) { + this.on("input", function(msg, send, done) { try { var property; if (node.mode === 'auto' && (!msg.hasOwnProperty("parts")||!msg.parts.hasOwnProperty("id"))) { node.warn("Message missing msg.parts property - cannot join in 'auto' mode") + done(); return; } @@ -535,6 +584,7 @@ module.exports = function(RED) { property = RED.util.getMessageProperty(msg,node.property); } catch(err) { node.warn("Message property "+node.property+" not found"); + done(); return; } } @@ -557,7 +607,7 @@ module.exports = function(RED) { propertyIndex = msg.parts.index; } else if (node.mode === 'reduce') { - return processReduceMessageQueue(msg); + return processReduceMessageQueue({msg, send, done}); } else { // Use the node configuration to identify all of the group information @@ -578,9 +628,11 @@ module.exports = function(RED) { if (inflight[partId].timeout) { clearTimeout(inflight[partId].timeout); } + inflight[partId].dones.forEach(f => f()); delete inflight[partId] } - return + done(); + return; } if ((payloadType === 'object') && (propertyKey === null || propertyKey === undefined || propertyKey === "")) { @@ -591,6 +643,7 @@ module.exports = function(RED) { if (msg.hasOwnProperty('complete')) { if (inflight[partId]) { inflight[partId].msg.complete = msg.complete; + inflight[partId].send = send; completeSend(partId); } } @@ -598,6 +651,7 @@ module.exports = function(RED) { node.warn("Message missing key property 'msg."+node.key+"' - cannot add to object") } } + done(); return; } @@ -608,7 +662,9 @@ module.exports = function(RED) { payload:{}, targetCount:targetCount, type:"object", - msg:RED.util.cloneMessage(msg) + msg:RED.util.cloneMessage(msg), + send: send, + dones: [] }; } else { @@ -617,7 +673,9 @@ module.exports = function(RED) { payload:[], targetCount:targetCount, type:payloadType, - msg:RED.util.cloneMessage(msg) + msg:RED.util.cloneMessage(msg), + send: send, + dones: [] }; if (payloadType === 'string') { inflight[partId].joinChar = joinChar; @@ -634,6 +692,7 @@ module.exports = function(RED) { }, node.timer) } } + inflight[partId].dones.push(done); var group = inflight[partId]; if (payloadType === 'buffer') { @@ -642,7 +701,7 @@ module.exports = function(RED) { inflight[partId].bufferLen += property.length; } else { - node.error(RED._("join.errors.invalid-type",{error:(typeof property)}),msg); + done(RED._("join.errors.invalid-type",{error:(typeof property)})); return; } } @@ -676,6 +735,7 @@ module.exports = function(RED) { } } group.msg = Object.assign(group.msg, msg); + group.send = send; var tcnt = group.targetCount; if (msg.hasOwnProperty("parts")) { tcnt = group.targetCount || msg.parts.count; } if ((tcnt > 0 && group.currentCount >= tcnt) || msg.hasOwnProperty('complete')) { @@ -683,6 +743,7 @@ module.exports = function(RED) { } } catch(err) { + done(err); console.log(err.stack); } }); @@ -691,6 +752,7 @@ module.exports = function(RED) { for (var i in inflight) { if (inflight.hasOwnProperty(i)) { clearTimeout(inflight[i].timeout); + inflight[i].dones.forEach(d => d()); } } }); diff --git a/test/nodes/core/sequence/17-split_spec.js b/test/nodes/core/sequence/17-split_spec.js index 2c45f0e02..3ffa424b6 100644 --- a/test/nodes/core/sequence/17-split_spec.js +++ b/test/nodes/core/sequence/17-split_spec.js @@ -1646,4 +1646,143 @@ describe('JOIN node', function() { }); }); + describe('messaging API', function() { + function mapiDoneSplitTestHelper(done, splt, spltType, stream, msgAndTimings) { + const completeNode = require("nr-test-utils").require("@node-red/nodes/core/common/24-complete.js"); + const catchNode = require("nr-test-utils").require("@node-red/nodes/core/common/25-catch.js"); + const flow = [ + { id: "splitNode1", type:"split", splt, spltType, stream, wires: [[]]}, + { id: "completeNode1", type: "complete", scope: ["splitNode1"], uncaught: false, wires: [["helperNode1"]] }, + { id: "catchNode1", type: "catch", scope: ["splitNode1"], uncaught: false, wires: [["helperNode1"]] }, + { id: "helperNode1", type: "helper", wires: [[]] }]; + const numMsgs = msgAndTimings.length; + helper.load([splitNode, completeNode, catchNode], flow, function () { + const splitNode1 = helper.getNode("splitNode1"); + const helperNode1 = helper.getNode("helperNode1"); + RED.settings.nodeMessageBufferMaxLength = 2; + const t = Date.now(); + let c = 0; + helperNode1.on("input", function (msg) { + msg.should.have.a.property('payload'); + (Date.now() - t).should.be.approximately(msgAndTimings[msg.seq].avr, msgAndTimings[msg.seq].var); + c += 1; + if (c === numMsgs) { + done(); + } + }); + for (let i = 0; i < numMsgs; i++) { + setTimeout(function () { splitNode1.receive(msgAndTimings[i].msg); }, msgAndTimings[i].delay); + } + }); + } + it('should call done() when message is sent (string)', function (done) { + mapiDoneSplitTestHelper(done, 2, "len", false, [ + { msg: { seq: 0, payload: "12345" }, delay: 0, avr: 0, var: 100 }, + ]); + }); + it('should call done() when message is sent (array)', function (done) { + mapiDoneSplitTestHelper(done, 2, "len", false, [ + { msg: { seq: 0, payload: [0,1,2,3,4] }, delay: 0, avr: 0, var: 100 }, + ]); + }); + it('should call done() when message is sent (object)', function (done) { + mapiDoneSplitTestHelper(done, 2, "len", false, [ + { msg: { seq: 0, payload: {a:1,b:2}}, delay: 0, avr: 0, var: 100 }, + ]); + }); + it('should call done() when consolidated message is emitted (string, len)', function (done) { + mapiDoneSplitTestHelper(done, 5, "len", true, [ + { msg: { seq: 0, payload: "12"}, delay: 0, avr: 500, var: 100 }, + { msg: { seq: 1, payload: "34"}, delay: 200, avr: 500, var: 100 }, + { msg: { seq: 2, payload: "5"}, delay: 500, avr: 500, var: 100 } + ]); + }); + it('should call done() when consolidated message is emitted (Buffer, len)', function (done) { + mapiDoneSplitTestHelper(done, 5, "len", true, [ + { msg: { seq: 0, payload: Buffer.from("12")}, delay: 0, avr: 500, var: 100 }, + { msg: { seq: 1, payload: Buffer.from("34")}, delay: 200, avr: 500, var: 100 }, + { msg: { seq: 2, payload: Buffer.from("5")}, delay: 500, avr: 500, var: 100 } + ]); + }); + it('should call done() when consolidated message is emitted (Buffer, str)', function (done) { + mapiDoneSplitTestHelper(done, "5", "str", true, [ + { msg: { seq: 0, payload: Buffer.from("12")}, delay: 0, avr: 500, var: 100 }, + { msg: { seq: 1, payload: Buffer.from("34")}, delay: 200, avr: 500, var: 100 }, + { msg: { seq: 2, payload: Buffer.from("5")}, delay: 500, avr: 500, var: 100 } + ]); + }); + it('should call done() when consolidated message is emitted (Buffer, bin)', function (done) { + mapiDoneSplitTestHelper(done, "[53]", "bin", true, [ + { msg: { seq: 0, payload: Buffer.from("12")}, delay: 0, avr: 500, var: 100 }, + { msg: { seq: 1, payload: Buffer.from("34")}, delay: 200, avr: 500, var: 100 }, + { msg: { seq: 2, payload: Buffer.from("5")}, delay: 500, avr: 500, var: 100 } + ]); + }); + + function mapiDoneJoinTestHelper(done, joinNodeSetting, msgAndTimings) { + const completeNode = require("nr-test-utils").require("@node-red/nodes/core/common/24-complete.js"); + const catchNode = require("nr-test-utils").require("@node-red/nodes/core/common/25-catch.js"); + const flow = [ + { ...joinNodeSetting, id: "joinNode1", type:"join", wires: [[]]}, + { id: "completeNode1", type: "complete", scope: ["joinNode1"], uncaught: false, wires: [["helperNode1"]] }, + { id: "catchNode1", type: "catch", scope: ["joinNode1"], uncaught: false, wires: [["helperNode1"]] }, + { id: "helperNode1", type: "helper", wires: [[]] }]; + const numMsgs = msgAndTimings.length; + helper.load([joinNode, completeNode, catchNode], flow, function () { + const joinNode1 = helper.getNode("joinNode1"); + const helperNode1 = helper.getNode("helperNode1"); + RED.settings.nodeMessageBufferMaxLength = 3; + const t = Date.now(); + let c = 0; + helperNode1.on("input", function (msg) { + msg.should.have.a.property('payload'); + (Date.now() - t).should.be.approximately(msgAndTimings[msg.seq].avr, msgAndTimings[msg.seq].var); + c += 1; + if (c === numMsgs) { + done(); + } + }); + for (let i = 0; i < numMsgs; i++) { + setTimeout(function () { joinNode1.receive(msgAndTimings[i].msg); }, msgAndTimings[i].delay); + } + }); + } + it('should call done() when all messages are joined', function (done) { + mapiDoneJoinTestHelper(done, {mode:"auto", timeout:1}, [ + { msg: {seq:0, payload:"A", parts:{id:1, type:"string", ch:",", index:0, count:3}}, delay:0, avr:500, var:100}, + { msg: {seq:1, payload:"B", parts:{id:1, type:"string", ch:",", index:1, count:3}}, delay:200, avr:500, var:100}, + { msg: {seq:2, payload:"C", parts:{id:1, type:"string", ch:",", index:2, count:3}}, delay:500, avr:500, var:100} + ]); + }); + it('should call done() when the node is reset', function (done) { + mapiDoneJoinTestHelper(done, {mode:"auto", timeout:1}, [ + { msg: {seq:0, payload:"A", parts:{id:1, type:"string", ch:",", index:0, count:3}}, delay:0, avr:500, var:100}, + { msg: {seq:1, payload:"B", parts:{id:1, type:"string", ch:",", index:1, count:3}}, delay:200, avr:500, var:100}, + { msg: {seq:2, payload:"dummy", reset: true, parts:{id:1}}, delay:500, avr:500, var:100} + ]); + }); + it('should call done() when timed out', function (done) { + mapiDoneJoinTestHelper(done, {mode:"custom", joiner:",", build:"string", timeout:0.5}, [ + { msg: {seq:0, payload:"A"}, delay:0, avr:500, var:100}, + { msg: {seq:1, payload:"B"}, delay:200, avr:500, var:100}, + ]); + }); + it('should call done() when all messages are reduced', function (done) { + mapiDoneJoinTestHelper(done, {mode:"reduce", reduceRight:false, reduceExp:"$A+payload", reduceInit:"0", + reduceInitType:"num", reduceFixup:undefined}, [ + { msg: {seq:0, payload:3, parts: {index:2, count:3, id:222}}, delay:0, avr:500, var:100}, + { msg: {seq:1, payload:2, parts: {index:1, count:3, id:222}}, delay:200, avr:500, var:100}, + { msg: {seq:2, payload:4, parts: {index:0, count:3, id:222}}, delay:500, avr:500, var:100} + ]); + }); + it('should call done() regardless of buffer overflow', function (done) { + mapiDoneJoinTestHelper(done, {mode:"reduce", reduceRight:false, reduceExp:"$A+payload", reduceInit:"0", + reduceInitType:"num", reduceFixup:undefined}, [ + { msg: {seq:0, payload:3, parts: {index:2, count:5, id:222}}, delay:0, avr:600, var:100}, + { msg: {seq:1, payload:2, parts: {index:1, count:5, id:222}}, delay:200, avr:600, var:100}, + { msg: {seq:2, payload:4, parts: {index:0, count:5, id:222}}, delay:400, avr:600, var:100}, + { msg: {seq:3, payload:1, parts: {index:3, count:5, id:222}}, delay:600, avr:600, var:100}, + ]); + }); + }); }); From f038069fe2c44df55d264432d6e7dd4559701f5e Mon Sep 17 00:00:00 2001 From: Kunihiko Toumura Date: Thu, 5 Nov 2020 15:09:41 +0900 Subject: [PATCH 012/114] Messaging API support in Trigger node --- .../nodes/core/function/89-trigger.js | 37 ++++++++-------- test/nodes/core/function/89-trigger_spec.js | 42 ++++++++++++++++++- 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js index 7b5b820b7..9339436ee 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js @@ -82,10 +82,10 @@ module.exports = function(RED) { var npay = {}; var pendingMessages = []; var activeMessagePromise = null; - var processMessageQueue = function(msg) { - if (msg) { + var processMessageQueue = function(msgInfo) { + if (msgInfo) { // A new message has arrived - add it to the message queue - pendingMessages.push(msg); + pendingMessages.push(msgInfo); if (activeMessagePromise !== null) { // The node is currently processing a message, so do nothing // more with this message @@ -101,17 +101,17 @@ module.exports = function(RED) { // There are more messages to process. Get the next message and // start processing it. Recurse back in to check for any more - var nextMsg = pendingMessages.shift(); - activeMessagePromise = processMessage(nextMsg) + var nextMsgInfo = pendingMessages.shift(); + activeMessagePromise = processMessage(nextMsgInfo) .then(processMessageQueue) .catch((err) => { - node.error(err,nextMsg); + nextMsgInfo.done(err); return processMessageQueue(); }); } - this.on('input', function(msg) { - processMessageQueue(msg); + this.on('input', function(msg, send, done) { + processMessageQueue({msg, send, done}); }); var stat = function() { @@ -121,7 +121,8 @@ module.exports = function(RED) { else return {fill:"blue",shape:"dot",text:l}; } - var processMessage = function(msg) { + var processMessage = function(msgInfo) { + let msg = msgInfo.msg; var topic = RED.util.getMessageProperty(msg,node.topic) || "_none"; var promise; var delayDuration = node.duration; @@ -179,7 +180,7 @@ module.exports = function(RED) { /* istanbul ignore else */ if (node.op1type !== "nul") { var msg2 = RED.util.cloneMessage(msg); - node.topics[topic].tout = setInterval(function() { node.send(RED.util.cloneMessage(msg2)); }, delayDuration); + node.topics[topic].tout = setInterval(function() { msgInfo.send(RED.util.cloneMessage(msg2)); }, delayDuration); } } else { @@ -203,14 +204,14 @@ module.exports = function(RED) { } promise.then(() => { if (node.op2type === "payl") { - if (node.second === true) { node.send([null,npay[topic]]); } - else { node.send(npay[topic]); } + if (node.second === true) { msgInfo.send([null,npay[topic]]); } + else { msgInfo.send(npay[topic]); } delete npay[topic]; } else { msg2.payload = node.topics[topic].m2; - if (node.second === true) { node.send([null,msg2]); } - else { node.send(msg2); } + if (node.second === true) { msgInfo.send([null,msg2]); } + else { msgInfo.send(msg2); } } delete node.topics[topic]; node.status(stat()); @@ -225,8 +226,9 @@ module.exports = function(RED) { }, delayDuration); } } + msgInfo.done(); node.status(stat()); - if (node.op1type !== "nul") { node.send(RED.util.cloneMessage(msg)); } + if (node.op1type !== "nul") { msgInfo.send(RED.util.cloneMessage(msg)); } }); }); } @@ -262,8 +264,8 @@ module.exports = function(RED) { } delete node.topics[topic]; node.status(stat()); - if (node.second === true) { node.send([null,msg2]); } - else { node.send(msg2); } + if (node.second === true) { msgInfo.send([null,msg2]); } + else { msgInfo.send(msg2); } }).catch(err => { node.error(err); }); @@ -273,6 +275,7 @@ module.exports = function(RED) { // if (node.op2type === "payl") {node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } // } } + msgInfo.done(); return Promise.resolve(); } this.on("close", function() { diff --git a/test/nodes/core/function/89-trigger_spec.js b/test/nodes/core/function/89-trigger_spec.js index dd77b37d9..ed25789a7 100644 --- a/test/nodes/core/function/89-trigger_spec.js +++ b/test/nodes/core/function/89-trigger_spec.js @@ -1154,5 +1154,45 @@ describe('trigger node', function() { },180); }); }); - + describe('messaging API', function () { + function mapiDoneTriggerTestHelper(done, nodeSetting, msgAndTimings) { + const completeNode = require("nr-test-utils").require("@node-red/nodes/core/common/24-complete.js"); + const catchNode = require("nr-test-utils").require("@node-red/nodes/core/common/25-catch.js"); + const flow = [ + { ...nodeSetting, id: "triggerNode1", type: "trigger", wires: [[]] }, + { id: "completeNode1", type: "complete", scope: ["triggerNode1"], uncaught: false, wires: [["helperNode1"]] }, + { id: "catchNode1", type: "catch", scope: ["triggerNode1"], uncaught: false, wires: [["helperNode1"]] }, + { id: "helperNode1", type: "helper", wires: [[]] }]; + const numMsgs = msgAndTimings.length; + helper.load([triggerNode, completeNode, catchNode], flow, function () { + const triggerNode1 = helper.getNode("triggerNode1"); + const helperNode1 = helper.getNode("helperNode1"); + RED.settings.nodeMessageBufferMaxLength = 3; + const t = Date.now(); + let c = 0; + helperNode1.on("input", function (msg) { + msg.should.have.a.property('payload'); + (Date.now() - t).should.be.approximately(msgAndTimings[msg.seq].avr, msgAndTimings[msg.seq].var); + c += 1; + if (c === numMsgs) { + done(); + } + }); + for (let i = 0; i < numMsgs; i++) { + setTimeout(function () { triggerNode1.receive(msgAndTimings[i].msg); }, msgAndTimings[i].delay); + } + }); + } + it('should call done() when first message has been processed', function (done) { + // not when second and more messages are emitted. + mapiDoneTriggerTestHelper(done, { units:"s", duration:"1" }, [ + { msg: { seq: 0, payload: "A"}, delay: 0, avr: 0, var: 100} + ]); + }); + it('should call done() when it receives reset message', function (done) { + mapiDoneTriggerTestHelper(done, {units:"s", duration:"1"}, [ + {msg: { seq: 0, payload: "A", reset:true}, delay: 0, avr: 0, var:100} + ]); + }) + }); }); From e149174696ce0f00aaebeb92c3ffff1b299b565e Mon Sep 17 00:00:00 2001 From: bartbutenaers Date: Fri, 6 Nov 2020 08:45:50 +0100 Subject: [PATCH 013/114] Disable TypedInput --- .../editor-client/src/js/ui/common/typedInput.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index 037e8e454..ba314c830 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -954,6 +954,18 @@ }, hide: function() { this.uiSelect.hide(); + }, + disable: function(val) { + if(val === true) { + this.uiSelect.attr("disabled", "disabled"); + } else if (val === false) { + this.uiSelect.attr("disabled", null); //remove attr + } else { + this.uiSelect.attr("disabled", val); //user value + } + }, + disabled: function() { + return this.uiSelect.attr("disabled"); } }); })(jQuery); From 32b04cd32fc5bec6ef516efff04bd215b4742cdf Mon Sep 17 00:00:00 2001 From: bartbutenaers Date: Fri, 6 Nov 2020 08:48:14 +0100 Subject: [PATCH 014/114] Disable TypedInput --- .../editor-client/src/sass/ui/common/typedInput.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss index ec865b116..9b106e9f9 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss @@ -25,6 +25,14 @@ box-sizing: border-box; overflow:visible; position: relative; + &[disabled] { + input, button { + background: $secondary-background-inactive; + pointer-events: none; + cursor: not-allowed; + } + } + .red-ui-typedInput-input-wrap { flex-grow: 1; } From 01b67c692b081921e51ed9d52fedce8314c15fd1 Mon Sep 17 00:00:00 2001 From: martinb Date: Thu, 12 Nov 2020 18:51:14 +0100 Subject: [PATCH 015/114] add test case for support of out of order messages support in auto mode of join node if exactly one message has count set --- test/nodes/core/sequence/17-split_spec.js | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/nodes/core/sequence/17-split_spec.js b/test/nodes/core/sequence/17-split_spec.js index 2c45f0e02..8369a70a6 100644 --- a/test/nodes/core/sequence/17-split_spec.js +++ b/test/nodes/core/sequence/17-split_spec.js @@ -1646,4 +1646,40 @@ describe('JOIN node', function() { }); }); + it('should handle msg.parts even if messages are out of order in auto mode if exactly one message has count set', function (done) { + var flow = [{ id: "n1", type: "join", wires: [["n2"]], joiner: "[44]", joinerType: "bin", build: "string", mode: "auto" }, + { id: "n2", type: "helper" }]; + helper.load(joinNode, flow, function () { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + + n2.on("input", function (msg) { + msg.payload.length.should.be.eql(5); + msg.payload.should.be.eql([0,1,2,3,4]); + done(); + }); + + var msg = {}; + msg.parts = { + id: RED.util.generateId() + }; + for(var elem = 1; elem < 5; ++elem) { + var _msg = RED.util.cloneMessage(msg); + _msg.parts.index = elem; + if(elem == 4) { + _msg.parts.count = 5; + } + _msg.payload = elem; + n1.receive(_msg); + } + + var _msg = RED.util.cloneMessage(msg); + delete _msg.parts.count; + _msg.parts.index = 0; + _msg.payload = 0; + n1.receive(_msg); + }); + + }) + }); From ccf4e737014c30134ea41d6d876edd8f72989b26 Mon Sep 17 00:00:00 2001 From: martinb Date: Thu, 12 Nov 2020 18:56:43 +0100 Subject: [PATCH 016/114] cleanup test case for support of out of order messages --- test/nodes/core/sequence/17-split_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/nodes/core/sequence/17-split_spec.js b/test/nodes/core/sequence/17-split_spec.js index 8369a70a6..62db4957d 100644 --- a/test/nodes/core/sequence/17-split_spec.js +++ b/test/nodes/core/sequence/17-split_spec.js @@ -1647,7 +1647,7 @@ describe('JOIN node', function() { }); it('should handle msg.parts even if messages are out of order in auto mode if exactly one message has count set', function (done) { - var flow = [{ id: "n1", type: "join", wires: [["n2"]], joiner: "[44]", joinerType: "bin", build: "string", mode: "auto" }, + var flow = [{ id: "n1", type: "join", wires: [["n2"]], mode: "auto" }, { id: "n2", type: "helper" }]; helper.load(joinNode, flow, function () { var n1 = helper.getNode("n1"); From 088419b38e715f276f76c04aa35dcc739a66fa80 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 18 Nov 2020 11:02:09 +0000 Subject: [PATCH 017/114] Fix unsecure command usage in GH Action --- .github/workflows/build.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9a278b223..ed34b9fad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,9 +27,8 @@ jobs: with: node-version: '12' - run: node ./node-red/.github/scripts/update-node-red-docker.js - with: - env: - ACTIONS_ALLOW_UNSECURE_COMMANDS: true + env: + ACTIONS_ALLOW_UNSECURE_COMMANDS: true - name: Create Docker Pull Request uses: peter-evans/create-pull-request@v2 with: From 81f200641b4d15dd7c0abbc19605d71b77a7798e Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 18 Nov 2020 12:02:45 +0000 Subject: [PATCH 018/114] Allow default project workflow to be set via settings --- .../src/js/ui/projects/projectUserSettings.js | 4 +++- .../src/js/ui/projects/tab-versionControl.js | 5 ++++- packages/node_modules/node-red/settings.js | 10 +++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js index 31acbc1e4..7ca13a3cf 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js @@ -43,9 +43,11 @@ RED.projects.userSettings = (function() { function createWorkflowSection(pane) { + var defaultWorkflowMode = RED.settings.theme("projects.workflow.mode","manual"); + var currentGitSettings = RED.settings.get('git') || {}; currentGitSettings.workflow = currentGitSettings.workflow || {}; - currentGitSettings.workflow.mode = currentGitSettings.workflow.mode || "manual"; + currentGitSettings.workflow.mode = currentGitSettings.workflow.mode || defaultWorkflowMode; var title = $('

').text(RED._("editor:sidebar.project.userSettings.workflow")).appendTo(pane); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js index 8aede9c2b..5512926be 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js @@ -294,7 +294,10 @@ RED.sidebar.versionControl = (function() { // TODO: this is a full refresh of the files - should be able to // just do an incremental refresh - var workflowMode = ((RED.settings.get('git') || {}).workflow || {}).mode || "manual"; + // Get the default workflow mode from theme settings + var defaultWorkflowMode = RED.settings.theme("projects.workflow.mode","manual"); + // Check for the user-defined choice of mode + var workflowMode = ((RED.settings.get('git') || {}).workflow || {}).mode || defaultWorkflowMode; if (workflowMode === 'auto') { refresh(true); } else { diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js index fd07630f2..d13c2b195 100644 --- a/packages/node_modules/node-red/settings.js +++ b/packages/node_modules/node-red/settings.js @@ -290,7 +290,15 @@ module.exports = { editorTheme: { projects: { // To enable the Projects feature, set this value to true - enabled: false + enabled: false, + workflow: { + // Set the default projects workflow mode. + // - manual - you must manually commit changes + // - auto - changes are automatically committed + // This can be overridden per-user from the 'Git config' + // section of 'User Settings' within the editor + mode: "manual" + } } } } From 0f7d185a61147207372e038a8fa4ea27343ab134 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 18 Nov 2020 12:23:27 +0000 Subject: [PATCH 019/114] Ensure runtime side picks up default project workflow mode --- .../lib/storage/localfilesystem/projects/Project.js | 2 +- .../runtime/lib/storage/localfilesystem/projects/index.js | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js index 0fbf89de4..6cc98049a 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js @@ -421,7 +421,7 @@ Project.prototype.update = function (user, data) { } return when.settle(promises).then(function(res) { var gitSettings = getUserGitSettings(user) || {}; - var workflowMode = (gitSettings.workflow||{}).mode || "manual"; + var workflowMode = (gitSettings.workflow||{}).mode || settings.editorTheme.projects.workflow.mode; if (workflowMode === 'auto') { return project.stageFile(modifiedFiles.map(f => project.paths[f])).then(() => { return project.commit(user,{message:"Update "+modifiedFiles.join(", ")}) diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js index 5e1d4f15b..2f2990e08 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js @@ -35,7 +35,7 @@ var projectsEnabled = false; var projectLogMessages = []; var projectsDir; -var activeProject +var activeProject; var globalGitUser = false; @@ -107,6 +107,10 @@ function init(_settings, _runtime) { } catch(err) { } } else { + // Ensure there's a default workflow mode set + settings.editorTheme.projects.workflow = { + mode: (settings.editorTheme.projects.workflow || {}).mode || "manual" + } globalGitUser = gitConfig.user; Projects.init(settings,runtime); sshTools.init(settings); @@ -576,7 +580,7 @@ function saveFlows(flows, user) { } return util.writeFile(flowsFullPath, flowData, flowsFileBackup).then(() => { var gitSettings = getUserGitSettings(user) || {}; - var workflowMode = (gitSettings.workflow||{}).mode || "manual"; + var workflowMode = (gitSettings.workflow||{}).mode || settings.editorTheme.projects.workflow.mode; if (activeProject && workflowMode === 'auto') { return activeProject.stageFile([flowsFullPath, credentialsFile]).then(() => { return activeProject.commit(user,{message:"Update flow files"}) From d57edaa4c102a1bc2ec09f7703c7c8e6cdf04894 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Fri, 20 Nov 2020 00:09:30 +0900 Subject: [PATCH 020/114] Update Japanese translations for 1.2.5 (#2764) --- .../node_modules/@node-red/editor-client/locales/ja/editor.json | 1 + packages/node_modules/@node-red/runtime/locales/ja/runtime.json | 1 + 2 files changed, 2 insertions(+) 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 723bee1cd..f5774d57d 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 @@ -1089,6 +1089,7 @@ "en-US": "英語", "ja": "日本語", "ko": "韓国語", + "ru": "ロシア語", "zh-CN": "中国語(簡体)", "zh-TW": "中国語(繁体)" } diff --git a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json index ff871e3c2..6f3d07c6d 100644 --- a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json @@ -169,6 +169,7 @@ "error-invalid-default-module": "デフォルトコンテキストストアが不明: '__storage__'", "unknown-store": "不明なコンテキストストア '__name__' が指定されました。デフォルトストアを使用します。", "localfilesystem": { + "invalid-json": "コンテキストファイル '__file__' のJSONが不正", "error-circular": "コンテキスト __scope__ は永続化できない循環参照を含んでいます", "error-write": "コンテキスト書込みエラー: __message__" } From 3999690062ed9659361c0a5218647a591067a708 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 25 Nov 2020 21:04:24 +0000 Subject: [PATCH 021/114] Support Windows paths when installing tarball by path name Fixes #2769 --- packages/node_modules/@node-red/registry/lib/installer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/registry/lib/installer.js b/packages/node_modules/@node-red/registry/lib/installer.js index 19cdd7e27..03f9eb11c 100644 --- a/packages/node_modules/@node-red/registry/lib/installer.js +++ b/packages/node_modules/@node-red/registry/lib/installer.js @@ -35,7 +35,7 @@ var settings; const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/; const slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/; const pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//; -const localtgzRe = /^\/.+tgz$/; +const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/; function init(runtime) { events = runtime.events; From 2f86bb1ca5f01102263d0c43766f553b00ed38a3 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 25 Nov 2020 21:05:31 +0000 Subject: [PATCH 022/114] Update MQTT to latest to fix Node 8 URL breakage --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 28af3b121..2e442f66b 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "memorystore": "1.6.4", "mime": "2.4.6", "moment-timezone": "0.5.32", - "mqtt": "4.2.5", + "mqtt": "4.2.6", "multer": "1.4.2", "mustache": "4.0.1", "node-red-admin": "^0.2.6", @@ -104,7 +104,7 @@ "grunt-simple-nyc": "^3.0.1", "http-proxy": "1.18.1", "jsdoc-nr-template": "github:node-red/jsdoc-nr-template", - "marked": "1.2.4", + "marked": "1.2.5", "minami": "1.2.3", "mocha": "^5.2.0", "node-red-node-test-helper": "^0.2.5", From c5d38d8962b3cb92d09b652ad62f016665225aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathana=C3=ABl=20L=C3=A9caud=C3=A9?= Date: Tue, 24 Nov 2020 18:29:39 -0500 Subject: [PATCH 023/114] Library: properly handle symlinked folders --- .../@node-red/runtime/lib/storage/localfilesystem/library.js | 3 ++- 1 file changed, 2 insertions(+), 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 04701321f..22f01c978 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 @@ -102,7 +102,8 @@ function getLibraryEntry(type,path) { var files = []; fns.sort().filter(function(fn) { var fullPath = fspath.join(path,fn); - var absoluteFullPath = fspath.join(root,fullPath); + // we use fs.realpathSync to also resolve Symbolic Link + var absoluteFullPath = fs.realpathSync(fspath.join(root,fullPath)); if (fn[0] != ".") { var stats = fs.lstatSync(absoluteFullPath); if (stats.isDirectory()) { From 4bfe9a9ae90be502fac8892feb5c02fc13c4ad3b Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 25 Nov 2020 21:09:45 +0000 Subject: [PATCH 024/114] Bump for 1.2.6 --- CHANGELOG.md | 20 +++++++++++++++++++ package.json | 2 +- .../@node-red/editor-api/package.json | 6 +++--- .../@node-red/editor-client/package.json | 2 +- .../node_modules/@node-red/nodes/package.json | 4 ++-- .../@node-red/registry/package.json | 4 ++-- .../@node-red/runtime/package.json | 6 +++--- .../node_modules/@node-red/util/package.json | 2 +- packages/node_modules/node-red/package.json | 10 +++++----- 9 files changed, 38 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9d7ba010..c12019755 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +### 1.2.6: Maintenance Release + + +Editor + + - Update Japanese translations for 1.2.5 (#2764) @kazuhitoyokoi + - Library: properly handle symlinked folders (#2768) @natcl + +Runtime + + - Support Windows paths when installing tarball by path name Fixes #2769 + - Fix unsecure command usage in GH Action + +Nodes + + - Update MQTT to latest to fix Node 8 URL breakage + + + + ### 1.2.5: Maintenance Release Editor diff --git a/package.json b/package.json index 2e442f66b..5fa8dca88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "1.2.5", + "version": "1.2.6", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 268a33a53..2b89c8023 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "1.2.5", + "version": "1.2.6", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "1.2.5", - "@node-red/editor-client": "1.2.5", + "@node-red/util": "1.2.6", + "@node-red/editor-client": "1.2.6", "bcryptjs": "2.4.3", "body-parser": "1.19.0", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index 082913650..06034b997 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "1.2.5", + "version": "1.2.6", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 62b92a4f8..d5f0c3e1a 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "1.2.5", + "version": "1.2.6", "license": "Apache-2.0", "repository": { "type": "git", @@ -31,7 +31,7 @@ "is-utf8": "0.2.1", "js-yaml": "3.14.0", "media-typer": "1.1.0", - "mqtt": "4.2.5", + "mqtt": "4.2.6", "multer": "1.4.2", "mustache": "4.0.1", "on-headers": "1.0.2", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index ebecc3b65..c8a570001 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "1.2.5", + "version": "1.2.6", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,7 +16,7 @@ } ], "dependencies": { - "@node-red/util": "1.2.5", + "@node-red/util": "1.2.6", "semver": "6.3.0", "tar": "6.0.5", "uglify-js": "3.11.6", diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index 2287bf3b4..620f2f7ed 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "1.2.5", + "version": "1.2.6", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "1.2.5", - "@node-red/util": "1.2.5", + "@node-red/registry": "1.2.6", + "@node-red/util": "1.2.6", "async-mutex": "0.2.4", "clone": "2.1.2", "express": "4.17.1", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index c6aa81d95..8878e4245 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "1.2.5", + "version": "1.2.6", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index f7db0d981..533b033d1 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "1.2.5", + "version": "1.2.6", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "1.2.5", - "@node-red/runtime": "1.2.5", - "@node-red/util": "1.2.5", - "@node-red/nodes": "1.2.5", + "@node-red/editor-api": "1.2.6", + "@node-red/runtime": "1.2.6", + "@node-red/util": "1.2.6", + "@node-red/nodes": "1.2.6", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.17.1", From 78f1cb8a66804e201e0da75917bd4a85eff3beff Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 1 Dec 2020 23:05:22 +0000 Subject: [PATCH 025/114] ensure trigger timestamp option sends .now() To close #2771 --- .../@node-red/nodes/core/function/89-trigger.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js index 7b5b820b7..910043d33 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js @@ -179,7 +179,10 @@ module.exports = function(RED) { /* istanbul ignore else */ if (node.op1type !== "nul") { var msg2 = RED.util.cloneMessage(msg); - node.topics[topic].tout = setInterval(function() { node.send(RED.util.cloneMessage(msg2)); }, delayDuration); + node.topics[topic].tout = setInterval(function() { + if (node.op1type === "date") { msg2.payload = Date.now(); } + node.send(RED.util.cloneMessage(msg2)); + }, delayDuration); } } else { @@ -209,6 +212,7 @@ module.exports = function(RED) { } else { msg2.payload = node.topics[topic].m2; + if (node.op2type === "date") { msg2.payload = Date.now(); } if (node.second === true) { node.send([null,msg2]); } else { node.send(msg2); } } From 50dd0354d16d9f82b27f86715ed6d7df60b31edb Mon Sep 17 00:00:00 2001 From: aaronmyatt Date: Fri, 4 Dec 2020 23:00:31 +0800 Subject: [PATCH 026/114] adds admin middleware tests --- .../unit/@node-red/editor-api/lib/index_spec.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/unit/@node-red/editor-api/lib/index_spec.js b/test/unit/@node-red/editor-api/lib/index_spec.js index 1d5e9380b..805169307 100644 --- a/test/unit/@node-red/editor-api/lib/index_spec.js +++ b/test/unit/@node-red/editor-api/lib/index_spec.js @@ -96,4 +96,21 @@ describe("api/index", function() { request(api.httpAdmin).get("/auth/login").expect(200).end(done) }) }); + + describe('initialises api with admin middleware', function(done) { + it('ignores non-function values',function(done) { + api.init({ httpAdminRoot: true, httpAdminMiddleware: undefined },{},{},{}); + const middlewareFound = api.httpAdmin._router.stack.filter((layer) => layer.name === 'testMiddleware') + should(middlewareFound).be.empty(); + done(); + }); + + it('only accepts functions as middleware',function(done) { + const testMiddleware = function(req, res, next){ next(); }; + api.init({ httpAdminRoot: true, httpAdminMiddleware: testMiddleware },{},{},{}); + const middlewareFound = api.httpAdmin._router.stack.filter((layer) => layer.name === 'testMiddleware') + should(middlewareFound).be.length(1); + done(); + }); + }); }); From 950fd7d2cf933ec60aa298c05b48750775d36e3a Mon Sep 17 00:00:00 2001 From: aaronmyatt Date: Sat, 5 Dec 2020 15:15:36 +0800 Subject: [PATCH 027/114] removes unused dependencies --- test/unit/@node-red/editor-api/lib/index_spec.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/unit/@node-red/editor-api/lib/index_spec.js b/test/unit/@node-red/editor-api/lib/index_spec.js index 805169307..c667ae1f2 100644 --- a/test/unit/@node-red/editor-api/lib/index_spec.js +++ b/test/unit/@node-red/editor-api/lib/index_spec.js @@ -18,9 +18,6 @@ var should = require("should"); var sinon = require("sinon"); var request = require("supertest"); var express = require("express"); -var when = require("when"); -var fs = require("fs"); -var path = require("path"); var NR_TEST_UTILS = require("nr-test-utils"); From 0b569a4120b30480f3f3a281204ff1cdd019c873 Mon Sep 17 00:00:00 2001 From: aaronmyatt Date: Sat, 5 Dec 2020 23:06:18 +0800 Subject: [PATCH 028/114] exercise admin auth pathways --- .../@node-red/editor-api/lib/index_spec.js | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/unit/@node-red/editor-api/lib/index_spec.js b/test/unit/@node-red/editor-api/lib/index_spec.js index c667ae1f2..44048e6df 100644 --- a/test/unit/@node-red/editor-api/lib/index_spec.js +++ b/test/unit/@node-red/editor-api/lib/index_spec.js @@ -20,6 +20,7 @@ var request = require("supertest"); var express = require("express"); var NR_TEST_UTILS = require("nr-test-utils"); +const auth = require("basic-auth"); var api = NR_TEST_UTILS.require("@node-red/editor-api"); @@ -110,4 +111,37 @@ describe("api/index", function() { done(); }); }); + + //adminAuth: { + // type: "credentials", + // users: [{ + // username: "admin", + // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", + // permissions: "*" + // }] + //}, + + describe('initialises api with authentication enabled', function(done) { + + it('enables an oauth/openID based authentication mechanism',function(done) { + const stub = sinon.stub(apiAuth, 'genericStrategy', function(){}); + const adminAuth = { type: 'strategy', strategy: {} } + api.init({ httpAdminRoot: true, adminAuth },{},{},{}); + should(stub.called).be.ok(); + stub.restore(); + done(); + }); + + it('enables password protection',function(done) { + const adminAuth = { type: 'credentials' } + api.init({ httpAdminRoot: true, adminAuth },{},{},{}); + + // is the name ("initialize") of the passport middleware present + const middlewareFound = api.httpAdmin._router.stack.filter((layer) => layer.name === 'initialize') + should(middlewareFound).be.length(1); + done(); + }); + + }); + }); From c9bc530df0dc5989e32ba23b137c656e51715637 Mon Sep 17 00:00:00 2001 From: aaronmyatt Date: Sun, 6 Dec 2020 15:29:54 +0800 Subject: [PATCH 029/114] tests custom cors settings --- .../@node-red/editor-api/lib/index_spec.js | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/test/unit/@node-red/editor-api/lib/index_spec.js b/test/unit/@node-red/editor-api/lib/index_spec.js index 44048e6df..2f84030aa 100644 --- a/test/unit/@node-red/editor-api/lib/index_spec.js +++ b/test/unit/@node-red/editor-api/lib/index_spec.js @@ -112,15 +112,6 @@ describe("api/index", function() { }); }); - //adminAuth: { - // type: "credentials", - // users: [{ - // username: "admin", - // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", - // permissions: "*" - // }] - //}, - describe('initialises api with authentication enabled', function(done) { it('enables an oauth/openID based authentication mechanism',function(done) { @@ -144,4 +135,25 @@ describe("api/index", function() { }); + describe('initialises api with custom cors config', function (done) { + const httpAdminCors = { + origin: "*", + methods: "GET,PUT,POST,DELETE" + }; + + it('uses default cors middleware when user settings absent', function(done){ + api.init({ httpAdminRoot: true }, {}, {}, {}); + const middlewareFound = api.httpAdmin._router.stack.filter((layer) => layer.name === 'corsMiddleware') + should(middlewareFound).be.length(1); + done(); + }) + + it('enables custom cors middleware when settings present', function(done){ + api.init({ httpAdminRoot: true, httpAdminCors }, {}, {}, {}); + const middlewareFound = api.httpAdmin._router.stack.filter((layer) => layer.name === 'corsMiddleware') + should(middlewareFound).be.length(2); + done(); + }) + }); + }); From 9f3e9786a8db514a437c089d199cb5d0d4002df6 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 7 Dec 2020 11:45:53 +0000 Subject: [PATCH 030/114] Disable nyc coverage reporting on older node versions --- .travis.yml | 6 ++++++ Gruntfile.js | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/.travis.yml b/.travis.yml index 315eb07a8..4cd0dffe1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,5 +11,11 @@ matrix: before_script: - npm install -g coveralls - node_js: "12" + script: + - ./node_modules/.bin/grunt no-coverage - node_js: "10" + script: + - ./node_modules/.bin/grunt no-coverage - node_js: "8" + script: + - ./node_modules/.bin/grunt no-coverage diff --git a/Gruntfile.js b/Gruntfile.js index 6272e9818..b01763ab0 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -623,6 +623,11 @@ module.exports = function(grunt) { 'Builds editor content then runs code style checks and unit tests on all components', ['build','verifyPackageDependencies','jshint:editor','nyc:all']); + grunt.registerTask('no-coverage', + 'Builds editor content then runs code style checks and unit tests on all components without code coverage', + ['build','verifyPackageDependencies','jshint:editor','simplemocha:all']); + + grunt.registerTask('test-core', 'Runs code style check and unit tests on core runtime code', ['build','nyc:core']); From 5992ed1fab40ee764c7c04473a18a619104f2dfc Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 30 Nov 2020 14:38:48 +0000 Subject: [PATCH 031/114] Fully remove when.js dependency --- package.json | 1 - .../editor-api/lib/editor/library.js | 1 - .../@node-red/editor-api/lib/index.js | 8 +- .../@node-red/editor-api/package.json | 1 - .../@node-red/registry/lib/loader.js | 9 +- .../@node-red/registry/lib/localfilesystem.js | 4 - .../@node-red/registry/package.json | 3 +- .../@node-red/runtime/lib/index.js | 27 ++--- .../runtime/lib/nodes/credentials.js | 27 ++--- .../@node-red/runtime/lib/nodes/index.js | 1 - .../@node-red/runtime/lib/settings.js | 6 +- .../@node-red/runtime/lib/storage/index.js | 111 ++++++++---------- .../lib/storage/localfilesystem/library.js | 14 +-- .../localfilesystem/projects/Project.js | 56 ++++----- .../storage/localfilesystem/projects/index.js | 23 ++-- .../localfilesystem/projects/ssh/index.js | 1 - .../lib/storage/localfilesystem/sessions.js | 9 +- .../@node-red/runtime/package.json | 3 +- .../node_modules/@node-red/util/lib/i18n.js | 9 +- .../node_modules/@node-red/util/package.json | 3 +- packages/node_modules/node-red/lib/red.js | 23 +++- packages/node_modules/node-red/red.js | 3 +- .../nodes/core/network/21-httprequest_spec.js | 1 - test/nodes/core/network/22-websocket_spec.js | 41 ++++--- .../editor-api/lib/admin/flow_spec.js | 1 - .../editor-api/lib/admin/nodes_spec.js | 1 - .../editor-api/lib/auth/index_spec.js | 3 +- .../editor-api/lib/auth/strategies_spec.js | 37 +++--- .../editor-api/lib/auth/tokens_spec.js | 21 ++-- .../editor-api/lib/auth/users_spec.js | 5 +- .../editor-api/lib/editor/comms_spec.js | 29 +++-- .../editor-api/lib/editor/credentials_spec.js | 1 - .../editor-api/lib/editor/index_spec.js | 4 - .../editor-api/lib/editor/theme_spec.js | 1 - .../@node-red/editor-api/lib/index_spec.js | 1 - .../unit/@node-red/registry/lib/index_spec.js | 11 +- .../@node-red/registry/lib/installer_spec.js | 22 ++-- .../@node-red/registry/lib/loader_spec.js | 1 - .../registry/lib/localfilesystem_spec.js | 1 - .../@node-red/registry/lib/registry_spec.js | 5 +- .../resources/local/TestNode2/TestNode2.js | 3 +- .../resources/local/TestNode3/TestNode3.js | 3 +- .../@node-red/runtime/lib/api/nodes_spec.js | 41 ++++--- .../runtime/lib/api/settings_spec.js | 5 +- .../@node-red/runtime/lib/flows/index_spec.js | 41 ++++--- .../@node-red/runtime/lib/flows/util_spec.js | 1 - .../runtime/lib/nodes/credentials_spec.js | 5 +- .../@node-red/runtime/lib/nodes/index_spec.js | 11 +- .../runtime/lib/storage/index_spec.js | 17 ++- 49 files changed, 299 insertions(+), 357 deletions(-) diff --git a/package.json b/package.json index 3ff26d859..b1981d8cc 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,6 @@ "semver": "6.3.0", "tar": "6.0.5", "uglify-js": "3.11.6", - "when": "3.7.8", "ws": "6.2.1", "xml2js": "0.4.23" }, diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/library.js b/packages/node_modules/@node-red/editor-api/lib/editor/library.js index 47a41bb7b..89b92fd11 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/library.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/library.js @@ -17,7 +17,6 @@ var apiUtils = require("../util"); var fs = require('fs'); var fspath = require('path'); -var when = require('when'); var runtimeAPI; diff --git a/packages/node_modules/@node-red/editor-api/lib/index.js b/packages/node_modules/@node-red/editor-api/lib/index.js index 457b99cc9..972e8daa6 100644 --- a/packages/node_modules/@node-red/editor-api/lib/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/index.js @@ -28,7 +28,6 @@ var express = require("express"); var bodyParser = require("body-parser"); var util = require('util'); var passport = require('passport'); -var when = require('when'); var cors = require('cors'); var auth = require("./auth"); @@ -111,11 +110,9 @@ function init(settings,_server,storage,runtimeAPI) { * @return {Promise} resolves when the application is ready to handle requests * @memberof @node-red/editor-api */ -function start() { +async function start() { if (editor) { return editor.start(); - } else { - return when.resolve(); } } @@ -124,11 +121,10 @@ function start() { * @return {Promise} resolves when the application is stopped * @memberof @node-red/editor-api */ -function stop() { +async function stop() { if (editor) { editor.stop(); } - return when.resolve(); } module.exports = { init: init, diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 6885aaabb..516532e37 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -32,7 +32,6 @@ "passport-http-bearer": "1.0.1", "passport-oauth2-client-password": "0.1.2", "passport": "0.4.1", - "when": "3.7.8", "ws": "6.2.1" }, "optionalDependencies": { diff --git a/packages/node_modules/@node-red/registry/lib/loader.js b/packages/node_modules/@node-red/registry/lib/loader.js index 14e2a0b7e..3a960c73c 100644 --- a/packages/node_modules/@node-red/registry/lib/loader.js +++ b/packages/node_modules/@node-red/registry/lib/loader.js @@ -14,7 +14,6 @@ * limitations under the License. **/ -var when = require("when"); var fs = require("fs-extra"); var path = require("path"); var semver = require("semver"); @@ -92,7 +91,7 @@ function loadNodeFiles(nodeFiles) { nodeFiles[m].nodes[n] = nodeSet; nodes.push(nodeSet); } - })())); + })()).catch(err => {})); } catch(err) { // } @@ -101,7 +100,7 @@ function loadNodeFiles(nodeFiles) { } } } - return when.settle(promises).then(function(results) { + return Promise.all(promises).then(function(results) { for (var module in nodeFiles) { if (nodeFiles.hasOwnProperty(module)) { if (!nodeFiles[module].err) { @@ -293,13 +292,13 @@ function loadNodeSetList(nodes) { var promises = []; nodes.forEach(function(node) { if (!node.err) { - promises.push(loadNodeSet(node)); + promises.push(loadNodeSet(node).catch(err => {})); } else { promises.push(node); } }); - return when.settle(promises).then(function() { + return Promise.all(promises).then(function() { if (settings.available()) { return registry.saveNodeList(); } else { diff --git a/packages/node_modules/@node-red/registry/lib/localfilesystem.js b/packages/node_modules/@node-red/registry/lib/localfilesystem.js index 7233d1da3..d5bef63cd 100644 --- a/packages/node_modules/@node-red/registry/lib/localfilesystem.js +++ b/packages/node_modules/@node-red/registry/lib/localfilesystem.js @@ -17,7 +17,6 @@ var fs = require("fs"); var path = require("path"); -var events; var log; var log = require("@node-red/util").log; @@ -29,7 +28,6 @@ var iconFileExtensions = [".png", ".gif", ".svg"]; function init(runtime) { settings = runtime.settings; - events = runtime.events; } function isIncluded(name) { @@ -75,7 +73,6 @@ function getLocalFile(file) { /** * Synchronously walks the directory looking for node files. - * Emits 'node-icon-dir' events for an icon dirs found * @param dir the directory to search * @return an array of fully-qualified paths to .js files */ @@ -229,7 +226,6 @@ function getModuleNodeFiles(module) { try { fs.statSync(examplesDir) result.examples = {path:examplesDir}; - // events.emit("node-examples-dir",{name:pkg.name,path:examplesDir}); } catch(err) { } return result; diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 47472f38e..3d517421c 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -19,7 +19,6 @@ "@node-red/util": "1.3.0-beta.1", "semver": "6.3.0", "tar": "6.0.5", - "uglify-js": "3.11.6", - "when": "3.7.8" + "uglify-js": "3.11.6" } } diff --git a/packages/node_modules/@node-red/runtime/lib/index.js b/packages/node_modules/@node-red/runtime/lib/index.js index f9a772ea3..89b0dcfd6 100644 --- a/packages/node_modules/@node-red/runtime/lib/index.js +++ b/packages/node_modules/@node-red/runtime/lib/index.js @@ -14,8 +14,6 @@ * limitations under the License. **/ -var when = require('when'); - var externalAPI = require("./api"); var redNodes = require("./nodes"); @@ -197,25 +195,24 @@ function start() { }); } -var reinstallAttempts; +var reinstallAttempts = 0; var reinstallTimeout; function reinstallModules(moduleList) { var promises = []; - var failedModules = []; + var reinstallList = []; + for (var i=0;i { + events.emit("runtime-event",{id:"node/added",retain:false,payload:m.nodes}); + }).catch(err => { + reinstallList.push(mod); + })); + })(moduleList[i]) } } - when.settle(promises).then(function(results) { - var reinstallList = []; - for (var i=0;i 0) { reinstallAttempts++; // First 5 at 1x timeout, next 5 at 2x, next 5 at 4x, then 8x diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js index 153ef4b06..86ea1d3a1 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js @@ -14,7 +14,6 @@ * limitations under the License. **/ -var when = require("when"); var crypto = require('crypto'); var runtime; var settings; @@ -60,7 +59,7 @@ var api = module.exports = { /** * Sets the credentials from storage. */ - load: function (credentials) { + load: async function (credentials) { dirty = false; var credentialsEncrypted = credentials.hasOwnProperty("$") && Object.keys(credentials).length === 1; @@ -77,7 +76,7 @@ var api = module.exports = { // Case 4: credentialSecret set // - use it - var setupEncryptionPromise = when.resolve(); + var setupEncryptionPromise = Promise.resolve(); var projectKey = false; var activeProject; @@ -134,7 +133,7 @@ var api = module.exports = { log.warn(log._("nodes.credentials.error",{message:err.toString()})) var error = new Error("Failed to decrypt credentials"); error.code = "credentials_load_failed"; - return when.reject(error); + throw error; } } dirty = true; @@ -163,7 +162,7 @@ var api = module.exports = { log.warn(log._("nodes.credentials.error",{message:err.toString()})) var error = new Error("Failed to decrypt credentials"); error.code = "credentials_load_failed"; - return when.reject(error); + throw error; } } dirty = true; @@ -217,9 +216,9 @@ var api = module.exports = { // This is a project with a bad key. Mark it as invalid // TODO: this delves too deep into Project structure activeProject.credentialSecretInvalid = true; - return when.reject(error); + throw error; } - return when.reject(error); + throw error; } // These are encrypted credentials try { @@ -235,9 +234,9 @@ var api = module.exports = { // This is a project with a bad key. Mark it as invalid // TODO: this delves too deep into Project structure activeProject.credentialSecretInvalid = true; - return when.reject(error); + throw error; } - return when.reject(error); + throw error; } } else { credentialCache = credentials; @@ -257,12 +256,11 @@ var api = module.exports = { * @param creds an object of credential key/value pairs * @return a promise for backwards compatibility TODO: can this be removed? */ - add: function (id, creds) { + add: async function (id, creds) { if (!credentialCache.hasOwnProperty(id) || JSON.stringify(creds) !== JSON.stringify(credentialCache[id])) { credentialCache[id] = creds; dirty = true; } - return when.resolve(); }, /** @@ -293,7 +291,7 @@ var api = module.exports = { * @param config a flow config * @return a promise for the saving of credentials to storage */ - clean: function (config) { + clean: async function (config) { var existingIds = {}; config.forEach(function(n) { existingIds[n.id] = true; @@ -313,7 +311,6 @@ var api = module.exports = { if (deletedCredentials) { dirty = true; } - return when.resolve(); }, /** @@ -432,7 +429,7 @@ var api = module.exports = { getKeyType: function() { return encryptionKeyType; }, - export: function() { + export: async function() { var result = credentialCache; if (encryptionEnabled) { @@ -455,7 +452,7 @@ var api = module.exports = { return result; }) } else { - return when.resolve(result); + return result; } } } diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/index.js index 42fcd72f6..be31df2f7 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/index.js @@ -14,7 +14,6 @@ * limitations under the License. **/ -var when = require("when"); var path = require("path"); var fs = require("fs"); var clone = require("clone"); diff --git a/packages/node_modules/@node-red/runtime/lib/settings.js b/packages/node_modules/@node-red/runtime/lib/settings.js index 16667d177..e17600305 100644 --- a/packages/node_modules/@node-red/runtime/lib/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/settings.js @@ -14,7 +14,6 @@ * limitations under the License. **/ -var when = require("when"); var clone = require("clone"); var assert = require("assert"); var log = require("@node-red/util").log; // TODO: separate module @@ -90,10 +89,10 @@ var persistentSettings = { globalSettings[prop] = clone(value); try { assert.deepEqual(current,value); - return when.resolve(); } catch(err) { return storage.saveSettings(clone(globalSettings)); } + return Promise.resolve(); }, delete: function(prop) { if (localSettings.hasOwnProperty(prop)) { @@ -106,7 +105,7 @@ var persistentSettings = { delete globalSettings[prop]; return storage.saveSettings(clone(globalSettings)); } - return when.resolve(); + return Promise.resolve(); }, available: function() { @@ -180,7 +179,6 @@ var persistentSettings = { userSettings[username] = settings; try { assert.deepEqual(current,settings); - return when.resolve(); } catch(err) { globalSettings.users = userSettings; return storage.saveSettings(clone(globalSettings)); diff --git a/packages/node_modules/@node-red/runtime/lib/storage/index.js b/packages/node_modules/@node-red/runtime/lib/storage/index.js index d3350a95e..e7f09c20f 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/index.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/index.js @@ -14,7 +14,6 @@ * limitations under the License. **/ -var when = require('when'); var Path = require('path'); var crypto = require('crypto'); @@ -50,15 +49,13 @@ function is_malicious(path) { } var storageModuleInterface = { - init: function(_runtime) { + init: async function(_runtime) { runtime = _runtime; - try { - storageModule = moduleSelector(runtime.settings); - settingsAvailable = storageModule.hasOwnProperty("getSettings") && storageModule.hasOwnProperty("saveSettings"); - sessionsAvailable = storageModule.hasOwnProperty("getSessions") && storageModule.hasOwnProperty("saveSessions"); - } catch (e) { - return when.reject(e); - } + // Any errors thrown by the module will get passed up to the called + // as a rejected promise + storageModule = moduleSelector(runtime.settings); + settingsAvailable = storageModule.hasOwnProperty("getSettings") && storageModule.hasOwnProperty("saveSettings"); + sessionsAvailable = storageModule.hasOwnProperty("getSessions") && storageModule.hasOwnProperty("saveSessions"); if (!!storageModule.projects) { var projectsEnabled = false; if (runtime.settings.hasOwnProperty("editorTheme") && runtime.settings.editorTheme.hasOwnProperty("projects")) { @@ -73,7 +70,7 @@ var storageModuleInterface = { } return storageModule.init(runtime.settings,runtime); }, - getFlows: function() { + getFlows: async function() { return storageModule.getFlows().then(function(flows) { return storageModule.getCredentials().then(function(creds) { var result = { @@ -85,14 +82,14 @@ var storageModuleInterface = { }) }); }, - saveFlows: function(config, user) { + saveFlows: async function(config, user) { var flows = config.flows; var credentials = config.credentials; var credentialSavePromise; if (config.credentialsDirty) { credentialSavePromise = storageModule.saveCredentials(credentials); } else { - credentialSavePromise = when.resolve(); + credentialSavePromise = Promise.resolve(); } delete config.credentialsDirty; @@ -105,64 +102,60 @@ var storageModuleInterface = { // getCredentials: function() { // return storageModule.getCredentials(); // }, - saveCredentials: function(credentials) { + saveCredentials: async function(credentials) { return storageModule.saveCredentials(credentials); }, - getSettings: function() { + getSettings: async function() { if (settingsAvailable) { return storageModule.getSettings(); } else { - return when.resolve(null); + return null } }, - saveSettings: function(settings) { + saveSettings: async function(settings) { if (settingsAvailable) { return settingsSaveMutex.runExclusive(() => storageModule.saveSettings(settings)) - } else { - return when.resolve(); } }, - getSessions: function() { + getSessions: async function() { if (sessionsAvailable) { return storageModule.getSessions(); } else { - return when.resolve(null); + return null } }, - saveSessions: function(sessions) { + saveSessions: async function(sessions) { if (sessionsAvailable) { return storageModule.saveSessions(sessions); - } else { - return when.resolve(); } }, /* Library Functions */ - getLibraryEntry: function(type, path) { + getLibraryEntry: async function(type, path) { if (is_malicious(path)) { var err = new Error(); err.code = "forbidden"; - return when.reject(err); + throw err; } return storageModule.getLibraryEntry(type, path); }, - saveLibraryEntry: function(type, path, meta, body) { + saveLibraryEntry: async function(type, path, meta, body) { if (is_malicious(path)) { var err = new Error(); err.code = "forbidden"; - return when.reject(err); + throw err; } return storageModule.saveLibraryEntry(type, path, meta, body); }, /* Deprecated functions */ - getAllFlows: function() { + getAllFlows: async function() { if (storageModule.hasOwnProperty("getAllFlows")) { return storageModule.getAllFlows(); } else { if (libraryFlowsCachedResult) { - return Promise.resolve(libraryFlowsCachedResult); + return libraryFlowsCachedResult; } else { return listFlows("/").then(function(result) { libraryFlowsCachedResult = result; @@ -175,7 +168,7 @@ var storageModuleInterface = { if (is_malicious(fn)) { var err = new Error(); err.code = "forbidden"; - return when.reject(err); + throw err; } if (storageModule.hasOwnProperty("getFlow")) { return storageModule.getFlow(fn); @@ -188,7 +181,7 @@ var storageModuleInterface = { if (is_malicious(fn)) { var err = new Error(); err.code = "forbidden"; - return when.reject(err); + throw err; } libraryFlowsCachedResult = null; if (storageModule.hasOwnProperty("saveFlow")) { @@ -204,40 +197,36 @@ var storageModuleInterface = { function listFlows(path) { return storageModule.getLibraryEntry("flows",path).then(function(res) { - return when.promise(function(resolve) { - var promises = []; - res.forEach(function(r) { - if (typeof r === "string") { - promises.push(listFlows(Path.join(path,r))); - } else { - promises.push(when.resolve(r)); - } - }); - var i=0; - when.settle(promises).then(function(res2) { - var result = {}; - res2.forEach(function(r) { - // TODO: name||fn - if (r.value.fn) { - var name = r.value.name; - if (!name) { - name = r.value.fn.replace(/\.json$/, ""); - } - result.f = result.f || []; - result.f.push(name); - } else { - result.d = result.d || {}; - result.d[res[i]] = r.value; - //console.log(">",r.value); + const promises = []; + res.forEach(function(r) { + if (typeof r === "string") { + promises.push(listFlows(Path.join(path,r))); + } else { + promises.push(Promise.resolve(r)); + } + }); + return Promise.all(promises).then(res2 => { + let i = 0; + const result = {}; + res2.forEach(function(r) { + // TODO: name||fn + if (r.fn) { + var name = r.name; + if (!name) { + name = r.fn.replace(/\.json$/, ""); } - i++; - }); - resolve(result); + result.f = result.f || []; + result.f.push(name); + } else { + result.d = result.d || {}; + result.d[res[i]] = r; + //console.log(">",r.value); + } + i++; }); + return result; }); }); } - - module.exports = storageModuleInterface; 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 22f01c978..fbcb44e2f 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 @@ -15,9 +15,7 @@ **/ var fs = require('fs-extra'); -var when = require('when'); var fspath = require("path"); -var nodeFn = require('when/node/function'); var util = require("./util"); @@ -90,14 +88,14 @@ function getLibraryEntry(type,path) { var rootPath = fspath.join(libDir,type,path); // don't create the folder if it does not exist - we are only reading.... - return nodeFn.call(fs.lstat, rootPath).then(function(stats) { + return fs.lstat(rootPath).then(function(stats) { if (stats.isFile()) { return getFileBody(root,path); } if (path.substr(-1) == '/') { path = path.substr(0,path.length-1); } - return nodeFn.call(fs.readdir, rootPath).then(function(fns) { + return fs.readdir(rootPath).then(function(fns) { var dirs = []; var files = []; fns.sort().filter(function(fn) { @@ -143,21 +141,19 @@ function getLibraryEntry(type,path) { } module.exports = { - init: function(_settings) { + init: async function(_settings) { settings = _settings; libDir = fspath.join(settings.userDir,"lib"); libFlowsDir = fspath.join(libDir,"flows"); if (!settings.readOnly) { return fs.ensureDir(libFlowsDir); - } else { - return when.resolve(); } }, getLibraryEntry: getLibraryEntry, - saveLibraryEntry: function(type,path,meta,body) { + saveLibraryEntry: async function(type,path,meta,body) { if (settings.readOnly) { - return when.resolve(); + return; } if (type === "flows" && !path.endsWith(".json")) { path += ".json"; diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js index 0fbf89de4..55bb9d078 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js @@ -16,7 +16,6 @@ var fs = require('fs-extra'); -var when = require('when'); var fspath = require("path"); var os = require('os'); @@ -108,12 +107,12 @@ Project.prototype.load = function () { // package.json isn't valid JSON... is a merge underway? project.package = {}; } - })); + }).catch(err => {})); // if (missingFiles.indexOf('README.md') === -1) { project.paths['README.md'] = fspath.join(project.paths.root,"README.md"); promises.push(fs.readFile(fspath.join(project.path,project.paths['README.md']),"utf8").then(function(content) { project.description = content; - })); + }).catch(err => {})); } else { project.description = ""; } @@ -133,9 +132,9 @@ Project.prototype.load = function () { // project.paths.credentialsFile = fspath.join(project.path,"flow_cred.json"); // } - promises.push(project.loadRemotes()); + promises.push(project.loadRemotes().catch(err => {})); - return when.settle(promises).then(function(results) { + return Promise.all(promises).then(function(results) { return project; }) }); @@ -239,7 +238,7 @@ Project.prototype.isMerging = function() { return this.merging; } -Project.prototype.update = function (user, data) { +Project.prototype.update = async function (user, data) { var username; if (!user) { username = "_"; @@ -279,7 +278,7 @@ Project.prototype.update = function (user, data) { this.credentialSecret !== existingSecret) { // key doesn't match provided existing key var e = new Error("Cannot change credentialSecret without current key"); e.code = "missing_current_credential_key"; - return when.reject(e); + throw e; } this.credentialSecret = secret; @@ -292,7 +291,7 @@ Project.prototype.update = function (user, data) { if (this.missingFiles.indexOf('package.json') !== -1) { if (!data.files || !data.files.package) { // Cannot update a project that doesn't have a known package.json - return Promise.reject("Cannot update project with missing package.json"); + throw new Error("Cannot update project with missing package.json"); } } @@ -302,7 +301,7 @@ Project.prototype.update = function (user, data) { // We have a package file. It could be one that doesn't exist yet, // or it does exist and we need to load it. if (!/package\.json$/.test(data.files.package)) { - return Promise.reject("Invalid package file: "+data.files.package) + return new Error("Invalid package file: "+data.files.package) } var root = data.files.package.substring(0,data.files.package.length-12); this.paths.root = root; @@ -391,20 +390,20 @@ Project.prototype.update = function (user, data) { modifyRemotesPromise = modifyRemotesPromise.then(function() { return project.loadRemotes(); }); - promises.push(modifyRemotesPromise); + promises.push(modifyRemotesPromise.catch(err => {})); } } } if (saveSettings) { - promises.push(settings.set("projects",globalProjectSettings)); + promises.push(settings.set("projects",globalProjectSettings).catch(err => {})); } var modifiedFiles = []; if (saveREADME) { - promises.push(util.writeFile(fspath.join(this.path,this.paths['README.md']), this.description)); + promises.push(util.writeFile(fspath.join(this.path,this.paths['README.md']), this.description).catch(err => {})); modifiedFiles.push('README.md'); } if (savePackage) { @@ -416,10 +415,10 @@ Project.prototype.update = function (user, data) { } this.package = Object.assign(currentPackage,this.package); return util.writeFile(fspath.join(project.path,this.paths['package.json']), JSON.stringify(this.package,"",4)); - })); + }).catch(err => {})); modifiedFiles.push('package.json'); } - return when.settle(promises).then(function(res) { + return Promise.all(promises).then(function(res) { var gitSettings = getUserGitSettings(user) || {}; var workflowMode = (gitSettings.workflow||{}).mode || "manual"; if (workflowMode === 'auto') { @@ -944,32 +943,17 @@ function createDefaultProject(user, project) { } function checkProjectFiles(project) { var promises = []; - var paths = []; + var missing = []; for (var file in defaultFileSet) { if (defaultFileSet.hasOwnProperty(file)) { - paths.push(file); - promises.push(fs.stat(fspath.join(project.path,project.paths.root,file))); + (function(f) { + promises.push(fs.stat(fspath.join(project.path,project.paths.root,f)).catch(err => { + missing.push(f); + })); + })(file); } } - return when.settle(promises).then(function(results) { - var missing = []; - results.forEach(function(result,i) { - if (result.state === 'rejected') { - missing.push(paths[i]); - } - }); - return missing; - }).then(function(missing) { - // if (createMissing) { - // var promises = []; - // missing.forEach(function(file) { - // promises.push(util.writeFile(fspath.join(projectPath,file),defaultFileSet[file](project))); - // }); - // return promises; - // } else { - return missing; - // } - }); + return Promise.all(promises).then(() => missing); } function createProject(user, metadata) { var username; diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js index 5e1d4f15b..80a3f31e4 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js @@ -15,9 +15,7 @@ **/ var fs = require('fs-extra'); -var when = require('when'); var fspath = require("path"); -var nodeFn = require('when/node/function'); var crypto = require('crypto'); var storageSettings = require("../settings"); @@ -223,7 +221,6 @@ function loadProject(name) { function getProject(user, name) { checkActiveProject(name); - //return when.resolve(activeProject.info); return Promise.resolve(activeProject.export()); } @@ -495,7 +492,7 @@ var flowsFileBackup; var credentialsFile; var credentialsFileBackup; -function getFlows() { +async function getFlows() { if (!initialFlowLoadComplete) { initialFlowLoadComplete = true; log.info(log._("storage.localfilesystem.user-dir",{path:settings.userDir})); @@ -522,25 +519,25 @@ function getFlows() { log.warn("Project repository is empty"); error = new Error("Project repository is empty"); error.code = "project_empty"; - return when.reject(error); + throw error; } if (activeProject.missingFiles && activeProject.missingFiles.indexOf('package.json') !== -1) { log.warn("Project missing package.json"); error = new Error("Project missing package.json"); error.code = "missing_package_file"; - return when.reject(error); + throw error; } if (!activeProject.getFlowFile()) { log.warn("Project has no flow file"); error = new Error("Project has no flow file"); error.code = "missing_flow_file"; - return when.reject(error); + throw error; } if (activeProject.isMerging()) { log.warn("Project has unmerged changes"); error = new Error("Project has unmerged changes. Cannot load flows"); error.code = "git_merge_conflict"; - return when.reject(error); + throw error; } } @@ -554,14 +551,14 @@ function getFlows() { }); } -function saveFlows(flows, user) { +async function saveFlows(flows, user) { if (settings.readOnly) { - return when.resolve(); + return } if (activeProject && activeProject.isMerging()) { var error = new Error("Project has unmerged changes. Cannot deploy new flows"); error.code = "git_merge_conflict"; - return when.reject(error); + throw error; } flowsFileExists = true; @@ -589,9 +586,9 @@ function getCredentials() { return util.readFile(credentialsFile,credentialsFileBackup,{},'credentials'); } -function saveCredentials(credentials) { +async function saveCredentials(credentials) { if (settings.readOnly) { - return when.resolve(); + return; } var credentialData; diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/ssh/index.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/ssh/index.js index 25dacd3b1..cafe9bcb7 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/ssh/index.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/ssh/index.js @@ -15,7 +15,6 @@ **/ var fs = require('fs-extra'); -var when = require('when'); var fspath = require("path"); var keygen = require("./keygen"); diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/sessions.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/sessions.js index 6b4deaa5a..575b0c056 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/sessions.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/sessions.js @@ -14,7 +14,6 @@ * limitations under the License. **/ -var when = require('when'); var fs = require('fs-extra'); var fspath = require("path"); @@ -30,8 +29,8 @@ module.exports = { settings = _settings; sessionsFile = fspath.join(settings.userDir,".sessions.json"); }, - getSessions: function() { - return when.promise(function(resolve,reject) { + getSessions: async function() { + return new Promise(function(resolve,reject) { fs.readFile(sessionsFile,'utf8',function(err,data){ if (!err) { try { @@ -44,9 +43,9 @@ module.exports = { }) }); }, - saveSessions: function(sessions) { + saveSessions: async function(sessions) { if (settings.readOnly) { - return when.resolve(); + return; } return util.writeFile(sessionsFile,JSON.stringify(sessions)); } diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index 17622c6b4..1a549e0c6 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -22,7 +22,6 @@ "clone": "2.1.2", "express": "4.17.1", "fs-extra": "8.1.0", - "json-stringify-safe": "5.0.1", - "when": "3.7.8" + "json-stringify-safe": "5.0.1" } } diff --git a/packages/node_modules/@node-red/util/lib/i18n.js b/packages/node_modules/@node-red/util/lib/i18n.js index d95f4bec6..cce2e8149 100644 --- a/packages/node_modules/@node-red/util/lib/i18n.js +++ b/packages/node_modules/@node-red/util/lib/i18n.js @@ -22,7 +22,6 @@ var i18n = require("i18next"); -var when = require("when"); var path = require("path"); var fs = require("fs"); @@ -38,16 +37,16 @@ var initPromise; */ function registerMessageCatalogs(catalogs) { var promises = catalogs.map(function(catalog) { - return registerMessageCatalog(catalog.namespace,catalog.dir,catalog.file); + return registerMessageCatalog(catalog.namespace,catalog.dir,catalog.file).catch(err => {}); }); - return when.settle(promises); + return Promise.all(promises); } /** * Register a message catalog with i18n. * @memberof @node-red/util_i18n */ -function registerMessageCatalog(namespace,dir,file) { +async function registerMessageCatalog(namespace,dir,file) { return initPromise.then(function() { return new Promise((resolve,reject) => { resourceMap[namespace] = { basedir:dir, file:file, lngs: []}; @@ -147,7 +146,7 @@ function init() { if (!initPromise) { // Keep this as a 'when' promise as top-level red.js uses 'otherwise' // and embedded users of NR may have copied that. - initPromise = when.promise((resolve,reject) => { + initPromise = new Promise((resolve,reject) => { i18n.use(MessageFileLoader); var opt = { // debug: true, diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 9d447a280..41e858c7a 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -20,7 +20,6 @@ "json-stringify-safe": "5.0.1", "jsonata": "1.8.4", "lodash.clonedeep": "^4.5.0", - "moment-timezone": "0.5.32", - "when": "3.7.8" + "moment-timezone": "0.5.32" } } diff --git a/packages/node_modules/node-red/lib/red.js b/packages/node_modules/node-red/lib/red.js index 684ff6d8a..86fa513d1 100644 --- a/packages/node_modules/node-red/lib/red.js +++ b/packages/node_modules/node-red/lib/red.js @@ -92,11 +92,32 @@ module.exports = { * @memberof node-red */ start: function() { - return runtime.start().then(function() { + // The top level red.js has always used 'otherwise' on the promise returned + // here. This is a non-standard promise function coming from our early use + // of the when.js library. + // We want to remove all dependency on when.js as native Promises now exist. + // But we have the issue that some embedders of Node-RED may have copied our + // top-level red.js a bit too much. + // + let startPromise = runtime.start().then(function() { if (apiEnabled) { return api.start(); } }); + startPromise._then = startPromise.then; + startPromise.then = function(resolve,reject) { + var inner = startPromise._then(resolve,reject); + inner.otherwise = function(cb) { + redUtil.log.error("**********************************************"); + redUtil.log.error("* Deprecated call to RED.start().otherwise() *"); + redUtil.log.error("* This will be removed in Node-RED 2.x *"); + redUtil.log.error("* Use RED.start().catch() instead *") + redUtil.log.error("**********************************************"); + return inner.catch(cb); + } + return inner; + } + return startPromise; }, /** * Stop the Node-RED application. diff --git a/packages/node_modules/node-red/red.js b/packages/node_modules/node-red/red.js index 32639ea3c..55e355a7c 100755 --- a/packages/node_modules/node-red/red.js +++ b/packages/node_modules/node-red/red.js @@ -429,7 +429,7 @@ httpsPromise.then(function(startupHttps) { } else { RED.log.info(RED.log._("server.headless-mode")); } - }).otherwise(function(err) { + }).catch(function(err) { RED.log.error(RED.log._("server.failed-to-start")); if (err.stack) { RED.log.error(err.stack); @@ -468,4 +468,5 @@ httpsPromise.then(function(startupHttps) { }).catch(function(err) { console.log("Failed to get https settings: " + err); + console.log(err.stack) }); diff --git a/test/nodes/core/network/21-httprequest_spec.js b/test/nodes/core/network/21-httprequest_spec.js index ab9020a64..e9792dd54 100644 --- a/test/nodes/core/network/21-httprequest_spec.js +++ b/test/nodes/core/network/21-httprequest_spec.js @@ -14,7 +14,6 @@ * limitations under the License. **/ -var when = require("when"); var http = require("http"); var https = require("https"); var should = require("should"); diff --git a/test/nodes/core/network/22-websocket_spec.js b/test/nodes/core/network/22-websocket_spec.js index e13b513c8..995248279 100644 --- a/test/nodes/core/network/22-websocket_spec.js +++ b/test/nodes/core/network/22-websocket_spec.js @@ -15,7 +15,6 @@ **/ var ws = require("ws"); -var when = require("when"); var should = require("should"); var helper = require("node-red-node-test-helper"); var websocketNode = require("nr-test-utils").require("@node-red/nodes/core/network/22-websocket.js"); @@ -27,7 +26,7 @@ function getWsUrl(path) { } function createClient(listenerid) { - return when.promise(function(resolve, reject) { + return new Promise(function(resolve, reject) { var node = helper.getNode(listenerid); var url = getWsUrl(node.path); var sock = new ws(url); @@ -300,23 +299,33 @@ describe('websocket Node', function() { { id: "n2", type: "websocket out", server: "n1" }, { id: "n3", type: "helper", wires: [["n2"]] }]; helper.load(websocketNode, flow, function() { - var def1 = when.defer(), - def2 = when.defer(); - when.all([createClient("n1"), createClient("n1")]).then(function(socks) { - socks[0].on("message", function(msg, flags) { - msg.should.equal("hello"); - def1.resolve(); - }); - socks[1].on("message", function(msg, flags) { - msg.should.equal("hello"); - def2.resolve(); - }); + Promise.all([createClient("n1"), createClient("n1")]).then(function(socks) { + var promises = [ + new Promise((resolve,reject) => { + socks[0].on("message", function(msg, flags) { + try { + msg.should.equal("hello"); + resolve(); + } catch(err) { + reject(err); + } + }); + }), + new Promise((resolve,reject) => { + socks[1].on("message", function(msg, flags) { + try { + msg.should.equal("hello"); + resolve(); + } catch(err) { + reject(err); + } + }); + }) + ]; helper.getNode("n3").send({ payload: "hello" }); - return when.all([def1.promise, def2.promise]).then(function() { - done(); - }); + return Promise.all(promises).then(() => {done()}); }).catch(function(err) { done(err); }); diff --git a/test/unit/@node-red/editor-api/lib/admin/flow_spec.js b/test/unit/@node-red/editor-api/lib/admin/flow_spec.js index 278c6a3f3..807fc8fb9 100644 --- a/test/unit/@node-red/editor-api/lib/admin/flow_spec.js +++ b/test/unit/@node-red/editor-api/lib/admin/flow_spec.js @@ -19,7 +19,6 @@ var request = require('supertest'); var express = require('express'); var bodyParser = require('body-parser'); var sinon = require('sinon'); -var when = require('when'); var NR_TEST_UTILS = require("nr-test-utils"); diff --git a/test/unit/@node-red/editor-api/lib/admin/nodes_spec.js b/test/unit/@node-red/editor-api/lib/admin/nodes_spec.js index f921d907d..872f76e23 100644 --- a/test/unit/@node-red/editor-api/lib/admin/nodes_spec.js +++ b/test/unit/@node-red/editor-api/lib/admin/nodes_spec.js @@ -19,7 +19,6 @@ var request = require('supertest'); var express = require('express'); var bodyParser = require('body-parser'); var sinon = require('sinon'); -var when = require('when'); var NR_TEST_UTILS = require("nr-test-utils"); diff --git a/test/unit/@node-red/editor-api/lib/auth/index_spec.js b/test/unit/@node-red/editor-api/lib/auth/index_spec.js index 3cf0111d6..def2335a7 100644 --- a/test/unit/@node-red/editor-api/lib/auth/index_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/index_spec.js @@ -15,7 +15,6 @@ **/ var should = require("should"); -var when = require("when"); var sinon = require("sinon"); var passport = require("passport"); @@ -60,7 +59,7 @@ describe("api/auth/index",function() { describe("revoke", function() { it("revokes a token", function(done) { var revokeToken = sinon.stub(Tokens,"revoke",function() { - return when.resolve(); + return Promise.resolve(); }); var req = { body: { token: "abcdef" } }; diff --git a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js index 848aaf99d..d95d78db2 100644 --- a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js @@ -15,7 +15,6 @@ **/ var should = require("should"); -var when = require('when'); var sinon = require('sinon'); var NR_TEST_UTILS = require("nr-test-utils"); @@ -37,7 +36,7 @@ describe("api/auth/strategies", function() { it('Handles authentication failure',function(done) { userAuthentication = sinon.stub(Users,"authenticate",function(username,password) { - return when.resolve(null); + return Promise.resolve(null); }); strategies.passwordTokenExchange({},"user","password","scope",function(err,token) { @@ -53,7 +52,7 @@ describe("api/auth/strategies", function() { it('Handles scope overreach',function(done) { userAuthentication = sinon.stub(Users,"authenticate",function(username,password) { - return when.resolve({username:"user",permissions:"read"}); + return Promise.resolve({username:"user",permissions:"read"}); }); strategies.passwordTokenExchange({},"user","password","*",function(err,token) { @@ -69,14 +68,14 @@ describe("api/auth/strategies", function() { it('Creates new token on authentication success',function(done) { userAuthentication = sinon.stub(Users,"authenticate",function(username,password) { - return when.resolve({username:"user",permissions:"*"}); + return Promise.resolve({username:"user",permissions:"*"}); }); var tokenDetails = {}; var tokenCreate = sinon.stub(Tokens,"create",function(username,client,scope) { tokenDetails.username = username; tokenDetails.client = client; tokenDetails.scope = scope; - return when.resolve({accessToken: "123456"}); + return Promise.resolve({accessToken: "123456"}); }); strategies.passwordTokenExchange({id:"myclient"},"user","password","read",function(err,token) { @@ -100,7 +99,7 @@ describe("api/auth/strategies", function() { describe("Anonymous Strategy", function() { it('Succeeds if anon user enabled',function(done) { var userDefault = sinon.stub(Users,"default",function() { - return when.resolve("anon"); + return Promise.resolve("anon"); }); strategies.anonymousStrategy._success = strategies.anonymousStrategy.success; strategies.anonymousStrategy.success = function(user) { @@ -113,7 +112,7 @@ describe("api/auth/strategies", function() { }); it('Fails if anon user not enabled',function(done) { var userDefault = sinon.stub(Users,"default",function() { - return when.resolve(null); + return Promise.resolve(null); }); strategies.anonymousStrategy._fail = strategies.anonymousStrategy.fail; strategies.anonymousStrategy.fail = function(err) { @@ -132,7 +131,7 @@ describe("api/auth/strategies", function() { describe("Tokens Strategy", function() { it('Succeeds if tokens user enabled custom header',function(done) { var userTokens = sinon.stub(Users,"tokens",function(token) { - return when.resolve("tokens-"+token); + return Promise.resolve("tokens-"+token); }); var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) { return "x-test-token"; @@ -148,7 +147,7 @@ describe("api/auth/strategies", function() { }); it('Succeeds if tokens user enabled default header',function(done) { var userTokens = sinon.stub(Users,"tokens",function(token) { - return when.resolve("tokens-"+token); + return Promise.resolve("tokens-"+token); }); var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) { return "authorization"; @@ -164,7 +163,7 @@ describe("api/auth/strategies", function() { }); it('Fails if tokens user not enabled',function(done) { var userTokens = sinon.stub(Users,"tokens",function() { - return when.resolve(null); + return Promise.resolve(null); }); var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) { return "authorization"; @@ -187,7 +186,7 @@ describe("api/auth/strategies", function() { describe("Bearer Strategy", function() { it('Rejects invalid token',function(done) { var getToken = sinon.stub(Tokens,"get",function(token) { - return when.resolve(null); + return Promise.resolve(null); }); strategies.bearerStrategy("1234",function(err,user) { @@ -204,10 +203,10 @@ describe("api/auth/strategies", function() { }); it('Accepts valid token',function(done) { var getToken = sinon.stub(Tokens,"get",function(token) { - return when.resolve({user:"user",scope:"scope"}); + return Promise.resolve({user:"user",scope:"scope"}); }); var getUser = sinon.stub(Users,"get",function(username) { - return when.resolve("aUser"); + return Promise.resolve("aUser"); }); strategies.bearerStrategy("1234",function(err,user,opts) { @@ -226,10 +225,10 @@ describe("api/auth/strategies", function() { }); it('Fail if no user for token',function(done) { var getToken = sinon.stub(Tokens,"get",function(token) { - return when.resolve({user:"user",scope:"scope"}); + return Promise.resolve({user:"user",scope:"scope"}); }); var getUser = sinon.stub(Users,"get",function(username) { - return when.resolve(null); + return Promise.resolve(null); }); strategies.bearerStrategy("1234",function(err,user,opts) { @@ -252,7 +251,7 @@ describe("api/auth/strategies", function() { it('Accepts valid client',function(done) { var testClient = {id:"node-red-editor",secret:"not_available"}; var getClient = sinon.stub(Clients,"get",function(client) { - return when.resolve(testClient); + return Promise.resolve(testClient); }); strategies.clientPasswordStrategy(testClient.id,testClient.secret,function(err,client) { @@ -270,7 +269,7 @@ describe("api/auth/strategies", function() { it('Rejects invalid client secret',function(done) { var testClient = {id:"node-red-editor",secret:"not_available"}; var getClient = sinon.stub(Clients,"get",function(client) { - return when.resolve(testClient); + return Promise.resolve(testClient); }); strategies.clientPasswordStrategy(testClient.id,"invalid_secret",function(err,client) { @@ -287,7 +286,7 @@ describe("api/auth/strategies", function() { }); it('Rejects invalid client id',function(done) { var getClient = sinon.stub(Clients,"get",function(client) { - return when.resolve(null); + return Promise.resolve(null); }); strategies.clientPasswordStrategy("invalid_id","invalid_secret",function(err,client) { try { @@ -305,7 +304,7 @@ describe("api/auth/strategies", function() { var userAuthentication; it('Blocks after 5 failures',function(done) { userAuthentication = sinon.stub(Users,"authenticate",function(username,password) { - return when.resolve(null); + return Promise.resolve(null); }); for (var z=0; z<5; z++) { strategies.passwordTokenExchange({},"user","badpassword","scope",function(err,token) { diff --git a/test/unit/@node-red/editor-api/lib/auth/tokens_spec.js b/test/unit/@node-red/editor-api/lib/auth/tokens_spec.js index 2b06932c7..9bb231eb5 100644 --- a/test/unit/@node-red/editor-api/lib/auth/tokens_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/tokens_spec.js @@ -15,7 +15,6 @@ **/ var should = require("should"); -var when = require("when"); var sinon = require("sinon"); var NR_TEST_UTILS = require("nr-test-utils"); @@ -35,7 +34,7 @@ describe("api/auth/tokens", function() { it('returns a valid token', function(done) { Tokens.init({},{ getSessions:function() { - return when.resolve({"1234":{"user":"fred","expires":Date.now()+1000}}); + return Promise.resolve({"1234":{"user":"fred","expires":Date.now()+1000}}); } }).then(function() { Tokens.get("1234").then(function(token) { @@ -52,7 +51,7 @@ describe("api/auth/tokens", function() { it('returns null for an invalid token', function(done) { Tokens.init({},{ getSessions:function() { - return when.resolve({}); + return Promise.resolve({}); } }).then(function() { Tokens.get("1234").then(function(token) { @@ -66,11 +65,11 @@ describe("api/auth/tokens", function() { }); }); it('returns null for an expired token', function(done) { - var saveSessions = sinon.stub().returns(when.resolve()); + var saveSessions = sinon.stub().returns(Promise.resolve()); var expiryTime = Date.now()+50; Tokens.init({},{ getSessions:function() { - return when.resolve({"1234":{"user":"fred","expires":expiryTime}}); + return Promise.resolve({"1234":{"user":"fred","expires":expiryTime}}); }, saveSessions: saveSessions }).then(function() { @@ -100,10 +99,10 @@ describe("api/auth/tokens", function() { tokens: [{ token: "1234", user: "fred", - }] + }] },{ getSessions:function() { - return when.resolve({}); + return Promise.resolve({}); } }).then(function() { Tokens.get("1234").then(function(token) { @@ -124,11 +123,11 @@ describe("api/auth/tokens", function() { var savedSession; Tokens.init({sessionExpiryTime: 10},{ getSessions:function() { - return when.resolve({}); + return Promise.resolve({}); }, saveSessions:function(sess) { savedSession = sess; - return when.resolve(); + return Promise.resolve(); } }); var expectedExpiryTime = Date.now()+10000; @@ -159,11 +158,11 @@ describe("api/auth/tokens", function() { var savedSession; Tokens.init({},{ getSessions:function() { - return when.resolve({"1234":{"user":"fred","expires":Date.now()+1000}}); + return Promise.resolve({"1234":{"user":"fred","expires":Date.now()+1000}}); }, saveSessions:function(sess) { savedSession = sess; - return when.resolve(); + return Promise.resolve(); } }).then(function() { Tokens.revoke("1234").then(function() { diff --git a/test/unit/@node-red/editor-api/lib/auth/users_spec.js b/test/unit/@node-red/editor-api/lib/auth/users_spec.js index 18a179ac7..e07a35a03 100644 --- a/test/unit/@node-red/editor-api/lib/auth/users_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/users_spec.js @@ -15,7 +15,6 @@ **/ var should = require("should"); -var when = require('when'); var sinon = require('sinon'); var NR_TEST_UTILS = require("nr-test-utils"); @@ -144,12 +143,12 @@ describe("api/auth/users", function() { Users.init({ type:"credentials", users:function(username) { - return when.resolve({'username':'dave','permissions':'read'}); + return Promise.resolve({'username':'dave','permissions':'read'}); }, authenticate: function(username,password) { authUsername = username; authPassword = password; - return when.resolve({'username':'pete','permissions':'write'}); + return Promise.resolve({'username':'pete','permissions':'write'}); } }); }); diff --git a/test/unit/@node-red/editor-api/lib/editor/comms_spec.js b/test/unit/@node-red/editor-api/lib/editor/comms_spec.js index 8f6f78315..06743e87e 100644 --- a/test/unit/@node-red/editor-api/lib/editor/comms_spec.js +++ b/test/unit/@node-red/editor-api/lib/editor/comms_spec.js @@ -18,7 +18,6 @@ var should = require("should"); var sinon = require("sinon"); const stoppable = require('stoppable'); -var when = require("when"); var http = require('http'); var express = require('express'); var app = express(); @@ -59,7 +58,7 @@ describe("api/editor/comms", function() { var url; var port; before(function(done) { - sinon.stub(Users,"default",function() { return when.resolve(null);}); + sinon.stub(Users,"default",function() { return Promise.resolve(null);}); server = stoppable(http.createServer(function(req,res){app(req,res)})); comms.init(server, {}, {comms: mockComms}); server.listen(listenPort, address); @@ -165,7 +164,7 @@ describe("api/editor/comms", function() { var url; var port; before(function(done) { - sinon.stub(Users,"default",function() { return when.resolve(null);}); + sinon.stub(Users,"default",function() { return Promise.resolve(null);}); server = stoppable(http.createServer(function(req,res){app(req,res)})); comms.init(server, {httpAdminRoot:"/adminPath"}, {comms: mockComms}); server.listen(listenPort, address); @@ -203,7 +202,7 @@ describe("api/editor/comms", function() { var url; var port; before(function(done) { - sinon.stub(Users,"default",function() { return when.resolve(null);}); + sinon.stub(Users,"default",function() { return Promise.resolve(null);}); server = stoppable(http.createServer(function(req,res){app(req,res)})); comms.init(server, {httpAdminRoot:"/adminPath/"}, {comms: mockComms}); server.listen(listenPort, address); @@ -241,7 +240,7 @@ describe("api/editor/comms", function() { var url; var port; before(function(done) { - sinon.stub(Users,"default",function() { return when.resolve(null);}); + sinon.stub(Users,"default",function() { return Promise.resolve(null);}); server = stoppable(http.createServer(function(req,res){app(req,res)})); comms.init(server, {httpAdminRoot:"adminPath"}, {comms: mockComms}); server.listen(listenPort, address); @@ -279,7 +278,7 @@ describe("api/editor/comms", function() { var url; var port; before(function(done) { - sinon.stub(Users,"default",function() { return when.resolve(null);}); + sinon.stub(Users,"default",function() { return Promise.resolve(null);}); server = stoppable(http.createServer(function(req,res){app(req,res)})); comms.init(server, {webSocketKeepAliveTime: 100}, {comms: mockComms}); server.listen(listenPort, address); @@ -345,28 +344,28 @@ describe("api/editor/comms", function() { var getToken; var getUserToken; before(function(done) { - getDefaultUser = sinon.stub(Users,"default",function() { return when.resolve(null);}); + getDefaultUser = sinon.stub(Users,"default",function() { return Promise.resolve(null);}); getUser = sinon.stub(Users,"get", function(username) { if (username == "fred") { - return when.resolve({permissions:"read"}); + return Promise.resolve({permissions:"read"}); } else { - return when.resolve(null); + return Promise.resolve(null); } }); getUserToken = sinon.stub(Users,"tokens", function(token) { if (token == "abcde") { - return when.resolve({user:"wilma", permissions:"*"}) + return Promise.resolve({user:"wilma", permissions:"*"}) } else { - return when.resolve(null); + return Promise.resolve(null); } }); getToken = sinon.stub(Tokens,"get",function(token) { if (token == "1234") { - return when.resolve({user:"fred",scope:["*"]}); + return Promise.resolve({user:"fred",scope:["*"]}); } else if (token == "5678") { - return when.resolve({user:"barney",scope:["*"]}); + return Promise.resolve({user:"barney",scope:["*"]}); } else { - return when.resolve(null); + return Promise.resolve(null); } }); @@ -484,7 +483,7 @@ describe("api/editor/comms", function() { var port; var getDefaultUser; before(function(done) { - getDefaultUser = sinon.stub(Users,"default",function() { return when.resolve({permissions:"read"});}); + getDefaultUser = sinon.stub(Users,"default",function() { return Promise.resolve({permissions:"read"});}); server = stoppable(http.createServer(function(req,res){app(req,res)})); comms.init(server, {adminAuth:{}}, {comms: mockComms}); server.listen(listenPort, address); diff --git a/test/unit/@node-red/editor-api/lib/editor/credentials_spec.js b/test/unit/@node-red/editor-api/lib/editor/credentials_spec.js index 2fc1ea5a3..c5a6c74f8 100644 --- a/test/unit/@node-red/editor-api/lib/editor/credentials_spec.js +++ b/test/unit/@node-red/editor-api/lib/editor/credentials_spec.js @@ -18,7 +18,6 @@ var should = require("should"); var request = require('supertest'); var express = require('express'); var sinon = require('sinon'); -var when = require('when'); var NR_TEST_UTILS = require("nr-test-utils"); diff --git a/test/unit/@node-red/editor-api/lib/editor/index_spec.js b/test/unit/@node-red/editor-api/lib/editor/index_spec.js index e45fcbdba..e142f8984 100644 --- a/test/unit/@node-red/editor-api/lib/editor/index_spec.js +++ b/test/unit/@node-red/editor-api/lib/editor/index_spec.js @@ -28,10 +28,6 @@ var auth = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth"); var log = NR_TEST_UTILS.require("@node-red/util").log; - -var when = require("when"); - - describe("api/editor/index", function() { var app; describe("disabled the editor", function() { diff --git a/test/unit/@node-red/editor-api/lib/editor/theme_spec.js b/test/unit/@node-red/editor-api/lib/editor/theme_spec.js index 67e305f71..55006f71b 100644 --- a/test/unit/@node-red/editor-api/lib/editor/theme_spec.js +++ b/test/unit/@node-red/editor-api/lib/editor/theme_spec.js @@ -17,7 +17,6 @@ var should = require("should"); var express = require('express'); var sinon = require('sinon'); -var when = require('when'); var fs = require("fs"); var app = express(); diff --git a/test/unit/@node-red/editor-api/lib/index_spec.js b/test/unit/@node-red/editor-api/lib/index_spec.js index 1d5e9380b..ec846cf65 100644 --- a/test/unit/@node-red/editor-api/lib/index_spec.js +++ b/test/unit/@node-red/editor-api/lib/index_spec.js @@ -18,7 +18,6 @@ var should = require("should"); var sinon = require("sinon"); var request = require("supertest"); var express = require("express"); -var when = require("when"); var fs = require("fs"); var path = require("path"); diff --git a/test/unit/@node-red/registry/lib/index_spec.js b/test/unit/@node-red/registry/lib/index_spec.js index 753486e9f..75a057730 100644 --- a/test/unit/@node-red/registry/lib/index_spec.js +++ b/test/unit/@node-red/registry/lib/index_spec.js @@ -17,7 +17,6 @@ var should = require("should"); var sinon = require("sinon"); var path = require("path"); -var when = require("when"); var fs = require("fs"); var NR_TEST_UTILS = require("nr-test-utils"); @@ -51,7 +50,7 @@ describe('red/registry/index', function() { describe('#addModule', function() { it('loads the module and returns its info', function(done) { stubs.push(sinon.stub(loader,"addModule",function(module) { - return when.resolve(); + return Promise.resolve(); })); stubs.push(sinon.stub(typeRegistry,"getModuleInfo", function(module) { return "info"; @@ -63,7 +62,7 @@ describe('red/registry/index', function() { }); it('rejects if loader rejects', function(done) { stubs.push(sinon.stub(loader,"addModule",function(module) { - return when.reject("error"); + return Promise.reject("error"); })); stubs.push(sinon.stub(typeRegistry,"getModuleInfo", function(module) { return "info"; @@ -80,7 +79,7 @@ describe('red/registry/index', function() { describe('#enableNode',function() { it('enables a node set',function(done) { stubs.push(sinon.stub(typeRegistry,"enableNodeSet",function() { - return when.resolve(); + return Promise.resolve(); })); stubs.push(sinon.stub(typeRegistry,"getNodeInfo", function() { return {id:"node-set",loaded:true}; @@ -104,14 +103,14 @@ describe('red/registry/index', function() { it('triggers a node load',function(done) { stubs.push(sinon.stub(typeRegistry,"enableNodeSet",function() { - return when.resolve(); + return Promise.resolve(); })); var calls = 0; stubs.push(sinon.stub(typeRegistry,"getNodeInfo", function() { // loaded=false on first call, true on subsequent return {id:"node-set",loaded:(calls++>0)}; })); - stubs.push(sinon.stub(loader,"loadNodeSet",function(){return when.resolve();})); + stubs.push(sinon.stub(loader,"loadNodeSet",function(){return Promise.resolve();})); stubs.push(sinon.stub(typeRegistry,"getFullNodeInfo")); registry.enableNode("node-set").then(function(ns) { diff --git a/test/unit/@node-red/registry/lib/installer_spec.js b/test/unit/@node-red/registry/lib/installer_spec.js index ad658fd16..b344dbbea 100644 --- a/test/unit/@node-red/registry/lib/installer_spec.js +++ b/test/unit/@node-red/registry/lib/installer_spec.js @@ -16,7 +16,6 @@ var should = require("should"); var sinon = require("sinon"); -var when = require("when"); var path = require("path"); var fs = require('fs-extra'); var EventEmitter = require('events'); @@ -185,7 +184,7 @@ describe('nodes/registry/installer', function() { initInstaller(p) var addModule = sinon.stub(registry,"addModule",function(md) { - return when.resolve(nodeInfo); + return Promise.resolve(nodeInfo); }); installer.installModule("this_wont_exist").then(function(info) { @@ -216,7 +215,7 @@ describe('nodes/registry/installer', function() { it("succeeds when path is valid node-red module", function(done) { var nodeInfo = {nodes:{module:"foo",types:["a"]}}; var addModule = sinon.stub(registry,"addModule",function(md) { - return when.resolve(nodeInfo); + return Promise.resolve(nodeInfo); }); var resourcesDir = path.resolve(path.join(__dirname,"resources","local","TestNodeModule","node_modules","TestNodeModule")); @@ -246,7 +245,7 @@ describe('nodes/registry/installer', function() { initInstaller(p) var addModule = sinon.stub(registry,"addModule",function(md) { - return when.resolve(nodeInfo); + return Promise.resolve(nodeInfo); }); installer.installModule("this_wont_exist",null,"https://example/foo-0.1.1.tgz").then(function(info) { @@ -259,19 +258,20 @@ describe('nodes/registry/installer', function() { describe("uninstalls module", function() { it("rejects invalid module names", function(done) { var promises = []; - promises.push(installer.uninstallModule("this_wont_exist ")); - promises.push(installer.uninstallModule("this_wont_exist;no_it_really_wont")); - when.settle(promises).then(function(results) { - results[0].state.should.be.eql("rejected"); - results[1].state.should.be.eql("rejected"); + var rejectedCount = 0; + + promises.push(installer.uninstallModule("this_wont_exist ").catch(() => {rejectedCount++})); + promises.push(installer.uninstallModule("this_wont_exist;no_it_really_wont").catch(() => {rejectedCount++})); + Promise.all(promises).then(function() { + rejectedCount.should.eql(2); done(); - }); + }).catch(done); }); it("rejects with generic error", function(done) { var nodeInfo = [{module:"foo",types:["a"]}]; var removeModule = sinon.stub(registry,"removeModule",function(md) { - return when.resolve(nodeInfo); + return Promise.resolve(nodeInfo); }); var res = { code: 1, diff --git a/test/unit/@node-red/registry/lib/loader_spec.js b/test/unit/@node-red/registry/lib/loader_spec.js index 6dc9daa55..d62a8a9b4 100644 --- a/test/unit/@node-red/registry/lib/loader_spec.js +++ b/test/unit/@node-red/registry/lib/loader_spec.js @@ -15,7 +15,6 @@ **/ var should = require("should"); -var when = require("when"); var sinon = require("sinon"); var path = require("path"); var fs = require("fs-extra"); diff --git a/test/unit/@node-red/registry/lib/localfilesystem_spec.js b/test/unit/@node-red/registry/lib/localfilesystem_spec.js index 82e4d1b2d..d86a08adb 100644 --- a/test/unit/@node-red/registry/lib/localfilesystem_spec.js +++ b/test/unit/@node-red/registry/lib/localfilesystem_spec.js @@ -15,7 +15,6 @@ **/ var should = require("should"); -var when = require("when"); var sinon = require("sinon"); var path = require("path"); diff --git a/test/unit/@node-red/registry/lib/registry_spec.js b/test/unit/@node-red/registry/lib/registry_spec.js index c7ad930c4..d493ed688 100644 --- a/test/unit/@node-red/registry/lib/registry_spec.js +++ b/test/unit/@node-red/registry/lib/registry_spec.js @@ -15,7 +15,6 @@ **/ var should = require("should"); -var when = require("when"); var sinon = require("sinon"); var path = require("path"); @@ -34,7 +33,7 @@ describe("red/nodes/registry/registry",function() { function stubSettings(s,available,initialConfig) { s.available = function() {return available;}; - s.set = sinon.spy(function(s,v) { return when.resolve();}); + s.set = sinon.spy(function(s,v) { return Promise.resolve();}); s.get = function(s) { return initialConfig;}; return s; } @@ -95,7 +94,7 @@ describe("red/nodes/registry/registry",function() { it('migrates legacy format', function(done) { var legacySettings = { available: function() { return true; }, - set: sinon.stub().returns(when.resolve()), + set: sinon.stub().returns(Promise.resolve()), get: function() { return { "123": { "name": "72-sentiment.js", diff --git a/test/unit/@node-red/registry/lib/resources/local/TestNode2/TestNode2.js b/test/unit/@node-red/registry/lib/resources/local/TestNode2/TestNode2.js index faf61a8f3..1bf2fa6c9 100644 --- a/test/unit/@node-red/registry/lib/resources/local/TestNode2/TestNode2.js +++ b/test/unit/@node-red/registry/lib/resources/local/TestNode2/TestNode2.js @@ -1,8 +1,7 @@ // A test node that exports a function which returns a resolving promise -var when = require("when"); module.exports = function(RED) { - return when.promise(function(resolve,reject) { + return new Promise(function(resolve,reject) { function TestNode(n) {} RED.nodes.registerType("test-node-2",TestNode); resolve(); diff --git a/test/unit/@node-red/registry/lib/resources/local/TestNode3/TestNode3.js b/test/unit/@node-red/registry/lib/resources/local/TestNode3/TestNode3.js index 756dc1398..b9ee6a624 100644 --- a/test/unit/@node-red/registry/lib/resources/local/TestNode3/TestNode3.js +++ b/test/unit/@node-red/registry/lib/resources/local/TestNode3/TestNode3.js @@ -1,8 +1,7 @@ // A test node that exports a function which returns a rejecting promise -var when = require("when"); module.exports = function(RED) { - return when.promise(function(resolve,reject) { + return new Promise(function(resolve,reject) { reject("fail"); }); } diff --git a/test/unit/@node-red/runtime/lib/api/nodes_spec.js b/test/unit/@node-red/runtime/lib/api/nodes_spec.js index 6481c653d..327df15e0 100644 --- a/test/unit/@node-red/runtime/lib/api/nodes_spec.js +++ b/test/unit/@node-red/runtime/lib/api/nodes_spec.js @@ -185,7 +185,6 @@ var request = require('supertest'); var express = require('express'); var bodyParser = require('body-parser'); var sinon = require('sinon'); -var when = require('when'); var nodes = require("../../../../red/api/admin/nodes"); var apiUtil = require("../../../../red/api/util"); @@ -418,7 +417,7 @@ describe("api/admin/nodes", function() { nodes:{ getModuleInfo: function(id) { return null; }, installModule: function() { - return when.resolve({ + return Promise.resolve({ name:"foo", nodes:[{id:"123"}] }); @@ -446,7 +445,7 @@ describe("api/admin/nodes", function() { nodes:{ getModuleInfo: function(id) { return {nodes:{id:"123"}}; }, installModule: function() { - return when.resolve({id:"123"}); + return Promise.resolve({id:"123"}); } } }); @@ -468,7 +467,7 @@ describe("api/admin/nodes", function() { nodes:{ getModuleInfo: function(id) { return null }, installModule: function() { - return when.reject(new Error("test error")); + return Promise.reject(new Error("test error")); } } }); @@ -492,7 +491,7 @@ describe("api/admin/nodes", function() { installModule: function() { var err = new Error("test error"); err.code = 404; - return when.reject(err); + return Promise.reject(err); } } }); @@ -533,7 +532,7 @@ describe("api/admin/nodes", function() { nodes:{ getModuleInfo: function(id) { return {nodes:[{id:"123"}]} }, getNodeInfo: function() { return null }, - uninstallModule: function() { return when.resolve({id:"123"});} + uninstallModule: function() { return Promise.resolve({id:"123"});} } }); request(app) @@ -572,7 +571,7 @@ describe("api/admin/nodes", function() { nodes:{ getModuleInfo: function(id) { return {nodes:[{id:"123"}]} }, getNodeInfo: function() { return null }, - uninstallModule: function() { return when.reject(new Error("test error"));} + uninstallModule: function() { return Promise.reject(new Error("test error"));} } }); request(app) @@ -686,7 +685,7 @@ describe("api/admin/nodes", function() { settings:{available:function(){return true}}, nodes:{ getNodeInfo: function() { return {id:"123",enabled: false} }, - enableNode: function() { return when.resolve({id:"123",enabled: true,types:['a']}); } + enableNode: function() { return Promise.resolve({id:"123",enabled: true,types:['a']}); } } }); request(app) @@ -709,7 +708,7 @@ describe("api/admin/nodes", function() { settings:{available:function(){return true}}, nodes:{ getNodeInfo: function() { return {id:"123",enabled: true} }, - disableNode: function() { return when.resolve({id:"123",enabled: false,types:['a']}); } + disableNode: function() { return Promise.resolve({id:"123",enabled: false,types:['a']}); } } }); request(app) @@ -729,8 +728,8 @@ describe("api/admin/nodes", function() { describe('no-ops if already in the right state', function() { function run(state,done) { - var enableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: true,types:['a']}) }); - var disableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: false,types:['a']}) }); + var enableNode = sinon.spy(function() { return Promise.resolve({id:"123",enabled: true,types:['a']}) }); + var disableNode = sinon.spy(function() { return Promise.resolve({id:"123",enabled: false,types:['a']}) }); initNodes({ settings:{available:function(){return true}}, @@ -768,8 +767,8 @@ describe("api/admin/nodes", function() { describe('does not no-op if err on node', function() { function run(state,done) { - var enableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: true,types:['a']}) }); - var disableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: false,types:['a']}) }); + var enableNode = sinon.spy(function() { return Promise.resolve({id:"123",enabled: true,types:['a']}) }); + var disableNode = sinon.spy(function() { return Promise.resolve({id:"123",enabled: false,types:['a']}) }); initNodes({ settings:{available:function(){return true}}, @@ -811,11 +810,11 @@ describe("api/admin/nodes", function() { var enableNode = sinon.stub(); enableNode.onFirstCall().returns((function() { n1.enabled = true; - return when.resolve(n1); + return Promise.resolve(n1); })()); enableNode.onSecondCall().returns((function() { n2.enabled = true; - return when.resolve(n2); + return Promise.resolve(n2); })()); enableNode.returns(null); initNodes({ @@ -849,11 +848,11 @@ describe("api/admin/nodes", function() { var disableNode = sinon.stub(); disableNode.onFirstCall().returns((function() { n1.enabled = false; - return when.resolve(n1); + return Promise.resolve(n1); })()); disableNode.onSecondCall().returns((function() { n2.enabled = false; - return when.resolve(n2); + return Promise.resolve(n2); })()); disableNode.returns(null); initNodes({ @@ -886,11 +885,11 @@ describe("api/admin/nodes", function() { var node = {id:"123",enabled:state,types:['a']}; var enableNode = sinon.spy(function(id) { node.enabled = true; - return when.resolve(node); + return Promise.resolve(node); }); var disableNode = sinon.spy(function(id) { node.enabled = false; - return when.resolve(node); + return Promise.resolve(node); }); initNodes({ @@ -933,11 +932,11 @@ describe("api/admin/nodes", function() { var node = {id:"123",enabled:state,types:['a'],err:"foo"}; var enableNode = sinon.spy(function(id) { node.enabled = true; - return when.resolve(node); + return Promise.resolve(node); }); var disableNode = sinon.spy(function(id) { node.enabled = false; - return when.resolve(node); + return Promise.resolve(node); }); initNodes({ diff --git a/test/unit/@node-red/runtime/lib/api/settings_spec.js b/test/unit/@node-red/runtime/lib/api/settings_spec.js index 3830e9ab2..02bbf48e7 100644 --- a/test/unit/@node-red/runtime/lib/api/settings_spec.js +++ b/test/unit/@node-red/runtime/lib/api/settings_spec.js @@ -588,7 +588,6 @@ var comms = require("../../../../red/api/editor/comms"); var info = require("../../../../red/api/editor/settings"); var auth = require("../../../../red/api/auth"); var sshkeys = require("../../../../red/api/editor/sshkeys"); -var when = require("when"); var bodyParser = require("body-parser"); var fs = require("fs-extra"); var fspath = require("path"); @@ -611,11 +610,11 @@ describe("api/editor/sshkeys", function() { exportNodeSettings:function(){}, storage: { getSessions: function(){ - return when.resolve(session_data); + return Promise.resolve(session_data); }, setSessions: function(_session) { session_data = _session; - return when.resolve(); + return Promise.resolve(); } } }, diff --git a/test/unit/@node-red/runtime/lib/flows/index_spec.js b/test/unit/@node-red/runtime/lib/flows/index_spec.js index df5012844..6f6066100 100644 --- a/test/unit/@node-red/runtime/lib/flows/index_spec.js +++ b/test/unit/@node-red/runtime/lib/flows/index_spec.js @@ -16,7 +16,6 @@ var should = require("should"); var sinon = require("sinon"); -var when = require("when"); var clone = require("clone"); var NR_TEST_UTILS = require("nr-test-utils"); @@ -65,13 +64,13 @@ describe('flows/index', function() { conf.forEach(function(n) { delete n.credentials; }); - return when.resolve(); + return Promise.resolve(); }); credentialsLoad = sinon.stub(credentials,"load",function(creds) { if (creds && creds.hasOwnProperty("$") && creds['$'] === "fail") { - return when.reject("creds error"); + return Promise.reject("creds error"); } - return when.resolve(); + return Promise.resolve(); }); flowCreate = sinon.stub(Flow,"create",function(parent, global, flow) { var id; @@ -101,7 +100,7 @@ describe('flows/index', function() { storage = { saveFlows: function(conf) { storage.conf = conf; - return when.resolve(); + return Promise.resolve(); } } }); @@ -145,10 +144,10 @@ describe('flows/index', function() { var loadStorage = { saveFlows: function(conf) { loadStorage.conf = conf; - return when.resolve(456); + return Promise.resolve(456); }, getFlows: function() { - return when.resolve({flows:originalConfig,rev:123}) + return Promise.resolve({flows:originalConfig,rev:123}) } } flows.init({log:mockLog, settings:{},storage:loadStorage}); @@ -207,7 +206,7 @@ describe('flows/index', function() { newConfig.push({id:"t2",type:"tab"}); newConfig.push({id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]}); storage.getFlows = function() { - return when.resolve({flows:originalConfig}); + return Promise.resolve({flows:originalConfig}); } events.once('flows:started',function() { flows.setFlows(newConfig,"nodes").then(function() { @@ -235,7 +234,7 @@ describe('flows/index', function() { newConfig.push({id:"t2",type:"tab"}); newConfig.push({id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]}); storage.getFlows = function() { - return when.resolve({flows:originalConfig}); + return Promise.resolve({flows:originalConfig}); } events.once('flows:started',function() { @@ -277,7 +276,7 @@ describe('flows/index', function() { {id:"t1",type:"tab"} ]; storage.getFlows = function() { - return when.resolve({flows:originalConfig}); + return Promise.resolve({flows:originalConfig}); } flows.init({log:mockLog, settings:{},storage:storage}); flows.load().then(function() { @@ -297,7 +296,7 @@ describe('flows/index', function() { {id:"t1",type:"tab"} ]; storage.getFlows = function() { - return when.resolve({flows:originalConfig}); + return Promise.resolve({flows:originalConfig}); } events.once('flows:started',function() { @@ -317,7 +316,7 @@ describe('flows/index', function() { {id:"t1",type:"tab"} ]; storage.getFlows = function() { - return when.resolve({flows:originalConfig}); + return Promise.resolve({flows:originalConfig}); } flows.init({log:mockLog, settings:{},storage:storage}); @@ -336,7 +335,7 @@ describe('flows/index', function() { {id:"t1",type:"tab"} ]; storage.getFlows = function() { - return when.resolve({flows:originalConfig}); + return Promise.resolve({flows:originalConfig}); } flows.init({log:mockLog, settings:{},storage:storage}); flows.load().then(function() { @@ -370,7 +369,7 @@ describe('flows/index', function() { {id:"t1",type:"tab"} ]; storage.getFlows = function() { - return when.resolve({flows:originalConfig}); + return Promise.resolve({flows:originalConfig}); } flows.init({log:mockLog, settings:{},storage:storage}); flows.load().then(function() { @@ -394,7 +393,7 @@ describe('flows/index', function() { // {id:"t1",type:"tab"} // ]; // storage.getFlows = function() { - // return when.resolve({flows:originalConfig}); + // return Promise.resolve({flows:originalConfig}); // } // // events.once('flows:started',function() { @@ -419,7 +418,7 @@ describe('flows/index', function() { // {id:"t3-1",x:10,y:10,z:"t3",type:"test",config:"configNode",wires:[]} // ]; // storage.getFlows = function() { - // return when.resolve({flows:originalConfig}); + // return Promise.resolve({flows:originalConfig}); // } // // events.once('flows:started',function() { @@ -447,7 +446,7 @@ describe('flows/index', function() { // {id:"t1",type:"tab"} // ]; // storage.getFlows = function() { - // return when.resolve({flows:originalConfig}); + // return Promise.resolve({flows:originalConfig}); // } // // events.once('flows:started',function() { @@ -473,7 +472,7 @@ describe('flows/index', function() { // {id:"t3-1",x:10,y:10,z:"t3",type:"test",config:"configNode",wires:[]} // ]; // storage.getFlows = function() { - // return when.resolve({flows:originalConfig}); + // return Promise.resolve({flows:originalConfig}); // } // // events.once('flows:started',function() { @@ -548,7 +547,7 @@ describe('flows/index', function() { {id:"t1",type:"tab"} ]; storage.getFlows = function() { - return when.resolve({flows:originalConfig}); + return Promise.resolve({flows:originalConfig}); } flows.init({log:mockLog, settings:{},storage:storage}); flows.load().then(function() { @@ -572,10 +571,10 @@ describe('flows/index', function() { {id:"t1",type:"tab"} ]; storage.getFlows = function() { - return when.resolve({flows:originalConfig}); + return Promise.resolve({flows:originalConfig}); } storage.setFlows = function() { - return when.resolve(); + return Promise.resolve(); } flows.init({log:mockLog, settings:{},storage:storage}); flows.load().then(function() { diff --git a/test/unit/@node-red/runtime/lib/flows/util_spec.js b/test/unit/@node-red/runtime/lib/flows/util_spec.js index 8e1f15754..59169376e 100644 --- a/test/unit/@node-red/runtime/lib/flows/util_spec.js +++ b/test/unit/@node-red/runtime/lib/flows/util_spec.js @@ -16,7 +16,6 @@ var should = require("should"); var sinon = require("sinon"); -var when = require("when"); var clone = require("clone"); var NR_TEST_UTILS = require("nr-test-utils"); var flowUtil = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/util"); diff --git a/test/unit/@node-red/runtime/lib/nodes/credentials_spec.js b/test/unit/@node-red/runtime/lib/nodes/credentials_spec.js index 63050a8b9..5e4bc8094 100644 --- a/test/unit/@node-red/runtime/lib/nodes/credentials_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/credentials_spec.js @@ -16,7 +16,6 @@ var should = require("should"); var sinon = require("sinon"); -var when = require("when"); var util = require("util"); var NR_TEST_UTILS = require("nr-test-utils"); @@ -228,11 +227,11 @@ describe('red/runtime/nodes/credentials', function() { }, set: function(key,value) { settings[key] = value; - return when.resolve(); + return Promise.resolve(); }, delete: function(key) { delete settings[key]; - return when.resolve(); + return Promise.resolve(); } } } diff --git a/test/unit/@node-red/runtime/lib/nodes/index_spec.js b/test/unit/@node-red/runtime/lib/nodes/index_spec.js index 7a4992624..a73e0a246 100644 --- a/test/unit/@node-red/runtime/lib/nodes/index_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/index_spec.js @@ -17,7 +17,6 @@ var should = require("should"); var fs = require('fs-extra'); var path = require('path'); -var when = require("when"); var sinon = require('sinon'); var inherits = require("util").inherits; @@ -47,11 +46,11 @@ describe("red/nodes/index", function() { var testCredentials = {"tab1":{"b":1, "c":"2", "d":"$(foo)"}}; var storage = { getFlows: function() { - return when({red:123,flows:testFlows,credentials:testCredentials}); + return Promise.resolve({red:123,flows:testFlows,credentials:testCredentials}); }, saveFlows: function(conf) { should.deepEqual(testFlows, conf.flows); - return when.resolve(123); + return Promise.resolve(123); } }; @@ -182,12 +181,12 @@ describe("red/nodes/index", function() { fs.remove(userDir,function(err) { fs.mkdir(userDir,function() { sinon.stub(index, 'load', function() { - return when.promise(function(resolve,reject){ + return new Promise(function(resolve,reject){ resolve([]); }); }); sinon.stub(localfilesystem, 'getCredentials', function() { - return when.promise(function(resolve,reject) { + return new Promise(function(resolve,reject) { resolve({"tab1":{"b":1,"c":2}}); }); }) ; @@ -282,7 +281,7 @@ describe("red/nodes/index", function() { } }); sinon.stub(registry,"disableNode",function(id) { - return when.resolve(randomNodeInfo); + return Promise.resolve(randomNodeInfo); }); }); afterEach(function() { diff --git a/test/unit/@node-red/runtime/lib/storage/index_spec.js b/test/unit/@node-red/runtime/lib/storage/index_spec.js index 3ede68c86..dd9fa6e9b 100644 --- a/test/unit/@node-red/runtime/lib/storage/index_spec.js +++ b/test/unit/@node-red/runtime/lib/storage/index_spec.js @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ -var when = require("when"); var should = require("should"); var paff = require('path'); @@ -79,16 +78,16 @@ describe("red/storage/index", function() { }, getFlows : function() { calledFlagGetFlows = true; - return when.resolve([]); + return Promise.resolve([]); }, saveFlows : function (flows) { flows.should.be.an.Array(); flows.should.have.lengthOf(0); - return when.resolve(""); + return Promise.resolve(""); }, getCredentials : function() { calledFlagGetCredentials = true; - return when.resolve({}); + return Promise.resolve({}); }, saveCredentials : function(credentials) { credentials.should.be.true(); @@ -147,7 +146,7 @@ describe("red/storage/index", function() { storage.getLibraryEntry(true, "name"); storage.saveLibraryEntry(true, "name", true, true); - when.settle(promises).then(function() { + Promise.all(promises).then(function() { try { calledInit.should.be.true(); calledFlagGetFlows.should.be.true(); @@ -174,11 +173,11 @@ describe("red/storage/index", function() { getLibraryEntry : function(type, path) { if (type === "flows") { if (path === "/" || path === "\\") { - return when.resolve(["a",{fn:"test.json"}]); + return Promise.resolve(["a",{fn:"test.json"}]); } else if (path == "/a" || path == "\\a") { - return when.resolve([{fn:"test2.json"}]); + return Promise.resolve([{fn:"test2.json"}]); } else if (path == paff.join("","a","test2.json")) { - return when.resolve("test content"); + return Promise.resolve("test content"); } } }, @@ -187,7 +186,7 @@ describe("red/storage/index", function() { savePath = path; saveContent = body; saveMeta = meta; - return when.resolve(); + return Promise.resolve(); } }; From a1f565f7560bd291002b7b35e7f7dc7f249aef34 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 30 Nov 2020 16:58:05 +0000 Subject: [PATCH 032/114] Use more async funcs in runtime/lib/api to reduce Promise creation --- .../@node-red/runtime/lib/api/comms.js | 11 +- .../@node-red/runtime/lib/api/context.js | 4 +- .../@node-red/runtime/lib/api/index.js | 8 +- .../@node-red/runtime/lib/api/library.js | 81 ++- .../@node-red/runtime/lib/api/nodes.js | 516 ++++++++---------- .../@node-red/runtime/lib/api/projects.js | 68 ++- .../@node-red/runtime/lib/api/settings.js | 215 ++++---- 7 files changed, 419 insertions(+), 484 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/api/comms.js b/packages/node_modules/@node-red/runtime/lib/api/comms.js index 55789b806..42e727412 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/comms.js +++ b/packages/node_modules/@node-red/runtime/lib/api/comms.js @@ -106,9 +106,8 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_comms */ - addConnection: function(opts) { + addConnection: async function(opts) { connections.push(opts.client); - return Promise.resolve(); }, /** @@ -119,14 +118,13 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_comms */ - removeConnection: function(opts) { + removeConnection: async function(opts) { for (var i=0;i} - resolves when complete * @memberof @node-red/runtime_comms */ - subscribe: function(opts) { + subscribe: async function(opts) { var re = new RegExp("^"+opts.topic.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$"); for (var t in retained) { if (re.test(t)) { opts.client.send(t,retained[t]); } } - return Promise.resolve(); }, /** @@ -159,5 +156,5 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_comms */ - unsubscribe: function(opts) {} + unsubscribe: async function(opts) {} }; diff --git a/packages/node_modules/@node-red/runtime/lib/api/context.js b/packages/node_modules/@node-red/runtime/lib/api/context.js index f69349a62..6716b6831 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/context.js +++ b/packages/node_modules/@node-red/runtime/lib/api/context.js @@ -71,7 +71,7 @@ var api = module.exports = { * @return {Promise} - the node information * @memberof @node-red/runtime_context */ - getValue: function(opts) { + getValue: async function(opts) { return new Promise(function(resolve,reject) { var scope = opts.scope; var id = opts.id; @@ -165,7 +165,7 @@ var api = module.exports = { * @return {Promise} - the node information * @memberof @node-red/runtime_context */ - delete: function(opts) { + delete: async function(opts) { return new Promise(function(resolve,reject) { var scope = opts.scope; var id = opts.id; diff --git a/packages/node_modules/@node-red/runtime/lib/api/index.js b/packages/node_modules/@node-red/runtime/lib/api/index.js index 8856ac3c2..b131470b0 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/index.js +++ b/packages/node_modules/@node-red/runtime/lib/api/index.js @@ -38,10 +38,10 @@ var api = module.exports = { projects: require("./projects"), context: require("./context"), - isStarted: function(opts) { - return Promise.resolve(runtime.isStarted()); + isStarted: async function(opts) { + return runtime.isStarted(); }, - version: function(opts) { - return Promise.resolve(runtime.version()); + version: async function(opts) { + return runtime.version(); } } diff --git a/packages/node_modules/@node-red/runtime/lib/api/library.js b/packages/node_modules/@node-red/runtime/lib/api/library.js index f252d3264..6b7ed6218 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/library.js +++ b/packages/node_modules/@node-red/runtime/lib/api/library.js @@ -36,32 +36,30 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_library */ - getEntry: function(opts) { - return new Promise(function(resolve,reject) { - runtime.library.getEntry(opts.library,opts.type,opts.path).then(function(result) { - runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,path:opts.path}, opts.req); - return resolve(result); - }).catch(function(err) { - if (err) { - runtime.log.warn(runtime.log._("api.library.error-load-entry",{library:opts.library,type:opts.type,path:opts.path,message:err.toString()})); - if (err.code === 'forbidden') { - err.status = 403; - return reject(err); - } else if (err.code === "not_found") { - err.status = 404; - } else { - err.status = 400; - } - runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,path:opts.path,error:err.code}, opts.req); - return reject(err); + getEntry: async function(opts) { + return runtime.library.getEntry(opts.library,opts.type,opts.path).then(function(result) { + runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,path:opts.path}, opts.req); + return result; + }).catch(function(err) { + if (err) { + runtime.log.warn(runtime.log._("api.library.error-load-entry",{library:opts.library,type:opts.type,path:opts.path,message:err.toString()})); + if (err.code === 'forbidden') { + err.status = 403; + throw err; + } else if (err.code === "not_found") { + err.status = 404; + } else { + err.status = 400; } - runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,error:"not_found"}, opts.req); - var error = new Error(); - error.code = "not_found"; - error.status = 404; - return reject(error); - }); - }) + runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,path:opts.path,error:err.code}, opts.req); + throw err; + } + runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,error:"not_found"}, opts.req); + var error = new Error(); + error.code = "not_found"; + error.status = 404; + throw error; + }); }, /** @@ -77,23 +75,20 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_library */ - saveEntry: function(opts) { - return new Promise(function(resolve,reject) { - runtime.library.saveEntry(opts.library,opts.type,opts.path,opts.meta,opts.body).then(function() { - runtime.log.audit({event: "library.set",type:opts.type,path:opts.path}, opts.req); - return resolve(); - }).catch(function(err) { - runtime.log.warn(runtime.log._("api.library.error-save-entry",{path:opts.path,message:err.toString()})); - if (err.code === 'forbidden') { - runtime.log.audit({event: "library.set",type:opts.type,path:opts.path,error:"forbidden"}, opts.req); - err.status = 403; - return reject(err); - } - runtime.log.audit({event: "library.set",type:opts.type,path:opts.path,error:"unexpected_error",message:err.toString()}, opts.req); - var error = new Error(); - error.status = 400; - return reject(error); - }); - }) + saveEntry: async function(opts) { + return runtime.library.saveEntry(opts.library,opts.type,opts.path,opts.meta,opts.body).then(function() { + runtime.log.audit({event: "library.set",type:opts.type,path:opts.path}, opts.req); + }).catch(function(err) { + runtime.log.warn(runtime.log._("api.library.error-save-entry",{path:opts.path,message:err.toString()})); + if (err.code === 'forbidden') { + runtime.log.audit({event: "library.set",type:opts.type,path:opts.path,error:"forbidden"}, opts.req); + err.status = 403; + throw err; + } + runtime.log.audit({event: "library.set",type:opts.type,path:opts.path,error:"unexpected_error",message:err.toString()}, opts.req); + var error = new Error(); + error.status = 400; + throw error; + }); } } diff --git a/packages/node_modules/@node-red/runtime/lib/api/nodes.js b/packages/node_modules/@node-red/runtime/lib/api/nodes.js index a06cbed96..6a05b4c17 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/nodes.js +++ b/packages/node_modules/@node-red/runtime/lib/api/nodes.js @@ -18,7 +18,7 @@ * @mixin @node-red/runtime_nodes */ -var fs = require("fs"); +var fs = require("fs-extra"); var runtime; @@ -52,22 +52,20 @@ var api = module.exports = { * @return {Promise} - the node information * @memberof @node-red/runtime_nodes */ - getNodeInfo: function(opts) { - return new Promise(function(resolve,reject) { - var id = opts.id; - var result = runtime.nodes.getNodeInfo(id); - if (result) { - runtime.log.audit({event: "nodes.info.get",id:id}, opts.req); - delete result.loaded; - return resolve(result); - } else { - runtime.log.audit({event: "nodes.info.get",id:id,error:"not_found"}, opts.req); - var err = new Error("Node not found"); - err.code = "not_found"; - err.status = 404; - return reject(err); - } - }) + getNodeInfo: async function(opts) { + var id = opts.id; + var result = runtime.nodes.getNodeInfo(id); + if (result) { + runtime.log.audit({event: "nodes.info.get",id:id}, opts.req); + delete result.loaded; + return result; + } else { + runtime.log.audit({event: "nodes.info.get",id:id,error:"not_found"}, opts.req); + var err = new Error("Node not found"); + err.code = "not_found"; + err.status = 404; + throw err; + } }, /** @@ -78,11 +76,9 @@ var api = module.exports = { * @return {Promise} - the list of node modules * @memberof @node-red/runtime_nodes */ - getNodeList: function(opts) { - return new Promise(function(resolve,reject) { - runtime.log.audit({event: "nodes.list.get"}, opts.req); - return resolve(runtime.nodes.getNodeList()); - }) + getNodeList: async function(opts) { + runtime.log.audit({event: "nodes.list.get"}, opts.req); + return runtime.nodes.getNodeList(); }, /** @@ -95,22 +91,20 @@ var api = module.exports = { * @return {Promise} - the node html content * @memberof @node-red/runtime_nodes */ - getNodeConfig: function(opts) { - return new Promise(function(resolve,reject) { - var id = opts.id; - var lang = opts.lang; - var result = runtime.nodes.getNodeConfig(id,lang); - if (result) { - runtime.log.audit({event: "nodes.config.get",id:id}, opts.req); - return resolve(result); - } else { - runtime.log.audit({event: "nodes.config.get",id:id,error:"not_found"}, opts.req); - var err = new Error("Node not found"); - err.code = "not_found"; - err.status = 404; - return reject(err); - } - }); + getNodeConfig: async function(opts) { + var id = opts.id; + var lang = opts.lang; + var result = runtime.nodes.getNodeConfig(id,lang); + if (result) { + runtime.log.audit({event: "nodes.config.get",id:id}, opts.req); + return result; + } else { + runtime.log.audit({event: "nodes.config.get",id:id,error:"not_found"}, opts.req); + var err = new Error("Node not found"); + err.code = "not_found"; + err.status = 404; + throw err; + } }, /** * Gets all node html content @@ -121,11 +115,9 @@ var api = module.exports = { * @return {Promise} - the node html content * @memberof @node-red/runtime_nodes */ - getNodeConfigs: function(opts) { - return new Promise(function(resolve,reject) { - runtime.log.audit({event: "nodes.configs.get"}, opts.req); - return resolve(runtime.nodes.getNodeConfigs(opts.lang)); - }); + getNodeConfigs: async function(opts) { + runtime.log.audit({event: "nodes.configs.get"}, opts.req); + return runtime.nodes.getNodeConfigs(opts.lang); }, /** @@ -137,20 +129,18 @@ var api = module.exports = { * @return {Promise} - the node module info * @memberof @node-red/runtime_nodes */ - getModuleInfo: function(opts) { - return new Promise(function(resolve,reject) { - var result = runtime.nodes.getModuleInfo(opts.module); - if (result) { - runtime.log.audit({event: "nodes.module.get",id:opts.module}, opts.req); - return resolve(result); - } else { - runtime.log.audit({event: "nodes.module.get",id:opts.module,error:"not_found"}, opts.req); - var err = new Error("Module not found"); - err.code = "not_found"; - err.status = 404; - return reject(err); - } - }) + getModuleInfo: async function(opts) { + var result = runtime.nodes.getModuleInfo(opts.module); + if (result) { + runtime.log.audit({event: "nodes.module.get",id:opts.module}, opts.req); + return result; + } else { + runtime.log.audit({event: "nodes.module.get",id:opts.module,error:"not_found"}, opts.req); + var err = new Error("Module not found"); + err.code = "not_found"; + err.status = 404; + throw err; + } }, /** @@ -165,83 +155,78 @@ var api = module.exports = { * @return {Promise} - the node module info * @memberof @node-red/runtime_nodes */ - addModule: function(opts) { - return new Promise(function(resolve,reject) { - if (!runtime.settings.available()) { - runtime.log.audit({event: "nodes.install",error:"settings_unavailable"}, opts.req); - var err = new Error("Settings unavailable"); - err.code = "settings_unavailable"; - err.status = 400; - return reject(err); - } - if (opts.tarball) { - if (runtime.settings.editorTheme && runtime.settings.editorTheme.palette && runtime.settings.editorTheme.palette.upload === false) { - runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,error:"invalid_request"}, opts.req); - var err = new Error("Invalid request"); - err.code = "invalid_request"; - err.status = 400; - return reject(err); - } - if (opts.module || opts.version || opts.url) { - runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,module:opts.module,error:"invalid_request"}, opts.req); - var err = new Error("Invalid request"); - err.code = "invalid_request"; - err.status = 400; - return reject(err); - } - runtime.nodes.installModule(opts.tarball.buffer).then(function(info) { - runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,module:info.id}, opts.req); - return resolve(info); - }).catch(function(err) { - - if (err.code) { - err.status = 400; - runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code}, opts.req); - } else { - err.status = 400; - runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code||"unexpected_error",message:err.toString()}, opts.req); - } - return reject(err); - }) - return; - } - if (opts.module) { - var existingModule = runtime.nodes.getModuleInfo(opts.module); - if (existingModule) { - if (!opts.version || existingModule.version === opts.version) { - runtime.log.audit({event: "nodes.install",module:opts.module, version:opts.version, error:"module_already_loaded"}, opts.req); - var err = new Error("Module already loaded"); - err.code = "module_already_loaded"; - err.status = 400; - return reject(err); - } - } - runtime.nodes.installModule(opts.module,opts.version,opts.url).then(function(info) { - runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url}, opts.req); - return resolve(info); - }).catch(function(err) { - if (err.code === 404) { - runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:"not_found"}, opts.req); - // TODO: code/status - err.status = 404; - } else if (err.code) { - err.status = 400; - runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code}, opts.req); - } else { - err.status = 400; - runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code||"unexpected_error",message:err.toString()}, opts.req); - } - return reject(err); - }) - } else { - runtime.log.audit({event: "nodes.install",module:opts.module,error:"invalid_request"}, opts.req); + addModule: async function(opts) { + if (!runtime.settings.available()) { + runtime.log.audit({event: "nodes.install",error:"settings_unavailable"}, opts.req); + var err = new Error("Settings unavailable"); + err.code = "settings_unavailable"; + err.status = 400; + throw err; + } + if (opts.tarball) { + if (runtime.settings.editorTheme && runtime.settings.editorTheme.palette && runtime.settings.editorTheme.palette.upload === false) { + runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,error:"invalid_request"}, opts.req); var err = new Error("Invalid request"); err.code = "invalid_request"; err.status = 400; - return reject(err); + throw err; } - - }); + if (opts.module || opts.version || opts.url) { + runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,module:opts.module,error:"invalid_request"}, opts.req); + var err = new Error("Invalid request"); + err.code = "invalid_request"; + err.status = 400; + throw err; + } + return runtime.nodes.installModule(opts.tarball.buffer).then(function(info) { + runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,module:info.id}, opts.req); + return info; + }).catch(function(err) { + if (err.code) { + err.status = 400; + runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code}, opts.req); + } else { + err.status = 400; + runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code||"unexpected_error",message:err.toString()}, opts.req); + } + throw err; + }) + } + if (opts.module) { + var existingModule = runtime.nodes.getModuleInfo(opts.module); + if (existingModule) { + if (!opts.version || existingModule.version === opts.version) { + runtime.log.audit({event: "nodes.install",module:opts.module, version:opts.version, error:"module_already_loaded"}, opts.req); + var err = new Error("Module already loaded"); + err.code = "module_already_loaded"; + err.status = 400; + throw err; + } + } + return runtime.nodes.installModule(opts.module,opts.version,opts.url).then(function(info) { + runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url}, opts.req); + return info; + }).catch(function(err) { + if (err.code === 404) { + runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:"not_found"}, opts.req); + // TODO: code/status + err.status = 404; + } else if (err.code) { + err.status = 400; + runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code}, opts.req); + } else { + err.status = 400; + runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code||"unexpected_error",message:err.toString()}, opts.req); + } + throw err; + }) + } else { + runtime.log.audit({event: "nodes.install",module:opts.module,error:"invalid_request"}, opts.req); + var err = new Error("Invalid request"); + err.code = "invalid_request"; + err.status = 400; + throw err; + } }, /** * Removes a module from the runtime @@ -252,38 +237,35 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_nodes */ - removeModule: function(opts) { - return new Promise(function(resolve,reject) { - if (!runtime.settings.available()) { - runtime.log.audit({event: "nodes.install",error:"settings_unavailable"}, opts.req); - var err = new Error("Settings unavailable"); - err.code = "settings_unavailable"; + removeModule: async function(opts) { + if (!runtime.settings.available()) { + runtime.log.audit({event: "nodes.install",error:"settings_unavailable"}, opts.req); + var err = new Error("Settings unavailable"); + err.code = "settings_unavailable"; + err.status = 400; + throw err; + } + var module = runtime.nodes.getModuleInfo(opts.module); + if (!module) { + runtime.log.audit({event: "nodes.remove",module:opts.module,error:"not_found"}, opts.req); + var err = new Error("Module not found"); + err.code = "not_found"; + err.status = 404; + throw err; + } + try { + return runtime.nodes.uninstallModule(opts.module).then(function() { + runtime.log.audit({event: "nodes.remove",module:opts.module}, opts.req); + }).catch(function(err) { err.status = 400; - return reject(err); - } - var module = runtime.nodes.getModuleInfo(opts.module); - if (!module) { - runtime.log.audit({event: "nodes.remove",module:opts.module,error:"not_found"}, opts.req); - var err = new Error("Module not found"); - err.code = "not_found"; - err.status = 404; - return reject(err); - } - try { - runtime.nodes.uninstallModule(opts.module).then(function() { - runtime.log.audit({event: "nodes.remove",module:opts.module}, opts.req); - resolve(); - }).catch(function(err) { - err.status = 400; - runtime.log.audit({event: "nodes.remove",module:opts.module,error:err.code||"unexpected_error",message:err.toString()}, opts.req); - return reject(err); - }) - } catch(error) { - runtime.log.audit({event: "nodes.remove",module:opts.module,error:error.code||"unexpected_error",message:error.toString()}, opts.req); - error.status = 400; - return reject(error); - } - }); + runtime.log.audit({event: "nodes.remove",module:opts.module,error:err.code||"unexpected_error",message:err.toString()}, opts.req); + throw err; + }) + } catch(error) { + runtime.log.audit({event: "nodes.remove",module:opts.module,error:error.code||"unexpected_error",message:error.toString()}, opts.req); + error.status = 400; + throw err; + } }, /** @@ -296,43 +278,41 @@ var api = module.exports = { * @return {Promise} - the module info object * @memberof @node-red/runtime_nodes */ - setModuleState: function(opts) { + setModuleState: async function(opts) { var mod = opts.module; - return new Promise(function(resolve,reject) { - if (!runtime.settings.available()) { - runtime.log.audit({event: "nodes.module.set",error:"settings_unavailable"}, opts.req); - var err = new Error("Settings unavailable"); - err.code = "settings_unavailable"; - err.status = 400; - return reject(err); + if (!runtime.settings.available()) { + runtime.log.audit({event: "nodes.module.set",error:"settings_unavailable"}, opts.req); + var err = new Error("Settings unavailable"); + err.code = "settings_unavailable"; + err.status = 400; + throw err; + } + try { + var module = runtime.nodes.getModuleInfo(mod); + if (!module) { + runtime.log.audit({event: "nodes.module.set",module:mod,error:"not_found"}, opts.req); + var err = new Error("Module not found"); + err.code = "not_found"; + err.status = 404; + throw err; } - try { - var module = runtime.nodes.getModuleInfo(mod); - if (!module) { - runtime.log.audit({event: "nodes.module.set",module:mod,error:"not_found"}, opts.req); - var err = new Error("Module not found"); - err.code = "not_found"; - err.status = 404; - return reject(err); - } - var nodes = module.nodes; - var promises = []; - for (var i = 0; i < nodes.length; ++i) { - promises.push(putNode(nodes[i],opts.enabled)); - } - Promise.all(promises).then(function() { - return resolve(runtime.nodes.getModuleInfo(mod)); - }).catch(function(err) { - err.status = 400; - return reject(err); - }); - } catch(error) { - runtime.log.audit({event: "nodes.module.set",module:mod,enabled:opts.enabled,error:error.code||"unexpected_error",message:error.toString()}, opts.req); - error.status = 400; - return reject(error); + var nodes = module.nodes; + var promises = []; + for (var i = 0; i < nodes.length; ++i) { + promises.push(putNode(nodes[i],opts.enabled)); } - }); + return Promise.all(promises).then(function() { + return runtime.nodes.getModuleInfo(mod); + }).catch(function(err) { + err.status = 400; + throw err; + }); + } catch(error) { + runtime.log.audit({event: "nodes.module.set",module:mod,enabled:opts.enabled,error:error.code||"unexpected_error",message:error.toString()}, opts.req); + error.status = 400; + throw err; + } }, /** @@ -345,43 +325,41 @@ var api = module.exports = { * @return {Promise} - the module info object * @memberof @node-red/runtime_nodes */ - setNodeSetState: function(opts) { - return new Promise(function(resolve,reject) { - if (!runtime.settings.available()) { - runtime.log.audit({event: "nodes.info.set",error:"settings_unavailable"}, opts.req); - var err = new Error("Settings unavailable"); - err.code = "settings_unavailable"; - err.status = 400; - return reject(err); - } + setNodeSetState: async function(opts) { + if (!runtime.settings.available()) { + runtime.log.audit({event: "nodes.info.set",error:"settings_unavailable"}, opts.req); + var err = new Error("Settings unavailable"); + err.code = "settings_unavailable"; + err.status = 400; + throw err; + } - var id = opts.id; - var enabled = opts.enabled; - try { - var node = runtime.nodes.getNodeInfo(id); - if (!node) { - runtime.log.audit({event: "nodes.info.set",id:id,error:"not_found"}, opts.req); - var err = new Error("Node not found"); - err.code = "not_found"; - err.status = 404; - return reject(err); - } else { - delete node.loaded; - putNode(node,enabled).then(function(result) { - runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled}, opts.req); - return resolve(result); - }).catch(function(err) { - runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled,error:err.code||"unexpected_error",message:err.toString()}, opts.req); - err.status = 400; - return reject(err); - }); - } - } catch(error) { - runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled,error:error.code||"unexpected_error",message:error.toString()}, opts.req); - error.status = 400; - return reject(error); + var id = opts.id; + var enabled = opts.enabled; + try { + var node = runtime.nodes.getNodeInfo(id); + if (!node) { + runtime.log.audit({event: "nodes.info.set",id:id,error:"not_found"}, opts.req); + var err = new Error("Node not found"); + err.code = "not_found"; + err.status = 404; + throw err; + } else { + delete node.loaded; + return putNode(node,enabled).then(function(result) { + runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled}, opts.req); + return result; + }).catch(function(err) { + runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled,error:err.code||"unexpected_error",message:err.toString()}, opts.req); + err.status = 400; + throw err; + }); } - }); + } catch(error) { + runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled,error:error.code||"unexpected_error",message:error.toString()}, opts.req); + error.status = 400; + throw err; + } }, /** @@ -393,23 +371,21 @@ var api = module.exports = { * @return {Promise} - the message catalogs * @memberof @node-red/runtime_nodes */ - getModuleCatalogs: function(opts) { - return new Promise(function(resolve,reject) { - var namespace = opts.module; - var lang = opts.lang; - var prevLang = runtime.i18n.i.language; - // Trigger a load from disk of the language if it is not the default - runtime.i18n.i.changeLanguage(lang, function(){ - var nodeList = runtime.nodes.getNodeList(); - var result = {}; - nodeList.forEach(function(n) { - if (n.module !== "node-red") { - result[n.id] = runtime.i18n.i.getResourceBundle(lang, n.id)||{}; - } - }); - resolve(result); + getModuleCatalogs: async function(opts) { + var namespace = opts.module; + var lang = opts.lang; + var prevLang = runtime.i18n.i.language; + // Trigger a load from disk of the language if it is not the default + return runtime.i18n.i.changeLanguage(lang, function(){ + var nodeList = runtime.nodes.getNodeList(); + var result = {}; + nodeList.forEach(function(n) { + if (n.module !== "node-red") { + result[n.id] = runtime.i18n.i.getResourceBundle(lang, n.id)||{}; + } }); runtime.i18n.i.changeLanguage(prevLang); + return result; }); }, @@ -423,17 +399,15 @@ var api = module.exports = { * @return {Promise} - the message catalog * @memberof @node-red/runtime_nodes */ - getModuleCatalog: function(opts) { - return new Promise(function(resolve,reject) { - var namespace = opts.module; - var lang = opts.lang; - var prevLang = runtime.i18n.i.language; - // Trigger a load from disk of the language if it is not the default - runtime.i18n.i.changeLanguage(lang, function(){ - var catalog = runtime.i18n.i.getResourceBundle(lang, namespace); - resolve(catalog||{}); - }); + getModuleCatalog: async function(opts) { + var namespace = opts.module; + var lang = opts.lang; + var prevLang = runtime.i18n.i.language; + // Trigger a load from disk of the language if it is not the default + return runtime.i18n.i.changeLanguage(lang, function(){ + var catalog = runtime.i18n.i.getResourceBundle(lang, namespace); runtime.i18n.i.changeLanguage(prevLang); + return catalog||{}; }); }, @@ -445,12 +419,9 @@ var api = module.exports = { * @return {Promise} - the list of all icons * @memberof @node-red/runtime_nodes */ - getIconList: function(opts) { - return new Promise(function(resolve,reject) { - runtime.log.audit({event: "nodes.icons.get"}, opts.req); - return resolve(runtime.nodes.getNodeIcons()); - }); - + getIconList: async function(opts) { + runtime.log.audit({event: "nodes.icons.get"}, opts.req); + return runtime.nodes.getNodeIcons(); }, /** * Gets a node icon @@ -462,20 +433,15 @@ var api = module.exports = { * @return {Promise} - the icon file as a Buffer or null if no icon available * @memberof @node-red/runtime_nodes */ - getIcon: function(opts) { - return new Promise(function(resolve,reject) { - var iconPath = runtime.nodes.getNodeIconPath(opts.module,opts.icon); - if (iconPath) { - fs.readFile(iconPath,function(err,data) { - if (err) { - err.status = 400; - return reject(err); - } - return resolve(data) - }); - } else { - resolve(null); - } - }); + getIcon: async function(opts) { + var iconPath = runtime.nodes.getNodeIconPath(opts.module,opts.icon); + if (iconPath) { + return fs.readFile(iconPath).catch(err => { + err.status = 400; + throw err; + }); + } else { + return null + } } } diff --git a/packages/node_modules/@node-red/runtime/lib/api/projects.js b/packages/node_modules/@node-red/runtime/lib/api/projects.js index d792f3765..14d1d0ec1 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/projects.js +++ b/packages/node_modules/@node-red/runtime/lib/api/projects.js @@ -24,8 +24,8 @@ var api = module.exports = { init: function(_runtime) { runtime = _runtime; }, - available: function(opts) { - return Promise.resolve(!!runtime.storage.projects); + available: async function(opts) { + return !!runtime.storage.projects; }, /** * List projects known to the runtime @@ -36,7 +36,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - listProjects: function(opts) { + listProjects: async function(opts) { return runtime.storage.projects.listProjects(opts.user).then(function(list) { var active = runtime.storage.projects.getActiveProject(opts.user); var response = { @@ -61,7 +61,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - createProject: function(opts) { + createProject: async function(opts) { runtime.log.audit({event: "projects.create",name:opts.project?opts.project.name:"missing-name"}, opts.req); return runtime.storage.projects.createProject(opts.user, opts.project) }, @@ -76,7 +76,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - initialiseProject: function(opts) { + initialiseProject: async function(opts) { // Initialised set when creating default files for an empty repo runtime.log.audit({event: "projects.initialise",id:opts.id}, opts.req); return runtime.storage.projects.initialiseProject(opts.user, opts.id, opts.project) @@ -90,8 +90,8 @@ var api = module.exports = { * @return {Promise} - the active project * @memberof @node-red/runtime_projects */ - getActiveProject: function(opts) { - return Promise.resolve(runtime.storage.projects.getActiveProject(opts.user)); + getActiveProject: async function(opts) { + return runtime.storage.projects.getActiveProject(opts.user); }, /** @@ -103,13 +103,11 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - setActiveProject: function(opts) { + setActiveProject: async function(opts) { var currentProject = runtime.storage.projects.getActiveProject(opts.user); runtime.log.audit({event: "projects.set",id:opts.id}, opts.req); if (!currentProject || opts.id !== currentProject.name) { return runtime.storage.projects.setActiveProject(opts.user, opts.id); - } else { - return Promise.resolve(); } }, @@ -122,7 +120,7 @@ var api = module.exports = { * @return {Promise} - the project metadata * @memberof @node-red/runtime_projects */ - getProject: function(opts) { + getProject: async function(opts) { return runtime.storage.projects.getProject(opts.user, opts.id) }, @@ -136,7 +134,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - updateProject: function(opts) { + updateProject: async function(opts) { runtime.log.audit({event: "projects.update",id:opts.id}, opts.req); return runtime.storage.projects.updateProject(opts.user, opts.id, opts.project); }, @@ -150,7 +148,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - deleteProject: function(opts) { + deleteProject: async function(opts) { runtime.log.audit({event: "projects.delete",id:opts.id}, opts.req); return runtime.storage.projects.deleteProject(opts.user, opts.id); }, @@ -165,7 +163,7 @@ var api = module.exports = { * @return {Promise} - the project status * @memberof @node-red/runtime_projects */ - getStatus: function(opts) { + getStatus: async function(opts) { return runtime.storage.projects.getStatus(opts.user, opts.id, opts.remote) }, @@ -179,7 +177,7 @@ var api = module.exports = { * @return {Promise} - a list of the local branches * @memberof @node-red/runtime_projects */ - getBranches: function(opts) { + getBranches: async function(opts) { return runtime.storage.projects.getBranches(opts.user, opts.id, opts.remote); }, @@ -193,7 +191,7 @@ var api = module.exports = { * @return {Promise} - the status of the branch * @memberof @node-red/runtime_projects */ - getBranchStatus: function(opts) { + getBranchStatus: async function(opts) { return runtime.storage.projects.getBranchStatus(opts.user, opts.id, opts.branch); }, @@ -208,7 +206,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - setBranch: function(opts) { + setBranch: async function(opts) { runtime.log.audit({event: "projects.branch.set",id:opts.id, branch: opts.branch, create:opts.create}, opts.req); return runtime.storage.projects.setBranch(opts.user, opts.id, opts.branch, opts.create) }, @@ -224,7 +222,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - deleteBranch: function(opts) { + deleteBranch: async function(opts) { runtime.log.audit({event: "projects.branch.delete",id:opts.id, branch: opts.branch, force:opts.force}, opts.req); return runtime.storage.projects.deleteBranch(opts.user, opts.id, opts.branch, false, opts.force); }, @@ -239,7 +237,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - commit: function(opts) { + commit: async function(opts) { runtime.log.audit({event: "projects.commit",id:opts.id}, opts.req); return runtime.storage.projects.commit(opts.user, opts.id,{message: opts.message}); }, @@ -254,7 +252,7 @@ var api = module.exports = { * @return {Promise} - the commit details * @memberof @node-red/runtime_projects */ - getCommit: function(opts) { + getCommit: async function(opts) { return runtime.storage.projects.getCommit(opts.user, opts.id, opts.sha); }, @@ -269,7 +267,7 @@ var api = module.exports = { * @return {Promise} - an array of commits * @memberof @node-red/runtime_projects */ - getCommits: function(opts) { + getCommits: async function(opts) { return runtime.storage.projects.getCommits(opts.user, opts.id, { limit: opts.limit || 20, before: opts.before @@ -285,7 +283,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - abortMerge: function(opts) { + abortMerge: async function(opts) { runtime.log.audit({event: "projects.merge.abort",id:opts.id}, opts.req); return runtime.storage.projects.abortMerge(opts.user, opts.id); }, @@ -301,7 +299,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - resolveMerge: function(opts) { + resolveMerge: async function(opts) { runtime.log.audit({event: "projects.merge.resolve",id:opts.id, file:opts.path}, opts.req); return runtime.storage.projects.resolveMerge(opts.user, opts.id, opts.path, opts.resolution); }, @@ -315,7 +313,7 @@ var api = module.exports = { * @return {Promise} - the file listing * @memberof @node-red/runtime_projects */ - getFiles: function(opts) { + getFiles: async function(opts) { return runtime.storage.projects.getFiles(opts.user, opts.id); }, @@ -330,7 +328,7 @@ var api = module.exports = { * @return {Promise} - the content of the file * @memberof @node-red/runtime_projects */ - getFile: function(opts) { + getFile: async function(opts) { return runtime.storage.projects.getFile(opts.user, opts.id,opts.path,opts.tree); }, @@ -344,7 +342,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - stageFile: function(opts) { + stageFile: async function(opts) { runtime.log.audit({event: "projects.file.stage",id:opts.id, file:opts.path}, opts.req); return runtime.storage.projects.stageFile(opts.user, opts.id, opts.path); }, @@ -359,7 +357,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - unstageFile: function(opts) { + unstageFile: async function(opts) { runtime.log.audit({event: "projects.file.unstage",id:opts.id, file:opts.path}, opts.req); return runtime.storage.projects.unstageFile(opts.user, opts.id, opts.path); }, @@ -374,7 +372,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - revertFile: function(opts) { + revertFile: async function(opts) { runtime.log.audit({event: "projects.file.revert",id:opts.id, file:opts.path}, opts.req); return runtime.storage.projects.revertFile(opts.user, opts.id,opts.path) }, @@ -390,7 +388,7 @@ var api = module.exports = { * @return {Promise} - the requested diff * @memberof @node-red/runtime_projects */ - getFileDiff: function(opts) { + getFileDiff: async function(opts) { return runtime.storage.projects.getFileDiff(opts.user, opts.id, opts.path, opts.type); }, @@ -403,7 +401,7 @@ var api = module.exports = { * @return {Promise} - a list of project remotes * @memberof @node-red/runtime_projects */ - getRemotes: function(opts) { + getRemotes: async function(opts) { return runtime.storage.projects.getRemotes(opts.user, opts.id); }, @@ -420,7 +418,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - addRemote: function(opts) { + addRemote: async function(opts) { runtime.log.audit({event: "projects.remote.add",id:opts.id, remote:opts.remote.name}, opts.req); return runtime.storage.projects.addRemote(opts.user, opts.id, opts.remote) }, @@ -435,7 +433,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - removeRemote: function(opts) { + removeRemote: async function(opts) { runtime.log.audit({event: "projects.remote.delete",id:opts.id, remote:opts.remote}, opts.req); return runtime.storage.projects.removeRemote(opts.user, opts.id, opts.remote); }, @@ -451,7 +449,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - updateRemote: function(opts) { + updateRemote: async function(opts) { runtime.log.audit({event: "projects.remote.update",id:opts.id, remote:opts.remote.name}, opts.req); return runtime.storage.projects.updateRemote(opts.user, opts.id, opts.remote.name, opts.remote) }, @@ -467,7 +465,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - pull: function(opts) { + pull: async function(opts) { runtime.log.audit({event: "projects.pull",id:opts.id, remote: opts.remote, track:opts.track}, opts.req); return runtime.storage.projects.pull(opts.user, opts.id, opts.remote, opts.track, opts.allowUnrelatedHistories); }, @@ -483,7 +481,7 @@ var api = module.exports = { * @return {Promise} - resolves when complete * @memberof @node-red/runtime_projects */ - push: function(opts) { + push: async function(opts) { runtime.log.audit({event: "projects.push",id:opts.id, remote: opts.remote, track:opts.track}, opts.req); return runtime.storage.projects.push(opts.user, opts.id, opts.remote, opts.track); } diff --git a/packages/node_modules/@node-red/runtime/lib/api/settings.js b/packages/node_modules/@node-red/runtime/lib/api/settings.js index 6ebeeccfb..b34e9a27c 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/api/settings.js @@ -64,64 +64,57 @@ var api = module.exports = { * @return {Promise} - the runtime settings * @memberof @node-red/runtime_settings */ - getRuntimeSettings: function(opts) { - return new Promise(function(resolve,reject) { - try { - var safeSettings = { - httpNodeRoot: runtime.settings.httpNodeRoot||"/", - version: runtime.settings.version - } - if (opts.user) { - safeSettings.user = {} - var props = ["anonymous","username","image","permissions"]; - props.forEach(prop => { - if (opts.user.hasOwnProperty(prop)) { - safeSettings.user[prop] = opts.user[prop]; - } - }) + getRuntimeSettings: async function(opts) { + var safeSettings = { + httpNodeRoot: runtime.settings.httpNodeRoot||"/", + version: runtime.settings.version + } + if (opts.user) { + safeSettings.user = {} + var props = ["anonymous","username","image","permissions"]; + props.forEach(prop => { + if (opts.user.hasOwnProperty(prop)) { + safeSettings.user[prop] = opts.user[prop]; } + }) + } - if (!runtime.settings.disableEditor) { - safeSettings.context = runtime.nodes.listContextStores(); + if (!runtime.settings.disableEditor) { + safeSettings.context = runtime.nodes.listContextStores(); - if (util.isArray(runtime.settings.paletteCategories)) { - safeSettings.paletteCategories = runtime.settings.paletteCategories; - } - - if (runtime.settings.flowFilePretty) { - safeSettings.flowFilePretty = runtime.settings.flowFilePretty; - } - - if (!runtime.nodes.paletteEditorEnabled()) { - safeSettings.editorTheme = safeSettings.editorTheme || {}; - safeSettings.editorTheme.palette = safeSettings.editorTheme.palette || {}; - safeSettings.editorTheme.palette.editable = false; - } - if (runtime.storage.projects) { - var activeProject = runtime.storage.projects.getActiveProject(); - if (activeProject) { - safeSettings.project = activeProject; - } else if (runtime.storage.projects.flowFileExists()) { - safeSettings.files = { - flow: runtime.storage.projects.getFlowFilename(), - credentials: runtime.storage.projects.getCredentialsFilename() - } - } - safeSettings.git = { - globalUser: runtime.storage.projects.getGlobalGitUser() - } - } - - safeSettings.flowEncryptionType = runtime.nodes.getCredentialKeyType(); - runtime.settings.exportNodeSettings(safeSettings); - } - - - resolve(safeSettings); - }catch(err) { - console.log(err); + if (util.isArray(runtime.settings.paletteCategories)) { + safeSettings.paletteCategories = runtime.settings.paletteCategories; } - }); + + if (runtime.settings.flowFilePretty) { + safeSettings.flowFilePretty = runtime.settings.flowFilePretty; + } + + if (!runtime.nodes.paletteEditorEnabled()) { + safeSettings.editorTheme = safeSettings.editorTheme || {}; + safeSettings.editorTheme.palette = safeSettings.editorTheme.palette || {}; + safeSettings.editorTheme.palette.editable = false; + } + if (runtime.storage.projects) { + var activeProject = runtime.storage.projects.getActiveProject(); + if (activeProject) { + safeSettings.project = activeProject; + } else if (runtime.storage.projects.flowFileExists()) { + safeSettings.files = { + flow: runtime.storage.projects.getFlowFilename(), + credentials: runtime.storage.projects.getCredentialsFilename() + } + } + safeSettings.git = { + globalUser: runtime.storage.projects.getGlobalGitUser() + } + } + + safeSettings.flowEncryptionType = runtime.nodes.getCredentialKeyType(); + runtime.settings.exportNodeSettings(safeSettings); + } + + return safeSettings; }, /** @@ -132,14 +125,14 @@ var api = module.exports = { * @return {Promise} - the user settings * @memberof @node-red/runtime_settings */ - getUserSettings: function(opts) { + getUserSettings: async function(opts) { var username; if (!opts.user || opts.user.anonymous) { username = '_'; } else { username = opts.user.username; } - return Promise.resolve(runtime.settings.getUserSettings(username)||{}); + return runtime.settings.getUserSettings(username)||{}; }, /** @@ -151,32 +144,30 @@ var api = module.exports = { * @return {Promise} - the user settings * @memberof @node-red/runtime_settings */ - updateUserSettings: function(opts) { + updateUserSettings: async function(opts) { var username; if (!opts.user || opts.user.anonymous) { username = '_'; } else { username = opts.user.username; } - return new Promise(function(resolve,reject) { - var currentSettings = runtime.settings.getUserSettings(username)||{}; - currentSettings = extend(currentSettings, opts.settings); - try { - runtime.settings.setUserSettings(username, currentSettings).then(function() { - runtime.log.audit({event: "settings.update",username:username}, opts.req); - return resolve(); - }).catch(function(err) { - runtime.log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()}, opts.req); - err.status = 400; - return reject(err); - }); - } catch(err) { - runtime.log.warn(runtime.log._("settings.user-not-available",{message:runtime.log._("settings.not-available")})); + var currentSettings = runtime.settings.getUserSettings(username)||{}; + currentSettings = extend(currentSettings, opts.settings); + try { + return runtime.settings.setUserSettings(username, currentSettings).then(function() { + runtime.log.audit({event: "settings.update",username:username}, opts.req); + return; + }).catch(function(err) { runtime.log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()}, opts.req); err.status = 400; - return reject(err); - } - }); + throw err; + }); + } catch(err) { + runtime.log.warn(runtime.log._("settings.user-not-available",{message:runtime.log._("settings.not-available")})); + runtime.log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()}, opts.req); + err.status = 400; + throw err; + } }, /** @@ -187,15 +178,12 @@ var api = module.exports = { * @return {Promise} - the user's ssh keys * @memberof @node-red/runtime_settings */ - getUserKeys: function(opts) { - return new Promise(function(resolve,reject) { - var username = getSSHKeyUsername(opts.user); - runtime.storage.projects.ssh.listSSHKeys(username).then(function(list) { - return resolve(list); - }).catch(function(err) { - err.status = 400; - return reject(err); - }); + getUserKeys: async function(opts) { + var username = getSSHKeyUsername(opts.user); + return runtime.storage.projects.ssh.listSSHKeys(username).catch(function(err) { + err.status = 400; + throw err; + return reject(err); }); }, @@ -208,23 +196,23 @@ var api = module.exports = { * @return {Promise} - the user's ssh public key * @memberof @node-red/runtime_settings */ - getUserKey: function(opts) { - return new Promise(function(resolve,reject) { - var username = getSSHKeyUsername(opts.user); - // console.log('username:', username); - runtime.storage.projects.ssh.getSSHKey(username, opts.id).then(function(data) { - if (data) { - return resolve(data); - } else { - var err = new Error("Key not found"); - err.code = "not_found"; - err.status = 404; - return reject(err); - } - }).catch(function(err) { + getUserKey: async function(opts) { + var username = getSSHKeyUsername(opts.user); + // console.log('username:', username); + return runtime.storage.projects.ssh.getSSHKey(username, opts.id).then(function(data) { + if (data) { + return data; + } else { + var err = new Error("Key not found"); + err.code = "not_found"; + err.status = 404; + throw err; + } + }).catch(function(err) { + if (!err.status) { err.status = 400; - return reject(err); - }); + } + throw err; }); }, @@ -240,15 +228,11 @@ var api = module.exports = { * @return {Promise} - the id of the generated key * @memberof @node-red/runtime_settings */ - generateUserKey: function(opts) { - return new Promise(function(resolve,reject) { - var username = getSSHKeyUsername(opts.user); - runtime.storage.projects.ssh.generateSSHKey(username, opts).then(function(name) { - return resolve(name); - }).catch(function(err) { - err.status = 400; - return reject(err); - }); + generateUserKey: async function(opts) { + var username = getSSHKeyUsername(opts.user); + return runtime.storage.projects.ssh.generateSSHKey(username, opts).catch(function(err) { + err.status = 400; + throw err; }); }, @@ -261,16 +245,11 @@ var api = module.exports = { * @return {Promise} - resolves when deleted * @memberof @node-red/runtime_settings */ - removeUserKey: function(opts) { - return new Promise(function(resolve,reject) { - var username = getSSHKeyUsername(opts.user); - runtime.storage.projects.ssh.deleteSSHKey(username, opts.id).then(function() { - return resolve(); - }).catch(function(err) { - err.status = 400; - return reject(err); - }); + removeUserKey: async function(opts) { + var username = getSSHKeyUsername(opts.user); + return runtime.storage.projects.ssh.deleteSSHKey(username, opts.id).catch(function(err) { + err.status = 400; + throw err; }); - } } From 6fb96fa3c157e6f34e2a99a2fd87c52e2fad0a2d Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 2 Dec 2020 09:25:10 +0000 Subject: [PATCH 033/114] Move exec and events components to util module The exec and events components are common components that are used by both runtime and registry. It makes sense to move them into the util package. This also adds some docs to the registry module --- API.md | 2 +- Gruntfile.js | 3 +- .../@node-red/registry/lib/index.js | 210 +++++++++++++++++- .../@node-red/registry/lib/installer.js | 13 +- .../@node-red/registry/lib/library.js | 62 +++--- .../@node-red/registry/lib/loader.js | 15 +- .../@node-red/registry/lib/localfilesystem.js | 7 +- .../@node-red/registry/lib/registry.js | 6 +- .../@node-red/registry/lib/util.js | 14 +- .../@node-red/runtime/lib/api/comms.js | 17 +- .../@node-red/runtime/lib/api/nodes.js | 20 +- .../@node-red/runtime/lib/flows/Flow.js | 3 +- .../@node-red/runtime/lib/flows/Subflow.js | 11 +- .../@node-red/runtime/lib/flows/index.js | 5 +- .../@node-red/runtime/lib/index.js | 28 +-- .../@node-red/runtime/lib/nodes/index.js | 2 +- .../@node-red/runtime/lib/storage/index.js | 2 +- .../localfilesystem/projects/Project.js | 9 +- .../localfilesystem/projects/git/index.js | 4 +- .../storage/localfilesystem/projects/index.js | 5 +- packages/node_modules/@node-red/util/index.js | 16 ++ .../@node-red/{runtime => util}/lib/events.js | 11 +- .../@node-red/{runtime => util}/lib/exec.js | 36 ++- .../node_modules/@node-red/util/lib/log.js | 5 +- packages/node_modules/node-red/lib/red.js | 8 +- .../@node-red/registry/lib/installer_spec.js | 37 ++- .../registry/lib/localfilesystem_spec.js | 59 ++--- .../@node-red/registry/lib/registry_spec.js | 50 ++--- .../@node-red/runtime/lib/api/comms_spec.js | 35 ++- .../@node-red/runtime/lib/flows/index_spec.js | 2 +- test/unit/@node-red/runtime/lib/index_spec.js | 85 ++++--- .../{runtime => util}/lib/events_spec.js | 4 +- .../{runtime => util}/lib/exec_spec.js | 24 +- 33 files changed, 491 insertions(+), 319 deletions(-) rename packages/node_modules/@node-red/{runtime => util}/lib/events.js (92%) rename packages/node_modules/@node-red/{runtime => util}/lib/exec.js (67%) rename test/unit/@node-red/{runtime => util}/lib/events_spec.js (88%) rename test/unit/@node-red/{runtime => util}/lib/exec_spec.js (90%) diff --git a/API.md b/API.md index f349ea8fe..692a34723 100644 --- a/API.md +++ b/API.md @@ -10,6 +10,6 @@ Module | Description [@node-red/editor-api](@node-red_editor-api.html) | an Express application that serves the Node-RED editor and provides the Admin HTTP API [@node-red/runtime](@node-red_runtime.html) | the core runtime of Node-RED [@node-red/util](@node-red_util.html) | common utilities for the Node-RED runtime and editor modules -@node-red/registry | the internal node registry +[@node-red/registry](@node-red_registry.html) | the internal node registry @node-red/nodes | the default set of core nodes @node-red/editor-client | the client-side resources of the Node-RED editor application diff --git a/Gruntfile.js b/Gruntfile.js index b01763ab0..2bdc2e3aa 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -461,7 +461,8 @@ module.exports = function(grunt) { 'packages/node_modules/@node-red/runtime/lib/hooks.js', 'packages/node_modules/@node-red/util/**/*.js', 'packages/node_modules/@node-red/editor-api/lib/index.js', - 'packages/node_modules/@node-red/editor-api/lib/auth/index.js' + 'packages/node_modules/@node-red/editor-api/lib/auth/index.js', + 'packages/node_modules/@node-red/registry/lib/index.js' ], options: { destination: 'docs', diff --git a/packages/node_modules/@node-red/registry/lib/index.js b/packages/node_modules/@node-red/registry/lib/index.js index 2805534a2..ebcde8cea 100644 --- a/packages/node_modules/@node-red/registry/lib/index.js +++ b/packages/node_modules/@node-red/registry/lib/index.js @@ -1,4 +1,4 @@ -/** +/*! * Copyright JS Foundation and other contributors, http://js.foundation * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,16 +29,27 @@ var loader = require("./loader"); var installer = require("./installer"); var library = require("./library"); -var settings; - +/** + * Initialise the registry with a reference to a runtime object + * @param {Object} runtime - a runtime object + * @memberof @node-red/registry + */ function init(runtime) { - settings = runtime.settings; - installer.init(runtime); + installer.init(runtime.settings); + // Loader requires the full runtime object because it initialises + // the util module it. The Util module is responsible for constructing the + // RED object passed to node modules when they are loaded. loader.init(runtime); - registry.init(settings,loader,runtime.events); + registry.init(runtime.settings,loader); library.init(); } +/** + * Triggers the intial discovery and loading of all Node-RED node modules. + * found on the node path. + * @return {Promise} - resolves when the registry has finised discovering node modules. + * @memberof @node-red/registry + */ function load() { registry.load(); return installer.checkPrereq().then(loader.load); @@ -66,34 +77,221 @@ module.exports = { init:init, load:load, clear: registry.clear, + + /** + * Register a node constructor function. + * + * @param {Object} nodeSet - the Node Set object the constructor is for + * @param {String} type - the node type + * @param {Function} constructor - the node constructor function + * @function + * @memberof @node-red/registry + */ registerType: registry.registerNodeConstructor, + /** + * Get a node constructor function. + * + * @param {String} type - the node type + * @return {Function} the node constructor function + * @function + * @memberof @node-red/registry + */ get: registry.getNodeConstructor, + + /** + * Get a node's set information. + * + * @param {String} type - the node type or set identifier + * @return {Object} the node set information + * @function + * @memberof @node-red/registry + */ getNodeInfo: registry.getNodeInfo, + + + /** + * Get a list of all nodes in the registry. + * + * @return {Object} the node list + * @function + * @memberof @node-red/registry + */ getNodeList: registry.getNodeList, + /** + * Get a modules's information. + * + * @param {String} type - the module identifier + * @return {Object} the module information + * @function + * @memberof @node-red/registry + */ getModuleInfo: registry.getModuleInfo, + + /** + * Get a list of all moduless in the registry. + * + * @return {Object} the module list + * @function + * @memberof @node-red/registry + */ getModuleList: registry.getModuleList, + /** + * Get the HTML configs for all nodes in the registry. + * + * @param {String} lang - the language to return, default `en-US` + * @return {String} the node configs + * @function + * @memberof @node-red/registry + */ getNodeConfigs: registry.getAllNodeConfigs, + + /** + * Get the HTML config for a single node set. + * + * @param {String} id - the node identifier + * @param {String} lang - the language to return, default `en-US` + * @return {String} the node config + * @function + * @memberof @node-red/registry + */ getNodeConfig: registry.getNodeConfig, + + /** + * Get the local path to a node's icon file. + * + * @param {String} module - the module that provides the icon + * @param {String} icon - the name of the icon + * @return {String} the local path to the icon + * @function + * @memberof @node-red/registry + */ getNodeIconPath: registry.getNodeIconPath, + + + /** + * Get the full list of all icons available. + * + * @return {String} the icon list + * @function + * @memberof @node-red/registry + */ getNodeIcons: registry.getNodeIcons, + /** + * Enables a node set, making it available for use. + * + * @param {String} type - the node type or set identifier + * @return {Promise} A promise that resolves when the node set has been enabled + * @throws if the identifier is not recognised or runtime settings are unavailable + * @function + * @memberof @node-red/registry + */ enableNode: enableNodeSet, + + /** + * Disables a node set, making it unavailable for use. + * + * @param {String} type - the node type or set identifier + * @return {Promise} A promise that resolves when the node set has been disabled + * @throws if the identifier is not recognised or runtime settings are unavailable + * @function + * @memberof @node-red/registry + */ disableNode: registry.disableNodeSet, + + /** + * Loads a new module into the registry. + * + * This will rescan the node module paths looking for this module. + * + * @param {String} module - the name of the module to add + * @return {Promise} A promise that resolves with the module information once it has been added + * @throws if the module has already been added or the runtime settings are unavailable + * @function + * @memberof @node-red/registry + */ addModule: addModule, + + /** + * Removes a module from the registry. + * + * @param {String} module - the name of the module to remove + * @return {Promise} A promise that resolves with the list of removed node sets + * @throws if the module is not found or the runtime settings are unavailable + * @function + * @memberof @node-red/registry + */ removeModule: registry.removeModule, + /** + * Installs a new node module using npm and then add to the registry + * + * @param {String|Buffer} module - the name of the module to install, or a Buffer containing a module tar file + * @param {String} version - the version of the module to install, default: `latest` + * @param {String} url - (optional) a url to install the module from + * @return {Promise} A promise that resolves with the module information once it has been installed + * @function + * @memberof @node-red/registry + */ installModule: installer.installModule, + + + /** + * Uninstalls a module using npm + * + * @param {String} module - the name of the module to uninstall + * @return {Promise} A promise that resolves when the module has been removed + * @function + * @memberof @node-red/registry + */ uninstallModule: installer.uninstallModule, + /** + * Update to internal list of available modules based on what has been actually + * loaded. + * + * The `autoInstallModules` runtime option means the runtime may try to install + * missing modules after the initial load is complete. If that flag is not set + * this function is used to remove the modules from the registry's saved list. + * @function + * @memberof @node-red/registry + */ cleanModuleList: registry.cleanModuleList, + /** + * Check if the regisrty is able to install/remove modules. + * + * This is based on whether it has found `npm` on the command-line. + * @return {Boolean} whether the installer is enabled + * + * @function + * @memberof @node-red/registry + */ paletteEditorEnabled: installer.paletteEditorEnabled, + /** + * Get a list of all example flows provided by nodes in the registry. + * @return {Object} an object, indexed by module, listing all example flows + * + * @function + * @memberof @node-red/registry + */ getNodeExampleFlows: library.getExampleFlows, + + + /** + * Gets the full path to a node example + * @param {String} module - the name of the module providing the example + * @param {String} path - the relative path of the example + * @return {String} the full path to the example + * + * @function + * @memberof @node-red/registry + */ getNodeExampleFlowPath: library.getExampleFlowPath, deprecated: require("./deprecated") diff --git a/packages/node_modules/@node-red/registry/lib/installer.js b/packages/node_modules/@node-red/registry/lib/installer.js index 03f9eb11c..9735aa8a1 100644 --- a/packages/node_modules/@node-red/registry/lib/installer.js +++ b/packages/node_modules/@node-red/registry/lib/installer.js @@ -22,11 +22,7 @@ var tar = require("tar"); var registry = require("./registry"); var library = require("./library"); -var log; -var exec; - -var events; - +const {exec,log,events} = require("@node-red/util"); var child_process = require('child_process'); var npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; var paletteEditorEnabled = false; @@ -37,11 +33,8 @@ const slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/; const pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//; const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/; -function init(runtime) { - events = runtime.events; - settings = runtime.settings; - log = runtime.log; - exec = runtime.exec; +function init(_settings) { + settings = _settings; } var activePromise = Promise.resolve(); diff --git a/packages/node_modules/@node-red/registry/lib/library.js b/packages/node_modules/@node-red/registry/lib/library.js index a8ca8a775..90b0fda82 100644 --- a/packages/node_modules/@node-red/registry/lib/library.js +++ b/packages/node_modules/@node-red/registry/lib/library.js @@ -14,7 +14,7 @@ * limitations under the License. **/ -var fs = require('fs'); +var fs = require('fs-extra'); var fspath = require('path'); var runtime; @@ -22,38 +22,34 @@ var runtime; var exampleRoots = {}; var exampleFlows = null; -function getFlowsFromPath(path) { - return new Promise(function(resolve,reject) { - var result = {}; - fs.readdir(path,function(err,files) { - var promises = []; - var validFiles = []; - files.forEach(function(file) { - var fullPath = fspath.join(path,file); - var stats = fs.lstatSync(fullPath); - if (stats.isDirectory()) { - validFiles.push(file); - promises.push(getFlowsFromPath(fullPath)); - } else if (/\.json$/.test(file)){ - validFiles.push(file); - promises.push(Promise.resolve(file.split(".")[0])) - } - }) - var i=0; - Promise.all(promises).then(function(results) { - results.forEach(function(r) { - if (typeof r === 'string') { - result.f = result.f||[]; - result.f.push(r); - } else { - result.d = result.d||{}; - result.d[validFiles[i]] = r; - } - i++; - }) - resolve(result); - }) - }); +async function getFlowsFromPath(path) { + var result = {}; + var validFiles = []; + return fs.readdir(path).then(files => { + var promises = []; + files.forEach(function(file) { + var fullPath = fspath.join(path,file); + var stats = fs.lstatSync(fullPath); + if (stats.isDirectory()) { + validFiles.push(file); + promises.push(getFlowsFromPath(fullPath)); + } else if (/\.json$/.test(file)){ + validFiles.push(file); + promises.push(Promise.resolve(file.split(".")[0])) + } + }) + return Promise.all(promises) + }).then(results => { + results.forEach(function(r,i) { + if (typeof r === 'string') { + result.f = result.f||[]; + result.f.push(r); + } else { + result.d = result.d||{}; + result.d[validFiles[i]] = r; + } + }) + return result; }) } diff --git a/packages/node_modules/@node-red/registry/lib/loader.js b/packages/node_modules/@node-red/registry/lib/loader.js index 3a960c73c..18d202420 100644 --- a/packages/node_modules/@node-red/registry/lib/loader.js +++ b/packages/node_modules/@node-red/registry/lib/loader.js @@ -22,15 +22,14 @@ var localfilesystem = require("./localfilesystem"); var registry = require("./registry"); var registryUtil = require("./util") var i18n = require("@node-red/util").i18n; +var log = require("@node-red/util").log; var settings; -var runtime; function init(_runtime) { - runtime = _runtime; - settings = runtime.settings; - localfilesystem.init(runtime); - registryUtil.init(runtime); + settings = _runtime.settings; + localfilesystem.init(settings); + registryUtil.init(_runtime); } function load(disableNodePathScan) { @@ -38,7 +37,7 @@ function load(disableNodePathScan) { // We should expose that as an option at some point, although the // performance gains are minimal. //return loadNodeFiles(registry.getModuleList()); - runtime.log.info(runtime.log._("server.loading")); + log.info(log._("server.loading")); var nodeFiles = localfilesystem.getNodeFiles(disableNodePathScan); return loadNodeFiles(nodeFiles); @@ -51,9 +50,9 @@ function loadNodeFiles(nodeFiles) { /* istanbul ignore else */ if (nodeFiles.hasOwnProperty(module)) { if (nodeFiles[module].redVersion && - !semver.satisfies(runtime.version().replace(/(\-[1-9A-Za-z-][0-9A-Za-z-\.]*)?(\+[0-9A-Za-z-\.]+)?$/,""), nodeFiles[module].redVersion)) { + !semver.satisfies((settings.version||"0.0.0").replace(/(\-[1-9A-Za-z-][0-9A-Za-z-\.]*)?(\+[0-9A-Za-z-\.]+)?$/,""), nodeFiles[module].redVersion)) { //TODO: log it - runtime.log.warn("["+module+"] "+runtime.log._("server.node-version-mismatch",{version:nodeFiles[module].redVersion})); + log.warn("["+module+"] "+log._("server.node-version-mismatch",{version:nodeFiles[module].redVersion})); nodeFiles[module].err = "version_mismatch"; continue; } diff --git a/packages/node_modules/@node-red/registry/lib/localfilesystem.js b/packages/node_modules/@node-red/registry/lib/localfilesystem.js index d5bef63cd..7aea3f57b 100644 --- a/packages/node_modules/@node-red/registry/lib/localfilesystem.js +++ b/packages/node_modules/@node-red/registry/lib/localfilesystem.js @@ -16,9 +16,6 @@ var fs = require("fs"); var path = require("path"); - -var log; - var log = require("@node-red/util").log; var i18n = require("@node-red/util").i18n; @@ -26,8 +23,8 @@ var settings; var disableNodePathScan = false; var iconFileExtensions = [".png", ".gif", ".svg"]; -function init(runtime) { - settings = runtime.settings; +function init(_settings) { + settings = _settings; } function isIncluded(name) { diff --git a/packages/node_modules/@node-red/registry/lib/registry.js b/packages/node_modules/@node-red/registry/lib/registry.js index 89f04eacc..a91735071 100644 --- a/packages/node_modules/@node-red/registry/lib/registry.js +++ b/packages/node_modules/@node-red/registry/lib/registry.js @@ -19,8 +19,7 @@ var path = require("path"); var fs = require("fs"); var library = require("./library"); - -var events; +const {events} = require("@node-red/util") var settings; var loader; @@ -31,10 +30,9 @@ var nodeConstructors = {}; var nodeTypeToId = {}; var moduleNodes = {}; -function init(_settings,_loader, _events) { +function init(_settings,_loader) { settings = _settings; loader = _loader; - events = _events; moduleNodes = {}; nodeTypeToId = {}; nodeConstructors = {}; diff --git a/packages/node_modules/@node-red/registry/lib/util.js b/packages/node_modules/@node-red/registry/lib/util.js index 5a6d3da38..dbb6c6fc7 100644 --- a/packages/node_modules/@node-red/registry/lib/util.js +++ b/packages/node_modules/@node-red/registry/lib/util.js @@ -14,9 +14,8 @@ * limitations under the License. **/ -var path = require("path"); -var i18n = require("@node-red/util").i18n; -var registry; +const path = require("path"); +const {events,i18n,log} = require("@node-red/util"); var runtime; function copyObjectProperties(src,dst,copyList,blockList) { @@ -40,7 +39,7 @@ function copyObjectProperties(src,dst,copyList,blockList) { } } function requireModule(name) { - var moduleInfo = registry.getModuleInfo(name); + var moduleInfo = require("./index").getModuleInfo(name); if (moduleInfo && moduleInfo.path) { var relPath = path.relative(__dirname, moduleInfo.path); return require(relPath); @@ -56,14 +55,14 @@ function createNodeApi(node) { nodes: {}, log: {}, settings: {}, - events: runtime.events, + events: events, hooks: runtime.hooks, util: runtime.util, version: runtime.version, require: requireModule, comms: { publish: function(topic,data,retain) { - runtime.events.emit("comms",{ + events.emit("comms",{ topic: topic, data: data, retain: retain @@ -83,7 +82,7 @@ function createNodeApi(node) { red.nodes.registerType = function(type,constructor,opts) { runtime.nodes.registerType(node.id,type,constructor,opts); } - copyObjectProperties(runtime.log,red.log,null,["init"]); + copyObjectProperties(log,red.log,null,["init"]); copyObjectProperties(runtime.settings,red.settings,null,["init","load","reset"]); if (runtime.adminApi) { red.auth = runtime.adminApi.auth; @@ -108,7 +107,6 @@ function createNodeApi(node) { module.exports = { init: function(_runtime) { runtime = _runtime; - registry = require("@node-red/registry/lib"); }, createNodeApi: createNodeApi } diff --git a/packages/node_modules/@node-red/runtime/lib/api/comms.js b/packages/node_modules/@node-red/runtime/lib/api/comms.js index 42e727412..90f1603cd 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/comms.js +++ b/packages/node_modules/@node-red/runtime/lib/api/comms.js @@ -33,6 +33,7 @@ var runtime; var retained = {}; var connections = []; +const events = require("@node-red/util").events; function handleCommsEvent(event) { publish(event.topic,event.data,event.retain); @@ -88,14 +89,14 @@ var api = module.exports = { runtime = _runtime; connections = []; retained = {}; - runtime.events.removeListener("node-status",handleStatusEvent); - runtime.events.on("node-status",handleStatusEvent); - runtime.events.removeListener("runtime-event",handleRuntimeEvent); - runtime.events.on("runtime-event",handleRuntimeEvent); - runtime.events.removeListener("comms",handleCommsEvent); - runtime.events.on("comms",handleCommsEvent); - runtime.events.removeListener("event-log",handleEventLog); - runtime.events.on("event-log",handleEventLog); + events.removeListener("node-status",handleStatusEvent); + events.on("node-status",handleStatusEvent); + events.removeListener("runtime-event",handleRuntimeEvent); + events.on("runtime-event",handleRuntimeEvent); + events.removeListener("comms",handleCommsEvent); + events.on("comms",handleCommsEvent); + events.removeListener("event-log",handleEventLog); + events.on("event-log",handleEventLog); }, /** diff --git a/packages/node_modules/@node-red/runtime/lib/api/nodes.js b/packages/node_modules/@node-red/runtime/lib/api/nodes.js index 6a05b4c17..9ac83c814 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/nodes.js +++ b/packages/node_modules/@node-red/runtime/lib/api/nodes.js @@ -376,16 +376,18 @@ var api = module.exports = { var lang = opts.lang; var prevLang = runtime.i18n.i.language; // Trigger a load from disk of the language if it is not the default - return runtime.i18n.i.changeLanguage(lang, function(){ - var nodeList = runtime.nodes.getNodeList(); - var result = {}; - nodeList.forEach(function(n) { - if (n.module !== "node-red") { - result[n.id] = runtime.i18n.i.getResourceBundle(lang, n.id)||{}; - } + return new Promise( (resolve,reject) => { + runtime.i18n.i.changeLanguage(lang, function(){ + var nodeList = runtime.nodes.getNodeList(); + var result = {}; + nodeList.forEach(function(n) { + if (n.module !== "node-red") { + result[n.id] = runtime.i18n.i.getResourceBundle(lang, n.id)||{}; + } + }); + runtime.i18n.i.changeLanguage(prevLang); + resolve(result); }); - runtime.i18n.i.changeLanguage(prevLang); - return result; }); }, diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js index a7e00bfbc..a4791e084 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js @@ -16,8 +16,8 @@ var clone = require("clone"); var redUtil = require("@node-red/util").util; +const events = require("@node-red/util").events; var flowUtil = require("./util"); -var events = require("../events"); const context = require('../nodes/context'); const hooks = require("../hooks"); @@ -679,7 +679,6 @@ module.exports = { asyncMessageDelivery = !runtime.settings.runtimeSyncDelivery Log = runtime.log; Subflow = require("./Subflow"); - Subflow.init(runtime); }, create: function(parent,global,conf) { return new Flow(parent,global,conf); diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js index 78ec9ee75..da15ecfe4 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js @@ -18,16 +18,11 @@ const clone = require("clone"); const Flow = require('./Flow').Flow; const context = require('../nodes/context'); const util = require("util"); -const events = require("../events"); - const redUtil = require("@node-red/util").util; +const events = require("@node-red/util").events; const flowUtil = require("./util"); - - const credentials = require("../nodes/credentials"); -var Log; - /** * Create deep copy of object */ @@ -509,8 +504,6 @@ function createSubflow(parent,globalFlow,subflowDef,subflowInstance) { } module.exports = { - init: function(runtime) { - Log = runtime.log; - }, + init: function(runtime) {}, create: createSubflow } diff --git a/packages/node_modules/@node-red/runtime/lib/flows/index.js b/packages/node_modules/@node-red/runtime/lib/flows/index.js index a207b8f05..350115f81 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/index.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/index.js @@ -25,9 +25,8 @@ var context = require("../nodes/context") var credentials = require("../nodes/credentials"); var flowUtil = require("./util"); var log; -var events = require("../events"); +const events = require("@node-red/util").events; var redUtil = require("@node-red/util").util; -const hooks = require("../hooks"); var storage = null; var settings = null; @@ -712,7 +711,7 @@ module.exports = { */ load: load, loadFlows: load, - + get:getNode, eachNode: eachNode, diff --git a/packages/node_modules/@node-red/runtime/lib/index.js b/packages/node_modules/@node-red/runtime/lib/index.js index 89b0dcfd6..dd1594467 100644 --- a/packages/node_modules/@node-red/runtime/lib/index.js +++ b/packages/node_modules/@node-red/runtime/lib/index.js @@ -20,19 +20,15 @@ var redNodes = require("./nodes"); var flows = require("./flows"); var storage = require("./storage"); var library = require("./library"); -var events = require("./events"); var hooks = require("./hooks"); var settings = require("./settings"); -var exec = require("./exec"); var express = require("express"); var path = require('path'); var fs = require("fs"); var os = require("os"); -var redUtil = require("@node-red/util"); -var log = redUtil.log; -var i18n = redUtil.i18n; +const {log,i18n,events,exec,util} = require("@node-red/util"); var runtimeMetricInterval = null; @@ -65,7 +61,7 @@ var server; * better abstracted. * @memberof @node-red/runtime */ -function init(userSettings,httpServer,_adminApi,__util) { +function init(userSettings,httpServer,_adminApi) { server = httpServer; userSettings.version = getVersion(); settings.init(userSettings); @@ -79,14 +75,6 @@ function init(userSettings,httpServer,_adminApi,__util) { redNodes.init(runtime); library.init(runtime); externalAPI.init(runtime); - exec.init(runtime); - if (__util) { - log = __util.log; - i18n = __util.i18n; - } else { - log = redUtil.log; - i18n = redUtil.i18n; - } } var version; @@ -246,6 +234,10 @@ function reportMetrics() { /** * Stops the runtime. + * + * Once called, Node-RED should not be restarted until the Node.JS process is + * restarted. + * * @return {Promise} - resolves when the runtime is stopped. * @memberof @node-red/runtime */ @@ -266,17 +258,17 @@ function stop() { // This is the internal api var runtime = { version: getVersion, - get log() { return log }, - get i18n() { return i18n }, + log: log, + i18n: i18n, + events: events, settings: settings, storage: storage, - events: events, hooks: hooks, nodes: redNodes, flows: flows, library: library, exec: exec, - util: require("@node-red/util").util, + util: util, get adminApi() { return adminApi }, get adminApp() { return adminApp }, get nodeApp() { return nodeApp }, diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/index.js index be31df2f7..858274be2 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/index.js @@ -28,7 +28,7 @@ var context = require("./context"); var Node = require("./Node"); var log; -var events = require("../events"); +const events = require("@node-red/util").events; var settings; diff --git a/packages/node_modules/@node-red/runtime/lib/storage/index.js b/packages/node_modules/@node-red/runtime/lib/storage/index.js index e7f09c20f..f5e07e254 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/index.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/index.js @@ -17,7 +17,7 @@ var Path = require('path'); var crypto = require('crypto'); -var log = require("@node-red/util").log; // TODO: separate module +var log = require("@node-red/util").log; var runtime; var storageModule; diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js index 55bb9d078..d87001f1f 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js @@ -26,6 +26,7 @@ var sshKeys = require("./ssh"); var settings; var runtime; var log = require("@node-red/util").log; +const events = require("@node-red/util").events; var projectsDir; @@ -532,7 +533,7 @@ Project.prototype.status = function(user, includeRemote) { result.merging = true; if (!self.merging) { self.merging = true; - runtime.events.emit("runtime-event",{ + events.emit("runtime-event",{ id:"runtime-state", payload:{ type:"warning", @@ -556,7 +557,7 @@ Project.prototype.status = function(user, includeRemote) { } if (result.commits.total === 0 && Object.keys(result.files).length === 0) { if (!self.empty) { - runtime.events.emit("runtime-event",{ + events.emit("runtime-event",{ id:"runtime-state", payload:{ type:"warning", @@ -570,9 +571,9 @@ Project.prototype.status = function(user, includeRemote) { } else { if (self.empty) { if (self.paths.flowFile) { - runtime.events.emit("runtime-event",{id:"runtime-state",retain:true}); + events.emit("runtime-event",{id:"runtime-state",retain:true}); } else { - runtime.events.emit("runtime-event",{ + events.emit("runtime-event",{ id:"runtime-state", payload:{ type:"warning", diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js index dbcb5fb61..e1ded4337 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js @@ -14,8 +14,6 @@ * limitations under the License. **/ -var exec = require("../../../../exec"); - var authResponseServer = require('./authServer').ResponseServer; var sshResponseServer = require('./authServer').ResponseSSHServer; var clone = require('clone'); @@ -23,7 +21,7 @@ var path = require("path"); var gitCommand = "git"; var gitVersion; -var log = require("@node-red/util").log; +const {log,exec} = require("@node-red/util"); function runGitCommand(args,cwd,env,emit) { log.trace(gitCommand + JSON.stringify(args)); diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js index 80a3f31e4..3bbeb823b 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js @@ -28,6 +28,7 @@ var Projects = require("./Project"); var settings; var runtime; var log = require("@node-red/util").log; +const events = require("@node-red/util").events; var projectsEnabled = false; var projectLogMessages = []; @@ -355,11 +356,11 @@ function getActiveProject(user) { function reloadActiveProject(action) { return runtime.nodes.stopFlows().then(function() { return runtime.nodes.loadFlows(true).then(function() { - runtime.events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}}); + events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}}); }).catch(function(err) { // We're committed to the project change now, so notify editors // that it has changed. - runtime.events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}}); + events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}}); throw err; }); }); diff --git a/packages/node_modules/@node-red/util/index.js b/packages/node_modules/@node-red/util/index.js index 66ac91bd3..29e1a480c 100644 --- a/packages/node_modules/@node-red/util/index.js +++ b/packages/node_modules/@node-red/util/index.js @@ -17,6 +17,8 @@ const log = require("./lib/log"); const i18n = require("./lib/i18n"); const util = require("./lib/util"); +const events = require("./lib/events"); +const exec = require("./lib/exec"); /** * This module provides common utilities for the Node-RED runtime and editor @@ -54,4 +56,18 @@ module.exports = { * @memberof @node-red/util */ util: util, + + /** + * Runtime events + * @mixes @node-red/util_event + * @memberof @node-red/util + */ + events: events, + + /** + * Run system commands with event-log integration + * @mixes @node-red/util_exec + * @memberof @node-red/util + */ + exec: exec } diff --git a/packages/node_modules/@node-red/runtime/lib/events.js b/packages/node_modules/@node-red/util/lib/events.js similarity index 92% rename from packages/node_modules/@node-red/runtime/lib/events.js rename to packages/node_modules/@node-red/util/lib/events.js index 8e6935222..df45b4d16 100644 --- a/packages/node_modules/@node-red/runtime/lib/events.js +++ b/packages/node_modules/@node-red/util/lib/events.js @@ -14,6 +14,11 @@ * limitations under the License. **/ + /** + * Runtime events + * @mixin @node-red/util_events + */ + const events = new (require("events")).EventEmitter(); @@ -45,14 +50,14 @@ module.exports = events; /** * Runtime events emitter - * @mixin @node-red/runtime_events + * @mixin @node-red/util_events */ /** * Register an event listener for a runtime event * @name on * @function - * @memberof @node-red/runtime_events + * @memberof @node-red/util_events * @param {String} eventName - the name of the event to listen to * @param {Function} listener - the callback function for the event */ @@ -61,7 +66,7 @@ module.exports = events; * Emit an event to all of its registered listeners * @name emit * @function - * @memberof @node-red/runtime_events + * @memberof @node-red/util_events * @param {String} eventName - the name of the event to emit * @param {any} ...args - the arguments to pass in the event * @return {Boolean} - whether the event had listeners or not diff --git a/packages/node_modules/@node-red/runtime/lib/exec.js b/packages/node_modules/@node-red/util/lib/exec.js similarity index 67% rename from packages/node_modules/@node-red/runtime/lib/exec.js rename to packages/node_modules/@node-red/util/lib/exec.js index 0ef3c069c..c7197ef65 100644 --- a/packages/node_modules/@node-red/runtime/lib/exec.js +++ b/packages/node_modules/@node-red/util/lib/exec.js @@ -1,4 +1,4 @@ -/** +/*! * Copyright JS Foundation and other contributors, http://js.foundation * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,19 +14,41 @@ * limitations under the License. **/ -const child_process = require('child_process'); -const { util } = require('@node-red/util'); +/** + * Run system commands with event-log integration + * @mixin @node-red/util_exec + */ -var events; +const child_process = require('child_process'); +const events = require("./events"); +const util = require('./util'); function logLines(id,type,data) { events.emit("event-log", {id:id,payload:{ts: Date.now(),data:data,type:type}}); } module.exports = { - init: function(_runtime) { - events = _runtime.events; - }, + /** + * Run a system command with stdout/err being emitted as 'event-log' events + * on the @node-red/util/events handler. + * + * The main arguments to this function are the same as passed to `child_process.spawn` + * + * @param {String} command - the command to run + * @param {Array} args - arguments for the command + * @param {Object} options - options to pass child_process.spawn + * @param {Boolean} emit - whether to emit events to the event-log for each line of stdout/err + * @return {Promise} A promise that resolves (rc=0) or rejects (rc!=0) when the command completes. The value + * of the promise is an object of the form: + * + * { + * code: , + * stdout: , + * stderr: + * } + + * @memberof @node-red/util_exec + */ run: function(command,args,options,emit) { var invocationId = util.generateId(); diff --git a/packages/node_modules/@node-red/util/lib/log.js b/packages/node_modules/@node-red/util/lib/log.js index 8d7b84966..341019080 100644 --- a/packages/node_modules/@node-red/util/lib/log.js +++ b/packages/node_modules/@node-red/util/lib/log.js @@ -1,4 +1,4 @@ -/** +/*! * Copyright JS Foundation and other contributors, http://js.foundation * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,7 +12,6 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * @ignore **/ /** @@ -91,7 +90,7 @@ var consoleLogger = function(msg) { } catch(e){ message = 'Exception trying to log: '+util.inspect(message); } - + util.log("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+message); } } diff --git a/packages/node_modules/node-red/lib/red.js b/packages/node_modules/node-red/lib/red.js index 86fa513d1..e46e40e26 100644 --- a/packages/node_modules/node-red/lib/red.js +++ b/packages/node_modules/node-red/lib/red.js @@ -121,6 +121,10 @@ module.exports = { }, /** * Stop the Node-RED application. + * + * Once called, Node-RED should not be restarted until the Node.JS process is + * restarted. + * * @return {Promise} - resolves when complete * @memberof node-red */ @@ -161,10 +165,10 @@ module.exports = { /** * Runtime events emitter - * @see @node-red/runtime_events + * @see @node-red/util_events * @memberof node-red */ - events: runtime.events, + events: redUtil.events, /** * Runtime hooks engine diff --git a/test/unit/@node-red/registry/lib/installer_spec.js b/test/unit/@node-red/registry/lib/installer_spec.js index b344dbbea..e0e7380f6 100644 --- a/test/unit/@node-red/registry/lib/installer_spec.js +++ b/test/unit/@node-red/registry/lib/installer_spec.js @@ -25,6 +25,7 @@ var NR_TEST_UTILS = require("nr-test-utils"); var installer = NR_TEST_UTILS.require("@node-red/registry/lib/installer"); var registry = NR_TEST_UTILS.require("@node-red/registry/lib/index"); var typeRegistry = NR_TEST_UTILS.require("@node-red/registry/lib/registry"); +const { events, exec, log } = NR_TEST_UTILS.require("@node-red/util"); describe('nodes/registry/installer', function() { @@ -38,21 +39,15 @@ describe('nodes/registry/installer', function() { _: function(msg) { return msg } } + var execResponse; + beforeEach(function() { - installer.init({log:mockLog, settings:{}, events: new EventEmitter(), exec: { - run: function() { - return Promise.resolve(""); - } - }}); + sinon.stub(exec,"run", () => execResponse || Promise.resolve("")) + installer.init({}) }); - function initInstaller(execResult) { - installer.init({log:mockLog, settings:{}, events: new EventEmitter(), exec: { - run: function() { - return execResult; - } - }}); - } + afterEach(function() { + execResponse = null; if (registry.addModule.restore) { registry.addModule.restore(); } @@ -72,7 +67,7 @@ describe('nodes/registry/installer', function() { if (fs.statSync.restore) { fs.statSync.restore(); } - + exec.run.restore(); }); describe("installs module", function() { @@ -108,7 +103,7 @@ describe('nodes/registry/installer', function() { } var p = Promise.reject(res); p.catch((err)=>{}); - initInstaller(p) + execResponse = p; installer.installModule("this_wont_exist").catch(function(err) { err.should.have.property("code",404); done(); @@ -122,7 +117,7 @@ describe('nodes/registry/installer', function() { } var p = Promise.reject(res); p.catch((err)=>{}); - initInstaller(p) + execResponse = p; sinon.stub(typeRegistry,"getModuleInfo", function() { return { version: "0.1.1" @@ -163,7 +158,7 @@ describe('nodes/registry/installer', function() { } var p = Promise.reject(res); p.catch((err)=>{}); - initInstaller(p) + execResponse = p; installer.installModule("this_wont_exist").then(function() { done(new Error("Unexpected success")); }).catch(err => { @@ -181,7 +176,7 @@ describe('nodes/registry/installer', function() { } var p = Promise.resolve(res); p.catch((err)=>{}); - initInstaller(p) + execResponse = p; var addModule = sinon.stub(registry,"addModule",function(md) { return Promise.resolve(nodeInfo); @@ -226,7 +221,7 @@ describe('nodes/registry/installer', function() { } var p = Promise.resolve(res); p.catch((err)=>{}); - initInstaller(p) + execResponse = p; installer.installModule(resourcesDir).then(function(info) { info.should.eql(nodeInfo); done(); @@ -242,7 +237,7 @@ describe('nodes/registry/installer', function() { } var p = Promise.resolve(res); p.catch((err)=>{}); - initInstaller(p) + execResponse = p; var addModule = sinon.stub(registry,"addModule",function(md) { return Promise.resolve(nodeInfo); @@ -280,7 +275,7 @@ describe('nodes/registry/installer', function() { } var p = Promise.reject(res); p.catch((err)=>{}); - initInstaller(p) + execResponse = p; installer.uninstallModule("this_wont_exist").then(function() { done(new Error("Unexpected success")); @@ -304,7 +299,7 @@ describe('nodes/registry/installer', function() { } var p = Promise.resolve(res); p.catch((err)=>{}); - initInstaller(p) + execResponse = p; sinon.stub(fs,"statSync", function(fn) { return {}; }); diff --git a/test/unit/@node-red/registry/lib/localfilesystem_spec.js b/test/unit/@node-red/registry/lib/localfilesystem_spec.js index d86a08adb..5cb3c180d 100644 --- a/test/unit/@node-red/registry/lib/localfilesystem_spec.js +++ b/test/unit/@node-red/registry/lib/localfilesystem_spec.js @@ -53,7 +53,7 @@ describe("red/nodes/registry/localfilesystem",function() { } describe("#getNodeFiles",function() { it("Finds all the node files in the resources tree",function(done) { - localfilesystem.init({settings:{coreNodesDir:resourcesDir}}); + localfilesystem.init({coreNodesDir:resourcesDir}); var nodeList = localfilesystem.getNodeFiles(true); nodeList.should.have.a.property("node-red"); var nm = nodeList['node-red']; @@ -68,7 +68,7 @@ describe("red/nodes/registry/localfilesystem",function() { done(); }); it("Includes node files from settings",function(done) { - localfilesystem.init({settings:{nodesIncludes:['TestNode1.js'],coreNodesDir:resourcesDir}}); + localfilesystem.init({nodesIncludes:['TestNode1.js'],coreNodesDir:resourcesDir}); var nodeList = localfilesystem.getNodeFiles(true); nodeList.should.have.a.property("node-red"); var nm = nodeList['node-red']; @@ -78,7 +78,7 @@ describe("red/nodes/registry/localfilesystem",function() { done(); }); it("Excludes node files from settings",function(done) { - localfilesystem.init({settings:{nodesExcludes:['TestNode1.js'],coreNodesDir:resourcesDir}}); + localfilesystem.init({nodesExcludes:['TestNode1.js'],coreNodesDir:resourcesDir}); var nodeList = localfilesystem.getNodeFiles(true); nodeList.should.have.a.property("node-red"); var nm = nodeList['node-red']; @@ -88,7 +88,7 @@ describe("red/nodes/registry/localfilesystem",function() { done(); }); it("Finds nodes in userDir/nodes",function(done) { - localfilesystem.init({settings:{userDir:userDir}}); + localfilesystem.init({userDir:userDir}); var nodeList = localfilesystem.getNodeFiles(true); nodeList.should.have.a.property("node-red"); var nm = nodeList['node-red']; @@ -99,7 +99,7 @@ describe("red/nodes/registry/localfilesystem",function() { }); it("Finds nodes in settings.nodesDir (string)",function(done) { - localfilesystem.init({settings:{nodesDir:userDir}}); + localfilesystem.init({nodesDir:userDir}); var nodeList = localfilesystem.getNodeFiles(true); nodeList.should.have.a.property("node-red"); var nm = nodeList['node-red']; @@ -110,7 +110,7 @@ describe("red/nodes/registry/localfilesystem",function() { }); it("Finds nodes in settings.nodesDir (string,relative path)",function(done) { var relativeUserDir = path.join("test","unit","@node-red","registry","lib","resources","userDir"); - localfilesystem.init({settings:{nodesDir:relativeUserDir}}); + localfilesystem.init({nodesDir:relativeUserDir}); var nodeList = localfilesystem.getNodeFiles(true); nodeList.should.have.a.property("node-red"); var nm = nodeList['node-red']; @@ -120,7 +120,7 @@ describe("red/nodes/registry/localfilesystem",function() { done(); }); it("Finds nodes in settings.nodesDir (array)",function(done) { - localfilesystem.init({settings:{nodesDir:[userDir]}}); + localfilesystem.init({nodesDir:[userDir]}); var nodeList = localfilesystem.getNodeFiles(true); nodeList.should.have.a.property("node-red"); var nm = nodeList['node-red']; @@ -139,7 +139,7 @@ describe("red/nodes/registry/localfilesystem",function() { } return _join.apply(null,arguments); })); - localfilesystem.init({settings:{coreNodesDir:moduleDir}}); + localfilesystem.init({coreNodesDir:moduleDir}); var nodeList = localfilesystem.getNodeFiles(); nodeList.should.have.a.property("node-red"); var nm = nodeList['node-red']; @@ -175,18 +175,7 @@ describe("red/nodes/registry/localfilesystem",function() { it("scans icon files in the resources tree",function(done) { var count = 0; localfilesystem.init({ - - // events:{emit:function(eventName,dir){ - // if (count === 0) { - // eventName.should.equal("node-icon-dir"); - // dir.name.should.equal("node-red"); - // dir.icons.should.be.an.Array(); - // count = 1; - // } else if (count === 1) { - // done(); - // } - // }}, - settings:{coreNodesDir:resourcesDir} + coreNodesDir: resourcesDir }); var list = localfilesystem.getNodeFiles(true); list.should.have.property("node-red"); @@ -201,22 +190,7 @@ describe("red/nodes/registry/localfilesystem",function() { it("scans icons dir in library",function(done) { var count = 0; localfilesystem.init({ - // - // events:{emit:function(eventName,dir){ - // eventName.should.equal("node-icon-dir"); - // if (count === 0) { - // dir.name.should.equal("node-red"); - // dir.icons.should.be.an.Array(); - // count = 1; - // } else if (count === 1) { - // dir.name.should.equal("Library"); - // dir.icons.should.be.an.Array(); - // dir.icons.length.should.equal(1); - // dir.icons[0].should.be.equal("test_icon.png"); - // done(); - // } - // }}, - settings:{userDir:userDir} + userDir: userDir }); var list = localfilesystem.getNodeFiles(true); list.should.have.property("node-red"); @@ -240,7 +214,7 @@ describe("red/nodes/registry/localfilesystem",function() { } return _join.apply(null,arguments); })); - localfilesystem.init({settings:{coreNodesDir:moduleDir}}); + localfilesystem.init({coreNodesDir:moduleDir}); var nodeModule = localfilesystem.getModuleFiles('TestNodeModule'); nodeModule.should.have.a.property('TestNodeModule'); nodeModule['TestNodeModule'].should.have.a.property('name','TestNodeModule'); @@ -266,7 +240,7 @@ describe("red/nodes/registry/localfilesystem",function() { } return _join.apply(null,arguments); })); - localfilesystem.init({settings:{coreNodesDir:moduleDir}}); + localfilesystem.init({coreNodesDir:moduleDir}); /*jshint immed: false */ (function(){ localfilesystem.getModuleFiles('WontExistModule'); @@ -286,14 +260,7 @@ describe("red/nodes/registry/localfilesystem",function() { return _join.apply(null,arguments); })); localfilesystem.init({ - - // events:{emit:function(eventName,dir){ - // eventName.should.equal("node-icon-dir"); - // dir.name.should.equal("TestNodeModule"); - // dir.icons.should.be.an.Array(); - // done(); - // }}, - settings:{coreNodesDir:moduleDir} + coreNodesDir: moduleDir }); var nodeModule = localfilesystem.getModuleFiles('TestNodeModule'); nodeModule.should.have.property("TestNodeModule"); diff --git a/test/unit/@node-red/registry/lib/registry_spec.js b/test/unit/@node-red/registry/lib/registry_spec.js index d493ed688..60b48938d 100644 --- a/test/unit/@node-red/registry/lib/registry_spec.js +++ b/test/unit/@node-red/registry/lib/registry_spec.js @@ -21,9 +21,7 @@ var path = require("path"); var NR_TEST_UTILS = require("nr-test-utils"); var typeRegistry = NR_TEST_UTILS.require("@node-red/registry/lib/registry"); -var EventEmitter = require('events'); - -var events = new EventEmitter(); +const { events } = NR_TEST_UTILS.require("@node-red/util"); describe("red/nodes/registry/registry",function() { @@ -84,7 +82,7 @@ describe("red/nodes/registry/registry",function() { describe('#init/load', function() { it('loads initial config', function(done) { - typeRegistry.init(settingsWithStorageAndInitialConfig,null,events); + typeRegistry.init(settingsWithStorageAndInitialConfig,null); typeRegistry.getNodeList().should.have.lengthOf(0); typeRegistry.load(); typeRegistry.getNodeList().should.have.lengthOf(1); @@ -121,7 +119,7 @@ describe("red/nodes/registry/registry",function() { }} }; var expected = JSON.parse('{"node-red":{"name":"node-red","nodes":{"sentiment":{"name":"sentiment","types":["sentiment"],"enabled":true,"module":"node-red"},"inject":{"name":"inject","types":["inject"],"enabled":true,"module":"node-red"}}},"testModule":{"name":"testModule","nodes":{"a-module.js":{"name":"a-module.js","types":["example"],"enabled":true,"module":"testModule"}}}}'); - typeRegistry.init(legacySettings,null,events); + typeRegistry.init(legacySettings,null); typeRegistry.load(); legacySettings.set.calledOnce.should.be.true(); legacySettings.set.args[0][1].should.eql(expected); @@ -133,7 +131,7 @@ describe("red/nodes/registry/registry",function() { describe.skip('#addNodeSet', function() { it('adds a node set for an unknown module', function() { - typeRegistry.init(settings,null,events); + typeRegistry.init(settings,null); typeRegistry.getNodeList().should.have.lengthOf(0); typeRegistry.getModuleList().should.eql({}); @@ -162,7 +160,7 @@ describe("red/nodes/registry/registry",function() { it('adds a node set to an existing module', function() { - typeRegistry.init(settings,null,events); + typeRegistry.init(settings,null); typeRegistry.getNodeList().should.have.lengthOf(0); typeRegistry.getModuleList().should.eql({}); @@ -191,7 +189,7 @@ describe("red/nodes/registry/registry",function() { }); it('doesnt add node set types if node set has an error', function() { - typeRegistry.init(settings,null,events); + typeRegistry.init(settings,null); typeRegistry.getNodeList().should.have.lengthOf(0); typeRegistry.getModuleList().should.eql({}); @@ -207,7 +205,7 @@ describe("red/nodes/registry/registry",function() { }); it('doesnt add node set if type already exists', function() { - typeRegistry.init(settings,null,events); + typeRegistry.init(settings,null); typeRegistry.getNodeList().should.have.lengthOf(0); typeRegistry.getModuleList().should.eql({}); @@ -241,7 +239,7 @@ describe("red/nodes/registry/registry",function() { describe("#enableNodeSet", function() { it('throws error if settings unavailable', function() { - typeRegistry.init(settings,null,events); + typeRegistry.init(settings,null); /*jshint immed: false */ (function(){ typeRegistry.enableNodeSet("test-module/test-name"); @@ -249,7 +247,7 @@ describe("red/nodes/registry/registry",function() { }); it('throws error if module unknown', function() { - typeRegistry.init(settingsWithStorageAndInitialConfig,null,events); + typeRegistry.init(settingsWithStorageAndInitialConfig,null); /*jshint immed: false */ (function(){ typeRegistry.enableNodeSet("test-module/unknown"); @@ -260,7 +258,7 @@ describe("red/nodes/registry/registry",function() { }); describe("#disableNodeSet", function() { it('throws error if settings unavailable', function() { - typeRegistry.init(settings,null,events); + typeRegistry.init(settings,null); /*jshint immed: false */ (function(){ typeRegistry.disableNodeSet("test-module/test-name"); @@ -268,7 +266,7 @@ describe("red/nodes/registry/registry",function() { }); it('throws error if module unknown', function() { - typeRegistry.init(settingsWithStorageAndInitialConfig,null,events); + typeRegistry.init(settingsWithStorageAndInitialConfig,null); /*jshint immed: false */ (function(){ typeRegistry.disableNodeSet("test-module/unknown"); @@ -279,7 +277,7 @@ describe("red/nodes/registry/registry",function() { describe('#getNodeConfig', function() { it('returns nothing for an unregistered type config', function(done) { - typeRegistry.init(settings,null,events); + typeRegistry.init(settings,null); var config = typeRegistry.getNodeConfig("imaginary-shark"); (config === null).should.be.true(); done(); @@ -288,7 +286,7 @@ describe("red/nodes/registry/registry",function() { describe('#saveNodeList',function() { it('rejects when settings unavailable',function(done) { - typeRegistry.init(stubSettings({},false,{}),null,events); + typeRegistry.init(stubSettings({},false,{}),null); typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {"test-name":{module:"test-module",name:"test-name",types:[]}}}); typeRegistry.saveNodeList().catch(function(err) { done(); @@ -296,7 +294,7 @@ describe("red/nodes/registry/registry",function() { }); it('saves the list',function(done) { var s = stubSettings({},true,{}); - typeRegistry.init(s,null,events); + typeRegistry.init(s,null); typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: { "test-name":testNodeSet1, @@ -325,7 +323,7 @@ describe("red/nodes/registry/registry",function() { describe('#removeModule',function() { it('throws error for unknown module', function() { var s = stubSettings({},true,{}); - typeRegistry.init(s,null,events); + typeRegistry.init(s,null); /*jshint immed: false */ (function(){ typeRegistry.removeModule("test-module/unknown"); @@ -333,7 +331,7 @@ describe("red/nodes/registry/registry",function() { }); it('throws error for unavaiable settings', function() { var s = stubSettings({},false,{}); - typeRegistry.init(s,null,events); + typeRegistry.init(s,null); /*jshint immed: false */ (function(){ typeRegistry.removeModule("test-module/unknown"); @@ -341,7 +339,7 @@ describe("red/nodes/registry/registry",function() { }); it('removes a known module', function() { var s = stubSettings({},true,{}); - typeRegistry.init(s,null,events); + typeRegistry.init(s,null); typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: { "test-name":testNodeSet1 }}); @@ -360,7 +358,7 @@ describe("red/nodes/registry/registry",function() { it('returns node config', function() { typeRegistry.init(settings,{ getNodeHelp: function(config) { return "HE"+config.name+"LP" } - },events); + }); typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: { "test-name":{ @@ -389,7 +387,7 @@ describe("red/nodes/registry/registry",function() { }); describe('#getModuleInfo', function() { it('returns module info', function() { - typeRegistry.init(settings,{},events); + typeRegistry.init(settings,{}); typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: { "test-name":{ id: "test-module/test-name", @@ -413,7 +411,7 @@ describe("red/nodes/registry/registry",function() { }); describe('#getNodeInfo', function() { it('returns node info', function() { - typeRegistry.init(settings,{},events); + typeRegistry.init(settings,{}); typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: { "test-name":{ id: "test-module/test-name", @@ -434,7 +432,7 @@ describe("red/nodes/registry/registry",function() { }); describe('#getFullNodeInfo', function() { it('returns node info', function() { - typeRegistry.init(settings,{},events); + typeRegistry.init(settings,{}); typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: { "test-name":{ id: "test-module/test-name", @@ -459,7 +457,7 @@ describe("red/nodes/registry/registry",function() { }); describe('#getNodeList', function() { it("returns a filtered list", function() { - typeRegistry.init(settings,{},events); + typeRegistry.init(settings,{}); typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: { "test-name":{ id: "test-module/test-name", @@ -526,7 +524,7 @@ describe("red/nodes/registry/registry",function() { it('returns a registered icon' , function() { var testIcon = path.resolve(__dirname+'/resources/userDir/lib/icons/'); - typeRegistry.init(settings,{},events); + typeRegistry.init(settings,{}); typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: { "test-name":{ id: "test-module/test-name", @@ -558,7 +556,7 @@ describe("red/nodes/registry/registry",function() { it('returns an icon list of registered node module', function() { var testIcon = path.resolve(__dirname+'/resources/userDir/lib/icons/'); - typeRegistry.init(settings,{},events); + typeRegistry.init(settings,{}); typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: { "test-name":{ id: "test-module/test-name", diff --git a/test/unit/@node-red/runtime/lib/api/comms_spec.js b/test/unit/@node-red/runtime/lib/api/comms_spec.js index e1359cb7d..2f2e35a54 100644 --- a/test/unit/@node-red/runtime/lib/api/comms_spec.js +++ b/test/unit/@node-red/runtime/lib/api/comms_spec.js @@ -19,6 +19,7 @@ var sinon = require("sinon"); var NR_TEST_UTILS = require("nr-test-utils"); var comms = NR_TEST_UTILS.require("@node-red/runtime/lib/api/comms"); +var events = NR_TEST_UTILS.require("@node-red/util/lib/events"); describe("runtime-api/comms", function() { describe("listens for events", function() { @@ -30,21 +31,19 @@ describe("runtime-api/comms", function() { } var eventHandlers = {}; before(function(done) { + sinon.stub(events,"removeListener", function() {}) + sinon.stub(events,"on", function(evt,handler) { eventHandlers[evt] = handler }) comms.init({ log: { trace: function(){} - }, - events: { - removeListener: function() {}, - on: function(evt,handler) { - eventHandlers[evt] = handler; - } } }) comms.addConnection({client: clientConnection}).then(done); }) after(function(done) { comms.removeConnection({client: clientConnection}).then(done); + events.removeListener.restore(); + events.on.restore(); }) afterEach(function() { messages = []; @@ -98,18 +97,18 @@ describe("runtime-api/comms", function() { } } before(function() { + sinon.stub(events,"removeListener", function() {}) + sinon.stub(events,"on", function(evt,handler) { eventHandlers[evt] = handler }) comms.init({ log: { trace: function(){} - }, - events: { - removeListener: function() {}, - on: function(evt,handler) { - eventHandlers[evt] = handler; - } } }) }) + after(function() { + events.removeListener.restore(); + events.on.restore(); + }) afterEach(function(done) { comms.removeConnection({client: clientConnection1}).then(function() { comms.removeConnection({client: clientConnection2}).then(done); @@ -178,18 +177,18 @@ describe("runtime-api/comms", function() { } var eventHandlers = {}; before(function() { + sinon.stub(events,"removeListener", function() {}) + sinon.stub(events,"on", function(evt,handler) { eventHandlers[evt] = handler }) comms.init({ log: { trace: function(){} - }, - events: { - removeListener: function() {}, - on: function(evt,handler) { - eventHandlers[evt] = handler; - } } }) }) + after(function() { + events.removeListener.restore(); + events.on.restore(); + }) afterEach(function(done) { messages = []; comms.removeConnection({client: clientConnection}).then(done); diff --git a/test/unit/@node-red/runtime/lib/flows/index_spec.js b/test/unit/@node-red/runtime/lib/flows/index_spec.js index 6f6066100..e230d2407 100644 --- a/test/unit/@node-red/runtime/lib/flows/index_spec.js +++ b/test/unit/@node-red/runtime/lib/flows/index_spec.js @@ -22,7 +22,7 @@ var NR_TEST_UTILS = require("nr-test-utils"); var flows = NR_TEST_UTILS.require("@node-red/runtime/lib/flows"); var RedNode = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/Node"); var RED = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes"); -var events = NR_TEST_UTILS.require("@node-red/runtime/lib/events"); +var events = NR_TEST_UTILS.require("@node-red/util/lib/events"); var credentials = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/credentials"); var typeRegistry = NR_TEST_UTILS.require("@node-red/registry") var Flow = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/Flow"); diff --git a/test/unit/@node-red/runtime/lib/index_spec.js b/test/unit/@node-red/runtime/lib/index_spec.js index f6a506de1..60bdc286b 100644 --- a/test/unit/@node-red/runtime/lib/index_spec.js +++ b/test/unit/@node-red/runtime/lib/index_spec.js @@ -28,6 +28,7 @@ var settings = NR_TEST_UTILS.require("@node-red/runtime/lib/settings"); var util = NR_TEST_UTILS.require("@node-red/util"); var log = NR_TEST_UTILS.require("@node-red/util").log; +var i18n = NR_TEST_UTILS.require("@node-red/util").i18n; describe("runtime", function() { afterEach(function() { @@ -43,43 +44,45 @@ describe("runtime", function() { delete process.env.NODE_RED_HOME; }); function mockUtil(metrics) { - - return { - log:{ - log: sinon.stub(), - warn: sinon.stub(), - info: sinon.stub(), - trace: sinon.stub(), - metric: sinon.stub().returns(!!metrics), - _: function() { return "abc"} - }, - i18n: { - registerMessageCatalog: function(){ - return Promise.resolve(); - } - } - } + sinon.stub(log,"log",function(){}) + sinon.stub(log,"warn",function(){}) + sinon.stub(log,"info",function(){}) + sinon.stub(log,"trace",function(){}) + sinon.stub(log,"metric",function(){ return !!metrics }) + sinon.stub(log,"_",function(){ return "abc"}) + sinon.stub(i18n,"registerMessageCatalog",function(){ return Promise.resolve()}) + } + function unmockUtil() { + log.log.restore && log.log.restore(); + log.warn.restore && log.warn.restore(); + log.info.restore && log.info.restore(); + log.trace.restore && log.trace.restore(); + log.metric.restore && log.metric.restore(); + log._.restore && log._.restore(); + i18n.registerMessageCatalog.restore && i18n.registerMessageCatalog.restore(); } describe("init", function() { beforeEach(function() { sinon.stub(log,"init",function() {}); sinon.stub(settings,"init",function() {}); sinon.stub(redNodes,"init",function() {}) + mockUtil(); }); afterEach(function() { log.init.restore(); settings.init.restore(); redNodes.init.restore(); + unmockUtil(); }) it("initialises components", function() { - runtime.init({testSettings: true, httpAdminRoot:"/"},mockUtil()); + runtime.init({testSettings: true, httpAdminRoot:"/"}); settings.init.called.should.be.true(); redNodes.init.called.should.be.true(); }); it("returns version", function() { - runtime.init({testSettings: true, httpAdminRoot:"/"},mockUtil()); + runtime.init({testSettings: true, httpAdminRoot:"/"}); return runtime.version().then(version => { /^\d+\.\d+\.\d+(-.*)?$/.test(version).should.be.true(); }); @@ -98,7 +101,6 @@ describe("runtime", function() { var redNodesLoadFlows; var redNodesStartFlows; var redNodesLoadContextsPlugin; - var i18nRegisterMessageCatalog; beforeEach(function() { storageInit = sinon.stub(storage,"init",function(settings) {return Promise.resolve();}); @@ -108,7 +110,7 @@ describe("runtime", function() { redNodesLoadFlows = sinon.stub(redNodes,"loadFlows",function() {return Promise.resolve()}); redNodesStartFlows = sinon.stub(redNodes,"startFlows",function() {}); redNodesLoadContextsPlugin = sinon.stub(redNodes,"loadContextsPlugin",function() {return Promise.resolve()}); - i18nRegisterMessageCatalog = sinon.stub(util.i18n,"registerMessageCatalog",function() {return Promise.resolve()}); + mockUtil(); }); afterEach(function() { storageInit.restore(); @@ -119,7 +121,7 @@ describe("runtime", function() { redNodesLoadFlows.restore(); redNodesStartFlows.restore(); redNodesLoadContextsPlugin.restore(); - i18nRegisterMessageCatalog.restore(); + unmockUtil(); }); it("reports errored/missing modules",function(done) { redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function(cb) { @@ -128,8 +130,7 @@ describe("runtime", function() { { module:"module",enabled:true,loaded:false,types:["typeA","typeB"]} // missing ].filter(cb); }); - var util = mockUtil(); - runtime.init({testSettings: true, httpAdminRoot:"/", load:function() { return Promise.resolve();}},util); + runtime.init({testSettings: true, httpAdminRoot:"/", load:function() { return Promise.resolve();}}); // sinon.stub(console,"log"); runtime.start().then(function() { // console.log.restore(); @@ -139,9 +140,9 @@ describe("runtime", function() { redNodesLoad.calledOnce.should.be.true(); redNodesLoadFlows.calledOnce.should.be.true(); - util.log.warn.calledWithMatch("Failed to register 1 node type"); - util.log.warn.calledWithMatch("Missing node modules"); - util.log.warn.calledWithMatch(" - module: typeA, typeB"); + log.warn.calledWithMatch("Failed to register 1 node type"); + log.warn.calledWithMatch("Missing node modules"); + log.warn.calledWithMatch(" - module: typeA, typeB"); redNodesCleanModuleList.calledOnce.should.be.true(); done(); } catch(err) { @@ -159,16 +160,15 @@ describe("runtime", function() { ].filter(cb); }); var serverInstallModule = sinon.stub(redNodes,"installModule",function(name) { return Promise.resolve({nodes:[]});}); - var util = mockUtil(); - runtime.init({testSettings: true, autoInstallModules:true, httpAdminRoot:"/", load:function() { return Promise.resolve();}},util); + runtime.init({testSettings: true, autoInstallModules:true, httpAdminRoot:"/", load:function() { return Promise.resolve();}}); sinon.stub(console,"log"); runtime.start().then(function() { console.log.restore(); try { - util.log.warn.calledWithMatch("Failed to register 2 node types"); - util.log.warn.calledWithMatch("Missing node modules"); - util.log.warn.calledWithMatch(" - module: typeA, typeB"); - util.log.warn.calledWithMatch(" - node-red: typeC, typeD"); + log.warn.calledWithMatch("Failed to register 2 node types"); + log.warn.calledWithMatch("Missing node modules"); + log.warn.calledWithMatch(" - module: typeA, typeB"); + log.warn.calledWithMatch(" - node-red: typeC, typeD"); redNodesCleanModuleList.calledOnce.should.be.false(); serverInstallModule.calledOnce.should.be.true(); serverInstallModule.calledWithMatch("module"); @@ -186,14 +186,13 @@ describe("runtime", function() { { err:"errored",name:"errName" } // error ].filter(cb); }); - var util = mockUtil(); - runtime.init({testSettings: true, verbose:true, httpAdminRoot:"/", load:function() { return Promise.resolve();}},util); + runtime.init({testSettings: true, verbose:true, httpAdminRoot:"/", load:function() { return Promise.resolve();}}); sinon.stub(console,"log"); runtime.start().then(function() { console.log.restore(); try { - util.log.warn.neverCalledWithMatch("Failed to register 1 node type"); - util.log.warn.calledWithMatch("[errName] errored"); + log.warn.neverCalledWithMatch("Failed to register 1 node type"); + log.warn.calledWithMatch("[errName] errored"); done(); } catch(err) { done(err); @@ -204,21 +203,21 @@ describe("runtime", function() { it("reports runtime metrics",function(done) { var stopFlows = sinon.stub(redNodes,"stopFlows",function() { return Promise.resolve();} ); redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []}); - var util = mockUtil(true); + unmockUtil(); + mockUtil(true); runtime.init( {testSettings: true, runtimeMetricInterval:200, httpAdminRoot:"/", load:function() { return Promise.resolve();}}, {}, - undefined, - util); + undefined); // sinon.stub(console,"log"); runtime.start().then(function() { // console.log.restore(); setTimeout(function() { try { - util.log.log.args.should.have.lengthOf(3); - util.log.log.args[0][0].should.have.property("event","runtime.memory.rss"); - util.log.log.args[1][0].should.have.property("event","runtime.memory.heapTotal"); - util.log.log.args[2][0].should.have.property("event","runtime.memory.heapUsed"); + log.log.args.should.have.lengthOf(3); + log.log.args[0][0].should.have.property("event","runtime.memory.rss"); + log.log.args[1][0].should.have.property("event","runtime.memory.heapTotal"); + log.log.args[2][0].should.have.property("event","runtime.memory.heapUsed"); done(); } catch(err) { done(err); diff --git a/test/unit/@node-red/runtime/lib/events_spec.js b/test/unit/@node-red/util/lib/events_spec.js similarity index 88% rename from test/unit/@node-red/runtime/lib/events_spec.js rename to test/unit/@node-red/util/lib/events_spec.js index 09b47693d..09f5d2ae0 100644 --- a/test/unit/@node-red/runtime/lib/events_spec.js +++ b/test/unit/@node-red/util/lib/events_spec.js @@ -17,9 +17,9 @@ var should = require("should"); var NR_TEST_UTILS = require("nr-test-utils"); -describe("runtime/events", function() { +describe("@node-red/util/events", function() { it('can be required without errors', function() { - NR_TEST_UTILS.require("@node-red/runtime/lib/events"); + NR_TEST_UTILS.require("@node-red/util/lib/events"); }); it.skip('more tests needed', function(){}) }); diff --git a/test/unit/@node-red/runtime/lib/exec_spec.js b/test/unit/@node-red/util/lib/exec_spec.js similarity index 90% rename from test/unit/@node-red/runtime/lib/exec_spec.js rename to test/unit/@node-red/util/lib/exec_spec.js index 1e37b7cc1..887938d16 100644 --- a/test/unit/@node-red/runtime/lib/exec_spec.js +++ b/test/unit/@node-red/util/lib/exec_spec.js @@ -16,30 +16,31 @@ var should = require("should"); var sinon = require("sinon"); var path = require("path"); -var events = require("events"); +var EventEmitter = require("events").EventEmitter; var child_process = require('child_process'); var NR_TEST_UTILS = require("nr-test-utils"); -var exec = NR_TEST_UTILS.require("@node-red/runtime/lib/exec"); +var events = NR_TEST_UTILS.require("@node-red/util/lib/events"); +var exec = NR_TEST_UTILS.require("@node-red/util/lib/exec"); describe("runtime/exec", function() { var logEvents; var mockProcess; + const eventLogHandler = function(ev) { + logEvents.push(ev); + } beforeEach(function() { - var logEventHandler = new events.EventEmitter(); - logEvents = []; - logEventHandler.on('event-log', function(ev) { - logEvents.push(ev); - }); - exec.init({events:logEventHandler}); - mockProcess = new events.EventEmitter(); - mockProcess.stdout = new events.EventEmitter(); - mockProcess.stderr = new events.EventEmitter(); + logEvents = []; + events.on("event-log", eventLogHandler); + + mockProcess = new EventEmitter(); + mockProcess.stdout = new EventEmitter(); + mockProcess.stderr = new EventEmitter(); sinon.stub(child_process,'spawn',function(command,args,options) { mockProcess._args = {command,args,options}; return mockProcess; @@ -47,6 +48,7 @@ describe("runtime/exec", function() { }); afterEach(function() { + events.removeListener("event-log", eventLogHandler); if (child_process.spawn.restore) { child_process.spawn.restore(); } From fca21ac1268f47f99aede1c83748444be2e0dbda Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 2 Dec 2020 13:14:39 +0000 Subject: [PATCH 034/114] Rename paletteEditorEnabled to installerEnabled --- .../node_modules/@node-red/registry/lib/index.js | 2 +- .../@node-red/registry/lib/installer.js | 14 +++++++------- .../@node-red/runtime/lib/api/settings.js | 2 +- .../@node-red/runtime/lib/nodes/index.js | 2 +- .../@node-red/runtime/lib/api/settings_spec.js | 14 +++++++------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/node_modules/@node-red/registry/lib/index.js b/packages/node_modules/@node-red/registry/lib/index.js index ebcde8cea..3632f40cf 100644 --- a/packages/node_modules/@node-red/registry/lib/index.js +++ b/packages/node_modules/@node-red/registry/lib/index.js @@ -271,7 +271,7 @@ module.exports = { * @function * @memberof @node-red/registry */ - paletteEditorEnabled: installer.paletteEditorEnabled, + installerEnabled: installer.installerEnabled, /** * Get a list of all example flows provided by nodes in the registry. diff --git a/packages/node_modules/@node-red/registry/lib/installer.js b/packages/node_modules/@node-red/registry/lib/installer.js index 9735aa8a1..0c3359382 100644 --- a/packages/node_modules/@node-red/registry/lib/installer.js +++ b/packages/node_modules/@node-red/registry/lib/installer.js @@ -25,7 +25,7 @@ var library = require("./library"); const {exec,log,events} = require("@node-red/util"); var child_process = require('child_process'); var npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; -var paletteEditorEnabled = false; +var installerEnabled = false; var settings; const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/; @@ -342,20 +342,20 @@ function checkPrereq() { settings.editorTheme.palette.editable === false ) { log.info(log._("server.palette-editor.disabled")); - paletteEditorEnabled = false; + installerEnabled = false; return Promise.resolve(); } else { return new Promise(resolve => { child_process.execFile(npmCommand,['-v'],function(err,stdout) { if (err) { log.info(log._("server.palette-editor.npm-not-found")); - paletteEditorEnabled = false; + installerEnabled = false; } else { if (parseInt(stdout.split(".")[0]) < 3) { log.info(log._("server.palette-editor.npm-too-old")); - paletteEditorEnabled = false; + installerEnabled = false; } else { - paletteEditorEnabled = true; + installerEnabled = true; } } resolve(); @@ -369,7 +369,7 @@ module.exports = { checkPrereq: checkPrereq, installModule: installModule, uninstallModule: uninstallModule, - paletteEditorEnabled: function() { - return paletteEditorEnabled + installerEnabled: function() { + return installerEnabled } } diff --git a/packages/node_modules/@node-red/runtime/lib/api/settings.js b/packages/node_modules/@node-red/runtime/lib/api/settings.js index b34e9a27c..067a8e83a 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/api/settings.js @@ -90,7 +90,7 @@ var api = module.exports = { safeSettings.flowFilePretty = runtime.settings.flowFilePretty; } - if (!runtime.nodes.paletteEditorEnabled()) { + if (!runtime.nodes.installerEnabled()) { safeSettings.editorTheme = safeSettings.editorTheme || {}; safeSettings.editorTheme.palette = safeSettings.editorTheme.palette || {}; safeSettings.editorTheme.palette.editable = false; diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/index.js index 858274be2..7c6b6085e 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/index.js @@ -187,7 +187,7 @@ module.exports = { getContext: context.get, - paletteEditorEnabled: registry.paletteEditorEnabled, + installerEnabled: registry.installerEnabled, installModule: installModule, uninstallModule: uninstallModule, diff --git a/test/unit/@node-red/runtime/lib/api/settings_spec.js b/test/unit/@node-red/runtime/lib/api/settings_spec.js index 02bbf48e7..d01235604 100644 --- a/test/unit/@node-red/runtime/lib/api/settings_spec.js +++ b/test/unit/@node-red/runtime/lib/api/settings_spec.js @@ -48,7 +48,7 @@ describe("runtime-api/settings", function() { }, nodes: { listContextStores: () => { return {stores:["file","memory"], default: "file"} }, - paletteEditorEnabled: () => false, + installerEnabled: () => false, getCredentialKeyType: () => "test-key-type" }, storage: {} @@ -79,7 +79,7 @@ describe("runtime-api/settings", function() { }, nodes: { listContextStores: () => { return {stores:["file","memory"], default: "file"} }, - paletteEditorEnabled: () => false, + installerEnabled: () => false, getCredentialKeyType: () => "test-key-type" }, storage: {} @@ -115,7 +115,7 @@ describe("runtime-api/settings", function() { }, nodes: { listContextStores: () => { return {stores:["file","memory"], default: "file"} }, - paletteEditorEnabled: () => false, + installerEnabled: () => false, getCredentialKeyType: () => "test-key-type" }, storage: { @@ -166,7 +166,7 @@ describe("runtime-api/settings", function() { }, nodes: { listContextStores: () => { return {stores:["file","memory"], default: "file"} }, - paletteEditorEnabled: () => false, + installerEnabled: () => false, getCredentialKeyType: () => "test-key-type" }, storage: { @@ -207,7 +207,7 @@ describe("runtime-api/settings", function() { }, nodes: { listContextStores: () => { return {stores:["file","memory"], default: "file"} }, - paletteEditorEnabled: () => false, + installerEnabled: () => false, getCredentialKeyType: () => "test-key-type" }, storage: { @@ -252,7 +252,7 @@ describe("runtime-api/settings", function() { }, nodes: { listContextStores: () => { return {stores:["file","memory"], default: "file"} }, - paletteEditorEnabled: () => false, + installerEnabled: () => false, getCredentialKeyType: () => "test-key-type" }, storage: { @@ -632,7 +632,7 @@ describe("api/editor/sshkeys", function() { }, events:{on:function(){},removeListener:function(){}}, isStarted: function() { return isStarted; }, - nodes: {paletteEditorEnabled: function() { return false }} + nodes: {installerEnabled: function() { return false }} }; before(function() { From fc7967d455a75d5eea57932a26c0fbf4c87de551 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 3 Dec 2020 11:24:28 +0000 Subject: [PATCH 035/114] Fix missing promise on setUserSettings --- packages/node_modules/@node-red/runtime/lib/settings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/runtime/lib/settings.js b/packages/node_modules/@node-red/runtime/lib/settings.js index e17600305..989b83d9b 100644 --- a/packages/node_modules/@node-red/runtime/lib/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/settings.js @@ -179,6 +179,7 @@ var persistentSettings = { userSettings[username] = settings; try { assert.deepEqual(current,settings); + return Promise.resolve(); } catch(err) { globalSettings.users = userSettings; return storage.saveSettings(clone(globalSettings)); From 4df27d4b0bcf15d0a5326d49a30e8c3faa2e9792 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 7 Dec 2020 14:04:42 +0000 Subject: [PATCH 036/114] Fix typos in parser examples --- ...arse CSV with default column name as message sequence.json | 4 ++-- .../csv/02 - Parse CSV with default column name as array.json | 4 ++-- ...se CSV with specified column name as message sequence.json | 2 +- ...CSV with column name in first row as message sequence.json | 2 +- ...n extracedt sequence of HTML element using join node.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/examples/parser/csv/01 - Parse CSV with default column name as message sequence.json b/packages/node_modules/@node-red/nodes/examples/parser/csv/01 - Parse CSV with default column name as message sequence.json index bcf37bed8..ed75294b1 100644 --- a/packages/node_modules/@node-red/nodes/examples/parser/csv/01 - Parse CSV with default column name as message sequence.json +++ b/packages/node_modules/@node-red/nodes/examples/parser/csv/01 - Parse CSV with default column name as message sequence.json @@ -90,8 +90,8 @@ "id": "783cfaa6.52fbe4", "type": "comment", "z": "4b63452d.672afc", - "name": "Partse CSV with default column name as messages", - "info": "CSV node can parse input CSV data.\nParsed CSV record can be send as a message sequence.\nEach message payload point to an object with `col`*N* as a key and CSV value as a value.\n", + "name": "Parse CSV with default column name as messages", + "info": "CSV node can parse input CSV data.\nParsed CSV record can be sent as a message sequence.\nEach message payload points to an object with `col`*N* as a key and CSV value as a value.\n", "x": 330, "y": 120, "wires": [] diff --git a/packages/node_modules/@node-red/nodes/examples/parser/csv/02 - Parse CSV with default column name as array.json b/packages/node_modules/@node-red/nodes/examples/parser/csv/02 - Parse CSV with default column name as array.json index bfaab885c..85a03e486 100644 --- a/packages/node_modules/@node-red/nodes/examples/parser/csv/02 - Parse CSV with default column name as array.json +++ b/packages/node_modules/@node-red/nodes/examples/parser/csv/02 - Parse CSV with default column name as array.json @@ -90,8 +90,8 @@ "id": "187f4ab3.4c9ab5", "type": "comment", "z": "4b63452d.672afc", - "name": "Partse CSV with default column name as array", - "info": "CSV node can send single message with array of parsed CSV records.\nEach element of the array consists of objects with key-value pair.", + "name": "Parse CSV with default column name as array", + "info": "CSV node can send a single message with array of parsed CSV records.\nEach element of the array consists of objects with key-value pair.", "x": 320, "y": 300, "wires": [] diff --git a/packages/node_modules/@node-red/nodes/examples/parser/csv/03 - Parse CSV with specified column name as message sequence.json b/packages/node_modules/@node-red/nodes/examples/parser/csv/03 - Parse CSV with specified column name as message sequence.json index abb1fccda..327a25313 100644 --- a/packages/node_modules/@node-red/nodes/examples/parser/csv/03 - Parse CSV with specified column name as message sequence.json +++ b/packages/node_modules/@node-red/nodes/examples/parser/csv/03 - Parse CSV with specified column name as message sequence.json @@ -90,7 +90,7 @@ "id": "aaa1ee8f.21e2c", "type": "comment", "z": "4b63452d.672afc", - "name": "Partse CSV with specified column name as messages", + "name": "Parse CSV with specified column name as messages", "info": "CSV node can specify column name of parsed objects in its settings panel.", "x": 340, "y": 500, diff --git a/packages/node_modules/@node-red/nodes/examples/parser/csv/04 - Parse CSV with column name in first row as message sequence.json b/packages/node_modules/@node-red/nodes/examples/parser/csv/04 - Parse CSV with column name in first row as message sequence.json index 580849567..e287b57e7 100644 --- a/packages/node_modules/@node-red/nodes/examples/parser/csv/04 - Parse CSV with column name in first row as message sequence.json +++ b/packages/node_modules/@node-red/nodes/examples/parser/csv/04 - Parse CSV with column name in first row as message sequence.json @@ -90,7 +90,7 @@ "id": "85091361.85644", "type": "comment", "z": "4b63452d.672afc", - "name": "Partse CSV with column name in first row as messages", + "name": "Parse CSV with column name in first row as messages", "info": "CSV node can use first row of input CSV text as a column name of each record object.\n", "x": 340, "y": 680, diff --git a/packages/node_modules/@node-red/nodes/examples/parser/html/04 - Join extracedt sequence of HTML element using join node.json b/packages/node_modules/@node-red/nodes/examples/parser/html/04 - Join extracedt sequence of HTML element using join node.json index 919953284..6a08486b9 100644 --- a/packages/node_modules/@node-red/nodes/examples/parser/html/04 - Join extracedt sequence of HTML element using join node.json +++ b/packages/node_modules/@node-red/nodes/examples/parser/html/04 - Join extracedt sequence of HTML element using join node.json @@ -51,7 +51,7 @@ "id": "a57d35d0.8aa538", "type": "comment", "z": "4b63452d.672afc", - "name": "Join extracedt sequence of HTML element using join node", + "name": "Join extracted sequence of HTML element using join node", "info": "Message sequence extracted by HTML node can be combined using join node.", "x": 310, "y": 700, From be828af3e2524fb40d9087431d3dc41a4ef291ce Mon Sep 17 00:00:00 2001 From: johnwang71 Date: Mon, 14 Dec 2020 18:18:50 +0800 Subject: [PATCH 037/114] Fix bug: Crash & quit while handling exception with undefine msg.error. i.e. flow with 3 nodes, http-in, delay 5-10s, http-out; client with 3s timeout request the flow; TypeError: Cannot read property 'hasOwnProperty' of undefined\r at Flow.handleError (/usr/src/node-red/node_modules/@node-red/runtime/lib/flows/Flow.js:474:27) --- packages/node_modules/@node-red/runtime/lib/flows/Flow.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js index a7e00bfbc..325639f64 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js @@ -470,8 +470,8 @@ class Flow { } // console.log("HE",logMessage); var count = 1; - if (msg && msg.hasOwnProperty("error") && msg.error !== null) { - if (msg.error.hasOwnProperty("source") && msg.error.source !== null) { + if (msg && msg.hasOwnProperty("error") && msg.error) { + if (msg.error.hasOwnProperty("source") && msg.error.source) { if (msg.error.source.id === node.id) { count = msg.error.source.count+1; if (count === 10) { From 496b5a092fc6fcdda51d783fa3c439f4dda41275 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 15 Dec 2020 17:20:22 +0000 Subject: [PATCH 038/114] Ensure subflow credential objects exist Fixes #2783 --- packages/node_modules/@node-red/runtime/lib/flows/Subflow.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js index 78ec9ee75..d8514dac4 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js @@ -117,8 +117,8 @@ class Subflow extends Flow { this.node_map = node_map; this.path = parent.path+"/"+(subflowInstance._alias||subflowInstance.id); - this.templateCredentials = credentials.get(subflowDef.id); - this.instanceCredentials = credentials.get(this.id); + this.templateCredentials = credentials.get(subflowDef.id) || {}; + this.instanceCredentials = credentials.get(this.id) || {}; var env = []; if (this.subflowDef.env) { From 55e6c6e01adf0afc71ec1601be727f21a9685c51 Mon Sep 17 00:00:00 2001 From: aaronmyatt Date: Wed, 16 Dec 2020 21:53:52 +0800 Subject: [PATCH 039/114] adds tests for editor-api.start() --- .../@node-red/editor-api/lib/index_spec.js | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/unit/@node-red/editor-api/lib/index_spec.js b/test/unit/@node-red/editor-api/lib/index_spec.js index 2f84030aa..28928181b 100644 --- a/test/unit/@node-red/editor-api/lib/index_spec.js +++ b/test/unit/@node-red/editor-api/lib/index_spec.js @@ -156,4 +156,27 @@ describe("api/index", function() { }) }); + describe('editor start', function (done) { + + it('cannot be started when editor is disabled', function (done) { + const stub = sinon.stub(apiEditor, 'start', function () { + return Promise.resolve(true); + }); + api.init({ httpAdminRoot: true, disableEditor: true }, {}, {}, {}); + should(api.start()).resolvedWith(true); + stub.restore(); + done(); + }); + + it('can be started when editor enabled', function (done) { + const stub = sinon.stub(apiEditor, 'start'); + api.init({ httpAdminRoot: true, disableEditor: false }, {}, {}, {}); + api.start(); + should(stub.called).be.true(); + stub.restore(); + done(); + }); + + }); + }); From c433f736a5b7d3182f3a538a129fea2f6b4350d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=B6dinger?= Date: Thu, 17 Dec 2020 09:25:35 +0100 Subject: [PATCH 040/114] Improvements to DE translation (#2192) * Gitter->Raster inconsistency * Set to -> Festlegen bei // Setzen als --- .../@node-red/editor-client/locales/de/editor.json | 2 +- .../node_modules/@node-red/nodes/locales/de/messages.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/de/editor.json b/packages/node_modules/@node-red/editor-client/locales/de/editor.json index 0c8dd42eb..7c408d742 100755 --- a/packages/node_modules/@node-red/editor-client/locales/de/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/de/editor.json @@ -32,7 +32,7 @@ "label" : { "view" : { "view" : "Ansicht", - "grid" : "Gitter", + "grid" : "Raster", "showGrid" : "Raster anzeigen", "snapGrid" : "Am Raster ausrichten", "gridSize" : "Rastergröße", 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 7d9bd3523..fe1e1dfc1 100755 --- a/packages/node_modules/@node-red/nodes/locales/de/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/de/messages.json @@ -583,11 +583,11 @@ "regex" : "Reguläre Ausdrücke verwenden" }, "action" : { - "set" : "Festlegen", + "set" : "Setze", "change" : "Ändern", "delete" : "Löschen", "move" : "Bewegen", - "to" : "bis", + "to" : "auf", "search" : "Suchen nach", "replace" : "Ersetzen durch" }, From 34b27f2e6830e12cd980d52e908882a95a451691 Mon Sep 17 00:00:00 2001 From: Kevin Godell Date: Tue, 22 Dec 2020 16:28:33 -0600 Subject: [PATCH 041/114] allow for adding an array of middleware functions --- packages/node_modules/@node-red/editor-api/lib/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/index.js b/packages/node_modules/@node-red/editor-api/lib/index.js index 972e8daa6..f66e9094b 100644 --- a/packages/node_modules/@node-red/editor-api/lib/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/index.js @@ -59,8 +59,8 @@ function init(settings,_server,storage,runtimeAPI) { adminApp.use(corsHandler); if (settings.httpAdminMiddleware) { - if (typeof settings.httpAdminMiddleware === "function") { - adminApp.use(settings.httpAdminMiddleware) + if (typeof settings.httpAdminMiddleware === "function" || Array.isArray(settings.httpAdminMiddleware)) { + adminApp.use(settings.httpAdminMiddleware); } } From 79b10ed18ae0a079275f57db10901dfa37e52fc1 Mon Sep 17 00:00:00 2001 From: Kevin Godell Date: Tue, 22 Dec 2020 16:30:38 -0600 Subject: [PATCH 042/114] allow for adding an array of middleware functions --- 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 d1e2941b4..26234b737 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 @@ -210,7 +210,7 @@ module.exports = function(RED) { var httpMiddleware = function(req,res,next) { next(); } if (RED.settings.httpNodeMiddleware) { - if (typeof RED.settings.httpNodeMiddleware === "function") { + if (typeof RED.settings.httpNodeMiddleware === "function" || Array.isArray(RED.settings.httpNodeMiddleware)) { httpMiddleware = RED.settings.httpNodeMiddleware; } } From 3151502a3f458b1891b69a8fcc878a60d956679f Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 23 Dec 2020 22:05:58 +0000 Subject: [PATCH 043/114] Deprecate autoInstallModules for externalModules.autoInstall --- .../@node-red/registry/lib/index.js | 3 +- .../@node-red/runtime/lib/index.js | 32 ++++++++++++++----- .../runtime/locales/en-US/runtime.json | 1 + 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/packages/node_modules/@node-red/registry/lib/index.js b/packages/node_modules/@node-red/registry/lib/index.js index 3632f40cf..426dfcb61 100644 --- a/packages/node_modules/@node-red/registry/lib/index.js +++ b/packages/node_modules/@node-red/registry/lib/index.js @@ -254,7 +254,8 @@ module.exports = { * Update to internal list of available modules based on what has been actually * loaded. * - * The `autoInstallModules` runtime option means the runtime may try to install + * The `externalModules.autoInstall` (previously `autoInstallModules`) + * runtime option means the runtime may try to install * missing modules after the initial load is complete. If that flag is not set * this function is used to remove the modules from the registry's saved list. * @function diff --git a/packages/node_modules/@node-red/runtime/lib/index.js b/packages/node_modules/@node-red/runtime/lib/index.js index dd1594467..2dd6db6c8 100644 --- a/packages/node_modules/@node-red/runtime/lib/index.js +++ b/packages/node_modules/@node-red/runtime/lib/index.js @@ -123,7 +123,15 @@ function start() { } log.info(os.type()+" "+os.release()+" "+os.arch()+" "+os.endianness()); return redNodes.load().then(function() { - + let autoInstallModules = false; + if (settings.hasOwnProperty('autoInstallModules')) { + log.warn(log._("runtime.deprecatedOption",{old:"autoInstallModules", new:"externalModules.autoInstall"})); + autoInstallModules = true; + } + if (settings.externalModules) { + // autoInstallModules = autoInstall enabled && (no palette setting || palette install not disabled) + autoInstallModules = settings.externalModules.autoInstall && (!settings.externalModules.palette || settings.externalModules.palette.allowInstall !== false) ; + } var i; var nodeErrors = redNodes.getNodeList(function(n) { return n.err!=null;}); var nodeMissing = redNodes.getNodeList(function(n) { return n.module && n.enabled && !n.loaded && !n.err;}); @@ -156,12 +164,12 @@ function start() { for (i in missingModules) { if (missingModules.hasOwnProperty(i)) { log.warn(" - "+i+" ("+missingModules[i].version+"): "+missingModules[i].types.join(", ")); - if (settings.autoInstallModules && i != "node-red") { + if (autoInstallModules && i != "node-red") { installingModules.push({id:i,version:missingModules[i].version}); } } } - if (!settings.autoInstallModules) { + if (!autoInstallModules) { log.info(log._("server.removing-modules")); redNodes.cleanModuleList(); } else if (installingModules.length > 0) { @@ -186,11 +194,19 @@ function start() { var reinstallAttempts = 0; var reinstallTimeout; function reinstallModules(moduleList) { - var promises = []; - var reinstallList = []; - + const promises = []; + const reinstallList = []; + const installRetry = 30000; + if (settings.hasOwnProperty('autoInstallModulesRetry')) { + log.warn(log._("runtime.deprecatedOption",{old:"autoInstallModulesRetry", new:"externalModules.autoInstallRetry"})); + installRetry = settings.autoInstallModulesRetry; + } + if (settings.externalModules && settings.externalModules.hasOwnProperty('autoInstallRetry')) { + installRetry = settings.externalModules.autoInstallRetry * 1000; + } + externalModules.autoInstallRetry for (var i=0;i { events.emit("runtime-event",{id:"node/added",retain:false,payload:m.nodes}); @@ -204,7 +220,7 @@ function reinstallModules(moduleList) { if (reinstallList.length > 0) { reinstallAttempts++; // First 5 at 1x timeout, next 5 at 2x, next 5 at 4x, then 8x - var timeout = (settings.autoInstallModulesRetry||30000) * Math.pow(2,Math.min(Math.floor(reinstallAttempts/5),3)); + var timeout = installRetry * Math.pow(2,Math.min(Math.floor(reinstallAttempts/5),3)); reinstallTimeout = setTimeout(function() { reinstallModules(reinstallList); },timeout); diff --git a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json index bc866e09e..eeb4299d7 100644 --- a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json @@ -42,6 +42,7 @@ "uninstall-failed-long": "Uninstall of module __name__ failed:", "uninstalled": "Uninstalled module: __name__" }, + "deprecatedOption": "use of __old__ is deprecated. Use __new__ instead", "unable-to-listen": "Unable to listen on __listenpath__", "port-in-use": "Error: port in use", "uncaught-exception": "Uncaught Exception:", From fc459be5315580c296e0cd8c6d34649b9caea7eb Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 23 Dec 2020 23:29:07 +0000 Subject: [PATCH 044/114] Deprecate editorTheme.palette.editable for externalModules.palette.allowInstall Also deprecates editorTheme.palette.editable for externalModules.palette.allowUpload --- .../@node-red/editor-client/src/js/red.js | 6 +++--- .../editor-client/src/js/settings.js | 11 +++++----- .../editor-client/src/js/ui/palette-editor.js | 10 +++++----- .../src/js/ui/projects/projectSettings.js | 2 +- .../@node-red/registry/lib/installer.js | 20 +++++++++++++++++-- .../@node-red/runtime/lib/api/settings.js | 11 +++++++--- 6 files changed, 40 insertions(+), 20 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index ff8dad5bf..97159e6d2 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -269,7 +269,7 @@ var RED = (function() { } } ] - // } else if (RED.settings.theme('palette.editable') !== false) { + // } else if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) { } else { options.buttons = [ { @@ -509,7 +509,7 @@ var RED = (function() { ]}); menuOptions.push(null); - if (RED.settings.theme('palette.editable') !== false) { + if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) { menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"}); menuOptions.push(null); } @@ -544,7 +544,7 @@ var RED = (function() { RED.palette.init(); RED.eventLog.init(); - if (RED.settings.theme('palette.editable') !== false) { + if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) { RED.palette.editor.init(); } else { console.log("Palette editor disabled"); diff --git a/packages/node_modules/@node-red/editor-client/src/js/settings.js b/packages/node_modules/@node-red/editor-client/src/js/settings.js index 760165581..109554418 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/settings.js +++ b/packages/node_modules/@node-red/editor-client/src/js/settings.js @@ -57,12 +57,11 @@ RED.settings = (function () { return JSON.parse(localStorage.getItem(key)); } else { var v; - try { - v = RED.utils.getMessageProperty(userSettings,key); - if (v === undefined) { - v = defaultIfUndefined; - } - } catch(err) { + try { v = RED.utils.getMessageProperty(userSettings,key); } catch(err) {} + if (v === undefined) { + try { v = RED.utils.getMessageProperty(RED.settings,key); } catch(err) {} + } + if (v === undefined) { v = defaultIfUndefined; } return v; 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 b0aeed0fe..147789940 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 @@ -439,7 +439,7 @@ RED.palette.editor = (function() { function init() { - if (RED.settings.theme('palette.editable') === false) { + if (RED.settings.get('externalModules.palette.allowInstall', true) === false) { return; } createSettingsPane(); @@ -880,7 +880,7 @@ RED.palette.editor = (function() { } }); - if (RED.settings.theme('palette.upload') !== false) { + if (RED.settings.get('externalModules.palette.allowUpload', true) !== false) { var uploadSpan = $('').prependTo(toolBar); var uploadButton = $('').appendTo(uploadSpan); @@ -962,7 +962,7 @@ RED.palette.editor = (function() { } function update(entry,version,url,container,done) { - if (RED.settings.theme('palette.editable') === false) { + if (RED.settings.get('externalModules.palette.allowInstall', true) === false) { done(new Error('Palette not editable')); return; } @@ -1021,7 +1021,7 @@ RED.palette.editor = (function() { }) } function remove(entry,container,done) { - if (RED.settings.theme('palette.editable') === false) { + if (RED.settings.get('externalModules.palette.allowInstall', true) === false) { done(new Error('Palette not editable')); return; } @@ -1078,7 +1078,7 @@ RED.palette.editor = (function() { }) } function install(entry,container,done) { - if (RED.settings.theme('palette.editable') === false) { + if (RED.settings.get('externalModules.palette.allowInstall', true) === false) { done(new Error('Palette not editable')); return; } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js index fdd106e23..c39ac97be 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js @@ -465,7 +465,7 @@ RED.projects.settings = (function() { metaRow = $('
').appendTo(headerRow); var buttons = $('
').appendTo(metaRow); if (RED.user.hasPermission("projects.write")) { - if (!entry.installed && RED.settings.theme('palette.editable') !== false) { + if (!entry.installed && RED.settings.get('externalModules.palette.allowInstall', true) !== false) { $('' + RED._("sidebar.project.projectSettings.install") + '').appendTo(buttons) .on("click", function(evt) { evt.preventDefault(); diff --git a/packages/node_modules/@node-red/registry/lib/installer.js b/packages/node_modules/@node-red/registry/lib/installer.js index 0c3359382..f94ebbad4 100644 --- a/packages/node_modules/@node-red/registry/lib/installer.js +++ b/packages/node_modules/@node-red/registry/lib/installer.js @@ -335,7 +335,24 @@ function uninstallModule(module) { return activePromise; } -function checkPrereq() { +async function checkPrereq() { + + try { + if (settings.editorTheme.palette.editable === false) { + log.info(log._("server.palette-editor.disabled")); + installerEnabled = false; + return + } + } catch(err) {} + + try { + if (settings.externalModules.palette.allowInstall === false) { + log.info(log._("server.palette-editor.disabled")); + installerEnabled = false; + return + } + } catch(err) {} + if (settings.hasOwnProperty('editorTheme') && settings.editorTheme.hasOwnProperty('palette') && settings.editorTheme.palette.hasOwnProperty('editable') && @@ -343,7 +360,6 @@ function checkPrereq() { ) { log.info(log._("server.palette-editor.disabled")); installerEnabled = false; - return Promise.resolve(); } else { return new Promise(resolve => { child_process.execFile(npmCommand,['-v'],function(err,stdout) { diff --git a/packages/node_modules/@node-red/runtime/lib/api/settings.js b/packages/node_modules/@node-red/runtime/lib/api/settings.js index 067a8e83a..8b52d4587 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/api/settings.js @@ -90,10 +90,15 @@ var api = module.exports = { safeSettings.flowFilePretty = runtime.settings.flowFilePretty; } + if (runtime.settings.externalModules) { + safeSettings.externalModules = runtime.settings.externalModules; + } + if (!runtime.nodes.installerEnabled()) { - safeSettings.editorTheme = safeSettings.editorTheme || {}; - safeSettings.editorTheme.palette = safeSettings.editorTheme.palette || {}; - safeSettings.editorTheme.palette.editable = false; + safeSettings.externalModules = safeSettings.externalModules || {}; + safeSettings.externalModules.palette = safeSettings.externalModules.palette || {}; + safeSettings.externalModules.palette.allowInstall = false; + safeSettings.externalModules.palette.allowUpload = false; } if (runtime.storage.projects) { var activeProject = runtime.storage.projects.getActiveProject(); From 4943bde3d4bbf6b952999d7bee73233a7e03b916 Mon Sep 17 00:00:00 2001 From: fellinga Date: Sun, 27 Dec 2020 12:59:12 +0100 Subject: [PATCH 045/114] add optional lang select --- packages/node_modules/@node-red/util/index.js | 2 +- packages/node_modules/@node-red/util/lib/i18n.js | 2 +- packages/node_modules/node-red/settings.js | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/util/index.js b/packages/node_modules/@node-red/util/index.js index 29e1a480c..aeb566540 100644 --- a/packages/node_modules/@node-red/util/index.js +++ b/packages/node_modules/@node-red/util/index.js @@ -33,7 +33,7 @@ module.exports = { */ init: function(settings) { log.init(settings); - i18n.init(); + i18n.init(settings); }, /** diff --git a/packages/node_modules/@node-red/util/lib/i18n.js b/packages/node_modules/@node-red/util/lib/i18n.js index cce2e8149..8ba1d08c5 100644 --- a/packages/node_modules/@node-red/util/lib/i18n.js +++ b/packages/node_modules/@node-red/util/lib/i18n.js @@ -160,7 +160,7 @@ function init() { suffix: '__' } }; - var lang = getCurrentLocale(); + var lang = settings.lang || getCurrentLocale(); if (lang) { opt.lng = lang; } diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js index fd07630f2..deedd726d 100644 --- a/packages/node_modules/node-red/settings.js +++ b/packages/node_modules/node-red/settings.js @@ -248,6 +248,8 @@ module.exports = { // their values. Setting this to true will cause the keys to be listed. exportGlobalContextKeys: false, + // Uncomment the following to run node-red in your preferred language: + // lang: "de", // Context Storage // The following property can be used to enable context storage. The configuration From aacb92a7aeb537d84961fdc8a4587adcf359f37d Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Sun, 27 Dec 2020 12:49:17 +0000 Subject: [PATCH 046/114] Implement allow/denyList when loading/installing modules --- .../@node-red/editor-api/lib/admin/index.js | 14 ++-- .../@node-red/registry/lib/installer.js | 46 +++++++++--- .../@node-red/registry/lib/localfilesystem.js | 35 ++++++--- .../@node-red/registry/lib/util.js | 72 ++++++++++++++++++- .../@node-red/runtime/lib/api/nodes.js | 2 +- test/unit/@node-red/registry/lib/util_spec.js | 49 ++++++++++++- 6 files changed, 191 insertions(+), 27 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/index.js b/packages/node_modules/@node-red/editor-api/lib/admin/index.js index ff32111f5..34c47b2cb 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/index.js @@ -50,12 +50,14 @@ module.exports = { // Nodes adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler); - if (!settings.editorTheme || !settings.editorTheme.palette || settings.editorTheme.palette.upload !== false) { - const multer = require('multer'); - const upload = multer({ storage: multer.memoryStorage() }); - adminApp.post("/nodes",needsPermission("nodes.write"),upload.single("tarball"),nodes.post,apiUtil.errorHandler); - } else { - adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler); + if (!settings.externalModules || !settings.externalModules.palette || settings.externalModules.palette.allowInstall !== false) { + if (!settings.externalModules || !settings.externalModules.palette || settings.externalModules.palette.allowUpload !== false) { + const multer = require('multer'); + const upload = multer({ storage: multer.memoryStorage() }); + adminApp.post("/nodes",needsPermission("nodes.write"),upload.single("tarball"),nodes.post,apiUtil.errorHandler); + } else { + adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler); + } } adminApp.get(/^\/nodes\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalogs,apiUtil.errorHandler); adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,apiUtil.errorHandler); diff --git a/packages/node_modules/@node-red/registry/lib/installer.js b/packages/node_modules/@node-red/registry/lib/installer.js index f94ebbad4..bde1f0f3d 100644 --- a/packages/node_modules/@node-red/registry/lib/installer.js +++ b/packages/node_modules/@node-red/registry/lib/installer.js @@ -15,26 +15,42 @@ **/ -var path = require("path"); -var os = require("os"); -var fs = require("fs-extra"); -var tar = require("tar"); +const path = require("path"); +const os = require("os"); +const fs = require("fs-extra"); +const tar = require("tar"); -var registry = require("./registry"); -var library = require("./library"); +const registry = require("./registry"); +const registryUtil = require("./util"); +const library = require("./library"); const {exec,log,events} = require("@node-red/util"); -var child_process = require('child_process'); -var npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; -var installerEnabled = false; +const child_process = require('child_process'); +const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; +let installerEnabled = false; -var settings; +let settings; const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/; const slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/; const pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//; const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/; +// Default allow/deny lists +let installAllowList = ['*']; +let installDenyList = []; + + function init(_settings) { settings = _settings; + // TODO: This is duplicated in localfilesystem.js + // Should it *all* be managed by util? + if (settings.externalModules && settings.externalModules.palette) { + if (settings.externalModules.palette.allowList || settings.externalModules.palette.denyList) { + installAllowList = settings.externalModules.palette.allowList; + installDenyList = settings.externalModules.palette.denyList; + } + } + installAllowList = registryUtil.parseModuleList(installAllowList); + installDenyList = registryUtil.parseModuleList(installDenyList); } var activePromise = Promise.resolve(); @@ -118,6 +134,12 @@ function installModule(module,version,url) { reject(e); return; } + if (!registryUtil.checkModuleAllowed(module,version,installAllowList,installDenyList)) { + const e = new Error("Install not allowed"); + e.code = "install_not_allowed"; + reject(e); + return + } isUpgrade = checkExistingModule(module,version); } catch(err) { return reject(err); @@ -215,6 +237,10 @@ async function getExistingPackageVersion(moduleName) { } async function installTarball(tarball) { + if (settings.externalModules && settings.externalModules.palette && settings.externalModules.palette.allowUpload === false) { + throw new Error("Module upload disabled") + } + // Check this tarball contains a valid node-red module. // Get its module name/version const moduleInfo = await getTarballModuleInfo(tarball); diff --git a/packages/node_modules/@node-red/registry/lib/localfilesystem.js b/packages/node_modules/@node-red/registry/lib/localfilesystem.js index 7aea3f57b..25e423571 100644 --- a/packages/node_modules/@node-red/registry/lib/localfilesystem.js +++ b/packages/node_modules/@node-red/registry/lib/localfilesystem.js @@ -14,10 +14,16 @@ * limitations under the License. **/ -var fs = require("fs"); -var path = require("path"); -var log = require("@node-red/util").log; -var i18n = require("@node-red/util").i18n; +const fs = require("fs"); +const path = require("path"); +const log = require("@node-red/util").log; +const i18n = require("@node-red/util").i18n; +const registryUtil = require("./util"); + +// Default allow/deny lists +let loadAllowList = ['*']; +let loadDenyList = []; + var settings; var disableNodePathScan = false; @@ -25,6 +31,16 @@ var iconFileExtensions = [".png", ".gif", ".svg"]; function init(_settings) { settings = _settings; + // TODO: This is duplicated in installer.js + // Should it *all* be managed by util? + if (settings.externalModules && settings.externalModules.palette) { + if (settings.externalModules.palette.allowList || settings.externalModules.palette.denyList) { + loadAllowList = settings.externalModules.palette.allowList; + loadDenyList = settings.externalModules.palette.denyList; + } + } + loadAllowList = registryUtil.parseModuleList(loadAllowList); + loadDenyList = registryUtil.parseModuleList(loadDenyList); } function isIncluded(name) { @@ -137,8 +153,12 @@ function scanDirForNodesModules(dir,moduleName) { try { var pkg = require(pkgfn); if (pkg['node-red']) { - var moduleDir = path.join(dir,fn); - results.push({dir:moduleDir,package:pkg}); + if (!registryUtil.checkModuleAllowed(pkg.name,pkg.version,loadAllowList,loadDenyList)) { + log.debug("! Module: "+pkg.name+" "+pkg.version+ " *ignored due to denyList*"); + } else { + var moduleDir = path.join(dir,fn); + results.push({dir:moduleDir,package:pkg}); + } } } catch(err) { if (err.code != "MODULE_NOT_FOUND") { @@ -308,8 +328,7 @@ function getNodeFiles(disableNodePathScan) { } else { result = false; } - log.debug("Module: "+mod.package.name+" "+mod.package.version+(result?"":" *ignored due to local copy*")); - log.debug(" "+mod.dir); + log.debug((result?"":"! ")+"Module: "+mod.package.name+" "+mod.package.version+" "+mod.dir+(result?"":" *ignored due to local copy*")); return result; }); diff --git a/packages/node_modules/@node-red/registry/lib/util.js b/packages/node_modules/@node-red/registry/lib/util.js index dbb6c6fc7..6e7609fd5 100644 --- a/packages/node_modules/@node-red/registry/lib/util.js +++ b/packages/node_modules/@node-red/registry/lib/util.js @@ -15,6 +15,7 @@ **/ const path = require("path"); +const semver = require("semver"); const {events,i18n,log} = require("@node-red/util"); var runtime; @@ -104,9 +105,78 @@ function createNodeApi(node) { return red; } + +function checkAgainstList(module,version,list) { + for (let i=0;i deniedRule.wildcardPos + } else { + // First wildcard in same position. + // Go with the longer matching rule. This isn't going to be 100% + // right, but we are deep into edge cases at this point. + return allowedRule.module.toString().length > deniedRule.module.toString().length + } + return false; +} + +function parseModuleList(list) { + list = list || ["*"]; + return list.map(rule => { + let m = /^(.+?)(?:@(.*))?$/.exec(rule); + let wildcardPos = m[1].indexOf("*"); + wildcardPos = wildcardPos===-1?Infinity:wildcardPos; + + return { + module: new RegExp("^"+m[1].replace(/\*/g,".*")+"$"), + version: m[2], + wildcardPos: wildcardPos + } + }) +} + + module.exports = { init: function(_runtime) { runtime = _runtime; }, - createNodeApi: createNodeApi + createNodeApi: createNodeApi, + parseModuleList: parseModuleList, + checkModuleAllowed: checkModuleAllowed } diff --git a/packages/node_modules/@node-red/runtime/lib/api/nodes.js b/packages/node_modules/@node-red/runtime/lib/api/nodes.js index 9ac83c814..ba4f9874b 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/nodes.js +++ b/packages/node_modules/@node-red/runtime/lib/api/nodes.js @@ -164,7 +164,7 @@ var api = module.exports = { throw err; } if (opts.tarball) { - if (runtime.settings.editorTheme && runtime.settings.editorTheme.palette && runtime.settings.editorTheme.palette.upload === false) { + if (runtime.settings.externalModules && runtime.settings.externalModules.palette && runtime.settings.externalModules.palette.upload === false) { runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,error:"invalid_request"}, opts.req); var err = new Error("Invalid request"); err.code = "invalid_request"; diff --git a/test/unit/@node-red/registry/lib/util_spec.js b/test/unit/@node-red/registry/lib/util_spec.js index a82519d11..d2384e5dc 100644 --- a/test/unit/@node-red/registry/lib/util_spec.js +++ b/test/unit/@node-red/registry/lib/util_spec.js @@ -14,7 +14,54 @@ * limitations under the License. **/ +const should = require("should"); +const NR_TEST_UTILS = require("nr-test-utils"); +const registryUtil = NR_TEST_UTILS.require("@node-red/registry/lib/util"); + describe("red/nodes/registry/util",function() { - it.skip("NEEDS TESTS"); + describe("createNodeApi", function() { + it.skip("needs tests"); + }); + describe("checkModuleAllowed", function() { + function checkList(module, version, allowList, denyList) { + return registryUtil.checkModuleAllowed( + module, + version, + registryUtil.parseModuleList(allowList), + registryUtil.parseModuleList(denyList) + ) + } + + it("allows module with no allow/deny list provided", function() { + checkList("abc","1.2.3",[],[]).should.be.true(); + }) + it("defaults allow to * when only deny list is provided", function() { + checkList("abc","1.2.3",["*"],["def"]).should.be.true(); + checkList("def","1.2.3",["*"],["def"]).should.be.false(); + }) + it("uses most specific matching rule", function() { + checkList("abc","1.2.3",["ab*"],["a*"]).should.be.true(); + checkList("def","1.2.3",["d*"],["de*"]).should.be.false(); + }) + it("checks version string using semver rules", function() { + // Deny + checkList("abc","1.2.3",["abc@1.2.2"],["*"]).should.be.false(); + checkList("abc","1.2.3",["abc@1.2.4"],["*"]).should.be.false(); + checkList("abc","1.2.3",["abc@>1.2.3"],["*"]).should.be.false(); + checkList("abc","1.2.3",["abc@>=1.2.3"],["abc"]).should.be.false(); + + + checkList("node-red-contrib-foo","1.2.3",["*"],["*contrib*"]).should.be.false(); + + + // Allow + checkList("abc","1.2.3",["abc@1.2.3"],["*"]).should.be.true(); + checkList("abc","1.2.3",["abc@<1.2.4"],["*"]).should.be.true(); + checkList("abc","1.2.3",["abc"],["abc@>1.2.3"]).should.be.true(); + checkList("abc","1.2.3",["abc"],["abc@<1.2.3||>1.2.3"]).should.be.true(); + checkList("node-red-contrib-foo","1.2.3",["*contrib*"],["*"]).should.be.true(); + }) + + }) }); From b36e7e172eecf66616e7b230943523792f83fd7c Mon Sep 17 00:00:00 2001 From: fellinga Date: Sun, 27 Dec 2020 14:44:32 +0100 Subject: [PATCH 047/114] add settings to init --- packages/node_modules/@node-red/util/lib/i18n.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/util/lib/i18n.js b/packages/node_modules/@node-red/util/lib/i18n.js index 8ba1d08c5..e9dbedace 100644 --- a/packages/node_modules/@node-red/util/lib/i18n.js +++ b/packages/node_modules/@node-red/util/lib/i18n.js @@ -142,7 +142,7 @@ function getCurrentLocale() { return undefined; } -function init() { +function init(settings) { if (!initPromise) { // Keep this as a 'when' promise as top-level red.js uses 'otherwise' // and embedded users of NR may have copied that. From 9d2d060decca8122030cb9c5637a53cc64647f7d Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Sun, 27 Dec 2020 20:59:31 +0000 Subject: [PATCH 048/114] Fix unit tests for externalModules --- packages/node_modules/@node-red/runtime/lib/index.js | 1 - test/unit/@node-red/runtime/lib/api/settings_spec.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/index.js b/packages/node_modules/@node-red/runtime/lib/index.js index 2dd6db6c8..6c6dbb495 100644 --- a/packages/node_modules/@node-red/runtime/lib/index.js +++ b/packages/node_modules/@node-red/runtime/lib/index.js @@ -204,7 +204,6 @@ function reinstallModules(moduleList) { if (settings.externalModules && settings.externalModules.hasOwnProperty('autoInstallRetry')) { installRetry = settings.externalModules.autoInstallRetry * 1000; } - externalModules.autoInstallRetry for (var i=0;i Date: Sun, 27 Dec 2020 21:34:21 +0000 Subject: [PATCH 049/114] Filter palette manager nodes based on allow/deny list --- .../editor-client/src/js/ui/palette-editor.js | 109 +++++++++++++++--- 1 file changed, 95 insertions(+), 14 deletions(-) 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 147789940..3dbff87a3 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 @@ -329,21 +329,26 @@ RED.palette.editor = (function() { catalogueLoadStatus.push(err||v); if (!err) { if (v.modules) { - v.modules.forEach(function(m) { - loadedIndex[m.id] = m; - m.index = [m.id]; - if (m.keywords) { - m.index = m.index.concat(m.keywords); + var a = false; + v.modules = v.modules.filter(function(m) { + if (checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) { + loadedIndex[m.id] = m; + m.index = [m.id]; + if (m.keywords) { + m.index = m.index.concat(m.keywords); + } + if (m.types) { + m.index = m.index.concat(m.types); + } + if (m.updated_at) { + m.timestamp = new Date(m.updated_at).getTime(); + } else { + m.timestamp = 0; + } + m.index = m.index.join(",").toLowerCase(); + return true; } - if (m.types) { - m.index = m.index.concat(m.types); - } - if (m.updated_at) { - m.timestamp = new Date(m.updated_at).getTime(); - } else { - m.timestamp = 0; - } - m.index = m.index.join(",").toLowerCase(); + return false; }) loadedList = loadedList.concat(v.modules); } @@ -437,11 +442,87 @@ RED.palette.editor = (function() { return -1 * (A.info.timestamp-B.info.timestamp); } + var installAllowList = ['*']; + var installDenyList = []; + + function parseModuleList(list) { + list = list || ["*"]; + return list.map(rule => { + var m = /^(.+?)(?:@(.*))?$/.exec(rule); + var wildcardPos = m[1].indexOf("*"); + wildcardPos = wildcardPos===-1?Infinity:wildcardPos; + + return { + module: new RegExp("^"+m[1].replace(/\*/g,".*")+"$"), + version: m[2], + wildcardPos: wildcardPos + } + }) + } + + function checkAgainstList(module,version,list) { + for (var i=0;i deniedRule.wildcardPos + } else { + // First wildcard in same position. + // Go with the longer matching rule. This isn't going to be 100% + // right, but we are deep into edge cases at this point. + return allowedRule.module.toString().length > deniedRule.module.toString().length + } + return false; + } function init() { if (RED.settings.get('externalModules.palette.allowInstall', true) === false) { return; } + var settingsAllowList = RED.settings.get("externalModules.palette.allowList") + var settingsDenyList = RED.settings.get("externalModules.palette.denyList") + if (settingsAllowList || settingsDenyList) { + installAllowList = settingsAllowList; + installDenyList = settingsDenyList + } + installAllowList = parseModuleList(installAllowList); + installDenyList = parseModuleList(installDenyList); + + console.log(installAllowList); + console.log(installDenyList); + createSettingsPane(); RED.userSettings.add({ From fa84c4e4613a67c5dc066cbde1a4793a6376c22e Mon Sep 17 00:00:00 2001 From: Tiago Ferreira Date: Tue, 29 Dec 2020 21:54:57 +0000 Subject: [PATCH 050/114] Allow to explicit use userMenu in the theme configuration Unit test to ensure that works after the theme is initialize Allow to explicti use userMenu in the theme configuration --- .../@node-red/editor-client/src/js/user.js | 2 +- .../@node-red/editor-api/lib/editor/theme_spec.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/user.js b/packages/node_modules/@node-red/editor-client/src/js/user.js index dd0ff8f90..bd7339285 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/user.js +++ b/packages/node_modules/@node-red/editor-client/src/js/user.js @@ -219,7 +219,7 @@ RED.user = (function() { function init() { if (RED.settings.user) { - if (!RED.settings.editorTheme || !RED.settings.editorTheme.hasOwnProperty("userMenu")) { + if (!RED.settings.editorTheme || !RED.settings.editorTheme.hasOwnProperty("userMenu") || RED.settings.editorTheme.userMenu) { var userMenu = $('
  • ') .prependTo(".red-ui-header-toolbar"); diff --git a/test/unit/@node-red/editor-api/lib/editor/theme_spec.js b/test/unit/@node-red/editor-api/lib/editor/theme_spec.js index 67e305f71..e7db72dd6 100644 --- a/test/unit/@node-red/editor-api/lib/editor/theme_spec.js +++ b/test/unit/@node-red/editor-api/lib/editor/theme_spec.js @@ -143,4 +143,19 @@ describe("api/editor/theme", function () { settings.projects.should.have.a.property("enabled", false); }); + it("test explicit userMenu set to true in theme setting", function () { + theme.init({ + editorTheme: { + userMenu: true, + } + }); + + theme.app(); + + var settings = theme.settings(); + settings.should.have.a.property("userMenu"); + settings.userMenu.should.be.eql(true); + + }); + }); From 30a68fefec167af1a5499b510d25b5eae66faae4 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 6 Jan 2021 11:22:52 +0000 Subject: [PATCH 051/114] Ensure subflow-scoped config nodes do not get moved on import Fixes #2789 --- packages/node_modules/@node-red/editor-client/src/js/nodes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index ddeea65e6..0d664ce3c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -1360,7 +1360,7 @@ RED.nodes = (function() { } } } else { - if (n.z && !workspaces[n.z]) { + if (n.z && !workspaces[n.z] && !subflow_map[n.z]) { n.z = activeWorkspace; } } From ea720bb4a58cc276492f9e81615da5a914c34ce3 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 6 Jan 2021 11:41:17 +0000 Subject: [PATCH 052/114] Bump dependencies --- package.json | 14 +++++++------- .../node_modules/@node-red/editor-api/package.json | 4 ++-- packages/node_modules/@node-red/nodes/package.json | 4 ++-- .../node_modules/@node-red/registry/package.json | 2 +- .../node_modules/@node-red/runtime/package.json | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 5fa8dca88..3bbb2f323 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ ], "dependencies": { "ajv": "6.12.6", - "async-mutex": "0.2.4", + "async-mutex": "0.2.6", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "body-parser": "1.19.0", @@ -38,7 +38,7 @@ "cookie-parser": "1.4.5", "cors": "2.8.5", "cron": "1.7.2", - "denque": "1.4.1", + "denque": "1.5.0", "express": "4.17.1", "express-session": "1.17.1", "fs-extra": "8.1.0", @@ -54,11 +54,11 @@ "lodash.clonedeep": "^4.5.0", "media-typer": "1.1.0", "memorystore": "1.6.4", - "mime": "2.4.6", + "mime": "2.4.7", "moment-timezone": "0.5.32", "mqtt": "4.2.6", "multer": "1.4.2", - "mustache": "4.0.1", + "mustache": "4.1.0", "node-red-admin": "^0.2.6", "node-red-node-rbe": "^0.2.9", "node-red-node-sentiment": "^0.1.6", @@ -73,7 +73,7 @@ "request": "2.88.0", "semver": "6.3.0", "tar": "6.0.5", - "uglify-js": "3.11.6", + "uglify-js": "3.12.4", "when": "3.7.8", "ws": "6.2.1", "xml2js": "0.4.23" @@ -82,7 +82,7 @@ "bcrypt": "3.0.8" }, "devDependencies": { - "dompurify": "2.2.2", + "dompurify": "2.2.6", "grunt": "1.3.0", "grunt-chmod": "~1.1.1", "grunt-cli": "~1.3.2", @@ -104,7 +104,7 @@ "grunt-simple-nyc": "^3.0.1", "http-proxy": "1.18.1", "jsdoc-nr-template": "github:node-red/jsdoc-nr-template", - "marked": "1.2.5", + "marked": "1.2.7", "minami": "1.2.3", "mocha": "^5.2.0", "node-red-node-test-helper": "^0.2.5", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 2b89c8023..ac4021030 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -25,9 +25,9 @@ "express-session": "1.17.1", "express": "4.17.1", "memorystore": "1.6.4", - "mime": "2.4.6", + "mime": "2.4.7", "multer": "1.4.2", - "mustache": "4.0.1", + "mustache": "4.1.0", "oauth2orize": "1.11.0", "passport-http-bearer": "1.0.1", "passport-oauth2-client-password": "0.1.2", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index d5f0c3e1a..1c8574e90 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -23,7 +23,7 @@ "cookie": "0.4.1", "cors": "2.8.5", "cron": "1.7.2", - "denque": "1.4.1", + "denque": "1.5.0", "fs-extra": "8.1.0", "fs.notify": "0.0.4", "hash-sum": "2.0.0", @@ -33,7 +33,7 @@ "media-typer": "1.1.0", "mqtt": "4.2.6", "multer": "1.4.2", - "mustache": "4.0.1", + "mustache": "4.1.0", "on-headers": "1.0.2", "raw-body": "2.4.1", "request": "2.88.0", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index c8a570001..174ce5b12 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -19,7 +19,7 @@ "@node-red/util": "1.2.6", "semver": "6.3.0", "tar": "6.0.5", - "uglify-js": "3.11.6", + "uglify-js": "3.12.4", "when": "3.7.8" } } diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index 620f2f7ed..7375bf9af 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -18,7 +18,7 @@ "dependencies": { "@node-red/registry": "1.2.6", "@node-red/util": "1.2.6", - "async-mutex": "0.2.4", + "async-mutex": "0.2.6", "clone": "2.1.2", "express": "4.17.1", "fs-extra": "8.1.0", From abe77ab96f8541fac21c2863256761e1a4041912 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 6 Jan 2021 11:49:31 +0000 Subject: [PATCH 053/114] Bump for 1.2.7 --- CHANGELOG.md | 22 +++++++++++++++++++ package.json | 2 +- .../@node-red/editor-api/package.json | 6 ++--- .../@node-red/editor-client/package.json | 2 +- .../node_modules/@node-red/nodes/package.json | 2 +- .../@node-red/registry/package.json | 4 ++-- .../@node-red/runtime/package.json | 6 ++--- .../node_modules/@node-red/util/package.json | 2 +- packages/node_modules/node-red/package.json | 10 ++++----- 9 files changed, 39 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c12019755..fdbb4ada9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +### 1.2.7: Maintenance Release + +Editor + + - Ensure subflow-scoped config nodes do not get moved on import Fixes #2789 + - Allow TypedInput to be disabled (#2752) @bartbutenaers + - Allow userMenu to be explicitly enabled (#2805) @tfmf + - Improvements to DE translation (#2192) @ketzu + + +Runtime + + - Handle `undefined` error passed to node.error (#2781) @johnwang71 + - Disable nyc coverage reporting on older node versions + - Improve Editor API unit test coverage (#2777) @aaronmyatt + + +Nodes + + - Trigger: ensure timestamp option sends .now() at point of sending + + ### 1.2.6: Maintenance Release diff --git a/package.json b/package.json index 3bbb2f323..1050b4bff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "1.2.6", + "version": "1.2.7", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index ac4021030..89838e602 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "1.2.6", + "version": "1.2.7", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "1.2.6", - "@node-red/editor-client": "1.2.6", + "@node-red/util": "1.2.7", + "@node-red/editor-client": "1.2.7", "bcryptjs": "2.4.3", "body-parser": "1.19.0", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index 06034b997..5adb7deed 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "1.2.6", + "version": "1.2.7", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 1c8574e90..25b277921 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "1.2.6", + "version": "1.2.7", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 174ce5b12..74816922e 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "1.2.6", + "version": "1.2.7", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,7 +16,7 @@ } ], "dependencies": { - "@node-red/util": "1.2.6", + "@node-red/util": "1.2.7", "semver": "6.3.0", "tar": "6.0.5", "uglify-js": "3.12.4", diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index 7375bf9af..10adaa423 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "1.2.6", + "version": "1.2.7", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "1.2.6", - "@node-red/util": "1.2.6", + "@node-red/registry": "1.2.7", + "@node-red/util": "1.2.7", "async-mutex": "0.2.6", "clone": "2.1.2", "express": "4.17.1", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 8878e4245..fafa7f6ec 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "1.2.6", + "version": "1.2.7", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index 533b033d1..3f18a811e 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "1.2.6", + "version": "1.2.7", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "1.2.6", - "@node-red/runtime": "1.2.6", - "@node-red/util": "1.2.6", - "@node-red/nodes": "1.2.6", + "@node-red/editor-api": "1.2.7", + "@node-red/runtime": "1.2.7", + "@node-red/util": "1.2.7", + "@node-red/nodes": "1.2.7", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.17.1", From af195362224ec5eb47e6ab4d29fa534adbd5f0aa Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 6 Jan 2021 17:36:59 +0000 Subject: [PATCH 054/114] Better logging when deprecated editorTheme.palette.* settings used --- .../editor-client/src/js/ui/palette-editor.js | 3 --- .../@node-red/registry/lib/installer.js | 8 ++++++++ .../@node-red/runtime/lib/api/settings.js | 15 ++++++++++++++- .../node_modules/@node-red/runtime/lib/index.js | 4 ++-- .../@node-red/runtime/locales/en-US/runtime.json | 2 +- 5 files changed, 25 insertions(+), 7 deletions(-) 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 3dbff87a3..4b36f671c 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 @@ -520,9 +520,6 @@ RED.palette.editor = (function() { installAllowList = parseModuleList(installAllowList); installDenyList = parseModuleList(installDenyList); - console.log(installAllowList); - console.log(installDenyList); - createSettingsPane(); RED.userSettings.add({ diff --git a/packages/node_modules/@node-red/registry/lib/installer.js b/packages/node_modules/@node-red/registry/lib/installer.js index bde1f0f3d..c5588bb95 100644 --- a/packages/node_modules/@node-red/registry/lib/installer.js +++ b/packages/node_modules/@node-red/registry/lib/installer.js @@ -362,6 +362,14 @@ function uninstallModule(module) { } async function checkPrereq() { + if (settings.editorTheme && settings.editorTheme.palette) { + if (settings.editorTheme.palette.hasOwnProperty("editable")) { + log.warn(log._("server.deprecatedOption",{old:"editorTheme.palette.editable", new:"externalModules.palette.allowInstall"})); + } + if (settings.editorTheme.palette.hasOwnProperty("upload")) { + log.warn(log._("server.deprecatedOption",{old:"editorTheme.palette.upload", new:"externalModules.palette.allowUpload"})); + } + } try { if (settings.editorTheme.palette.editable === false) { diff --git a/packages/node_modules/@node-red/runtime/lib/api/settings.js b/packages/node_modules/@node-red/runtime/lib/api/settings.js index 8b52d4587..d96ec5b58 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/api/settings.js @@ -90,8 +90,21 @@ var api = module.exports = { safeSettings.flowFilePretty = runtime.settings.flowFilePretty; } + if (runtime.settings.editorTheme && runtime.settings.editorTheme.palette) { + if (runtime.settings.editorTheme.palette.upload === false || runtime.settings.editorTheme.palette.editable === false) { + safeSettings.externalModules = {palette: { } } + } + if (runtime.settings.editorTheme.palette.upload === false) { + safeSettings.externalModules.palette.allowUpload = false; + } + if (runtime.settings.editorTheme.palette.editable === false) { + safeSettings.externalModules.palette.allowInstall = false; + safeSettings.externalModules.palette.allowUpload = false; + } + } + if (runtime.settings.externalModules) { - safeSettings.externalModules = runtime.settings.externalModules; + safeSettings.externalModules = extend(safeSettings.externalModules||{},runtime.settings.externalModules); } if (!runtime.nodes.installerEnabled()) { diff --git a/packages/node_modules/@node-red/runtime/lib/index.js b/packages/node_modules/@node-red/runtime/lib/index.js index 6c6dbb495..40aea7e36 100644 --- a/packages/node_modules/@node-red/runtime/lib/index.js +++ b/packages/node_modules/@node-red/runtime/lib/index.js @@ -125,7 +125,7 @@ function start() { return redNodes.load().then(function() { let autoInstallModules = false; if (settings.hasOwnProperty('autoInstallModules')) { - log.warn(log._("runtime.deprecatedOption",{old:"autoInstallModules", new:"externalModules.autoInstall"})); + log.warn(log._("server.deprecatedOption",{old:"autoInstallModules", new:"externalModules.autoInstall"})); autoInstallModules = true; } if (settings.externalModules) { @@ -198,7 +198,7 @@ function reinstallModules(moduleList) { const reinstallList = []; const installRetry = 30000; if (settings.hasOwnProperty('autoInstallModulesRetry')) { - log.warn(log._("runtime.deprecatedOption",{old:"autoInstallModulesRetry", new:"externalModules.autoInstallRetry"})); + log.warn(log._("server.deprecatedOption",{old:"autoInstallModulesRetry", new:"externalModules.autoInstallRetry"})); installRetry = settings.autoInstallModulesRetry; } if (settings.externalModules && settings.externalModules.hasOwnProperty('autoInstallRetry')) { diff --git a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json index eeb4299d7..58eb7308d 100644 --- a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json @@ -42,7 +42,7 @@ "uninstall-failed-long": "Uninstall of module __name__ failed:", "uninstalled": "Uninstalled module: __name__" }, - "deprecatedOption": "use of __old__ is deprecated. Use __new__ instead", + "deprecatedOption": "Use of __old__ is deprecated. Use __new__ instead", "unable-to-listen": "Unable to listen on __listenpath__", "port-in-use": "Error: port in use", "uncaught-exception": "Uncaught Exception:", From 8a87f937411bfda9a27ff51aa5d29cad9865acf2 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 6 Jan 2021 20:03:22 +0000 Subject: [PATCH 055/114] Use npm info to check pending install version --- .../@node-red/registry/lib/installer.js | 239 +++++++++++------- 1 file changed, 153 insertions(+), 86 deletions(-) diff --git a/packages/node_modules/@node-red/registry/lib/installer.js b/packages/node_modules/@node-red/registry/lib/installer.js index c5588bb95..c7cd0d5e0 100644 --- a/packages/node_modules/@node-red/registry/lib/installer.js +++ b/packages/node_modules/@node-red/registry/lib/installer.js @@ -37,7 +37,8 @@ const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/; // Default allow/deny lists let installAllowList = ['*']; let installDenyList = []; - +let installAllAllowed = true; +let installVersionRestricted = false; function init(_settings) { settings = _settings; @@ -51,6 +52,18 @@ function init(_settings) { } installAllowList = registryUtil.parseModuleList(installAllowList); installDenyList = registryUtil.parseModuleList(installDenyList); + installAllAllowed = installDenyList.length === 0; + if (!installAllAllowed) { + installAllowList.forEach(function(rule) { + installVersionRestricted = installVersionRestricted || (!!rule.version); + }) + if (!installVersionRestricted) { + installDenyList.forEach(function(rule) { + installVersionRestricted = installVersionRestricted || (!!rule.version); + }) + + } + } } var activePromise = Promise.resolve(); @@ -95,99 +108,101 @@ function checkExistingModule(module,version) { return false; } -function installModule(module,version,url) { +async function installModule(module,version,url) { if (Buffer.isBuffer(module)) { return installTarball(module) } module = module || ""; - activePromise = activePromise.then(() => { + activePromise = activePromise.then(async function() { //TODO: ensure module is 'safe' - return new Promise((resolve,reject) => { - var installName = module; - var isUpgrade = false; - try { - if (url) { - if (pkgurlRe.test(url) || localtgzRe.test(url)) { - // Git remote url or Tarball url - check the valid package url - installName = url; - } else { - log.warn(log._("server.install.install-failed-url",{name:module,url:url})); - const e = new Error("Invalid url"); - e.code = "invalid_module_url"; - reject(e); - return; - } - } else if (moduleRe.test(module)) { - // Simple module name - assume it can be npm installed - if (version) { - installName += "@"+version; - } - } else if (slashRe.test(module)) { - // A path - check if there's a valid package.json - installName = module; - let info = checkModulePath(module); - module = info.name; - } else { - log.warn(log._("server.install.install-failed-name",{name:module})); - const e = new Error("Invalid module name"); - e.code = "invalid_module_name"; - reject(e); - return; - } - if (!registryUtil.checkModuleAllowed(module,version,installAllowList,installDenyList)) { - const e = new Error("Install not allowed"); - e.code = "install_not_allowed"; - reject(e); - return - } - isUpgrade = checkExistingModule(module,version); - } catch(err) { - return reject(err); - } - if (!isUpgrade) { - log.info(log._("server.install.installing",{name: module,version: version||"latest"})); + var installName = module; + let isRegistryPackage = true; + var isUpgrade = false; + if (url) { + if (pkgurlRe.test(url) || localtgzRe.test(url)) { + // Git remote url or Tarball url - check the valid package url + installName = url; + isRegistryPackage = false; } else { - log.info(log._("server.install.upgrading",{name: module,version: version||"latest"})); + log.warn(log._("server.install.install-failed-url",{name:module,url:url})); + const e = new Error("Invalid url"); + e.code = "invalid_module_url"; + throw e; + } + } else if (moduleRe.test(module)) { + // Simple module name - assume it can be npm installed + if (version) { + installName += "@"+version; + } + } else if (slashRe.test(module)) { + // A path - check if there's a valid package.json + installName = module; + let info = checkModulePath(module); + module = info.name; + isRegistryPackage = false; + } else { + log.warn(log._("server.install.install-failed-name",{name:module})); + const e = new Error("Invalid module name"); + e.code = "invalid_module_name"; + throw e; + } + if (!installAllAllowed) { + let installVersion = version; + if (installVersionRestricted && isRegistryPackage) { + installVersion = await getModuleVersionFromNPM(module, version); } - var installDir = settings.userDir || process.env.NODE_RED_HOME || "."; - var args = ['install','--no-audit','--no-update-notifier','--no-fund','--save','--save-prefix=~','--production',installName]; - log.trace(npmCommand + JSON.stringify(args)); - exec.run(npmCommand,args,{ - cwd: installDir - }, true).then(result => { - if (!isUpgrade) { - log.info(log._("server.install.installed",{name:module})); - resolve(require("./index").addModule(module).then(reportAddedModules)); - } else { - log.info(log._("server.install.upgraded",{name:module, version:version})); - events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true}); - resolve(require("./registry").setModulePendingUpdated(module,version)); - } - }).catch(result => { - var output = result.stderr; - var e; - var lookFor404 = new RegExp(" 404 .*"+module,"m"); - var lookForVersionNotFound = new RegExp("version not found: "+module+"@"+version,"m"); - if (lookFor404.test(output)) { - log.warn(log._("server.install.install-failed-not-found",{name:module})); - e = new Error("Module not found"); - e.code = 404; - reject(e); - } else if (isUpgrade && lookForVersionNotFound.test(output)) { - log.warn(log._("server.install.upgrade-failed-not-found",{name:module})); - e = new Error("Module not found"); - e.code = 404; - reject(e); - } else { - log.warn(log._("server.install.install-failed-long",{name:module})); - log.warn("------------------------------------------"); - log.warn(output); - log.warn("------------------------------------------"); - reject(new Error(log._("server.install.install-failed"))); - } - }) - }); + if (!registryUtil.checkModuleAllowed(module,installVersion,installAllowList,installDenyList)) { + const e = new Error("Install not allowed"); + e.code = "install_not_allowed"; + throw e; + } + } + isUpgrade = checkExistingModule(module,version); + + if (!isUpgrade) { + log.info(log._("server.install.installing",{name: module,version: version||"latest"})); + } else { + log.info(log._("server.install.upgrading",{name: module,version: version||"latest"})); + } + + var installDir = settings.userDir || process.env.NODE_RED_HOME || "."; + var args = ['install','--no-audit','--no-update-notifier','--no-fund','--save','--save-prefix=~','--production',installName]; + log.trace(npmCommand + JSON.stringify(args)); + return exec.run(npmCommand,args,{ + cwd: installDir + }, true).then(result => { + if (!isUpgrade) { + log.info(log._("server.install.installed",{name:module})); + return require("./index").addModule(module).then(reportAddedModules); + } else { + log.info(log._("server.install.upgraded",{name:module, version:version})); + events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true}); + return require("./registry").setModulePendingUpdated(module,version); + } + }).catch(result => { + var output = result.stderr; + var e; + var lookFor404 = new RegExp(" 404 .*"+module,"m"); + var lookForVersionNotFound = new RegExp("version not found: "+module+"@"+version,"m"); + if (lookFor404.test(output)) { + log.warn(log._("server.install.install-failed-not-found",{name:module})); + e = new Error("Module not found"); + e.code = 404; + throw e; + } else if (isUpgrade && lookForVersionNotFound.test(output)) { + log.warn(log._("server.install.upgrade-failed-not-found",{name:module})); + e = new Error("Module not found"); + e.code = 404; + throw e; + } else { + log.warn(log._("server.install.install-failed-long",{name:module})); + log.warn("------------------------------------------"); + log.warn(output); + log.warn("------------------------------------------"); + throw new Error(log._("server.install.install-failed")); + } + }) }).catch(err => { // In case of error, reset activePromise to be resolvable activePromise = Promise.resolve(); @@ -236,6 +251,58 @@ async function getExistingPackageVersion(moduleName) { return null; } +async function getModuleVersionFromNPM(module, version) { + let installName = module; + if (version) { + installName += "@" + version; + } + + return new Promise((resolve, reject) => { + child_process.execFile(npmCommand,['info','--json',installName],function(err,stdout,stderr) { + try { + if (!stdout) { + log.warn(log._("server.install.install-failed-not-found",{name:module})); + e = new Error("Version not found"); + e.code = 404; + reject(e); + return; + } + const response = JSON.parse(stdout); + if (response.error) { + if (response.error.code === "E404") { + log.warn(log._("server.install.install-failed-not-found",{name:module})); + e = new Error("Module not found"); + e.code = 404; + reject(e); + } else { + log.warn(log._("server.install.install-failed-long",{name:module})); + log.warn("------------------------------------------"); + log.warn(response.error.summary); + log.warn("------------------------------------------"); + reject(new Error(log._("server.install.install-failed"))); + } + return; + } else { + resolve(response.version); + } + } catch(err) { + log.warn(log._("server.install.install-failed-long",{name:module})); + log.warn("------------------------------------------"); + if (stdout) { + log.warn(stdout); + } + if (stderr) { + log.warn(stderr); + } + log.warn(err); + log.warn("------------------------------------------"); + reject(new Error(log._("server.install.install-failed"))); + } + }); + }) +} + + async function installTarball(tarball) { if (settings.externalModules && settings.externalModules.palette && settings.externalModules.palette.allowUpload === false) { throw new Error("Module upload disabled") From 0284ef401e03d06c40fa2793715226f2642b81a8 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 6 Jan 2021 20:20:32 +0000 Subject: [PATCH 056/114] Fix loading individual module catalog --- .../node_modules/@node-red/runtime/lib/api/nodes.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/api/nodes.js b/packages/node_modules/@node-red/runtime/lib/api/nodes.js index ba4f9874b..dfaee4e54 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/nodes.js +++ b/packages/node_modules/@node-red/runtime/lib/api/nodes.js @@ -406,10 +406,12 @@ var api = module.exports = { var lang = opts.lang; var prevLang = runtime.i18n.i.language; // Trigger a load from disk of the language if it is not the default - return runtime.i18n.i.changeLanguage(lang, function(){ - var catalog = runtime.i18n.i.getResourceBundle(lang, namespace); - runtime.i18n.i.changeLanguage(prevLang); - return catalog||{}; + return new Promise(resolve => { + runtime.i18n.i.changeLanguage(lang, function() { + var catalog = runtime.i18n.i.getResourceBundle(lang, namespace); + runtime.i18n.i.changeLanguage(prevLang); + resolve(catalog||{}); + }); }); }, From 65b4ef6c3db9ca691b2c7faa5091d49c06527e81 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 7 Jan 2021 10:06:08 +0000 Subject: [PATCH 057/114] Remove ES6 from editor code --- .../@node-red/editor-client/src/js/ui/palette-editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4b36f671c..3ff3e7436 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 @@ -447,7 +447,7 @@ RED.palette.editor = (function() { function parseModuleList(list) { list = list || ["*"]; - return list.map(rule => { + return list.map(function(rule) { var m = /^(.+?)(?:@(.*))?$/.exec(rule); var wildcardPos = m[1].indexOf("*"); wildcardPos = wildcardPos===-1?Infinity:wildcardPos; From 06ceb056f36ada560e66c16e71453c1c15dd0795 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 7 Jan 2021 10:09:49 +0000 Subject: [PATCH 058/114] Update build.yml --- .github/workflows/build.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ed34b9fad..b7239546c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,6 @@ name: PublishDockerImage - +env: + ACTIONS_ALLOW_UNSECURE_COMMANDS: true on: release: types: [published] @@ -27,8 +28,6 @@ jobs: with: node-version: '12' - run: node ./node-red/.github/scripts/update-node-red-docker.js - env: - ACTIONS_ALLOW_UNSECURE_COMMANDS: true - name: Create Docker Pull Request uses: peter-evans/create-pull-request@v2 with: From 6e1466e4115b95ed0d6492b6b7de506449fe4f3e Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 28 Aug 2020 16:35:47 +0100 Subject: [PATCH 059/114] Tidy some subflow env props css --- .../node_modules/@node-red/editor-client/src/js/ui/subflow.js | 2 ++ .../node_modules/@node-red/editor-client/src/sass/editor.scss | 4 ++++ .../node_modules/@node-red/editor-client/src/sass/flow.scss | 3 +++ 3 files changed, 9 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index e89f3c235..3f4dc9d74 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -993,6 +993,7 @@ RED.subflow = (function() { icon: "", type: "cred" } + opt.ui.type = "cred"; } else { opt.ui = opt.ui || { icon: "", @@ -1488,6 +1489,7 @@ RED.subflow = (function() { var locale = RED.i18n.lang(); var labelText = lookupLabel(labels, labels["en-US"]||tenv.name, locale); var label = $('