From d6f6b41145b5bd459a275cb8f1545aba8e5c9760 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 17 Jan 2017 09:54:17 +0000 Subject: [PATCH 01/25] Fix inner reference in install fail message catalog entry Fixes #1120 --- red/runtime/locales/en-US/runtime.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/red/runtime/locales/en-US/runtime.json b/red/runtime/locales/en-US/runtime.json index 703a0b513..90cfbd3dc 100644 --- a/red/runtime/locales/en-US/runtime.json +++ b/red/runtime/locales/en-US/runtime.json @@ -28,7 +28,7 @@ "installed": "Installed module: __name__", "install-failed": "Install failed", "install-failed-long": "Installation of module __name__ failed:", - "install-failed-not-found": "$t(install-failed-long) module not found", + "install-failed-not-found": "$t(server.install.install-failed-long) module not found", "uninstalling": "Uninstalling module: __name__", "uninstall-failed": "Uninstall failed", From 0ffeb0c5afa98371b892c49a27b1f1cc313db88c Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 17 Jan 2017 20:48:05 +0000 Subject: [PATCH 02/25] Avoid creating multiple reconnect timers in websocket node --- nodes/core/io/22-websocket.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nodes/core/io/22-websocket.js b/nodes/core/io/22-websocket.js index 2e5808473..a42c3fcf9 100644 --- a/nodes/core/io/22-websocket.js +++ b/nodes/core/io/22-websocket.js @@ -36,6 +36,7 @@ module.exports = function(RED) { node.closing = false; function startconn() { // Connect to remote endpoint + node.tout = null; var socket = new ws(node.path); socket.setMaxListeners(0); node.server = socket; // keep for closing @@ -52,6 +53,7 @@ module.exports = function(RED) { if (node.isServer) { delete node._clients[id]; node.emit('closed',Object.keys(node._clients).length); } else { node.emit('closed'); } if (!node.closing && !node.isServer) { + clearTimeout(node.tout); node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ? } }); @@ -61,6 +63,7 @@ module.exports = function(RED) { socket.on('error', function(err) { node.emit('erro'); if (!node.closing && !node.isServer) { + clearTimeout(node.tout); node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ? } }); @@ -124,7 +127,10 @@ module.exports = function(RED) { else { node.closing = true; node.server.close(); - if (node.tout) { clearTimeout(node.tout); } + if (node.tout) { + clearTimeout(node.tout); + node.tout = null; + } } }); } From 5ba9a0eb3fb07c252cc88a6e254ccd23966eeec6 Mon Sep 17 00:00:00 2001 From: Daisuke Baba Date: Thu, 19 Jan 2017 16:55:57 +0900 Subject: [PATCH 03/25] Fix empty extra node help content issue --- red/runtime/nodes/registry/loader.js | 4 +++- .../red/runtime/nodes/registry/loader_spec.js | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/red/runtime/nodes/registry/loader.js b/red/runtime/nodes/registry/loader.js index dd4292c23..9917a0532 100644 --- a/red/runtime/nodes/registry/loader.js +++ b/red/runtime/nodes/registry/loader.js @@ -381,8 +381,10 @@ function getNodeHelp(node,lang) { } if (help) { node.help[lang] = help; + } else if (lang === runtime.i18n.defaultLang) { + return null; } else { - node.help[lang] = node.help[runtime.i18n.defaultLang]; + node.help[lang] = getNodeHelp(node, runtime.i18n.defaultLang); } } return node.help[lang]; diff --git a/test/red/runtime/nodes/registry/loader_spec.js b/test/red/runtime/nodes/registry/loader_spec.js index 39b25d55e..c3df70bd5 100644 --- a/test/red/runtime/nodes/registry/loader_spec.js +++ b/test/red/runtime/nodes/registry/loader_spec.js @@ -564,6 +564,27 @@ describe("red/nodes/registry/loader",function() { loader.getNodeHelp(node,"fr").should.eql("foo"); fs.readFileSync.calledOnce.should.be.true(); }); + it("loads help, defaulting to en-US content for extra nodes", function() { + stubs.push(sinon.stub(fs,"readFileSync", function(path) { + if (path.indexOf("en-US") >= 0) { + return 'bar'; + } + throw new Error("not found"); + })); + var node = { + template: "/tmp/node/directory/file.html", + help:{} + }; + delete node.help['en-US']; + + loader.getNodeHelp(node,"fr").should.eql("bar"); + node.help['fr'].should.eql("bar"); + fs.readFileSync.calledTwice.should.be.true(); + fs.readFileSync.firstCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/fr/file.html")); + fs.readFileSync.lastCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/en-US/file.html")); + loader.getNodeHelp(node,"fr").should.eql("bar"); + fs.readFileSync.calledTwice.should.be.true(); + }); }); }); From 57c529758e8208648ac2bb62e2d2eb9a34dc1fd0 Mon Sep 17 00:00:00 2001 From: Daisuke Baba Date: Thu, 19 Jan 2017 17:19:41 +0900 Subject: [PATCH 04/25] Add an edge case test --- test/red/runtime/nodes/registry/loader_spec.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/red/runtime/nodes/registry/loader_spec.js b/test/red/runtime/nodes/registry/loader_spec.js index c3df70bd5..b7f32a2de 100644 --- a/test/red/runtime/nodes/registry/loader_spec.js +++ b/test/red/runtime/nodes/registry/loader_spec.js @@ -585,6 +585,24 @@ describe("red/nodes/registry/loader",function() { loader.getNodeHelp(node,"fr").should.eql("bar"); fs.readFileSync.calledTwice.should.be.true(); }); + it("fails to load en-US help content", function() { + stubs.push(sinon.stub(fs,"readFileSync", function(path) { + throw new Error("not found"); + })); + var node = { + template: "/tmp/node/directory/file.html", + help:{} + }; + delete node.help['en-US']; + + should.not.exist(loader.getNodeHelp(node,"en-US")); + should.not.exist(node.help['en-US']); + fs.readFileSync.calledTwice.should.be.true(); + fs.readFileSync.firstCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/en-US/file.html")); + fs.readFileSync.lastCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/en/file.html")); + should.not.exist(loader.getNodeHelp(node,"en-US")); + fs.readFileSync.callCount.should.eql(4); + }); }); }); From 4195840b2c206db890ac34d9505d9781080d48a1 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 19 Jan 2017 11:00:22 +0000 Subject: [PATCH 05/25] make links in added info open in blank page rather than current window --- editor/js/ui/tab-info.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/editor/js/ui/tab-info.js b/editor/js/ui/tab-info.js index fd9e74a42..156e29292 100644 --- a/editor/js/ui/tab-info.js +++ b/editor/js/ui/tab-info.js @@ -124,7 +124,8 @@ RED.sidebar.info = (function() { } else if (node._def && node._def.info) { var info = node._def.info; var textInfo = (typeof info === "function" ? info.call(node) : info); - $('
'+marked(textInfo)+'
').appendTo(content); + var ma = marked(textInfo).replace(/href=/g, 'target="_blank" href='); + $('
'+ma+'
').appendTo(content); //$('
'+(typeof info === "function" ? info.call(node) : info)+'
'; } From daca78b6cd078d859b51a10dbad471498ecc642b Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 19 Jan 2017 13:24:21 +0000 Subject: [PATCH 06/25] Ensure links do not span tabs in the editor --- editor/js/nodes.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/editor/js/nodes.js b/editor/js/nodes.js index e3a906bf6..40e9806ba 100644 --- a/editor/js/nodes.js +++ b/editor/js/nodes.js @@ -1004,9 +1004,13 @@ RED.nodes = (function() { var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]]; for (var w2=0;w2",node_map[wires[w2]].id); + } } } } From 540472a093ce6835cd1f3449b27d4a81ab9f9dee Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 19 Jan 2017 13:52:38 +0000 Subject: [PATCH 07/25] Ensure all a tags have blank target in info sidebar --- editor/js/ui/tab-info.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/editor/js/ui/tab-info.js b/editor/js/ui/tab-info.js index 156e29292..f96fbdbf3 100644 --- a/editor/js/ui/tab-info.js +++ b/editor/js/ui/tab-info.js @@ -66,6 +66,15 @@ RED.sidebar.info = (function() { return value; } + function addTargetToExternalLinks(el) { + $(el).find("a").each(function(el) { + var href = $(this).attr('href'); + if (/^https?:/.test(href)) { + $(this).attr('target','_blank'); + } + }); + return el; + } function refresh(node) { tips.stop(); $(content).empty(); @@ -117,15 +126,14 @@ RED.sidebar.info = (function() { $("
").appendTo(content); if (!subflowNode && node.type != "comment") { var helpText = $("script[data-help-name$='"+node.type+"']").html()||""; - $('
'+helpText+'
').appendTo(content); + addTargetToExternalLinks($('
'+helpText+'
').appendTo(content)); } if (subflowNode) { - $('
'+marked(subflowNode.info||"")+'
').appendTo(content); + addTargetToExternalLinks($('
'+marked(subflowNode.info||"")+'
').appendTo(content)); } else if (node._def && node._def.info) { var info = node._def.info; var textInfo = (typeof info === "function" ? info.call(node) : info); - var ma = marked(textInfo).replace(/href=/g, 'target="_blank" href='); - $('
'+ma+'
').appendTo(content); + addTargetToExternalLinks($('
'+marked(textInfo)+'
').appendTo(content)); //$('
'+(typeof info === "function" ? info.call(node) : info)+'
'; } From 3fdeb38bb74738d8097cf1b823558b151df062ff Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Sat, 21 Jan 2017 23:49:20 +0000 Subject: [PATCH 08/25] Ensure auth-tokens are removed when no user is specified in settings --- editor/js/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/js/settings.js b/editor/js/settings.js index ae01c66dc..b26dfe990 100644 --- a/editor/js/settings.js +++ b/editor/js/settings.js @@ -102,7 +102,7 @@ RED.settings = (function () { url: 'settings', success: function (data) { setProperties(data); - if (RED.settings.user && RED.settings.user.anonymous) { + if (!RED.settings.user || RED.settings.user.anonymous) { RED.settings.remove("auth-tokens"); } console.log("Node-RED: " + data.version); From fd6f7cd8813a5e5359ca5cfca4049003ab8d3bff Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 23 Jan 2017 13:57:06 +0000 Subject: [PATCH 09/25] Display debug node name in debug panel if its known --- nodes/core/core/58-debug.html | 2 +- nodes/core/core/lib/debug/debug-utils.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nodes/core/core/58-debug.html b/nodes/core/core/58-debug.html index d53623162..c421c0604 100644 --- a/nodes/core/core/58-debug.html +++ b/nodes/core/core/58-debug.html @@ -170,7 +170,7 @@ this.handleDebugMessage = function(t,o) { var sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z); if (sourceNode) { - o._source = {id:sourceNode.id,z:sourceNode.z}; + o._source = {id:sourceNode.id,z:sourceNode.z,name:sourceNode.name}; } RED.debug.handleDebugMessage(o); diff --git a/nodes/core/core/lib/debug/debug-utils.js b/nodes/core/core/lib/debug/debug-utils.js index ad18dab2c..9c9f48661 100644 --- a/nodes/core/core/lib/debug/debug-utils.js +++ b/nodes/core/core/lib/debug/debug-utils.js @@ -205,7 +205,7 @@ RED.debug = (function() { var metaRow = $('
').appendTo(msg); $(''+ getTimestamp()+'').appendTo(metaRow); if (sourceNode) { - $('',{href:"#",class:"debug-message-name"}).html('node: '+sourceNode.id) + $('',{href:"#",class:"debug-message-name"}).html('node: '+(sourceNode.name||sourceNode.id)) .appendTo(metaRow) .click(function(evt) { evt.preventDefault(); From 7759aacb359f479dfe3943fb22ba041f63eab18d Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 23 Jan 2017 15:34:34 +0000 Subject: [PATCH 10/25] Ensure custom mustache context parent set in Template node fixes #1126 --- nodes/core/core/80-template.js | 48 ++++++++++++++---------- test/nodes/core/core/80-template_spec.js | 12 ++++++ 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/nodes/core/core/80-template.js b/nodes/core/core/80-template.js index c03388b30..4637da06f 100644 --- a/nodes/core/core/80-template.js +++ b/nodes/core/core/80-template.js @@ -19,11 +19,11 @@ module.exports = function(RED) { var mustache = require("mustache"); /** - * Custom Mustache Context capable to resolve message property and node - * flow and global context + * Custom Mustache Context capable to resolve message property and node + * flow and global context */ - function NodeContext(msg, nodeContext) { - this.msgContext = new mustache.Context(msg); + function NodeContext(msg, nodeContext,parent) { + this.msgContext = new mustache.Context(msg,parent); this.nodeContext = nodeContext; } @@ -31,26 +31,34 @@ module.exports = function(RED) { NodeContext.prototype.lookup = function (name) { // try message first: - var value = this.msgContext.lookup(name); - if (value !== undefined) { - return value; - } - - // try node context: - var dot = name.indexOf("."); - if (dot > 0) { - var contextName = name.substr(0, dot); - var variableName = name.substr(dot + 1); - - if (contextName === "flow" && this.nodeContext.flow) { - return this.nodeContext.flow.get(variableName); + try { + var value = this.msgContext.lookup(name); + if (value !== undefined) { + return value; } - else if (contextName === "global" && this.nodeContext.global) { - return this.nodeContext.global.get(variableName); + + // try node context: + var dot = name.indexOf("."); + if (dot > 0) { + var contextName = name.substr(0, dot); + var variableName = name.substr(dot + 1); + + if (contextName === "flow" && this.nodeContext.flow) { + return this.nodeContext.flow.get(variableName); + } + else if (contextName === "global" && this.nodeContext.global) { + return this.nodeContext.global.get(variableName); + } } + }catch(err) { + throw err; } } + NodeContext.prototype.push = function push (view) { + return new NodeContext(view, this.nodeContext,this.msgContext); + }; + function TemplateNode(n) { RED.nodes.createNode(this,n); this.name = n.name; @@ -64,7 +72,7 @@ module.exports = function(RED) { try { var value; if (node.syntax === "mustache") { - value = mustache.render(node.template, new NodeContext(msg, node.context())); + value = mustache.render(node.template,new NodeContext(msg, node.context())); } else { value = node.template; } diff --git a/test/nodes/core/core/80-template_spec.js b/test/nodes/core/core/80-template_spec.js index 315aef6ae..811e7f179 100644 --- a/test/nodes/core/core/80-template_spec.js +++ b/test/nodes/core/core/80-template_spec.js @@ -166,6 +166,18 @@ describe('template node', function() { }); }); + it('should handle block contexts objects', function(done) { + var flow = [{id:"n1", type:"template", template: "A{{#payload.A}}{{payload.A}}{{.}}{{/payload.A}}B",wires:[["n2"]]},{id:"n2",type:"helper"}]; + helper.load(templateNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('payload','AabcabcB'); + done(); + }); + n1.receive({payload:{A:"abc"}}); + }); + }); it('should raise error if passed bad template', function(done) { var flow = [{id:"n1", type:"template", field: "payload", template: "payload={{payload",wires:[["n2"]]},{id:"n2",type:"helper"}]; helper.load(templateNode, flow, function() { From 1324f5e59c7f096551bd2c856e0c19dd337725ef Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 23 Jan 2017 15:57:37 +0000 Subject: [PATCH 11/25] Update CHANGELOG for 0.16.2 --- CHANGELOG.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7f67ba22..af804b1d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +#### 0.16.2: Maintenance Release + + - Ensure custom mustache context parent set in Template node fixes #1126 + - Display debug node name in debug panel if its known + - Ensure auth-tokens are removed when no user is specified in settings + - Ensure all a tags have blank target in info sidebar + - Ensure links do not span tabs in the editor + - Avoid creating multiple reconnect timers in websocket node + - Fix inner reference in install fail message catalog entry Fixes #1120 + - Display buffer data properly for truncated buffers under Object property + #### 0.16.1: Maintenance Release - Add colour swatches to debug when hex colour matched diff --git a/package.json b/package.json index 005b0e2ae..0c67d361d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name" : "node-red", - "version" : "0.16.1", + "version" : "0.16.2", "description" : "A visual tool for wiring the Internet of Things", "homepage" : "http://nodered.org", "license" : "Apache-2.0", From 939768eec0193b6dc907ae490cd863547fab0035 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Sat, 28 Jan 2017 14:44:47 +0000 Subject: [PATCH 12/25] Cache auth details to save needlessly recalculating hashes --- red.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/red.js b/red.js index 8969f2e4c..2cea58be2 100755 --- a/red.js +++ b/red.js @@ -196,6 +196,7 @@ try { function basicAuthMiddleware(user,pass) { var basicAuth = require('basic-auth'); var checkPassword; + var localCachedPassword; if (pass.length == "32") { // Assume its a legacy md5 password checkPassword = function(p) { @@ -207,12 +208,26 @@ function basicAuthMiddleware(user,pass) { } } + var checkPasswordAndCache = function(p) { + // For BasicAuth routes we know the password cannot change without + // a restart of Node-RED. This means we can cache the provided crypted + // version to save recalculating each time. + if (localCachedPassword === p) { + return true; + } + var result = checkPassword(p); + if (result) { + localCachedPassword = p; + } + return result; + } + return function(req,res,next) { if (req.method === 'OPTIONS') { return next(); } var requestUser = basicAuth(req); - if (!requestUser || requestUser.name !== user || !checkPassword(requestUser.pass)) { + if (!requestUser || requestUser.name !== user || !checkPasswordAndCache(requestUser.pass)) { res.set('WWW-Authenticate', 'Basic realm=Authorization Required'); return res.sendStatus(401); } From 0643f149b7e4ec778d7e95208fcb566373e93674 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 30 Jan 2017 09:37:08 +0000 Subject: [PATCH 13/25] Extract line number if available from node load errors --- red/runtime/nodes/registry/loader.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/red/runtime/nodes/registry/loader.js b/red/runtime/nodes/registry/loader.js index dd4292c23..cd7d038a2 100644 --- a/red/runtime/nodes/registry/loader.js +++ b/red/runtime/nodes/registry/loader.js @@ -315,6 +315,18 @@ function loadNodeSet(node) { return loadPromise; } catch(err) { node.err = err; + var stack = err.stack; + var message; + if (stack) { + var i = stack.indexOf(node.file); + if (i > -1) { + var excerpt = stack.substring(i+node.file.length+1,i+node.file.length+20); + var m = /^(\d+):(\d+)/.exec(excerpt); + if (m) { + node.err = err+" (line:"+m[1]+")"; + } + } + } return when.resolve(node); } } From 3e021b3a752e45c1f6e8cb653bb760d5365cdb8f Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 30 Jan 2017 09:58:43 +0000 Subject: [PATCH 14/25] Fix loader test to expect line numbers in load errors --- test/red/runtime/nodes/registry/loader_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/red/runtime/nodes/registry/loader_spec.js b/test/red/runtime/nodes/registry/loader_spec.js index 39b25d55e..7dddf826c 100644 --- a/test/red/runtime/nodes/registry/loader_spec.js +++ b/test/red/runtime/nodes/registry/loader_spec.js @@ -514,7 +514,7 @@ describe("red/nodes/registry/loader",function() { node.enabled.should.be.true(); nodes.registerType.called.should.be.false(); node.should.have.property('err'); - node.err.message.should.eql("fail to require"); + node.err.toString().should.eql("Error: fail to require (line:1)"); done(); }).otherwise(function(err) { From 94ee465682e90adb3f0b0edac99b4dc74d87cdc2 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 2 Feb 2017 09:57:01 +0000 Subject: [PATCH 15/25] clone message before send in stay connected mode to Fix #1137 --- nodes/core/io/31-tcpin.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nodes/core/io/31-tcpin.js b/nodes/core/io/31-tcpin.js index 709552f28..4a9650802 100644 --- a/nodes/core/io/31-tcpin.js +++ b/nodes/core/io/31-tcpin.js @@ -444,7 +444,7 @@ module.exports = function(RED) { //node.log(RED._("tcpin.errors.client-connected")); node.status({fill:"green",shape:"dot",text:"common.status.connected"}); if (clients[connection_id] && clients[connection_id].client) { - clients[connection_id].connected = true; + clients[connection_id].connected = true; clients[connection_id].client.write(clients[connection_id].msg.payload); } }); @@ -454,10 +454,10 @@ module.exports = function(RED) { } clients[connection_id].client.on('data', function(data) { - if (node.out == "sit") { // if we are staying connected just send the buffer + if (node.out === "sit") { // if we are staying connected just send the buffer if (clients[connection_id]) { clients[connection_id].msg.payload = data; - node.send(clients[connection_id].msg); + node.send(RED.util.cloneMessage(clients[connection_id].msg)); } } else if (node.splitc === 0) { @@ -533,7 +533,7 @@ module.exports = function(RED) { //console.log("END"); node.status({fill:"grey",shape:"ring",text:"common.status.disconnected"}); if (clients[connection_id] && clients[connection_id].client) { - clients[connection_id].connected = false; + clients[connection_id].connected = false; clients[connection_id].client = null; } }); @@ -541,7 +541,7 @@ module.exports = function(RED) { clients[connection_id].client.on('close', function() { //console.log("CLOSE"); if (clients[connection_id]) { - clients[connection_id].connected = false; + clients[connection_id].connected = false; } var anyConnected = false; From 2913e13a30ddbc35e2ca2e17629a60d7ba2b0428 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 13 Feb 2017 21:39:31 +0000 Subject: [PATCH 16/25] Misconfigured WebSocket nodes should not register msg handlers --- nodes/core/io/22-websocket.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nodes/core/io/22-websocket.js b/nodes/core/io/22-websocket.js index a42c3fcf9..fe1b47fdf 100644 --- a/nodes/core/io/22-websocket.js +++ b/nodes/core/io/22-websocket.js @@ -212,7 +212,9 @@ module.exports = function(RED) { this.error(RED._("websocket.errors.missing-conf")); } this.on('close', function() { - node.serverConfig.removeInputNode(node); + if (node.serverConfig) { + node.serverConfig.removeInputNode(node); + } node.status({}); }); } @@ -224,7 +226,7 @@ module.exports = function(RED) { this.server = (n.client)?n.client:n.server; this.serverConfig = RED.nodes.getNode(this.server); if (!this.serverConfig) { - this.error(RED._("websocket.errors.missing-conf")); + return this.error(RED._("websocket.errors.missing-conf")); } else { // TODO: nls From b24fac3dd88a67ca769b479638e98217dbc290c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Unneb=C3=A4ck?= Date: Thu, 16 Feb 2017 16:28:00 +0100 Subject: [PATCH 17/25] Use textContent to avoid manual escaping --- editor/js/ui/view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/js/ui/view.js b/editor/js/ui/view.js index f94059d71..346d53509 100644 --- a/editor/js/ui/view.js +++ b/editor/js/ui/view.js @@ -1297,7 +1297,7 @@ RED.view = (function() { sp.className = className; sp.style.position = "absolute"; sp.style.top = "-1000px"; - sp.innerHTML = (str||"").replace(/&/g,"&").replace(//g,">"); + sp.textContent = (str||""); document.body.appendChild(sp); var w = sp.offsetWidth; document.body.removeChild(sp); From 37dd075309ef8709d90e90615d1049130ef25300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Unneb=C3=A4ck?= Date: Thu, 16 Feb 2017 18:05:59 +0100 Subject: [PATCH 18/25] Use pre-calculated values for connection path --- editor/js/ui/view.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/editor/js/ui/view.js b/editor/js/ui/view.js index 346d53509..56e616feb 100644 --- a/editor/js/ui/view.js +++ b/editor/js/ui/view.js @@ -2227,10 +2227,10 @@ RED.view = (function() { d.x2 = d.target.x-d.target.w/2; d.y2 = d.target.y; - return "M "+(d.source.x+d.source.w/2)+" "+(d.source.y+y)+ - " C "+(d.source.x+d.source.w/2+scale*node_width)+" "+(d.source.y+y+scaleY*node_height)+" "+ - (d.target.x-d.target.w/2-scale*node_width)+" "+(d.target.y-scaleY*node_height)+" "+ - (d.target.x-d.target.w/2)+" "+d.target.y; + return "M "+d.x1+" "+d.y1+ + " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+ + (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+ + d.x2+" "+d.y2; }); } }) From e2a9be9cec62660060233d9f522efd75dc4c1915 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Feb 2017 21:41:20 +0000 Subject: [PATCH 19/25] Defer resizing tray components until they have finished building --- editor/js/ui/editor.js | 118 ++++++++++++++++++++------------------ editor/js/ui/tray.js | 125 ++++++++++++++++++----------------------- 2 files changed, 118 insertions(+), 125 deletions(-) diff --git a/editor/js/ui/editor.js b/editor/js/ui/editor.js index de1e691dd..8b772d8f6 100644 --- a/editor/js/ui/editor.js +++ b/editor/js/ui/editor.js @@ -430,7 +430,7 @@ RED.editor = (function() { * @param definition - the node definition * @param prefix - the prefix to use in the input element ids (node-input|node-config-input) */ - function prepareEditDialog(node,definition,prefix) { + function prepareEditDialog(node,definition,prefix,done) { for (var d in definition.defaults) { if (definition.defaults.hasOwnProperty(d)) { if (definition.defaults[d].type) { @@ -474,6 +474,9 @@ RED.editor = (function() { } } validateNodeEditor(node,prefix); + if (done) { + done(); + } } if (definition.credentials) { @@ -791,7 +794,7 @@ RED.editor = (function() { } } }, - open: function(tray) { + open: function(tray,done) { if (editing_node) { RED.sidebar.info.refresh(editing_node); } @@ -802,8 +805,11 @@ RED.editor = (function() { ns = node._def.set.id; } var dialogForm = buildEditForm(tray,"dialog-form",type,ns); - prepareEditDialog(node,node._def,"node-input"); - dialogForm.i18n(); + prepareEditDialog(node,node._def,"node-input", function() { + dialogForm.i18n(); + done(); + }); + }, close: function() { if (RED.view.state() != RED.state.IMPORT_DRAGGING) { @@ -888,11 +894,11 @@ RED.editor = (function() { try { editing_config_node._def.oneditresize.call(editing_config_node,{width:form.width(),height:form.height()}); } catch(err) { - console.log("oneditresize",editing_node.id,editing_node.type,err.toString()); + console.log("oneditresize",editing_config_node.id,editing_config_node.type,err.toString()); } } }, - open: function(tray) { + open: function(tray, done) { var trayHeader = tray.find(".editor-tray-header"); var trayFooter = tray.find(".editor-tray-footer"); @@ -903,58 +909,60 @@ RED.editor = (function() { var dialogForm = buildEditForm(tray,"node-config-dialog-edit-form",type,ns); - prepareEditDialog(editing_config_node,node_def,"node-config-input"); - if (editing_config_node._def.exclusive) { - $("#node-config-dialog-scope").hide(); - } else { - $("#node-config-dialog-scope").show(); - } - $("#node-config-dialog-scope-warning").hide(); + prepareEditDialog(editing_config_node,node_def,"node-config-input", function() { + if (editing_config_node._def.exclusive) { + $("#node-config-dialog-scope").hide(); + } else { + $("#node-config-dialog-scope").show(); + } + $("#node-config-dialog-scope-warning").hide(); - var nodeUserFlows = {}; - editing_config_node.users.forEach(function(n) { - nodeUserFlows[n.z] = true; - }); - var flowCount = Object.keys(nodeUserFlows).length; - var tabSelect = $("#node-config-dialog-scope").empty(); - tabSelect.off("change"); - tabSelect.append(''); - tabSelect.append(''); - RED.nodes.eachWorkspace(function(ws) { - var workspaceLabel = ws.label; - if (nodeUserFlows[ws.id]) { - workspaceLabel = "* "+workspaceLabel; - } - tabSelect.append(''); - }); - tabSelect.append(''); - RED.nodes.eachSubflow(function(ws) { - var workspaceLabel = ws.name; - if (nodeUserFlows[ws.id]) { - workspaceLabel = "* "+workspaceLabel; - } - tabSelect.append(''); - }); - if (flowCount > 0) { - tabSelect.on('change',function() { - var newScope = $(this).val(); - if (newScope === '') { - // global scope - everyone can use it - $("#node-config-dialog-scope-warning").hide(); - } else if (!nodeUserFlows[newScope] || flowCount > 1) { - // a user will loose access to it - $("#node-config-dialog-scope-warning").show(); - } else { - $("#node-config-dialog-scope-warning").hide(); - } + var nodeUserFlows = {}; + editing_config_node.users.forEach(function(n) { + nodeUserFlows[n.z] = true; }); - } - tabSelect.i18n(); + var flowCount = Object.keys(nodeUserFlows).length; + var tabSelect = $("#node-config-dialog-scope").empty(); + tabSelect.off("change"); + tabSelect.append(''); + tabSelect.append(''); + RED.nodes.eachWorkspace(function(ws) { + var workspaceLabel = ws.label; + if (nodeUserFlows[ws.id]) { + workspaceLabel = "* "+workspaceLabel; + } + tabSelect.append(''); + }); + tabSelect.append(''); + RED.nodes.eachSubflow(function(ws) { + var workspaceLabel = ws.name; + if (nodeUserFlows[ws.id]) { + workspaceLabel = "* "+workspaceLabel; + } + tabSelect.append(''); + }); + if (flowCount > 0) { + tabSelect.on('change',function() { + var newScope = $(this).val(); + if (newScope === '') { + // global scope - everyone can use it + $("#node-config-dialog-scope-warning").hide(); + } else if (!nodeUserFlows[newScope] || flowCount > 1) { + // a user will loose access to it + $("#node-config-dialog-scope-warning").show(); + } else { + $("#node-config-dialog-scope-warning").hide(); + } + }); + } + tabSelect.i18n(); - dialogForm.i18n(); - if (node_def.hasUsers !== false) { - $("#node-config-dialog-user-count").find("span").html(RED._("editor.nodesUse", {count:editing_config_node.users.length})).parent().show(); - } + dialogForm.i18n(); + if (node_def.hasUsers !== false) { + $("#node-config-dialog-user-count").find("span").html(RED._("editor.nodesUse", {count:editing_config_node.users.length})).parent().show(); + } + done(); + }); }, close: function() { RED.workspaces.refresh(); diff --git a/editor/js/ui/tray.js b/editor/js/ui/tray.js index 8ba3d4e97..397a0b154 100644 --- a/editor/js/ui/tray.js +++ b/editor/js/ui/tray.js @@ -104,85 +104,70 @@ RED.tray = (function() { } }); - if (options.open) { - options.open(el); - } + function finishBuild() { + $("#header-shade").show(); + $("#editor-shade").show(); + $("#palette-shade").show(); + $(".sidebar-shade").show(); - $("#header-shade").show(); - $("#editor-shade").show(); - $("#palette-shade").show(); - $(".sidebar-shade").show(); + tray.preferredWidth = Math.max(el.width(),500); + body.css({"minWidth":tray.preferredWidth-40}); - tray.preferredWidth = Math.max(el.width(),500); - body.css({"minWidth":tray.preferredWidth-40}); - - if (options.width) { - if (options.width > $("#editor-stack").position().left-8) { - options.width = $("#editor-stack").position().left-8; + if (options.width) { + if (options.width > $("#editor-stack").position().left-8) { + options.width = $("#editor-stack").position().left-8; + } + el.width(options.width); + } else { + el.width(tray.preferredWidth); } - el.width(options.width); - } else { - el.width(tray.preferredWidth); - } - tray.width = el.width(); - if (tray.width > $("#editor-stack").position().left-8) { - tray.width = Math.max(0/*tray.preferredWidth*/,$("#editor-stack").position().left-8); - el.width(tray.width); - } + tray.width = el.width(); + if (tray.width > $("#editor-stack").position().left-8) { + tray.width = Math.max(0/*tray.preferredWidth*/,$("#editor-stack").position().left-8); + el.width(tray.width); + } - // tray.body.parent().width(Math.min($("#editor-stack").position().left-8,tray.width)); + // tray.body.parent().width(Math.min($("#editor-stack").position().left-8,tray.width)); - el.css({ - right: -(el.width()+10)+"px", - transition: "right 0.25s ease" - }); - $("#workspace").scrollLeft(0); - handleWindowResize(); - openingTray = true; - setTimeout(function() { + el.css({ + right: -(el.width()+10)+"px", + transition: "right 0.25s ease" + }); + $("#workspace").scrollLeft(0); + handleWindowResize(); + openingTray = true; setTimeout(function() { - if (!options.width) { - el.width(Math.min(tray.preferredWidth,$("#editor-stack").position().left-8)); - } - if (options.resize) { - options.resize({width:el.width()}); - } - if (options.show) { - options.show(); - } setTimeout(function() { - // Delay resetting the flag, so we don't close prematurely - openingTray = false; - },200); - body.find(":focusable:first").focus(); - - },150); - el.css({right:0}); - },0); - - // growButton.click(function(e) { - // e.preventDefault(); - // tray.lastWidth = tray.width; - // tray.width = $("#editor-stack").position().left-8; - // el.width(tray.width); - // if (options.resize) { - // options.resize({width:tray.width}); - // } - // }); - // shrinkButton.click(function(e) { - // e.preventDefault(); - // if (tray.lastWidth && tray.width > tray.lastWidth) { - // tray.width = tray.lastWidth; - // } else if (tray.width > tray.preferredWidth) { - // tray.width = tray.preferredWidth; - // } - // el.width(tray.width); - // if (options.resize) { - // options.resize({width:tray.width}); - // } - // }); + if (!options.width) { + el.width(Math.min(tray.preferredWidth,$("#editor-stack").position().left-8)); + } + if (options.resize) { + options.resize({width:el.width()}); + } + if (options.show) { + options.show(); + } + setTimeout(function() { + // Delay resetting the flag, so we don't close prematurely + openingTray = false; + },200); + body.find(":focusable:first").focus(); + },150); + el.css({right:0}); + },0); + } + if (options.open) { + if (options.open.length === 1) { + options.open(el); + finishBuild(); + } else { + options.open(el,finishBuild); + } + } else { + finishBuild(); + } } function handleWindowResize() { From a625eeeac87dbaef69744fda67cf0a412daa8a77 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Wed, 22 Feb 2017 20:19:44 +0000 Subject: [PATCH 20/25] move csv fixes to master to fix #1142 in master --- nodes/core/parsers/70-CSV.js | 29 +++++++++++++++++++++++--- test/nodes/core/parsers/70-CSV_spec.js | 24 ++++++++++++++++----- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/nodes/core/parsers/70-CSV.js b/nodes/core/parsers/70-CSV.js index f02a8d080..fa1ece00f 100644 --- a/nodes/core/parsers/70-CSV.js +++ b/nodes/core/parsers/70-CSV.js @@ -28,6 +28,7 @@ module.exports = function(RED) { this.hdrin = n.hdrin || false; this.hdrout = n.hdrout || false; this.goodtmpl = true; + var tmpwarn = true; var node = this; // pass in an array of column names to be trimed, de-quoted and retrimed @@ -71,7 +72,27 @@ module.exports = function(RED) { } else { if ((node.template.length === 1) && (node.template[0] === '')) { - node.warn(RED._("csv.errors.obj_csv")); + if (tmpwarn === true) { // just warn about missing template once + node.warn(RED._("csv.errors.obj_csv")); + tmpwarn = false; + } + ou = ""; + for (var p in msg.payload[0]) { + if (msg.payload[0].hasOwnProperty(p)) { + if (typeof msg.payload[0][p] !== "object") { + var q = msg.payload[0][p]; + if (q.indexOf(node.quo) !== -1) { // add double quotes if any quotes + q = q.replace(/"/g, '""'); + ou += node.quo + q + node.quo + node.sep; + } + else if (q.indexOf(node.sep) !== -1) { // add quotes if any "commas" + ou += node.quo + q + node.quo + node.sep; + } + else { ou += q + node.sep; } // otherwise just add + } + } + } + ou = ou.slice(0,-1) + node.ret; } else { for (var t=0; t < node.template.length; t++) { @@ -112,7 +133,7 @@ module.exports = function(RED) { var first = true; // is this the first line var line = msg.payload; var tmp = ""; - var reg = new RegExp("^[-]?[0-9.]*[\.]?[0-9]*$"); + var reg = /^[-]?[0-9]*\.?[0-9]+$/; // For now we are just going to assume that any \r or \n means an end of line... // got to be a weird csv that has singleton \r \n in it for another reason... @@ -129,7 +150,9 @@ module.exports = function(RED) { else { if (line[i] === node.quo) { // if it's a quote toggle inside or outside f = !f; - if (line[i-1] === node.quo) { k[j] += '\"'; } // if it's a quotequote then it's actually a quote + if (line[i-1] === node.quo) { + if (f === false) { k[j] += '\"'; } + } // if it's a quotequote then it's actually a quote //if ((line[i-1] !== node.sep) && (line[i+1] !== node.sep)) { k[j] += line[i]; } } else if ((line[i] === node.sep) && f) { // if it is the end of the line then finish diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js index 5c7f2b3dd..98a842e30 100644 --- a/test/nodes/core/parsers/70-CSV_spec.js +++ b/test/nodes/core/parsers/70-CSV_spec.js @@ -119,7 +119,7 @@ describe('CSV node', function() { msg.should.have.property('payload', { a: 1, b: -2, c: '+3', d: 4, e: -5, f: 'ab"cd', g: 'with,a,comma' }); done(); }); - var testString = '"1","-2","+3","04","-05",ab""cd,"with,a,comma"'+String.fromCharCode(10); + var testString = '"1","-2","+3","04","-05","ab""cd","with,a,comma"'+String.fromCharCode(10); n1.emit("input", {payload:testString}); }); }); @@ -132,12 +132,11 @@ describe('CSV node', function() { var n2 = helper.getNode("n2"); n2.on("input", function(msg) { //console.log(msg); - msg.should.have.property('payload', { a: 1, b: -2, c: '+3', d: 4, e: -5, f: 'ab"cd', g: 'with,a,comma' }); + msg.should.have.property('payload', { a: "with,an", b: "odd,number", c: "ofquotes" }); + //msg.should.have.property('payload', { a: 1, b: -2, c: '+3', d: 4, e: -5, f: 'ab"cd', g: 'with,a,comma' }); done(); }); - var testString = '"with,a"n,odd",num"ber,of"quotes'+String.fromCharCode(10); - n1.emit("input", {payload:testString}); - testString = '"1","-2","+3","04","-05",ab""cd,"with,a,comma"'+String.fromCharCode(10); + var testString = '"with,a"n,odd","num"ber","of"qu"ot"es"'+String.fromCharCode(10); n1.emit("input", {payload:testString}); }); }); @@ -179,6 +178,21 @@ describe('CSV node', function() { n1.emit("input", {payload:testString}); }); }); + + it('should handle numbers in strings but not IP addresses', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('payload', { a: "a", b: "127.0.0.1", c: 56.7, d: -32.8, e: "+76.22C" }); + done(); + }); + var testString = "a,127.0.0.1,56.7,-32.8,+76.22C"; + n1.emit("input", {payload:testString}); + }); + }); }); describe('json object to csv', function() { From ee0bd49918db1becdb0b166c8e43340c6b06402b Mon Sep 17 00:00:00 2001 From: cinhcet Date: Wed, 22 Feb 2017 23:22:06 +0100 Subject: [PATCH 21/25] exec node returns 0 on the third output if command ended without error. (#1160) * exec node returns 0 on the third output if command ended without error. Otherwise, the status of the node is updated and the error code is send through the third output. * info text updated and the second output returns only something if stderr is not empty * proper stderror handling * proper handling of stderr --- nodes/core/core/75-exec.html | 5 ++++- nodes/core/core/75-exec.js | 12 ++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/nodes/core/core/75-exec.html b/nodes/core/core/75-exec.html index 0f3a11c59..6aa423fb4 100644 --- a/nodes/core/core/75-exec.html +++ b/nodes/core/core/75-exec.html @@ -47,10 +47,13 @@

Calls out to a system command.

Provides 3 outputs: stdout, stderr, and return code.

By default uses the exec system call which calls the command, then gets a callback - on completion, returning the complete result in one message, along with any errors.

+ on completion, returning stdout as the payload to the first, the error code as the third + and, if available, stderr to the second output. If no error occurred, a zero is returned on the third output.

Optionally can use spawn instead, which returns the output from stdout and stderr as the command runs (usually one line at a time). On completion it then returns a return code (on the 3rd output).

+

The exec method spawns a subshell and therefore can be used for more complicated + commands involving pipes. However, it waits for completion of the whole command before returing anything.

The optional append gets added to the command after msg.payload - so you can do things like pipe the result to another command.

Commands or parameters with spaces should be enclosed in quotes - "This is a single parameter"

diff --git a/nodes/core/core/75-exec.js b/nodes/core/core/75-exec.js index 546a3f130..0847957a4 100644 --- a/nodes/core/core/75-exec.js +++ b/nodes/core/core/75-exec.js @@ -102,15 +102,19 @@ module.exports = function(RED) { child = exec(cl, {encoding: 'binary', maxBuffer:10000000}, function (error, stdout, stderr) { msg.payload = new Buffer(stdout,"binary"); if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); } - var msg2 = {payload:stderr}; - var msg3 = null; + var msg2 = null; + if(stderr) { + msg2 = {payload: stderr}; + } + var msg3 = {payload:0}; + node.status({}); //console.log('[exec] stdout: ' + stdout); //console.log('[exec] stderr: ' + stderr); if (error !== null) { - msg3 = {payload:error}; + msg3 = {payload:error.code}; + node.status({fill:"red",shape:"dot",text:"error: "+error.code}); //console.log('[exec] error: ' + error); } - node.status({}); node.send([msg,msg2,msg3]); if (child.tout) { clearTimeout(child.tout); } delete node.activeProcesses[child.pid]; From 8b31a918a4ef352f4b36086592844d181ad9cddc Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 27 Feb 2017 19:22:02 +0000 Subject: [PATCH 22/25] Fix Pi GPIO debounce To close #1139 --- nodes/core/hardware/36-rpi-gpio.html | 3 --- nodes/core/hardware/36-rpi-gpio.js | 11 ++++++++--- nodes/core/hardware/nrgpio.py | 6 +++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/nodes/core/hardware/36-rpi-gpio.html b/nodes/core/hardware/36-rpi-gpio.html index 627129364..37c63fafd 100644 --- a/nodes/core/hardware/36-rpi-gpio.html +++ b/nodes/core/hardware/36-rpi-gpio.html @@ -218,7 +218,6 @@ var pinsInUse = {}; RED.nodes.registerType('rpi-gpio out',{ category: 'Raspberry Pi', - label: 'Raspberry Pi', color:"#c6dbef", defaults: { name: { value:"" }, @@ -351,7 +350,6 @@