Merge branch 'dev' into get-got

This commit is contained in:
Nick O'Leary
2021-06-09 09:59:23 +01:00
committed by GitHub
243 changed files with 43788 additions and 1320 deletions

View File

@@ -162,7 +162,62 @@
height += 16;
$("#node-input-property-container").editableList('height',height);
}
/** Retrieve editableList items (refactored for re-use in the form inject button)*/
function getProps(el, legacy) {
var result = {
props: []
}
el.each(function(i) {
var prop = $(this);
var p = {
p:prop.find(".node-input-prop-property-name").typedInput('value')
};
if (p.p) {
p.v = prop.find(".node-input-prop-property-value").typedInput('value');
p.vt = prop.find(".node-input-prop-property-value").typedInput('type');
if(legacy) {
if (p.p === "payload") { // save payload to old "legacy" property
result.payloadType = p.vt;
result.payload = p.v;
delete p.v;
delete p.vt;
} else if (p.p === "topic" && p.vt === "str") {
result.topic = p.v;
delete p.v;
}
}
result.props.push(p);
}
});
return result;
}
/** Perform inject, optionally sending a custom msg (refactored for re-use in the form inject button)*/
function doInject(node, customMsg) {
var label = node._def.label.call(node);
if (label.length > 30) {
label = label.substring(0, 50) + "...";
}
label = label.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
$.ajax({
url: "inject/" + node.id,
type: "POST",
data: customMsg,
success: function (resp) {
RED.notify(node._("inject.success", { label: label }), { type: "success", id: "inject", timeout: 2000 });
},
error: function (jqXHR, textStatus, errorThrown) {
if (jqXHR.status == 404) {
RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.not-deployed") }), "error");
} else if (jqXHR.status == 500) {
RED.notify(node._("common.notification.error", { message: node._("inject.errors.failed") }), "error");
} else if (jqXHR.status == 0) {
RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.no-response") }), "error");
} else {
RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.unexpected", { status: jqXHR.status, message: textStatus }) }), "error");
}
}
});
}
RED.nodes.registerType('inject',{  
category: 'common',
color:"#a6bbcf",
@@ -214,7 +269,7 @@
for (var i=0,l=props.length; i<l; i++) {
if (i > 0) lab += "\n";
if (i === 5) {
lab += " + "+(props.length-4);
lab += "... +"+(props.length-5);
break;
}
lab += props[i].p+": ";
@@ -283,14 +338,15 @@
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if (this.payloadType == null) {
if (this.payload == "") {
this.payloadType = "date";
var node = this;
if (node.payloadType == null) {
if (node.payload == "") {
node.payloadType = "date";
} else {
this.payloadType = "str";
node.payloadType = "str";
}
} else if (this.payloadType === 'string' || this.payloadType === 'none') {
this.payloadType = "str";
} else if (node.payloadType === 'string' || node.payloadType === 'none') {
node.payloadType = "str";
}
$("#inject-time-type-select").on("change", function() {
@@ -345,17 +401,17 @@
});
var repeattype = "none";
if (this.repeat != "" && this.repeat != 0) {
if (node.repeat != "" && node.repeat != 0) {
repeattype = "interval";
var r = "s";
var c = this.repeat;
if (this.repeat % 60 === 0) { r = "m"; c = c/60; }
if (this.repeat % 1440 === 0) { r = "h"; c = c/60; }
var c = node.repeat;
if (node.repeat % 60 === 0) { r = "m"; c = c/60; }
if (node.repeat % 1440 === 0) { r = "h"; c = c/60; }
$("#inject-time-interval-count").val(c);
$("#inject-time-interval-units").val(r);
$("#inject-time-interval-days").prop("disabled","disabled");
} else if (this.crontab) {
var cronparts = this.crontab.split(" ");
} else if (node.crontab) {
var cronparts = node.crontab.split(" ");
var days = cronparts[4];
if (!isNaN(cronparts[0]) && !isNaN(cronparts[1])) {
repeattype = "time";
@@ -431,7 +487,24 @@
/* */
$('#node-input-property-container').css('min-height','120px').css('min-width','450px').editableList({
var eList = $('#node-input-property-container').css('min-height','120px').css('min-width','450px');
eList.editableList({
buttons: [
{
id: "node-inject-test-inject-button",
label: node._("inject.injectNow"),
click: function(e) {
var items = eList.editableList('items');
var result = getProps(items);
var m = {__user_inject_props__: []};
if (result && result.props && result.props.length) {
m.__user_inject_props__ = result.props;
}
doInject(node, m);
}
}
],
addItem: function(container,i,opt) {
var prop = opt;
if (!prop.hasOwnProperty('p')) {
@@ -455,7 +528,7 @@
var propertyValue = $('<input/>',{class:"node-input-prop-property-value",type:"text"})
.css("width","calc(70% - 30px)")
.appendTo(row)
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata','env']});
.typedInput({default:'str',types:['flow','global','str','num','bool','json','bin','date','jsonata','env','msg']});
propertyName.typedInput('value',prop.p);
@@ -465,33 +538,38 @@
removable: true,
sortable: true
});
$('#node-inject-test-inject-button').css("float", "right").css("margin-right", "unset");
if (!this.props) {
if (RED.nodes.subflow(node.z)) {
$('#node-inject-test-inject-button').attr("disabled",true);
}
if (!node.props) {
var payload = {
p:'payload',
v: this.payload ? this.payload : '',
vt:this.payloadType ? this.payloadType : 'date'
v: node.payload ? node.payload : '',
vt:node.payloadType ? node.payloadType : 'date'
};
var topic = {
p:'topic',
v: this.topic ? this.topic : '',
v: node.topic ? node.topic : '',
vt:'string'
}
this.props = [payload,topic];
node.props = [payload,topic];
}
for (var i=0; i<this.props.length; i++) {
var prop = this.props[i];
for (var i=0; i<node.props.length; i++) {
var prop = node.props[i];
var newProp = { p: prop.p, v: prop.v, vt: prop.vt };
if (newProp.v === undefined) {
if (prop.p === 'payload') {
newProp.v = this.payload ? this.payload : '';
newProp.vt = this.payloadType ? this.payloadType : 'date';
newProp.v = node.payload ? node.payload : '';
newProp.vt = node.payloadType ? node.payloadType : 'date';
} else if (prop.p === 'topic' && prop.vt === "str") {
newProp.v = this.topic ? this.topic : '';
newProp.v = node.topic ? node.topic : '';
}
}
$("#node-input-property-container").editableList('addItem',newProp);
eList.editableList('addItem',newProp);
}
$("#inject-time-type-select").trigger("change");
@@ -589,67 +667,26 @@
$("#node-input-repeat").val(repeat);
$("#node-input-crontab").val(crontab);
/* Gather the injected properties of the msg object */
var props = $("#node-input-property-container").editableList('items');
var node = this;
node.props= [];
delete node.payloadType;
delete node.payload;
node.topic = "";
props.each(function(i) {
var prop = $(this);
var p = {
p:prop.find(".node-input-prop-property-name").typedInput('value')
};
if (p.p) {
p.v = prop.find(".node-input-prop-property-value").typedInput('value');
p.vt = prop.find(".node-input-prop-property-value").typedInput('type');
if (p.p === "payload") { // save payload to old "legacy" property
node.payloadType = p.vt;
node.payload = p.v;
delete p.v;
delete p.vt;
} else if (p.p === "topic" && p.vt === "str") {
node.topic = p.v;
delete p.v;
}
node.props.push(p);
}
});
/* Gather the properties */
var items = $("#node-input-property-container").editableList('items');
delete this.payloadType;
delete this.payload;
this.topic = "";
var result = getProps(items, true);
this.props = result.props;
if(result.payloadType) { this.payloadType = result.payloadType; };
if(result.payload) { this.payload = result.payload; };
if(result.topic) { this.topic = result.topic; };
},
button: {
enabled: function() {
return !this.changed
},
onclick: function() {
onclick: function () {
if (this.changed) {
return RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.undeployedChanges")}),"warning");
return RED.notify(RED._("notification.warning", { message: RED._("notification.warnings.undeployedChanges") }), "warning");
}
var label = this._def.label.call(this);
if (label.length > 30) {
label = label.substring(0,50)+"...";
}
label = label.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
var node = this;
$.ajax({
url: "inject/"+this.id,
type:"POST",
success: function(resp) {
RED.notify(node._("inject.success",{label:label}),{type:"success",id:"inject"});
},
error: function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status == 404) {
RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.not-deployed")}),"error");
} else if (jqXHR.status == 500) {
RED.notify(node._("common.notification.error",{message:node._("inject.errors.failed")}),"error");
} else if (jqXHR.status == 0) {
RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.no-response")}),"error");
} else {
RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.unexpected",{status:jqXHR.status,message:textStatus})}),"error");
}
}
});
doInject(this);
}
},
oneditresize: resizeDialog

View File

@@ -16,7 +16,7 @@
module.exports = function(RED) {
"use strict";
var cron = require("cron");
const {scheduleTask} = require("cronosjs");
function InjectNode(n) {
RED.nodes.createNode(this,n);
@@ -85,7 +85,7 @@ module.exports = function(RED) {
if (RED.settings.verbose) {
this.log(RED._("inject.crontab", this));
}
this.cronjob = new cron.CronJob(this.crontab, function() { node.emit("input", {}); }, null, true);
this.cronjob = scheduleTask(this.crontab,() => { node.emit("input", {})});
}
};
@@ -100,8 +100,12 @@ module.exports = function(RED) {
this.on("input", function(msg, send, done) {
var errors = [];
this.props.forEach(p => {
var props = this.props;
if(msg.__user_inject_props__ && Array.isArray(msg.__user_inject_props__)) {
props = msg.__user_inject_props__;
}
delete msg.__user_inject_props__;
props.forEach(p => {
var property = p.p;
var value = p.v ? p.v : '';
var valueType = p.vt ? p.vt : 'str';
@@ -156,7 +160,11 @@ module.exports = function(RED) {
var node = RED.nodes.getNode(req.params.id);
if (node != null) {
try {
node.receive();
if (req.body && req.body.__user_inject_props__) {
node.receive(req.body);
} else {
node.receive();
}
res.sendStatus(200);
} catch(err) {
res.sendStatus(500);

View File

@@ -129,9 +129,9 @@
RED.history.push(historyEvent);
RED.view.redraw();
if (xhr.status == 200) {
RED.notify(node._("debug.notification.activated",{label:label}),"success");
RED.notify(node._("debug.notification.activated",{label:label}),{type: "success", timeout: 2000});
} else if (xhr.status == 201) {
RED.notify(node._("debug.notification.deactivated",{label:label}),"success");
RED.notify(node._("debug.notification.deactivated",{label:label}),{type: "success", timeout: 2000});
}
});
}

View File

@@ -2,7 +2,8 @@ module.exports = function(RED) {
"use strict";
var util = require("util");
var events = require("events");
//var path = require("path");
const fs = require("fs-extra");
const path = require("path");
var debuglength = RED.settings.debugMaxLength || 1000;
var useColors = RED.settings.debugUseColors || false;
util.inspect.styles.boolean = "red";
@@ -249,11 +250,34 @@ module.exports = function(RED) {
}
});
let cachedDebugView;
RED.httpAdmin.get("/debug/view/view.html", function(req,res) {
if (!cachedDebugView) {
fs.readFile(path.join(__dirname,"lib","debug","view.html")).then(data => {
let customStyles = "";
try {
let customStyleList = RED.settings.editorTheme.page._.css || [];
customStyleList.forEach(style => {
customStyles += `<link rel="stylesheet" href="../../${style}">\n`
})
} catch(err) {}
cachedDebugView = data.toString().replace("<!-- INSERT-THEME-CSS -->",customStyles)
res.set('Content-Type', 'text/html');
res.send(cachedDebugView).end();
}).catch(err => {
res.sendStatus(404);
})
} else {
res.send(cachedDebugView).end();
}
});
// As debug/view/debug-utils.js is loaded via <script> tag, it won't get
// the auth header attached. So do not use RED.auth.needsPermission here.
RED.httpAdmin.get("/debug/view/*",function(req,res) {
var options = {
root: __dirname + '/lib/debug/',
root: path.join(__dirname,"lib","debug"),
dotfiles: 'deny'
};
res.sendFile(req.params[0], options);

View File

@@ -2,6 +2,7 @@
<head>
<link rel="stylesheet" href="../../red/style.min.css">
<link rel="stylesheet" href="../../vendor/font-awesome/css/font-awesome.min.css">
<!-- INSERT-THEME-CSS -->
<title>Node-RED Debug Tools</title>
</head>
<body class="red-ui-editor red-ui-debug-window">

View File

@@ -74,21 +74,21 @@
<div id="func-tab-init" style="display:none">
<div class="form-row node-text-editor-row" style="position:relative">
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-init-editor" ></div>
<div style="position: absolute; right:0; bottom: calc(100% - 20px);"><button id="node-init-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
<div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 10;"><button id="node-init-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
</div>
</div>
<div id="func-tab-body" style="display:none">
<div class="form-row node-text-editor-row" style="position:relative">
<div style="height: 220px; min-height:150px;" class="node-text-editor" id="node-input-func-editor" ></div>
<div style="position: absolute; right:0; bottom: calc(100% - 20px);"><button id="node-function-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
<div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 10;"><button id="node-function-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
</div>
</div>
<div id="func-tab-finalize" style="display:none">
<div class="form-row node-text-editor-row" style="position:relative">
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-finalize-editor" ></div>
<div style="position: absolute; right:0; bottom: calc(100% - 20px);"><button id="node-finalize-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
<div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 10;"><button id="node-finalize-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
</div>
</div>
@@ -312,6 +312,30 @@
}
}
function getLibsList() {
var _libs = [];
if (RED.settings.functionExternalModules === true) {
var libs = $("#node-input-libs-container").editableList("items");
libs.each(function(i) {
var item = $(this);
var v = item.find(".node-input-libs-var").val();
var n = item.find(".node-input-libs-val").typedInput("type");
if (n === "_custom_") {
n = item.find(".node-input-libs-val").val();
}
if ((!v || (v === "")) ||
(!n || (n === ""))) {
return;
}
_libs.push({
var: v,
module: n
});
});
}
return _libs;
}
RED.nodes.registerType('function',{
color:"#fdd0a2",
@@ -360,6 +384,13 @@
onchange: function(tab) {
$("#func-tabs-content").children().hide();
$("#" + tab.id).show();
let editor = $("#" + tab.id).find('.monaco-editor').first();
if(editor.length) {
if(that.editor.nodered && that.editor.type == "monaco") {
that.editor.nodered.refreshModuleLibs(getLibsList());
}
RED.tray.resize();
}
}
});
tabs.addTab({
@@ -393,7 +424,7 @@
}
});
var buildEditor = function(id, value, defaultValue) {
var buildEditor = function(id, value, defaultValue, extraLibs) {
var editor = RED.editor.createEditor({
id: id,
mode: 'ace/mode/nrjavascript',
@@ -411,7 +442,8 @@
clearTimeout: true,
setInterval: true,
clearInterval: true
}
},
extraLibs: extraLibs
});
if (defaultValue && value === "") {
editor.moveCursorTo(defaultValue.split("\n").length - 1, 0);
@@ -419,7 +451,7 @@
return editor;
}
this.initEditor = buildEditor('node-input-init-editor',$("#node-input-initialize").val(),RED._("node-red:function.text.initialize"))
this.editor = buildEditor('node-input-func-editor',$("#node-input-func").val())
this.editor = buildEditor('node-input-func-editor',$("#node-input-func").val(), undefined, that.libs || [])
this.finalizeEditor = buildEditor('node-input-finalize-editor',$("#node-input-finalize").val(),RED._("node-red:function.text.finalize"))
RED.library.create({
@@ -523,28 +555,7 @@
$("#node-input-noerr").val(noerr);
this.noerr = noerr;
if (RED.settings.functionExternalModules === true) {
var libs = $("#node-input-libs-container").editableList("items");
node.libs = [];
libs.each(function(i) {
var item = $(this);
var v = item.find(".node-input-libs-var").val();
var n = item.find(".node-input-libs-val").typedInput("type");
if (n === "_custom_") {
n = item.find(".node-input-libs-val").val();
}
if ((!v || (v === "")) ||
(!n || (n === ""))) {
return;
}
node.libs.push({
var: v,
module: n
});
});
} else {
node.libs = [];
}
node.libs = getLibsList();
},
oneditcancel: function() {
var node = this;

View File

@@ -19,6 +19,8 @@ module.exports = function(RED) {
var util = require("util");
var vm = require("vm");
var acorn = require("acorn");
var acornWalk = require("acorn-walk");
function sendResults(node,send,_msgid,msgs,cloneFirstMessage) {
if (msgs == null) {
@@ -102,14 +104,7 @@ module.exports = function(RED) {
throw new Error(RED._("function.error.externalModuleNotAllowed"));
}
var handleNodeDoneCall = true;
// Check to see if the Function appears to call `node.done()`. If so,
// we will assume it is well written and does actually call node.done().
// Otherwise, we will call node.done() after the function returns regardless.
if (/node\.done\s*\(\s*\)/.test(node.func)) {
handleNodeDoneCall = false;
}
var functionText = "var results = null;"+
"results = (async function(msg,__send__,__done__){ "+
@@ -130,6 +125,26 @@ module.exports = function(RED) {
"};\n"+
node.func+"\n"+
"})(msg,__send__,__done__);";
var handleNodeDoneCall = true;
// Check to see if the Function appears to call `node.done()`. If so,
// we will assume it is well written and does actually call node.done().
// Otherwise, we will call node.done() after the function returns regardless.
if (/node\.done\s*\(\s*\)/.test(functionText)) {
// We have spotted the code contains `node.done`. It could be in a comment
// so need to do the extra work to parse the AST and examine it properly.
acornWalk.simple(acorn.parse(functionText,{ecmaVersion: "latest"} ), {
CallExpression(astNode) {
if (astNode.callee && astNode.callee.object) {
if (astNode.callee.object.name === "node" && astNode.callee.property.name === "done") {
handleNodeDoneCall = false;
}
}
}
})
}
var finScript = null;
var finOpt = null;
node.topic = n.topic;
@@ -302,7 +317,7 @@ module.exports = function(RED) {
}
});
if (moduleErrors) {
throw new Error("Function node failed to load external modules");
throw new Error(RED._("function.error.externalModuleLoadError"));
}
}
@@ -366,7 +381,8 @@ module.exports = function(RED) {
__node__.error("Cannot send from close function");
}
};
`+node.fin +`})();`;
`+node.fin +`
})();`;
finOpt = createVMOpt(node, " cleanup");
finScript = new vm.Script(finText, finOpt);
}
@@ -489,4 +505,3 @@ module.exports = function(RED) {
});
RED.library.register("functions");
};

View File

@@ -217,6 +217,10 @@ module.exports = function(RED) {
function applyRules(node, msg, property,state,done) {
if (!state) {
if (node.rules.length === 0) {
done(undefined, []);
return;
}
state = {
currentRule: 0,
elseflag: true,

View File

@@ -45,9 +45,9 @@
</div>
<div id="random-details" class="form-row">
<label for="node-input-randomFirst"><i class="fa fa-clock-o"></i> <span data-i18n="delay.between"></span></label>
<input type="text" id="node-input-randomFirst" placeholder="" style="text-align:end; width:30px !important">
<span data-i18n="delay.and"></span>
<input type="text" id="node-input-randomLast" placeholder="" style="text-align:end; width:30px !important">
<input type="text" id="node-input-randomFirst" placeholder="" style="text-align:end; width:50px !important">
&nbsp;<span data-i18n="delay.and"></span>&nbsp;
<input type="text" id="node-input-randomLast" placeholder="" style="text-align:end; width:50px !important">
<select id="node-input-randomUnits" style="width:140px !important">
<option value="milliseconds" data-i18n="delay.milisecs"></option>
<option value="seconds" data-i18n="delay.secs"></option>
@@ -78,8 +78,11 @@
<option value="day" data-i18n="delay.label.units.day.singular"></option>
</select>
</div>
<div class="form-row" id="rate-override">
<label></label><input style="width:30px; vertical-align:baseline;" type="checkbox" id="node-input-allowrate"><label style="width: 250px;" for="node-input-allowrate" data-i18n="delay.allowrate"></label>
</div>
<div class="form-row" id="rate-details-drop">
<label></label><input style="width: 30px;" type="checkbox" id="node-input-drop"><label style="width: 250px;" for="node-input-drop" data-i18n="delay.dropmsg"></label>
<label></label><input style="width:30px;; vertical-align:baseline;" type="checkbox" id="node-input-drop"><label style="width: 250px;" for="node-input-drop" data-i18n="delay.dropmsg"></label>
</div>
<div class="form-row" id="rate-details-per-topic">
<label></label>
@@ -110,7 +113,8 @@
randomFirst: {value:"1", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
randomLast: {value:"5", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
randomUnits: {value: "seconds"},
drop: {value:false}
drop: {value:false},
allowrate: {value:false}
},
inputs:1,
outputs:1,

View File

@@ -20,6 +20,20 @@ module.exports = function(RED) {
var MILLIS_TO_NANOS = 1000000;
var SECONDS_TO_NANOS = 1000000000;
var _maxKeptMsgsCount;
function maxKeptMsgsCount(node) {
if (_maxKeptMsgsCount === undefined) {
var name = "nodeMessageBufferMaxLength";
if (RED.settings.hasOwnProperty(name)) {
_maxKeptMsgsCount = RED.settings[name];
}
else {
_maxKeptMsgsCount = 0;
}
}
return _maxKeptMsgsCount;
}
function DelayNode(n) {
RED.nodes.createNode(this,n);
@@ -78,6 +92,9 @@ module.exports = function(RED) {
this.randomID = -1;
this.lastSent = null;
this.drop = n.drop;
this.droppedMsgs = 0;
this.allowrate = n.allowrate|| false;
this.fixedrate = this.rate;
var node = this;
function ourTimeout(handler, delay, clearHandler) {
@@ -88,6 +105,19 @@ module.exports = function(RED) {
};
}
var sendMsgFromBuffer = function() {
if (node.buffer.length === 0) {
clearInterval(node.intervalID);
node.intervalID = -1;
}
if (node.buffer.length > 0) {
const msgInfo = node.buffer.shift();
msgInfo.send(msgInfo.msg);
msgInfo.done();
}
node.reportDepth();
}
var clearDelayList = function(s) {
for (var i=0; i<node.idList.length; i++ ) { node.idList[i].clear(); }
node.idList = [];
@@ -112,21 +142,28 @@ module.exports = function(RED) {
}
}
var loggerId = setInterval(function () {
if (node.droppedMsgs !== 0) {
node.debug("node.droppedMsgs = " + node.droppedMsgs);
node.droppedMsgs = 0;
}
}, 15 * 1000);
node.on("close", function() { clearInterval(loggerId); });
if (node.pauseType === "delay") {
node.on("input", function(msg, send, done) {
var id = ourTimeout(function() {
node.idList.splice(node.idList.indexOf(id),1);
if (node.idList.length === 0) { node.status({}); }
send(msg);
done();
}, node.timeout, () => done());
if (Object.keys(msg).length === 2 && msg.hasOwnProperty("flush")) { id.clear(); }
else { node.idList.push(id); }
if (msg.hasOwnProperty("reset")) { clearDelayList(true); }
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({}); }
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:" "});
}
if (msg.hasOwnProperty("reset")) { clearDelayList(true); }
if ((node.timeout > 1000) && (node.idList.length !== 0)) {
node.status({fill:"blue",shape:"dot",text:" "});
}
});
node.on("close", function() { clearDelayList(); });
@@ -145,11 +182,11 @@ module.exports = function(RED) {
done();
}, delayvar, () => done());
node.idList.push(id);
if (msg.hasOwnProperty("reset")) { clearDelayList(true); }
if (msg.hasOwnProperty("flush")) { flushDelayList(); done(); }
if ((delayvar >= 0) && (node.idList.length !== 0)) {
node.status({fill:"blue",shape:"dot",text:delayvar/1000+"s"});
}
if (msg.hasOwnProperty("reset")) { clearDelayList(true); }
if (msg.hasOwnProperty("flush")) { flushDelayList(); }
});
node.on("close", function() { clearDelayList(); });
}
@@ -162,32 +199,37 @@ module.exports = function(RED) {
}
delete node.lastSent;
node.buffer = [];
node.rate = node.fixedrate;
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({msg: m, send: send, done: done});
node.reportDepth();
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate)) && node.rate !== msg.rate) {
node.rate = msg.rate;
clearInterval(node.intervalID);
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
}
var max_msgs = maxKeptMsgsCount(node);
if ((max_msgs > 0) && (node.buffer.length >= max_msgs)) {
node.buffer = [];
node.error(RED._("delay.errors.too-many"), msg);
} else {
node.buffer.push({msg: m, send: send, done: done});
node.reportDepth();
}
}
else {
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) {
node.rate = msg.rate;
}
send(m);
node.reportDepth();
node.intervalID = setInterval(function() {
if (node.buffer.length === 0) {
clearInterval(node.intervalID);
node.intervalID = -1;
}
if (node.buffer.length > 0) {
const msgInfo = node.buffer.shift();
msgInfo.send(msgInfo.msg);
msgInfo.done();
}
node.reportDepth();
}, node.rate);
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
done();
}
if (msg.hasOwnProperty("flush")) {
@@ -201,17 +243,40 @@ module.exports = function(RED) {
}
}
else {
var timeSinceLast;
if (node.lastSent) {
timeSinceLast = process.hrtime(node.lastSent);
}
if (!node.lastSent) { // ensuring that we always send the first message
node.lastSent = process.hrtime();
send(msg);
}
else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) {
node.lastSent = process.hrtime();
send(msg);
if (maxKeptMsgsCount(node) > 0) {
if (node.intervalID === -1) {
node.send(msg);
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
} else {
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate)) && node.rate !== msg.rate) {
node.rate = msg.rate;
clearInterval(node.intervalID);
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
}
if (node.buffer.length < _maxKeptMsgsCount) {
var m = RED.util.cloneMessage(msg);
node.buffer.push({msg: m, send: send, done: done});
} else {
node.trace("dropped due to buffer overflow. msg._msgid = " + msg._msgid);
node.droppedMsgs++;
}
}
} else {
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) {
node.rate = msg.rate;
}
var timeSinceLast;
if (node.lastSent) {
timeSinceLast = process.hrtime(node.lastSent);
}
if (!node.lastSent) { // ensuring that we always send the first message
node.lastSent = process.hrtime();
send(msg);
}
else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) {
node.lastSent = process.hrtime();
send(msg);
}
}
done();
}
@@ -245,6 +310,11 @@ module.exports = function(RED) {
var hit;
node.on("input", function(msg, send, done) {
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate)) && node.rate !== msg.rate) {
node.rate = msg.rate;
clearInterval(node.intervalID);
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
}
if (!msg.hasOwnProperty("topic")) { msg.topic = "_none_"; }
hit = false;
for (var b in node.buffer) { // check if already in queue
@@ -265,6 +335,7 @@ module.exports = function(RED) {
msgInfo.done();
}
node.buffer = [];
node.rate = node.fixedrate;
node.status({text:"reset"});
done();
}
@@ -297,12 +368,13 @@ module.exports = function(RED) {
node.status({});
done();
}, wait, () => done());
node.idList.push(id);
if (Object.keys(msg).length === 2 && msg.hasOwnProperty("flush")) { id.clear(); }
else { node.idList.push(id); }
if (msg.hasOwnProperty("reset")) { clearDelayList(true); }
if (msg.hasOwnProperty("flush")) { flushDelayList(); done(); }
if ((node.timeout >= 1000) && (node.idList.length !== 0)) {
node.status({fill:"blue",shape:"dot",text:parseInt(wait/10)/100+"s"});
}
if (msg.hasOwnProperty("reset")) { clearDelayList(true); }
if (msg.hasOwnProperty("flush")) { flushDelayList(); }
});
node.on("close", function() { clearDelayList(); });
}

View File

@@ -67,6 +67,10 @@
<label style="width: 120px;" for="node-config-input-servername"><i class="fa fa-server"></i> <span data-i18n="tls.label.servername"></span></label>
<input style="width: calc(100% - 170px);" type="text" id="node-config-input-servername" data-i18n="[placeholder]tls.placeholder.servername">
</div>
<div class="form-row">
<label style="width: 120px;" for="node-config-input-alpnprotocol"><i class="fa fa-cogs"></i> <span data-i18n="tls.label.alpnprotocol"></span></label>
<input style="width: calc(100% - 170px);" type="text" id="node-config-input-alpnprotocol" data-i18n="[placeholder]tls.placeholder.alpnprotocol">
</div>
<hr>
<div class="form-row">
<label style="width: 120px;" for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
@@ -98,7 +102,8 @@
keyname: {value:""},
caname: {value:""},
servername: {value:""},
verifyservercert: {value: true}
verifyservercert: {value: true},
alpnprotocol: {value: ""}
},
credentials: {
certdata: {type:"text"},

View File

@@ -26,6 +26,7 @@ module.exports = function(RED) {
var keyPath = n.key.trim();
var caPath = n.ca.trim();
this.servername = (n.servername||"").trim();
this.alpnprotocol = (n.alpnprotocol||"").trim();
if ((certPath.length > 0) || (keyPath.length > 0) || (caPath.length > 0)) {
@@ -106,6 +107,9 @@ module.exports = function(RED) {
if (this.servername) {
opts.servername = this.servername;
}
if (this.alpnprotocol) {
opts.ALPNProtocols = [this.alpnprotocol];
}
opts.rejectUnauthorized = this.verifyservercert;
}
return opts;

View File

@@ -186,8 +186,8 @@
<input type="text" id="node-config-input-port" data-i18n="[placeholder]mqtt.label.port" style="width:55px">
</div>
<div class="form-row" style="height: 34px;">
<input type="checkbox" id="node-config-input-usetls" style="height: 34px; margin: 0 0 0 104px; display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-usetls" style="width: 80px; line-height: 34px;"><span data-i18n="mqtt.label.use-tls"></span></label>
<input type="checkbox" id="node-config-input-usetls" style="height: 34px; margin: 0 5px 0 104px; display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-usetls" style="width: 100px; line-height: 34px;"><span data-i18n="mqtt.label.use-tls"></span></label>
<span id="node-config-row-tls" class="hide"><input style="width: 320px;" type="text" id="node-config-input-tls"></span>
</div>
<div class="form-row">

View File

@@ -314,7 +314,7 @@ module.exports = function(RED) {
}
// Build options for passing to the MQTT.js API
this.options.clientId = this.clientid || 'mqtt_' + (1+Math.random()*4294967295).toString(16);
this.options.clientId = this.clientid || 'mqtt_' + RED.util.generateId();
this.options.username = this.username;
this.options.password = this.password;
this.options.keepalive = this.keepalive;
@@ -400,7 +400,15 @@ module.exports = function(RED) {
}
if (Object.keys(node.users).length === 0) {
if (node.client && node.client.connected) {
return node.client.end(done);
// Send close message
if (node.closeMessage) {
node.publish(node.closeMessage,function(err) {
node.client.end(done);
});
} else {
node.client.end(done);
}
return;
} else {
node.client.end();
return done();
@@ -639,10 +647,6 @@ module.exports = function(RED) {
this.on('close', function(done) {
this.closing = true;
if (this.connected) {
// Send close message
if (node.closeMessage) {
node.publish(node.closeMessage);
}
this.client.once('close', function() {
done();
});
@@ -873,4 +877,4 @@ module.exports = function(RED) {
}
}
RED.nodes.registerType("mqtt out",MQTTOutNode);
};
};

View File

@@ -46,7 +46,9 @@ module.exports = function(RED) {
isText = true;
} else if (parsedType.type !== "application") {
isText = false;
} else if ((parsedType.subtype !== "octet-stream") && (parsedType.subtype !== "cbor")) {
} else if ((parsedType.subtype !== "octet-stream")
&& (parsedType.subtype !== "cbor")
&& (parsedType.subtype !== "x-protobuf")) {
checkUTF = true;
} else {
// application/octet-stream or application/cbor

View File

@@ -62,14 +62,14 @@ module.exports = function(RED) {
if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
if (process.env.NO_PROXY) { noprox = process.env.NO_PROXY.split(","); }
var noproxy = false;
if (noprox) {
for (var i in noprox) {
if (node.path.indexOf(noprox[i].trim()) !== -1) { noproxy=true; }
}
}
var agent = undefined;
if (prox && !noproxy) {
agent = new HttpsProxyAgent(prox);
@@ -92,7 +92,7 @@ module.exports = function(RED) {
}
function handleConnection(/*socket*/socket) {
var id = (1+Math.random()*4294967295).toString(16);
var id = RED.util.generateId();
if (node.isServer) {
node._clients[id] = socket;
node.emit('opened',{count:Object.keys(node._clients).length,id:id});

View File

@@ -69,7 +69,7 @@ module.exports = function(RED) {
var setupTcpClient = function() {
node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port}));
node.status({fill:"grey",shape:"dot",text:"common.status.connecting"});
var id = (1+Math.random()*4294967295).toString(16);
var id = RED.util.generateId();
client = net.connect(node.port, node.host, function() {
buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : "";
node.connected = true;
@@ -153,7 +153,7 @@ module.exports = function(RED) {
var server = net.createServer(function (socket) {
socket.setKeepAlive(true,120000);
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
var id = (1+Math.random()*4294967295).toString(16);
var id = RED.util.generateId();
var fromi;
var fromp;
connectionPool[id] = socket;

View File

@@ -38,17 +38,18 @@ module.exports = function(RED) {
if (this.hdrout === true) { this.hdrout = "all"; }
var tmpwarn = true;
var node = this;
var re = new RegExp(',(?=(?:(?:[^"]*"){2})*[^"]*$)','g');
var re = new RegExp(node.sep.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g,'\\$&') + '(?=(?:(?:[^"]*"){2})*[^"]*$)','g');
// pass in an array of column names to be trimed, de-quoted and retrimed
var clean = function(col) {
// pass in an array of column names to be trimmed, de-quoted and retrimmed
var clean = function(col,sep) {
if (sep) { re = new RegExp(sep.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g,'\\$&') +'(?=(?:(?:[^"]*"){2})*[^"]*$)','g'); }
col = col.trim().split(re) || [""];
col = col.map(x => x.replace(/"/g,'').trim());
if ((col.length === 1) && (col[0] === "")) { node.goodtmpl = false; }
else { node.goodtmpl = true; }
return col;
}
var template = clean(node.template);
var template = clean(node.template,',');
var notemplate = template.length === 1 && template[0] === '';
node.hdrSent = false;
@@ -67,7 +68,7 @@ module.exports = function(RED) {
if (node.hdrout !== "none" && node.hdrSent === false) {
if ((template.length === 1) && (template[0] === '')) {
if (msg.hasOwnProperty("columns")) {
template = clean(msg.columns || "");
template = clean(msg.columns || "",",");
}
else {
template = Object.keys(msg.payload[0]);
@@ -80,7 +81,7 @@ module.exports = function(RED) {
if ((Array.isArray(msg.payload[s])) || (typeof msg.payload[s] !== "object")) {
if (typeof msg.payload[s] !== "object") { msg.payload = [ msg.payload ]; }
for (var t = 0; t < msg.payload[s].length; t++) {
if (!msg.payload[s][t] && (msg.payload[s][t] !== 0)) { msg.payload[s][t] = ""; }
if (msg.payload[s][t] === undefined) { msg.payload[s][t] = ""; }
if (msg.payload[s][t].toString().indexOf(node.quo) !== -1) { // add double quotes if any quotes
msg.payload[s][t] = msg.payload[s][t].toString().replace(/"/g, '""');
msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
@@ -93,7 +94,7 @@ module.exports = function(RED) {
}
else {
if ((template.length === 1) && (template[0] === '') && (msg.hasOwnProperty("columns"))) {
template = clean(msg.columns || "");
template = clean(msg.columns || "",",");
}
if ((template.length === 1) && (template[0] === '')) {
/* istanbul ignore else */
@@ -184,7 +185,7 @@ module.exports = function(RED) {
if ((node.hdrin === true) && first) { // if the template is in the first line
if ((line[i] === "\n")||(line[i] === "\r")||(line.length - i === 1)) { // look for first line break
if (line.length - i === 1) { tmp += line[i]; }
template = clean(tmp);
template = clean(tmp,node.sep);
first = false;
}
else { tmp += line[i]; }

View File

@@ -32,7 +32,7 @@ module.exports = function(RED) {
var tag = node.tag;
if (msg.hasOwnProperty("select")) { tag = node.tag || msg.select; }
try {
var $ = cheerio.load(value);
var $ = cheerio.load(value,null,false);
var pay = [];
var count = 0;
$(tag).each(function() {
@@ -42,9 +42,11 @@ module.exports = function(RED) {
$(tag).each(function() {
if (node.as === "multi") {
var pay2 = null;
if (node.ret === "html") { pay2 = cheerio.load($(this).html().trim()).xml(); }
if (node.ret === "html") { pay2 = cheerio.load($(this).html().trim(),null,false).xml(); }
if (node.ret === "text") { pay2 = $(this).text(); }
if (node.ret === "attr") { pay2 = this.attribs; }
if (node.ret === "attr") {
pay2 = Object.assign({},this.attribs);
}
//if (node.ret === "val") { pay2 = $(this).val(); }
/* istanbul ignore else */
if (pay2) {
@@ -61,9 +63,12 @@ module.exports = function(RED) {
}
}
if (node.as === "single") {
if (node.ret === "html") { pay.push( cheerio.load($(this).html().trim()).xml() ); }
if (node.ret === "html") { pay.push( cheerio.load($(this).html().trim(),null,false).xml() ); }
if (node.ret === "text") { pay.push( $(this).text() ); }
if (node.ret === "attr") { pay.push( this.attribs ); }
if (node.ret === "attr") {
var attribs = Object.assign({},this.attribs);
pay.push( attribs );
}
//if (node.ret === "val") { pay.push( $(this).val() ); }
}
index++;

View File

@@ -17,8 +17,7 @@
module.exports = function(RED) {
"use strict";
const Ajv = require('ajv');
const ajv = new Ajv({allErrors: true, schemaId: 'auto'});
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
const ajv = new Ajv({allErrors: true});
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
function JSONNode(n) {

View File

@@ -219,6 +219,10 @@
value: "none",
label: label
}).text(label).appendTo(encSel);
$("<option/>", {
value: "setbymsg",
label: node._("file.encoding.setbymsg")
}).text(label).appendTo(encSel);
encodings.forEach(function(item) {
if(Array.isArray(item)) {
var group = $("<optgroup/>", {

View File

@@ -51,6 +51,10 @@ module.exports = function(RED) {
function processMsg(msg,nodeSend, done) {
var filename = node.filename || msg.filename || "";
var fullFilename = filename;
if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename));
}
if ((!node.filename) && (!node.tout)) {
node.tout = setTimeout(function() {
node.status({fill:"grey",shape:"dot",text:filename});
@@ -62,10 +66,11 @@ module.exports = function(RED) {
node.warn(RED._("file.errors.nofilename"));
done();
} else if (node.overwriteFile === "delete") {
fs.unlink(filename, function (err) {
fs.unlink(fullFilename, function (err) {
if (err) {
node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg);
} else {
}
else {
if (RED.settings.verbose) {
node.log(RED._("file.status.deletedfile",{file:filename}));
}
@@ -74,11 +79,12 @@ module.exports = function(RED) {
done();
});
} else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
var dir = path.dirname(filename);
var dir = path.dirname(fullFilename);
if (node.createDir) {
try {
fs.ensureDirSync(dir);
} catch(err) {
}
catch(err) {
node.error(RED._("file.errors.createfail",{error:err.toString()}),msg);
done();
return;
@@ -92,19 +98,24 @@ module.exports = function(RED) {
if (typeof data === "boolean") { data = data.toString(); }
if (typeof data === "number") { data = data.toString(); }
if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
var buf = encode(data, node.encoding);
var buf;
if (node.encoding === "setbymsg") {
buf = encode(data, msg.encoding || "none");
}
else { buf = encode(data, node.encoding); }
if (node.overwriteFile === "true") {
var wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true });
var wstream = fs.createWriteStream(fullFilename, { encoding:'binary', flags:'w', autoClose:true });
node.wstream = wstream;
wstream.on("error", function(err) {
node.error(RED._("file.errors.writefail",{error:err.toString()}),msg);
done();
});
wstream.on("open", function() {
wstream.end(buf, function() {
wstream.once("close", function() {
nodeSend(msg);
done();
});
wstream.end(buf);
})
return;
}
@@ -116,7 +127,7 @@ module.exports = function(RED) {
// of the file. Check the file hasn't been deleted
// or deleted and recreated.
try {
var stat = fs.statSync(filename);
var stat = fs.statSync(fullFilename);
// File exists - check the inode matches
if (stat.ino !== node.wstreamIno) {
// The file has been recreated. Close the current
@@ -126,7 +137,8 @@ module.exports = function(RED) {
delete node.wstream;
delete node.wstreamIno;
}
} catch(err) {
}
catch(err) {
// File does not exist
recreateStream = true;
node.wstream.end();
@@ -135,10 +147,10 @@ module.exports = function(RED) {
}
}
if (recreateStream) {
node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'a', autoClose:true });
node.wstream = fs.createWriteStream(fullFilename, { encoding:'binary', flags:'a', autoClose:true });
node.wstream.on("open", function(fd) {
try {
var stat = fs.statSync(filename);
var stat = fs.statSync(fullFilename);
node.wstreamIno = stat.ino;
} catch(err) {
}
@@ -154,14 +166,16 @@ module.exports = function(RED) {
nodeSend(msg);
done();
});
} else {
}
else {
// Dynamic filename - write and close the stream
node.wstream.end(buf, function() {
node.wstream.once("close", function() {
nodeSend(msg);
delete node.wstream;
delete node.wstreamIno;
done();
});
node.wstream.end(buf);
}
}
}
@@ -258,6 +272,10 @@ module.exports = function(RED) {
this.on("input",function(msg, nodeSend, nodeDone) {
var filename = (node.filename || msg.filename || "").replace(/\t|\r|\n/g,'');
var fullFilename = filename;
if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename));
}
if (!node.filename) {
node.status({fill:"grey",shape:"dot",text:filename});
}
@@ -276,10 +294,9 @@ module.exports = function(RED) {
ch = "\n";
type = "string";
}
var hwm;
var getout = false;
var rs = fs.createReadStream(filename)
var rs = fs.createReadStream(fullFilename)
.on('readable', function () {
var chunk;
var hwm = rs._readableState.highWaterMark;
@@ -340,16 +357,17 @@ module.exports = function(RED) {
nodeSend(msg);
}
else if (node.format === "lines") {
var m = { payload: spare,
topic:msg.topic,
parts: {
index: count,
count: count+1,
ch: ch,
type: type,
id: msg._msgid
}
};
var m = {
payload: spare,
topic:msg.topic,
parts: {
index: count,
count: count+1,
ch: ch,
type: type,
id: msg._msgid
}
};
nodeSend(m);
}
else if (getout) { // last chunk same size as high water mark - have to send empty extra packet.

View File

@@ -23,9 +23,13 @@ module.exports = function(RED) {
var getAllDirs = function (dir, filelist) {
filelist = filelist || [];
fs.readdirSync(dir).forEach(file => {
if (fs.statSync(path.join(dir, file)).isDirectory() ) {
filelist.push(path.join(dir, file));
getAllDirs(path.join(dir, file), filelist);
try {
if (fs.statSync(path.join(dir, file)).isDirectory() ) {
filelist.push(path.join(dir, file));
getAllDirs(path.join(dir, file), filelist);
}
} catch (error) {
//should we raise an error?
}
});
return filelist;