From ba0823c38c3f80357ae608f268e502f98687a4be Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Thu, 23 Mar 2017 19:48:48 +0000 Subject: [PATCH 001/104] Add support for rejectUnauthorized msg property This update lets you pass msg.rejectUnauthorized=false to allow you to connect to https sites that don't have certs signed by recognised CAs --- nodes/core/io/21-httprequest.html | 1 + nodes/core/io/21-httprequest.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/nodes/core/io/21-httprequest.html b/nodes/core/io/21-httprequest.html index 0c23d0f11..aa83831fc 100644 --- a/nodes/core/io/21-httprequest.html +++ b/nodes/core/io/21-httprequest.html @@ -79,6 +79,7 @@
  • headers, if set, should be an object containing field/value pairs to be added as request headers
  • payload is sent as the body of the request
  • +
  • rejectUnauthorized allows requests to be made
  • When configured within the node, the URL property can contain mustache-style tags. These allow the url to be constructed using values of the incoming message. For example, if the url is set to diff --git a/nodes/core/io/21-httprequest.js b/nodes/core/io/21-httprequest.js index d6d6f42de..56f377872 100644 --- a/nodes/core/io/21-httprequest.js +++ b/nodes/core/io/21-httprequest.js @@ -154,6 +154,10 @@ module.exports = function(RED) { } if (tlsNode) { tlsNode.addTLSOptions(opts); + } else { + if (msg.hasOwnProperty('rejectUnauthorized')) { + opts.rejectUnauthorized = msg.rejectUnauthorized; + } } var req = ((/^https/.test(urltotest))?https:http).request(opts,function(res) { (node.ret === "bin") ? res.setEncoding('binary') : res.setEncoding('utf8'); From 8cc9aeba4a180ec8480d4f14cce2349c0d2e71ac Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Thu, 23 Mar 2017 20:06:11 +0000 Subject: [PATCH 002/104] Fix docs --- nodes/core/io/21-httprequest.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodes/core/io/21-httprequest.html b/nodes/core/io/21-httprequest.html index aa83831fc..557b51c07 100644 --- a/nodes/core/io/21-httprequest.html +++ b/nodes/core/io/21-httprequest.html @@ -79,7 +79,7 @@

  • headers, if set, should be an object containing field/value pairs to be added as request headers
  • payload is sent as the body of the request
  • -
  • rejectUnauthorized allows requests to be made
  • +
  • rejectUnauthorized allows requests to be made to https sites that use self signed certificates
  • When configured within the node, the URL property can contain mustache-style tags. These allow the url to be constructed using values of the incoming message. For example, if the url is set to From 8dcc114873e6bf5f5089dd678c2f2454c9796a9b Mon Sep 17 00:00:00 2001 From: Simon Hailes Date: Wed, 12 Apr 2017 18:31:49 +0100 Subject: [PATCH 003/104] MQTT node - if Server/URL config contains '//' use it as a complete url; enabled ws:// and wss:// --- nodes/core/io/10-mqtt.html | 37 +++++++++++++++++++++++++++++++++---- nodes/core/io/10-mqtt.js | 28 ++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/nodes/core/io/10-mqtt.html b/nodes/core/io/10-mqtt.html index 37c065b9f..1f11a3439 100644 --- a/nodes/core/io/10-mqtt.html +++ b/nodes/core/io/10-mqtt.html @@ -173,7 +173,7 @@ - +

    @@ -256,7 +256,8 @@ @@ -266,7 +267,7 @@ category: 'config', defaults: { broker: {value:"",required:true}, - port: {value:1883,required:true,validate:RED.validators.number()}, + port: {value:1883,required:false,validate:function(v){ return RED.validators.number() || (v === null) }}, tls: {type:"tls-config",required: false}, clientid: {value:"", validate: function(v) { if ($("#node-config-input-clientid").length) { @@ -297,7 +298,13 @@ label: function() { var b = this.broker; if (b === "") { b = "undefined"; } - return (this.clientid?this.clientid+"@":"")+b+":"+this.port; + var lab = ""; + lab = (this.clientid?this.clientid+"@":"")+b; + if (b.indexOf("://") === -1){ + if (!this.port){ lab = lab + ":1883"; } + else { lab = lab + ":" + this.port; } + } + return lab; }, oneditprepare: function () { var tabs = RED.tabs.create({ @@ -373,6 +380,28 @@ $("#node-config-input-cleansession").on("click",function() { updateClientId(); }); + + function updatePortEntry(){ + var disabled = $("#node-config-input-port").prop("disabled"); + if ($("#node-config-input-broker").val().indexOf("://") === -1){ + if (disabled){ + $("#node-config-input-port").prop("disabled", false); + } + } + else { + if (!disabled){ + $("#node-config-input-port").prop("disabled", true); + } + } + } + $("#node-config-input-broker").change(function() { + updatePortEntry(); + }); + $("#node-config-input-broker").on( "keyup", function() { + updatePortEntry(); + }); + setTimeout(updatePortEntry()); + }, oneditsave: function() { if (!$("#node-config-input-usetls").is(':checked')) { diff --git a/nodes/core/io/10-mqtt.js b/nodes/core/io/10-mqtt.js index b01df652b..87c7bfb13 100644 --- a/nodes/core/io/10-mqtt.js +++ b/nodes/core/io/10-mqtt.js @@ -86,15 +86,27 @@ module.exports = function(RED) { // Create the URL to pass in to the MQTT.js library if (this.brokerurl === "") { - if (this.usetls) { - this.brokerurl="mqtts://"; + // if the broken may be ws:// or wss:// or even tcp:// + if (this.broker.indexOf("://") > -1) { + this.brokerurl = this.broker; } else { - this.brokerurl="mqtt://"; - } - if (this.broker !== "") { - this.brokerurl = this.brokerurl+this.broker+":"+this.port; - } else { - this.brokerurl = this.brokerurl+"localhost:1883"; + // construct the std mqtt:// url + if (this.usetls) { + this.brokerurl="mqtts://"; + } else { + this.brokerurl="mqtt://"; + } + if (this.broker !== "") { + this.brokerurl = this.brokerurl+this.broker+":"; + // port now defaults to 1883 if unset. + if (!this.port){ + this.brokerurl = this.brokerurl+"1883"; + } else { + this.brokerurl = this.brokerurl+this.port; + } + } else { + this.brokerurl = this.brokerurl+"localhost:1883"; + } } } From e09efba313ab8724fd03cbb8cc22e26dd7663f6b Mon Sep 17 00:00:00 2001 From: btsimonh Date: Wed, 9 Aug 2017 22:22:40 +0100 Subject: [PATCH 004/104] mqtt: Add 'name' to mqtt-broker node, and label it by this if it is set. (#1364) This allows you to easily distinguish between broker nodes which are talking to the same server but with different credentials. --- nodes/core/io/10-mqtt.html | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/nodes/core/io/10-mqtt.html b/nodes/core/io/10-mqtt.html index b778f6c77..4e4613ba7 100644 --- a/nodes/core/io/10-mqtt.html +++ b/nodes/core/io/10-mqtt.html @@ -165,6 +165,10 @@ \n\ -snippet scriptsrc\n\ - \n\ -snippet section\n\ -
    \n\ - ${1}\n\ -
    \n\ -snippet section.\n\ -
    \n\ - ${2}\n\ -
    \n\ -snippet section#\n\ -
    \n\ - ${2}\n\ -
    \n\ -snippet select\n\ - \n\ -snippet select.\n\ - \n\ -snippet select+\n\ - \n\ -snippet small\n\ - ${1}\n\ -snippet source\n\ - \n\ -snippet span\n\ - ${1}\n\ -snippet strong\n\ - ${1}\n\ -snippet style\n\ - \n\ -snippet sub\n\ - ${1}\n\ -snippet summary\n\ - \n\ - ${1}\n\ - \n\ -snippet sup\n\ - ${1}\n\ -snippet table\n\ - \n\ - ${2}\n\ -
    \n\ -snippet table.\n\ - \n\ - ${3}\n\ -
    \n\ -snippet table#\n\ - \n\ - ${3}\n\ -
    \n\ -snippet tbody\n\ - \n\ - ${1}\n\ - \n\ -snippet td\n\ - ${1}\n\ -snippet td.\n\ - ${2}\n\ -snippet td#\n\ - ${2}\n\ -snippet td+\n\ - ${1}\n\ - td+${2}\n\ -snippet textarea\n\ - ${6}\n\ -snippet tfoot\n\ - \n\ - ${1}\n\ - \n\ -snippet th\n\ - ${1}\n\ -snippet th.\n\ - ${2}\n\ -snippet th#\n\ - ${2}\n\ -snippet th+\n\ - ${1}\n\ - th+${2}\n\ -snippet thead\n\ - \n\ - ${1}\n\ - \n\ -snippet time\n\ - \n\ -snippet title\n\ - ${1:`substitute(Filename('', 'Page Title'), '^.', '\\u&', '')`}\n\ -snippet tr\n\ - \n\ - ${1}\n\ - \n\ -snippet tr+\n\ - \n\ - ${1}\n\ - td+${2}\n\ - \n\ -snippet track\n\ - ${5}${6}\n\ -snippet ul\n\ -
      \n\ - ${1}\n\ -
    \n\ -snippet ul.\n\ -
      \n\ - ${2}\n\ -
    \n\ -snippet ul#\n\ -
      \n\ - ${2}\n\ -
    \n\ -snippet ul+\n\ -
      \n\ -
    • ${1}
    • \n\ - li+${2}\n\ -
    \n\ -snippet var\n\ - ${1}\n\ -snippet video\n\ - ${8}\n\ -snippet wbr\n\ - ${1}\n\ -"; -exports.scope = "html"; - -}); +ace.define("ace/snippets/html",["require","exports","module"],function(e,t,n){"use strict";t.snippetText='# Some useful Unicode entities\n# Non-Breaking Space\nsnippet nbs\n  \n# \u2190\nsnippet left\n ←\n# \u2192\nsnippet right\n →\n# \u2191\nsnippet up\n ↑\n# \u2193\nsnippet down\n ↓\n# \u21a9\nsnippet return\n ↩\n# \u21e4\nsnippet backtab\n ⇤\n# \u21e5\nsnippet tab\n ⇥\n# \u21e7\nsnippet shift\n ⇧\n# \u2303\nsnippet ctrl\n ⌃\n# \u2305\nsnippet enter\n ⌅\n# \u2318\nsnippet cmd\n ⌘\n# \u2325\nsnippet option\n ⌥\n# \u2326\nsnippet delete\n ⌦\n# \u232b\nsnippet backspace\n ⌫\n# \u238b\nsnippet esc\n ⎋\n# Generic Doctype\nsnippet doctype HTML 4.01 Strict\n \nsnippet doctype HTML 4.01 Transitional\n \nsnippet doctype HTML 5\n \nsnippet doctype XHTML 1.0 Frameset\n \nsnippet doctype XHTML 1.0 Strict\n \nsnippet doctype XHTML 1.0 Transitional\n \nsnippet doctype XHTML 1.1\n \n# HTML Doctype 4.01 Strict\nsnippet docts\n \n# HTML Doctype 4.01 Transitional\nsnippet doct\n \n# HTML Doctype 5\nsnippet doct5\n \n# XHTML Doctype 1.0 Frameset\nsnippet docxf\n \n# XHTML Doctype 1.0 Strict\nsnippet docxs\n \n# XHTML Doctype 1.0 Transitional\nsnippet docxt\n \n# XHTML Doctype 1.1\nsnippet docx\n \n# Attributes\nsnippet attr\n ${1:attribute}="${2:property}"\nsnippet attr+\n ${1:attribute}="${2:property}" attr+${3}\nsnippet .\n class="${1}"${2}\nsnippet #\n id="${1}"${2}\nsnippet alt\n alt="${1}"${2}\nsnippet charset\n charset="${1:utf-8}"${2}\nsnippet data\n data-${1}="${2:$1}"${3}\nsnippet for\n for="${1}"${2}\nsnippet height\n height="${1}"${2}\nsnippet href\n href="${1:#}"${2}\nsnippet lang\n lang="${1:en}"${2}\nsnippet media\n media="${1}"${2}\nsnippet name\n name="${1}"${2}\nsnippet rel\n rel="${1}"${2}\nsnippet scope\n scope="${1:row}"${2}\nsnippet src\n src="${1}"${2}\nsnippet title=\n title="${1}"${2}\nsnippet type\n type="${1}"${2}\nsnippet value\n value="${1}"${2}\nsnippet width\n width="${1}"${2}\n# Elements\nsnippet a\n ${2:$1}\nsnippet a.\n ${3:$1}\nsnippet a#\n ${3:$1}\nsnippet a:ext\n ${2:$1}\nsnippet a:mail\n ${3:email me}\nsnippet abbr\n ${2}\nsnippet address\n
    \n ${1}\n
    \nsnippet area\n ${4}\nsnippet area+\n ${4}\n area+${5}\nsnippet area:c\n ${3}\nsnippet area:d\n ${3}\nsnippet area:p\n ${3}\nsnippet area:r\n ${3}\nsnippet article\n
    \n ${1}\n
    \nsnippet article.\n
    \n ${2}\n
    \nsnippet article#\n
    \n ${2}\n
    \nsnippet aside\n \nsnippet aside.\n \nsnippet aside#\n \nsnippet audio\n

    +
    + + +
    @@ -103,6 +110,7 @@ extend: {value:"false"}, units: {value:"ms"}, reset: {value:""}, + bytopic: {value: "all"}, name: {value:""} }, inputs:1, diff --git a/nodes/core/core/89-trigger.js b/nodes/core/core/89-trigger.js index 5314645a0..9a664495a 100644 --- a/nodes/core/core/89-trigger.js +++ b/nodes/core/core/89-trigger.js @@ -19,6 +19,7 @@ module.exports = function(RED) { var mustache = require("mustache"); function TriggerNode(n) { RED.nodes.createNode(this,n); + this.bytopic = n.bytopic || "all"; this.op1 = n.op1 || "1"; this.op2 = n.op2 || "0"; this.op1type = n.op1type || "str"; @@ -47,7 +48,7 @@ module.exports = function(RED) { this.extend = n.extend || "false"; this.units = n.units || "ms"; this.reset = n.reset || ''; - this.duration = parseInt(n.duration); + this.duration = parseFloat(n.duration); if (isNaN(this.duration)) { this.duration = 250; } @@ -65,29 +66,32 @@ module.exports = function(RED) { this.op2Templated = (this.op2type === 'str' && this.op2.indexOf("{{") != -1); if ((this.op1type === "num") && (!isNaN(this.op1))) { this.op1 = Number(this.op1); } if ((this.op2type === "num") && (!isNaN(this.op2))) { this.op2 = Number(this.op2); } - if (this.op1 == "null") { this.op1 = null; } - if (this.op2 == "null") { this.op2 = null; } + //if (this.op1 == "null") { this.op1 = null; } + //if (this.op2 == "null") { this.op2 = null; } //try { this.op1 = JSON.parse(this.op1); } //catch(e) { this.op1 = this.op1; } //try { this.op2 = JSON.parse(this.op2); } //catch(e) { this.op2 = this.op2; } var node = this; - var tout = null; - var m2; + node.topics = {}; + this.on("input", function(msg) { + var topic = msg.topic || "_none"; + if (node.bytopic === "all") { topic = "_none"; } + node.topics[topic] = node.topics[topic] || {}; if (msg.hasOwnProperty("reset") || ((node.reset !== '') && (msg.payload == node.reset)) ) { - if (node.loop === true) { clearInterval(tout); } - else { clearTimeout(tout); } - tout = null; + if (node.loop === true) { clearInterval(node.topics[topic].tout); } + else { clearTimeout(node.topics[topic].tout); } + delete node.topics[topic]; node.status({}); } else { - if (((!tout) && (tout !== 0)) || (node.loop === true)) { - if (node.op2type === "pay" || node.op2type === "payl") { m2 = msg.payload; } - else if (node.op2Templated) { m2 = mustache.render(node.op2,msg); } + if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) { + if (node.op2type === "pay" || node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } + else if (node.op2Templated) { node.topics[topic].m2 = mustache.render(node.op2,msg); } else if (node.op2type !== "nul") { - m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg); + node.topics[topic].m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg); } if (node.op1type === "pay") { } @@ -96,58 +100,60 @@ module.exports = function(RED) { msg.payload = RED.util.evaluateNodeProperty(node.op1,node.op1type,node,msg); } - if (node.op1type !== "nul") { node.send(msg); } + if (node.op1type !== "nul") { node.send(RED.util.cloneMessage(msg)); } - if (node.duration === 0) { tout = 0; } + if (node.duration === 0) { node.topics[topic].tout = 0; } else if (node.loop === true) { - if (tout) { clearInterval(tout); } + if (node.topics[topic].tout) { clearInterval(node.topics[topic].tout); } if (node.op1type !== "nul") { var msg2 = RED.util.cloneMessage(msg); - tout = setInterval(function() { node.send(msg2); },node.duration); + node.topics[topic].tout = setInterval(function() { node.send(RED.util.cloneMessage(msg2)); }, node.duration); } } else { - tout = setTimeout(function() { + node.topics[topic].tout = setTimeout(function() { if (node.op2type !== "nul") { var msg2 = RED.util.cloneMessage(msg); if (node.op2type === "flow" || node.op2type === "global") { - m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg); + node.topics[topic].m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg); } - msg2.payload = m2; + msg2.payload = node.topics[topic].m2; node.send(msg2); } - tout = null; + delete node.topics[topic]; node.status({}); - },node.duration); + }, node.duration); } node.status({fill:"blue",shape:"dot",text:" "}); } else if ((node.extend === "true" || node.extend === true) && (node.duration > 0)) { - if (tout) { clearTimeout(tout); } - if (node.op2type === "payl") { m2 = msg.payload; } - tout = setTimeout(function() { + if (node.topics[topic].tout) { clearTimeout(node.topics[topic].tout); } + if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } + node.topics[topic].tout = setTimeout(function() { if (node.op2type !== "nul") { var msg2 = RED.util.cloneMessage(msg); if (node.op2type === "flow" || node.op2type === "global") { - m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg); + node.topics[topic].m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg); } - msg2.payload = m2; + msg2.payload = node.topics[topic].m2; node.send(msg2); } - tout = null; + delete node.topics[topic]; node.status({}); - },node.duration); + }, node.duration); } else { - if (node.op2type === "payl") { m2 = msg.payload; } + if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } } } }); this.on("close", function() { - if (tout) { - if (node.loop === true) { clearInterval(tout); } - else { clearTimeout(tout); } - tout = null; + for (var t in node.topics) { + if (node.topics[t]) { + if (node.loop === true) { clearInterval(node.topics[t].tout); } + else { clearTimeout(node.topics[t].tout); } + delete node.topics[t]; + } } node.status({}); }); diff --git a/nodes/core/locales/en-US/messages.json b/nodes/core/locales/en-US/messages.json index 113ddfdca..6728db7c1 100644 --- a/nodes/core/locales/en-US/messages.json +++ b/nodes/core/locales/en-US/messages.json @@ -270,6 +270,9 @@ "wait-reset": "wait to be reset", "wait-for": "wait for", "wait-loop": "resend it every", + "for": "Handling", + "bytopics": "each msg.topic independently", + "alltopics": "all messages", "duration": { "ms": "Milliseconds", "s": "Seconds", diff --git a/test/nodes/core/core/89-trigger_spec.js b/test/nodes/core/core/89-trigger_spec.js index 85726b385..74ba19d16 100644 --- a/test/nodes/core/core/89-trigger_spec.js +++ b/test/nodes/core/core/89-trigger_spec.js @@ -15,8 +15,10 @@ **/ var should = require("should"); +var sinon = require("sinon"); var helper = require("../../helper.js"); var triggerNode = require("../../../../nodes/core/core/89-trigger.js"); +var RED = require("../../../../red/red.js"); describe('trigger node', function() { @@ -81,14 +83,17 @@ describe('trigger node', function() { var n2 = helper.getNode("n2"); var c = 0; n2.on("input", function(msg) { - if (c === 0) { - msg.should.have.a.property("payload", '1'); - c+=1; - } - else { - msg.should.have.a.property("payload", '0'); - done(); + try { + if (c === 0) { + msg.should.have.a.property("payload", '1'); + c+=1; + } + else { + msg.should.have.a.property("payload", '0'); + done(); + } } + catch(err) { done(err); } }); n1.emit("input", {payload:null}); }); @@ -161,6 +166,155 @@ describe('trigger node', function() { }); }); + it('should handle multiple topics as one if not asked to handle', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", bytopic:"all", op1:"1", op2:"0", op1type:"num", op2type:"num", duration:"30", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + try { + c += 1; + if (c === 1) { + msg.should.have.a.property("payload", 1); + msg.should.have.a.property("topic", "A"); + } + else if (c === 2) { + msg.should.have.a.property("payload", 0); + msg.should.have.a.property("topic", "A"); + done(); + } + } catch(err) { + done(err); + } + }); + n1.emit("input", {payload:1,topic:"A"}); + n1.emit("input", {payload:2,topic:"B"}); + n1.emit("input", {payload:3,topic:"C"}); + }); + }); + + it('should handle multiple topics individually if asked to do so', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", bytopic:"topic", op1:"1", op2:"0", op1type:"num", op2type:"num", duration:"30", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + try { + c += 1; + if (c === 1) { + msg.should.have.a.property("payload", 1); + msg.should.have.a.property("topic", "A"); + } + else if (c === 2) { + msg.should.have.a.property("payload", 1); + msg.should.have.a.property("topic", "B"); + } + else if (c === 3) { + msg.should.have.a.property("payload", 1); + msg.should.have.a.property("topic", "C"); + } + else if (c === 4) { + msg.should.have.a.property("payload", 0); + msg.should.have.a.property("topic", "A"); + } + else if (c === 5) { + msg.should.have.a.property("payload", 0); + msg.should.have.a.property("topic", "B"); + } + else if (c === 6) { + msg.should.have.a.property("payload", 0); + msg.should.have.a.property("topic", "C"); + done(); + } + } catch(err) { + done(err); + } + }); + n1.emit("input", {payload:1,topic:"A"}); + n1.emit("input", {payload:2,topic:"B"}); + n1.emit("input", {payload:3,topic:"C"}); + }); + }); + + it('should handle multiple topics individually, and extend one, if asked to do so', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", bytopic:"topic", extend:"true", op1:"1", op2:"0", op1type:"num", op2type:"num", duration:"30", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + try { + c += 1; + if (c === 1) { + msg.should.have.a.property("payload", 1); + msg.should.have.a.property("topic", "A"); + } + else if (c === 2) { + msg.should.have.a.property("payload", 1); + msg.should.have.a.property("topic", "B"); + } + else if (c === 3) { + msg.should.have.a.property("payload", 1); + msg.should.have.a.property("topic", "C"); + } + else if (c === 4) { + msg.should.have.a.property("payload", 0); + msg.should.have.a.property("topic", "A"); + } + else if (c === 5) { + msg.should.have.a.property("payload", 0); + msg.should.have.a.property("topic", "C"); + } + else if (c === 6) { + msg.should.have.a.property("payload", 0); + msg.should.have.a.property("topic", "B"); + done(); + } + } catch(err) { + done(err); + } + }); + n1.emit("input", {payload:1,topic:"A"}); + n1.emit("input", {payload:2,topic:"B"}); + n1.emit("input", {payload:3,topic:"C"}); + setTimeout( function() { n1.emit("input", {payload:2,topic:"B"})}, 25 ); + }); + }); + + it('should be able to return things from flow and global context variables', function(done) { + var spy = sinon.stub(RED.util, 'evaluateNodeProperty', + function(arg1, arg2, arg3, arg4) { return arg1; } + ); + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:"foo", op1type:"flow", op2:"bar", op2type:"global", duration:"20", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + try { + if (c === 0) { + msg.should.have.a.property("payload", "foo"); + c+=1; + } + else { + msg.should.have.a.property("payload", "bar"); + spy.restore(); + done(); + } + } + catch(err) { spy.restore(); done(err); } + }); + n1.emit("input", {payload:null}); + }); + + }); + it('should be able to not output anything on first trigger', function(done) { var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"nul", op1:"true",op2:"false",op2type:"val",duration:"30", wires:[["n2"]] }, {id:"n2", type:"helper"} ]; @@ -187,8 +341,11 @@ describe('trigger node', function() { var n2 = helper.getNode("n2"); var c = 0; n2.on("input", function(msg) { - msg.should.have.a.property("payload", true); - c += 1; + try { + msg.should.have.a.property("payload", true); + c += 1; + } + catch(err) { done(err); } }); setTimeout( function() { c.should.equal(1); // should only have had one output. @@ -199,23 +356,30 @@ describe('trigger node', function() { }); it('should be able to extend the delay', function(done) { - var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"pay", op1:"false", op2:"true", duration:"100", wires:[["n2"]] }, + var spy = sinon.stub(RED.util, 'evaluateNodeProperty', + function(arg1, arg2, arg3, arg4) { return arg1; } + ); + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"flow", op1:"foo", op2:"bar", op2type:"global", duration:"100", wires:[["n2"]] }, {id:"n2", type:"helper"} ]; helper.load(triggerNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); var c = 0; n2.on("input", function(msg) { - if (c === 0) { - msg.should.have.a.property("payload", "Hello"); - c += 1; - } - else { - msg.should.have.a.property("payload", "true"); - //console.log(Date.now() - ss); - (Date.now() - ss).should.be.greaterThan(149); - done(); + try { + if (c === 0) { + msg.should.have.a.property("payload", "foo"); + c += 1; + } + else { + msg.should.have.a.property("payload", "bar"); + //console.log(Date.now() - ss); + (Date.now() - ss).should.be.greaterThan(149); + spy.restore(); + done(); + } } + catch(err) { spy.restore(); done(err); } }); var ss = Date.now(); n1.emit("input", {payload:"Hello"}); @@ -233,16 +397,19 @@ describe('trigger node', function() { var n2 = helper.getNode("n2"); var c = 0; n2.on("input", function(msg) { - if (c === 0) { - msg.should.have.a.property("payload", "Hello"); - c += 1; - } - else { - msg.should.have.a.property("payload", "World"); - //console.log(Date.now() - ss); - (Date.now() - ss).should.be.greaterThan(70); - done(); + try { + if (c === 0) { + msg.should.have.a.property("payload", "Hello"); + c += 1; + } + else { + msg.should.have.a.property("payload", "World"); + //console.log(Date.now() - ss); + (Date.now() - ss).should.be.greaterThan(70); + done(); + } } + catch(err) { done(err); } }); var ss = Date.now(); n1.emit("input", {payload:"Hello"}); @@ -263,15 +430,18 @@ describe('trigger node', function() { var n2 = helper.getNode("n2"); var c = 0; n2.on("input", function(msg) { - if (c === 0) { - msg.should.have.a.property("payload", "Goodbye"); - c += 1; - } - else { - msg.should.have.a.property("payload", "World"); - (Date.now() - ss).should.be.greaterThan(70); - done(); + try { + if (c === 0) { + msg.should.have.a.property("payload", "Goodbye"); + c += 1; + } + else { + msg.should.have.a.property("payload", "World"); + (Date.now() - ss).should.be.greaterThan(70); + done(); + } } + catch(err) { done(err); } }); var ss = Date.now(); n1.emit("input", {payload:"Hello"}); @@ -292,15 +462,18 @@ describe('trigger node', function() { var n2 = helper.getNode("n2"); var c = 0; n2.on("input", function(msg) { - if (c === 0) { - msg.should.have.a.property("payload", "Goodbye"); - c += 1; - } - else { - msg.should.have.a.property("payload", "World"); - (Date.now() - ss).should.be.greaterThan(70); - done(); + try { + if (c === 0) { + msg.should.have.a.property("payload", "Goodbye"); + c += 1; + } + else { + msg.should.have.a.property("payload", "World"); + (Date.now() - ss).should.be.greaterThan(70); + done(); + } } + catch(err) { done(err); } }); var ss = Date.now(); n1.emit("input", {payload:"Hello"}); @@ -321,14 +494,17 @@ describe('trigger node', function() { var n2 = helper.getNode("n2"); var c = 0; n2.on("input", function(msg) { - if (c === 0) { - msg.should.have.a.property("payload", "Hello"); - c+=1; - } - else { - msg.should.have.a.property("payload", "World"); - done(); + try { + if (c === 0) { + msg.should.have.a.property("payload", "Hello"); + c+=1; + } + else { + msg.should.have.a.property("payload", "World"); + done(); + } } + catch(err) { done(err); } }); n1.emit("input", {payload:"Hello",topic:"World"}); }); @@ -342,19 +518,46 @@ describe('trigger node', function() { var n2 = helper.getNode("n2"); var c = 0; n2.on("input", function(msg) { - if (c === 0) { - msg.should.have.a.property("payload", null); - c+=1; - } - else { - msg.should.have.a.property("payload", "World"); - done(); + try { + if (c === 0) { + msg.should.have.a.property("payload", null); + c+=1; + } + else { + msg.should.have.a.property("payload", "World"); + done(); + } } + catch(err) { done(err); } }); n1.emit("input", {payload:"World"}); }); }); + it('should handle string null as null on op2', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"null", op2:"null", duration:"40", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + try { + if (c === 0) { + msg.should.have.a.property("payload", null); + c+=1; + } + else { + msg.should.have.a.property("payload", null); + done(); + } + } + catch(err) { done(err); } + }); + n1.emit("input", {payload:"null"}); + }); + }); + it('should be able to set infinite timeout, and clear timeout', function(done) { var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", duration:"0", extend: false, wires:[["n2"]] }, {id:"n2", type:"helper"} ]; @@ -363,8 +566,11 @@ describe('trigger node', function() { var n2 = helper.getNode("n2"); var c = 0; n2.on("input", function(msg) { - c += 1; - msg.should.have.a.property("payload", 1); + try { + c += 1; + msg.should.have.a.property("payload", "1"); + } + catch(err) { done(err); } }); setTimeout( function() { if (c === 2) { done(); } @@ -388,8 +594,11 @@ describe('trigger node', function() { var n2 = helper.getNode("n2"); var c = 0; n2.on("input", function(msg) { - c += 1; - msg.should.have.a.property("payload", 1); + try { + c += 1; + msg.should.have.a.property("payload", "1"); + } + catch(err) { done(err); } }); setTimeout( function() { if (c === 2) { done(); } @@ -406,7 +615,7 @@ describe('trigger node', function() { }); it('should be able to set a repeat, and clear loop by reset', function(done) { - var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", reset:"boo", duration:-25, wires:[["n2"]] }, + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", reset:"boo", op1:"", op1type:"pay", duration:-25, wires:[["n2"]] }, {id:"n2", type:"helper"} ]; helper.load(triggerNode, flow, function() { var n1 = helper.getNode("n1"); @@ -414,9 +623,14 @@ describe('trigger node', function() { var c = 0; n2.on("input", function(msg) { c += 1; - msg.should.have.a.property("payload", "foo"); + try { + msg.should.have.property('payload','foo'); + msg.payload = "bar"; // try to provoke pass by reference error + } + catch(err) { done(err); } }); n1.emit("input", {payload:"foo"}); // trigger + n1.emit("input", {payload:"foo"}); // trigger setTimeout( function() { n1.emit("input", {reset:true}); // reset },90); From 6d2389945b283d68247d1763a16ee24fe00a428a Mon Sep 17 00:00:00 2001 From: Kazuki Nakanishi Date: Thu, 30 Nov 2017 13:13:35 +0000 Subject: [PATCH 051/104] allow a node's icon to be set dynamically (#1490) * create a proto type * Fixed some problems after reviewing --- editor/js/main.js | 22 +++- editor/js/nodes.js | 17 +++ editor/js/ui/editor.js | 123 ++++++++++++++++++ editor/js/ui/utils.js | 54 ++++++-- editor/sass/editor.scss | 7 + red/api/index.js | 3 +- red/api/locales/en-US/editor.json | 3 +- red/api/locales/ja/editor.json | 3 +- red/api/nodes.js | 5 + red/runtime/nodes/index.js | 1 + red/runtime/nodes/registry/index.js | 1 + red/runtime/nodes/registry/localfilesystem.js | 25 +++- red/runtime/nodes/registry/registry.js | 34 +++++ test/red/api/nodes_spec.js | 27 +++- .../nodes/registry/localfilesystem_spec.js | 39 +++++- .../runtime/nodes/registry/registry_spec.js | 22 +++- 16 files changed, 367 insertions(+), 19 deletions(-) diff --git a/editor/js/main.js b/editor/js/main.js index f4f42b90d..3cf8ca549 100644 --- a/editor/js/main.js +++ b/editor/js/main.js @@ -24,7 +24,25 @@ url: 'nodes', success: function(data) { RED.nodes.setNodeList(data); - RED.i18n.loadNodeCatalogs(loadNodes); + RED.i18n.loadNodeCatalogs(function() { + loadIconList(loadNodes); + }); + } + }); + } + + function loadIconList(done) { + $.ajax({ + headers: { + "Accept":"application/json" + }, + cache: false, + url: 'icons', + success: function(data) { + RED.nodes.setIconSets(data); + if (done) { + done(); + } } }); } @@ -122,6 +140,7 @@ typeList = "
    • "+addedTypes.join("
    • ")+"
    "; RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success"); } + loadIconList(); } else if (topic == "notification/node/removed") { for (i=0;i 0 && n.outputLabels && !/^\s*$/.test(n.outputLabels.join(""))) { node.outputLabels = n.outputLabels.slice(); } + if (n.icon) { + var defIcon = RED.utils.getDefaultNodeIcon(n._def, n); + if (n.icon !== defIcon.module+"/"+defIcon.file) { + node.icon = n.icon; + } + } } return node; } @@ -915,6 +928,7 @@ RED.nodes = (function() { wires:n.wires, inputLabels: n.inputLabels, outputLabels: n.outputLabels, + icon: n.icon, changed:false, _config:{} }; @@ -1272,6 +1286,9 @@ RED.nodes = (function() { enableNodeSet: registry.enableNodeSet, disableNodeSet: registry.disableNodeSet, + setIconSets: registry.setIconSets, + getIconSets: registry.getIconSets, + registerType: registry.registerNodeType, getType: registry.getNodeType, convertNode: convertNode, diff --git a/editor/js/ui/editor.js b/editor/js/ui/editor.js index ceea0beff..b91b06a25 100644 --- a/editor/js/ui/editor.js +++ b/editor/js/ui/editor.js @@ -108,6 +108,14 @@ RED.editor = (function() { } } } + if (node.icon) { + var iconPath = RED.utils.separateIconPath(node.icon); + var iconSets = RED.nodes.getIconSets(); + var iconFileList = iconSets[iconPath.module]; + if (!iconFileList || iconFileList.indexOf(iconPath.file) === -1) { + isValid = false; + } + } return isValid; } @@ -159,6 +167,23 @@ RED.editor = (function() { } } } + if (node.icon) { + var iconPath = RED.utils.separateIconPath(node.icon); + var iconSets = RED.nodes.getIconSets(); + var iconFileList = iconSets[iconPath.module]; + var iconModule = $("#node-settings-icon-module"); + var iconFile = $("#node-settings-icon-file"); + if (!iconFileList) { + iconModule.addClass("input-error"); + iconFile.removeClass("input-error"); + } else if (iconFileList.indexOf(iconPath.file) === -1) { + iconModule.removeClass("input-error"); + iconFile.addClass("input-error"); + } else { + iconModule.removeClass("input-error"); + iconFile.removeClass("input-error"); + } + } } function validateNodeEditorProperty(node,defaults,property,prefix) { var input = $("#"+prefix+"-"+property); @@ -710,10 +735,76 @@ RED.editor = (function() { } else { buildLabelRow().appendTo(outputsDiv); } + + $('
    ').appendTo(dialogForm); + var iconDiv = $("#node-settings-icon"); + $('