mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge pull request #4320 from node-red/dev
Sync `dev` to `master` for 3.1.0 release
This commit is contained in:
@@ -117,14 +117,21 @@ module.exports = function(RED) {
|
||||
if (p.v) {
|
||||
try {
|
||||
var exp = RED.util.prepareJSONataExpression(p.v, node);
|
||||
var val = RED.util.evaluateJSONataExpression(exp, msg);
|
||||
RED.util.setMessageProperty(msg, property, val, true);
|
||||
}
|
||||
catch (err) {
|
||||
RED.util.evaluateJSONataExpression(exp, msg, (err, newValue) => {
|
||||
if (err) {
|
||||
errors.push(err.toString())
|
||||
} else {
|
||||
RED.util.setMessageProperty(msg,property,newValue,true);
|
||||
}
|
||||
evaluateProperty(doneEvaluating)
|
||||
});
|
||||
} catch (err) {
|
||||
errors.push(err.message);
|
||||
evaluateProperty(doneEvaluating)
|
||||
}
|
||||
} else {
|
||||
evaluateProperty(doneEvaluating)
|
||||
}
|
||||
evaluateProperty(doneEvaluating)
|
||||
} else {
|
||||
try {
|
||||
RED.util.evaluateNodeProperty(value, valueType, node, msg, (err, newValue) => {
|
||||
|
@@ -4,6 +4,7 @@
|
||||
<label style="width: auto" for="node-input-scope" data-i18n="catch.label.source"></label>
|
||||
<select id="node-input-scope-select">
|
||||
<option value="all" data-i18n="catch.scope.all"></option>
|
||||
<option value="group" data-i18n="catch.scope.group"></option>
|
||||
<option value="target" data-i18n="catch.scope.selected"></option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -40,7 +41,9 @@
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
if (this.scope) {
|
||||
if (this.scope === "group") {
|
||||
return this._("catch.catchGroup");
|
||||
} else if (Array.isArray(this.scope)) {
|
||||
return this._("catch.catchNodes",{number:this.scope.length});
|
||||
}
|
||||
return this.uncaught?this._("catch.catchUncaught"):this._("catch.catch")
|
||||
@@ -170,6 +173,8 @@
|
||||
});
|
||||
if (this.scope === null) {
|
||||
$("#node-input-scope-select").val("all");
|
||||
} else if(this.scope === "group"){
|
||||
$("#node-input-scope-select").val("group");
|
||||
} else {
|
||||
$("#node-input-scope-select").val("target");
|
||||
}
|
||||
@@ -179,6 +184,8 @@
|
||||
var scope = $("#node-input-scope-select").val();
|
||||
if (scope === 'all') {
|
||||
this.scope = null;
|
||||
} else if(scope === 'group') {
|
||||
this.scope = "group";
|
||||
} else {
|
||||
$("#node-input-uncaught").prop("checked",false);
|
||||
this.scope = $("#node-input-catch-target-container-div").treeList('selected').map(function(i) { return i.node.id})
|
||||
|
@@ -4,6 +4,7 @@
|
||||
<label style="width: auto" for="node-input-scope" data-i18n="status.label.source"></label>
|
||||
<select id="node-input-scope-select">
|
||||
<option value="all" data-i18n="status.scope.all"></option>
|
||||
<option value="group" data-i18n="status.scope.group"></option>
|
||||
<option value="target" data-i18n="status.scope.selected"></option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -32,7 +33,15 @@
|
||||
outputs:1,
|
||||
icon: "status.svg",
|
||||
label: function() {
|
||||
return this.name||(this.scope?this._("status.statusNodes",{number:this.scope.length}):this._("status.status"));
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
if (this.scope === "group") {
|
||||
return this._("status.statusGroup");
|
||||
} else if (Array.isArray(this.scope)) {
|
||||
return this._("status.statusNodes",{number:this.scope.length});
|
||||
}
|
||||
return this._("status.status")
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
@@ -157,6 +166,8 @@
|
||||
});
|
||||
if (this.scope === null) {
|
||||
$("#node-input-scope-select").val("all");
|
||||
} else if(this.scope === "group"){
|
||||
$("#node-input-scope-select").val("group");
|
||||
} else {
|
||||
$("#node-input-scope-select").val("target");
|
||||
}
|
||||
@@ -166,6 +177,8 @@
|
||||
var scope = $("#node-input-scope-select").val();
|
||||
if (scope === 'all') {
|
||||
this.scope = null;
|
||||
} else if(scope === 'group') {
|
||||
this.scope = "group";
|
||||
} else {
|
||||
this.scope = $("#node-input-status-target-container-div").treeList('selected').map(function(i) { return i.node.id})
|
||||
}
|
||||
|
@@ -28,7 +28,7 @@
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-timeout"><span data-i18n="exec.label.timeout"></span></label>
|
||||
<label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="exec.label.timeout"></span></label>
|
||||
<input type="text" id="node-input-timeout" placeholder="30" style="width: 70px; margin-right: 5px;"><span data-i18n="inject.seconds"></span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
|
@@ -248,6 +248,14 @@ module.exports = function(RED) {
|
||||
}
|
||||
});
|
||||
|
||||
this.on("close", function () {
|
||||
for (const event of Object.values(messageEvents)) {
|
||||
if (event.ts) {
|
||||
clearTimeout(event.ts)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.returnLinkMessage = function(eventId, msg) {
|
||||
if (Array.isArray(msg._linkSource) && msg._linkSource.length === 0) {
|
||||
delete msg._linkSource;
|
||||
|
27
packages/node_modules/@node-red/nodes/core/common/91-global-config.html
vendored
Normal file
27
packages/node_modules/@node-red/nodes/core/common/91-global-config.html
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<script type="text/html" data-template-name="global-config">
|
||||
<div class="form-row">
|
||||
<label style="width: 100%"><span data-i18n="global-config.label.open-conf"></span>:</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<button class="red-ui-button" type="button" id="node-input-edit-env-var" data-i18n="editor:env-var.header" style="margin-left: 20px"></button>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('global-config',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
name: { value: "" },
|
||||
env: { value: [] },
|
||||
},
|
||||
credentials: {
|
||||
map: { type: "map" }
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$('#node-input-edit-env-var').on('click', function(evt) {
|
||||
RED.actions.invoke('core:show-user-settings', 'envvar')
|
||||
});
|
||||
},
|
||||
hasUsers: false
|
||||
});
|
||||
</script>
|
7
packages/node_modules/@node-red/nodes/core/common/91-global-config.js
vendored
Normal file
7
packages/node_modules/@node-red/nodes/core/common/91-global-config.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
function GlobalConfigNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
}
|
||||
RED.nodes.registerType("global-config", GlobalConfigNode);
|
||||
}
|
@@ -77,9 +77,15 @@
|
||||
<div id="func-tabs-content" style="min-height: calc(100% - 95px);">
|
||||
|
||||
<div id="func-tab-config" style="display:none">
|
||||
<div class="form-row">
|
||||
<label for="node-input-outputs"><i class="fa fa-random"></i> <span data-i18n="function.label.outputs"></span></label>
|
||||
<input id="node-input-outputs" style="width: 60px;" value="1">
|
||||
<div>
|
||||
<div class="form-row" style="display: inline-block; margin-right: 50px;">
|
||||
<label for="node-input-outputs"><i class="fa fa-random"></i> <span data-i18n="function.label.outputs"></span></label>
|
||||
<input id="node-input-outputs" style="width: 60px;" value="1">
|
||||
</div>
|
||||
<div class="form-row" style="display: inline-block;">
|
||||
<label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="function.label.timeout"></span></label>
|
||||
<input id="node-input-timeout" style="width: 60px;" data-i18n="[placeholder]join.seconds">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row node-input-libs-row hide" style="margin-bottom: 0px;">
|
||||
@@ -360,6 +366,7 @@
|
||||
name: {value:"_DEFAULT_"},
|
||||
func: {value:"\nreturn msg;"},
|
||||
outputs: {value:1},
|
||||
timeout:{value:RED.settings.functionTimeout || 0},
|
||||
noerr: {value:0,required:true,
|
||||
validate: function(v, opt) {
|
||||
if (!v) {
|
||||
@@ -464,6 +471,26 @@
|
||||
}
|
||||
});
|
||||
|
||||
// 4294967 is max in node.js timeout.
|
||||
$( "#node-input-timeout" ).spinner({
|
||||
min: 0,
|
||||
max: 4294967,
|
||||
change: function(event, ui) {
|
||||
var value = this.value;
|
||||
if(value == ""){
|
||||
value = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = parseInt(value);
|
||||
}
|
||||
value = isNaN(value) ? 1 : value;
|
||||
value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
|
||||
value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
|
||||
if (value !== this.value) { $(this).spinner("value", value); }
|
||||
}
|
||||
});
|
||||
|
||||
var buildEditor = function(id, stateId, focus, value, defaultValue, extraLibs, offset) {
|
||||
var editor = RED.editor.createEditor({
|
||||
id: id,
|
||||
@@ -503,7 +530,7 @@
|
||||
editor:this.editor, // the field name the main text body goes to
|
||||
mode:"ace/mode/nrjavascript",
|
||||
fields:[
|
||||
'name', 'outputs',
|
||||
'name', 'outputs', 'timeout',
|
||||
{
|
||||
name: 'initialize',
|
||||
get: function() {
|
||||
|
@@ -96,6 +96,13 @@ module.exports = function(RED) {
|
||||
node.name = n.name;
|
||||
node.func = n.func;
|
||||
node.outputs = n.outputs;
|
||||
node.timeout = n.timeout*1000;
|
||||
if(node.timeout>0){
|
||||
node.timeoutOptions = {
|
||||
timeout:node.timeout,
|
||||
breakOnSigint:true
|
||||
}
|
||||
}
|
||||
node.ini = n.initialize ? n.initialize.trim() : "";
|
||||
node.fin = n.finalize ? n.finalize.trim() : "";
|
||||
node.libs = n.libs || [];
|
||||
@@ -362,6 +369,10 @@ module.exports = function(RED) {
|
||||
})(__initSend__);`;
|
||||
iniOpt = createVMOpt(node, " setup");
|
||||
iniScript = new vm.Script(iniText, iniOpt);
|
||||
if(node.timeout>0){
|
||||
iniOpt.timeout = node.timeout;
|
||||
iniOpt.breakOnSigint = true;
|
||||
}
|
||||
}
|
||||
node.script = vm.createScript(functionText, createVMOpt(node, ""));
|
||||
if (node.fin && (node.fin !== "")) {
|
||||
@@ -385,6 +396,10 @@ module.exports = function(RED) {
|
||||
})();`;
|
||||
finOpt = createVMOpt(node, " cleanup");
|
||||
finScript = new vm.Script(finText, finOpt);
|
||||
if(node.timeout>0){
|
||||
finOpt.timeout = node.timeout;
|
||||
finOpt.breakOnSigint = true;
|
||||
}
|
||||
}
|
||||
var promise = Promise.resolve();
|
||||
if (iniScript) {
|
||||
@@ -396,9 +411,12 @@ module.exports = function(RED) {
|
||||
var start = process.hrtime();
|
||||
context.msg = msg;
|
||||
context.__send__ = send;
|
||||
context.__done__ = done;
|
||||
|
||||
node.script.runInContext(context);
|
||||
context.__done__ = done;
|
||||
var opts = {};
|
||||
if (node.timeout>0){
|
||||
opts = node.timeoutOptions;
|
||||
}
|
||||
node.script.runInContext(context,opts);
|
||||
context.results.then(function(results) {
|
||||
sendResults(node,send,msg._msgid,results,false);
|
||||
if (handleNodeDoneCall) {
|
||||
@@ -503,7 +521,8 @@ module.exports = function(RED) {
|
||||
RED.nodes.registerType("function",FunctionNode, {
|
||||
dynamicModuleList: "libs",
|
||||
settings: {
|
||||
functionExternalModules: { value: true, exportable: true }
|
||||
functionExternalModules: { value: true, exportable: true },
|
||||
functionTimeout: { value:0, exportable: true }
|
||||
}
|
||||
});
|
||||
RED.library.register("functions");
|
||||
|
@@ -10,6 +10,7 @@
|
||||
<option value="scale" data-i18n="range.scale.payload"></option>
|
||||
<option value="clamp" data-i18n="range.scale.limit"></option>
|
||||
<option value="roll" data-i18n="range.scale.wrap"></option>
|
||||
<option value="drop" data-i18n="range.scale.drop"></option>
|
||||
</select>
|
||||
</div>
|
||||
<br/>
|
||||
|
@@ -32,11 +32,15 @@ module.exports = function(RED) {
|
||||
if (value !== undefined) {
|
||||
var n = Number(value);
|
||||
if (!isNaN(n)) {
|
||||
if (node.action == "clamp") {
|
||||
if (node.action === "drop") {
|
||||
if (n < node.minin) { done(); return; }
|
||||
if (n > node.maxin) { done(); return; }
|
||||
}
|
||||
if (node.action === "clamp") {
|
||||
if (n < node.minin) { n = node.minin; }
|
||||
if (n > node.maxin) { n = node.maxin; }
|
||||
}
|
||||
if (node.action == "roll") {
|
||||
if (node.action === "roll") {
|
||||
var divisor = node.maxin - node.minin;
|
||||
n = ((n - node.minin) % divisor + divisor) % divisor + node.minin;
|
||||
}
|
||||
|
@@ -201,6 +201,7 @@ module.exports = function(RED) {
|
||||
});
|
||||
node.on("close", function() { clearDelayList(); });
|
||||
}
|
||||
|
||||
else if (node.pauseType === "random") {
|
||||
node.on("input", function(msg, send, done) {
|
||||
var wait = node.randomFirst + (node.diff * Math.random());
|
||||
@@ -226,34 +227,20 @@ module.exports = function(RED) {
|
||||
// The rate limit/queue type modes
|
||||
else if (node.pauseType === "rate") {
|
||||
node.on("input", function(msg, send, done) {
|
||||
if (msg.hasOwnProperty("reset")) {
|
||||
if (node.intervalID !== -1 ) {
|
||||
clearInterval(node.intervalID);
|
||||
node.intervalID = -1;
|
||||
}
|
||||
delete node.lastSent;
|
||||
node.buffer = [];
|
||||
node.rate = node.fixedrate;
|
||||
node.status({fill:"blue",shape:"ring",text:0});
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node.drop) {
|
||||
var m = RED.util.cloneMessage(msg);
|
||||
delete m.flush;
|
||||
delete m.lifo;
|
||||
if (Object.keys(m).length > 1) {
|
||||
if (node.intervalID !== -1) {
|
||||
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate)) && node.rate !== msg.rate) {
|
||||
node.rate = msg.rate;
|
||||
if (node.allowrate && m.hasOwnProperty("rate") && !isNaN(parseFloat(m.rate)) && node.rate !== m.rate) {
|
||||
node.rate = m.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);
|
||||
node.error(RED._("delay.errors.too-many"), m);
|
||||
} else if (msg.toFront === true) {
|
||||
node.buffer.unshift({msg: m, send: send, done: done});
|
||||
node.reportDepth();
|
||||
@@ -263,8 +250,8 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) {
|
||||
node.rate = msg.rate;
|
||||
if (node.allowrate && m.hasOwnProperty("rate") && !isNaN(parseFloat(m.rate))) {
|
||||
node.rate = m.rate;
|
||||
}
|
||||
send(m);
|
||||
node.reportDepth();
|
||||
@@ -282,6 +269,8 @@ module.exports = function(RED) {
|
||||
else {
|
||||
while (len > 0) {
|
||||
const msgInfo = node.buffer.shift();
|
||||
delete msgInfo.msg.flush;
|
||||
delete msgInfo.msg.reset;
|
||||
if (Object.keys(msgInfo.msg).length > 1) {
|
||||
node.send(msgInfo.msg);
|
||||
msgInfo.done();
|
||||
@@ -335,6 +324,21 @@ module.exports = function(RED) {
|
||||
}
|
||||
done();
|
||||
}
|
||||
|
||||
if (msg.hasOwnProperty("reset")) {
|
||||
if (msg.flush === undefined) {
|
||||
if (node.intervalID !== -1 ) {
|
||||
clearInterval(node.intervalID);
|
||||
node.intervalID = -1;
|
||||
}
|
||||
delete node.lastSent;
|
||||
}
|
||||
node.buffer = [];
|
||||
node.rate = node.fixedrate;
|
||||
node.status({fill:"blue",shape:"ring",text:0});
|
||||
done();
|
||||
return;
|
||||
}
|
||||
});
|
||||
node.on("close", function() {
|
||||
clearInterval(node.intervalID);
|
||||
@@ -387,6 +391,22 @@ module.exports = function(RED) {
|
||||
node.buffer.push({msg, send, done}); // if not add to end of queue
|
||||
node.reportDepth();
|
||||
}
|
||||
if (msg.hasOwnProperty("flush")) {
|
||||
var len = node.buffer.length;
|
||||
if (typeof(msg.flush) == 'number') { len = Math.min(Math.floor(msg.flush,len)); }
|
||||
while (len > 0) {
|
||||
const msgInfo = node.buffer.shift();
|
||||
delete msgInfo.msg.flush;
|
||||
delete msgInfo.msg.reset;
|
||||
if (Object.keys(msgInfo.msg).length > 2) {
|
||||
node.send(msgInfo.msg);
|
||||
msgInfo.done();
|
||||
}
|
||||
len = len - 1;
|
||||
}
|
||||
node.status({});
|
||||
done();
|
||||
}
|
||||
if (msg.hasOwnProperty("reset")) {
|
||||
while (node.buffer.length > 0) {
|
||||
const msgInfo = node.buffer.shift();
|
||||
@@ -397,21 +417,6 @@ module.exports = function(RED) {
|
||||
node.status({text:"reset"});
|
||||
done();
|
||||
}
|
||||
if (msg.hasOwnProperty("flush")) {
|
||||
var len = node.buffer.length;
|
||||
if (typeof(msg.flush) == 'number') { len = Math.min(Math.floor(msg.flush,len)); }
|
||||
while (len > 0) {
|
||||
const msgInfo = node.buffer.shift();
|
||||
delete msgInfo.msg.flush;
|
||||
if (Object.keys(msgInfo.msg).length > 2) {
|
||||
node.send(msgInfo.msg);
|
||||
msgInfo.done();
|
||||
}
|
||||
len = len - 1;
|
||||
}
|
||||
node.status({});
|
||||
done();
|
||||
}
|
||||
});
|
||||
node.on("close", function() {
|
||||
clearInterval(node.intervalID);
|
||||
|
@@ -25,7 +25,7 @@
|
||||
<select id="node-then-type" style="width:70%;">
|
||||
<option value="block" data-i18n="trigger.wait-reset"></option>
|
||||
<option value="wait" data-i18n="trigger.wait-for"></option>
|
||||
<option value="loop" data-i18n="trigger.wait-loop"></option>
|
||||
<option id="node-trigger-wait-loop" value="loop" data-i18n="trigger.wait-loop"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-type-duration">
|
||||
@@ -181,6 +181,13 @@
|
||||
$("#node-input-op2type").val('str');
|
||||
}
|
||||
|
||||
$("#node-input-op1").on("change", function() {
|
||||
if ($("#node-input-op1type").val() === "nul") {
|
||||
$("#node-trigger-wait-loop").hide();
|
||||
}
|
||||
else { $("#node-trigger-wait-loop").show(); }
|
||||
});
|
||||
|
||||
var optionNothing = {value:"nul",label:this._("trigger.output.nothing"),hasValue:false};
|
||||
var optionPayload = {value:"pay",label:this._("trigger.output.existing"),hasValue:false};
|
||||
var optionOriginalPayload = {value:"pay",label:this._("trigger.output.original"),hasValue:false};
|
||||
|
@@ -249,6 +249,12 @@
|
||||
<span id="node-config-input-cleansession-label" data-i18n="mqtt.label.cleansession"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row mqtt-persistence">
|
||||
<label for="node-config-input-autoUnsubscribe" style="width: auto;">
|
||||
<input type="checkbox" id="node-config-input-autoUnsubscribe" style="position: relative;vertical-align: bottom; top: -2px; width: 15px;height: 15px;">
|
||||
<span id="node-config-input-autoUnsubscribe-label" data-i18n="mqtt.label.autoUnsubscribe"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row mqtt5">
|
||||
<label style="width:auto" for="node-config-input-sessionExpiry"><span data-i18n="mqtt.label.sessionExpiry"></span></label>
|
||||
<input type="number" min="0" id="node-config-input-sessionExpiry" style="width: 100px" >
|
||||
@@ -483,17 +489,23 @@
|
||||
tls: {type:"tls-config",required: false,
|
||||
label:RED._("node-red:mqtt.label.use-tls") },
|
||||
clientid: {value:"", validate: function(v, opt) {
|
||||
var ok = false;
|
||||
let ok = true;
|
||||
if ($("#node-config-input-clientid").length) {
|
||||
// Currently editing the node
|
||||
ok = $("#node-config-input-cleansession").is(":checked") || (v||"").length > 0;
|
||||
let needClientId = !$("#node-config-input-cleansession").is(":checked")
|
||||
if (needClientId) {
|
||||
ok = (v||"").length > 0;
|
||||
}
|
||||
} else {
|
||||
ok = (this.cleansession===undefined || this.cleansession) || (v||"").length > 0;
|
||||
let needClientId = !(this.cleansession===undefined || this.cleansession)
|
||||
if (needClientId) {
|
||||
ok = (v||"").length > 0;
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
return ok;
|
||||
if (!ok) {
|
||||
return RED._("node-red:mqtt.errors.invalid-client-id");
|
||||
}
|
||||
return RED._("node-red:mqtt.errors.invalid-client-id");
|
||||
return true;
|
||||
}},
|
||||
autoConnect: {value: true},
|
||||
usetls: {value: false},
|
||||
@@ -505,6 +517,7 @@
|
||||
label: RED._("node-red:mqtt.label.keepalive"),
|
||||
validate:RED.validators.number(false)},
|
||||
cleansession: {value: true},
|
||||
autoUnsubscribe: {value: true},
|
||||
birthTopic: {value:"", validate:validateMQTTPublishTopic},
|
||||
birthQos: {value:"0"},
|
||||
birthRetain: {value:"false"},
|
||||
@@ -620,6 +633,10 @@
|
||||
this.cleansession = true;
|
||||
$("#node-config-input-cleansession").prop("checked",true);
|
||||
}
|
||||
if (typeof this.autoUnsubscribe === 'undefined') {
|
||||
this.autoUnsubscribe = true;
|
||||
$("#node-config-input-autoUnsubscribe").prop("checked",true);
|
||||
}
|
||||
if (typeof this.usetls === 'undefined') {
|
||||
this.usetls = false;
|
||||
$("#node-config-input-usetls").prop("checked",false);
|
||||
@@ -635,6 +652,14 @@
|
||||
if (typeof this.protocolVersion === 'undefined') {
|
||||
this.protocolVersion = 4;
|
||||
}
|
||||
$("#node-config-input-cleansession").on("change", function() {
|
||||
const useCleanSession = $("#node-config-input-cleansession").is(':checked');
|
||||
if(useCleanSession) {
|
||||
$("div.form-row.mqtt-persistence").hide();
|
||||
} else {
|
||||
$("div.form-row.mqtt-persistence").show();
|
||||
}
|
||||
});
|
||||
$("#node-config-input-protocolVersion").on("change", function() {
|
||||
var v5 = $("#node-config-input-protocolVersion").val() == "5";
|
||||
if(v5) {
|
||||
|
@@ -24,7 +24,6 @@ module.exports = function(RED) {
|
||||
"text/css":"string",
|
||||
"text/html":"string",
|
||||
"text/plain":"string",
|
||||
"text/html":"string",
|
||||
"application/json":"json",
|
||||
"application/octet-stream":"buffer",
|
||||
"application/pdf":"buffer",
|
||||
@@ -219,8 +218,10 @@ module.exports = function(RED) {
|
||||
* Handle the payload / packet recieved in MQTT In and MQTT Sub nodes
|
||||
*/
|
||||
function subscriptionHandler(node, datatype ,topic, payload, packet) {
|
||||
const v5 = node.brokerConn.options && node.brokerConn.options.protocolVersion == 5;
|
||||
var msg = {topic:topic, payload:null, qos:packet.qos, retain:packet.retain};
|
||||
const msg = {topic:topic, payload:null, qos:packet.qos, retain:packet.retain};
|
||||
const v5 = (node && node.brokerConn)
|
||||
? node.brokerConn.v5()
|
||||
: Object.prototype.hasOwnProperty.call(packet, "properties");
|
||||
if(v5 && packet.properties) {
|
||||
setStrProp(packet.properties, msg, "responseTopic");
|
||||
setBufferProp(packet.properties, msg, "correlationData");
|
||||
@@ -300,7 +301,7 @@ module.exports = function(RED) {
|
||||
//}
|
||||
}
|
||||
msg.payload = payload;
|
||||
if ((node.brokerConn.broker === "localhost")||(node.brokerConn.broker === "127.0.0.1")) {
|
||||
if (node.brokerConn && (node.brokerConn.broker === "localhost" || node.brokerConn.broker === "127.0.0.1")) {
|
||||
msg._topic = topic;
|
||||
}
|
||||
node.send(msg);
|
||||
@@ -412,6 +413,12 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the connect action
|
||||
* @param {MQTTInNode|MQTTOutNode} node
|
||||
* @param {Object} msg
|
||||
* @param {Function} done
|
||||
*/
|
||||
function handleConnectAction(node, msg, done) {
|
||||
let actionData = typeof msg.broker === 'object' ? msg.broker : null;
|
||||
if (node.brokerConn.canConnect()) {
|
||||
@@ -442,12 +449,17 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the disconnect action
|
||||
* @param {MQTTInNode|MQTTOutNode} node
|
||||
* @param {Function} done
|
||||
*/
|
||||
function handleDisconnectAction(node, done) {
|
||||
node.brokerConn.disconnect(function () {
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
const unsubscribeCandidates = {}
|
||||
//#endregion "Supporting functions"
|
||||
|
||||
//#region "Broker node"
|
||||
@@ -482,6 +494,7 @@ module.exports = function(RED) {
|
||||
setIfHasProperty(opts, node, "protocolVersion", init);
|
||||
setIfHasProperty(opts, node, "keepalive", init);
|
||||
setIfHasProperty(opts, node, "cleansession", init);
|
||||
setIfHasProperty(opts, node, "autoUnsubscribe", init);
|
||||
setIfHasProperty(opts, node, "topicAliasMaximum", init);
|
||||
setIfHasProperty(opts, node, "maximumPacketSize", init);
|
||||
setIfHasProperty(opts, node, "receiveMaximum", init);
|
||||
@@ -590,7 +603,9 @@ module.exports = function(RED) {
|
||||
if (typeof node.cleansession === 'undefined') {
|
||||
node.cleansession = true;
|
||||
}
|
||||
|
||||
if (typeof node.autoUnsubscribe !== 'boolean') {
|
||||
node.autoUnsubscribe = true;
|
||||
}
|
||||
//use url or build a url from usetls://broker:port
|
||||
if (node.url && node.brokerurl !== node.url) {
|
||||
node.brokerurl = node.url;
|
||||
@@ -780,18 +795,11 @@ module.exports = function(RED) {
|
||||
// Re-subscribe to stored topics
|
||||
for (var s in node.subscriptions) {
|
||||
if (node.subscriptions.hasOwnProperty(s)) {
|
||||
let topic = s;
|
||||
let qos = 0;
|
||||
let _options = {};
|
||||
for (var r in node.subscriptions[s]) {
|
||||
if (node.subscriptions[s].hasOwnProperty(r)) {
|
||||
qos = Math.max(qos,node.subscriptions[s][r].qos);
|
||||
_options = node.subscriptions[s][r].options;
|
||||
node._clientOn('message',node.subscriptions[s][r].handler);
|
||||
node.subscribe(node.subscriptions[s][r])
|
||||
}
|
||||
}
|
||||
_options.qos = _options.qos || qos;
|
||||
node.client.subscribe(topic, _options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -853,22 +861,28 @@ module.exports = function(RED) {
|
||||
if(!node.client) { return _callback(); }
|
||||
if(node.closing) { return _callback(); }
|
||||
|
||||
/**
|
||||
* Call end and wait for the client to end (or timeout)
|
||||
* @param {mqtt.MqttClient} client The broker client
|
||||
* @param {number} ms The time to wait for the client to end
|
||||
* @returns
|
||||
*/
|
||||
let waitEnd = (client, ms) => {
|
||||
return new Promise( (resolve, reject) => {
|
||||
node.closing = true;
|
||||
if(!client) {
|
||||
if (!client) {
|
||||
resolve();
|
||||
} else {
|
||||
} else {
|
||||
const t = setTimeout(() => {
|
||||
//clean end() has exceeded WAIT_END, lets force end!
|
||||
client && client.end(true);
|
||||
reject();
|
||||
resolve();
|
||||
}, ms);
|
||||
client.end(() => {
|
||||
clearTimeout(t);
|
||||
resolve()
|
||||
});
|
||||
}
|
||||
clearTimeout(t);
|
||||
resolve()
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
if(node.connected && node.closeMessage) {
|
||||
@@ -889,69 +903,222 @@ module.exports = function(RED) {
|
||||
}
|
||||
node.subscriptionIds = {};
|
||||
node.subid = 1;
|
||||
node.subscribe = function (topic,options,callback,ref) {
|
||||
ref = ref||0;
|
||||
var qos;
|
||||
if(typeof options == "object") {
|
||||
qos = options.qos;
|
||||
} else {
|
||||
qos = options;
|
||||
options = {};
|
||||
|
||||
//typedef for subscription object:
|
||||
/**
|
||||
* @typedef {Object} Subscription
|
||||
* @property {String} topic - topic to subscribe to
|
||||
* @property {Object} [options] - options object
|
||||
* @property {Number} [options.qos] - quality of service
|
||||
* @property {Number} [options.nl] - no local
|
||||
* @property {Number} [options.rap] - retain as published
|
||||
* @property {Number} [options.rh] - retain handling
|
||||
* @property {Number} [options.properties] - MQTT 5.0 properties
|
||||
* @property {Number} [options.properties.subscriptionIdentifier] - MQTT 5.0 subscription identifier
|
||||
* @property {Number} [options.properties.userProperties] - MQTT 5.0 user properties
|
||||
* @property {Function} callback
|
||||
* @property {String} ref - reference to the node that created the subscription
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a subscription object
|
||||
* @param {String} _topic - topic to subscribe to
|
||||
* @param {Object} _options - options object
|
||||
* @param {String} _ref - reference to the node that created the subscription
|
||||
* @returns {Subscription}
|
||||
*/
|
||||
function createSubscriptionObject(_topic, _options, _ref, _brokerId) {
|
||||
/** @type {Subscription} */
|
||||
const subscription = {};
|
||||
const ref = _ref || 0;
|
||||
let options
|
||||
let qos = 1 // default to QoS 1 (AWS and several other brokers don't support QoS 2)
|
||||
|
||||
// if options is an object, then clone it
|
||||
if (typeof _options == "object") {
|
||||
options = RED.util.cloneMessage(_options || {})
|
||||
qos = _options.qos;
|
||||
} else if (typeof _options == "number") {
|
||||
qos = _options;
|
||||
}
|
||||
options.qos = qos;
|
||||
options = options || {};
|
||||
|
||||
// sanitise qos
|
||||
if (typeof qos === "number" && qos >= 0 && qos <= 2) {
|
||||
options.qos = qos;
|
||||
}
|
||||
|
||||
subscription.topic = _topic;
|
||||
subscription.qos = qos;
|
||||
subscription.options = RED.util.cloneMessage(options);
|
||||
subscription.ref = ref;
|
||||
subscription.brokerId = _brokerId;
|
||||
return subscription;
|
||||
}
|
||||
|
||||
/**
|
||||
* If topic is a subscription object, then use that, otherwise look up the topic in
|
||||
* the subscriptions object. If the topic is not found, then create a new subscription
|
||||
* object and add it to the subscriptions object.
|
||||
* @param {Subscription|String} topic
|
||||
* @param {*} options
|
||||
* @param {*} callback
|
||||
* @param {*} ref
|
||||
*/
|
||||
node.subscribe = function (topic, options, callback, ref) {
|
||||
/** @type {Subscription} */
|
||||
let subscription
|
||||
let doCompare = false
|
||||
let changesFound = false
|
||||
|
||||
// function signature 1: subscribe(subscription: Subscription)
|
||||
if (typeof topic === "object" && topic !== null) {
|
||||
subscription = topic
|
||||
topic = subscription.topic
|
||||
options = subscription.options
|
||||
ref = subscription.ref
|
||||
callback = subscription.callback
|
||||
}
|
||||
|
||||
// function signature 2: subscribe(topic: String, options: Object, callback: Function, ref: String)
|
||||
else if (typeof topic === "string") {
|
||||
// since this is a call where all params are provided, it might be
|
||||
// a node change (modification) so we need to check for changes
|
||||
doCompare = true
|
||||
subscription = node.subscriptions[topic] && node.subscriptions[topic][ref]
|
||||
}
|
||||
|
||||
// bad function call
|
||||
else {
|
||||
console.warn('Invalid call to node.subscribe')
|
||||
return
|
||||
}
|
||||
const thisBrokerId = node.type === 'mqtt-broker' ? node.id : node.broker
|
||||
|
||||
// unsubscribe topics where the broker has changed
|
||||
const oldBrokerSubs = (unsubscribeCandidates[ref] || []).filter(sub => sub.brokerId !== thisBrokerId)
|
||||
oldBrokerSubs.forEach(sub => {
|
||||
/** @type {MQTTBrokerNode} */
|
||||
const _brokerConn = RED.nodes.getNode(sub.brokerId)
|
||||
if (_brokerConn) {
|
||||
_brokerConn.unsubscribe(sub.topic, sub.ref, true)
|
||||
}
|
||||
})
|
||||
|
||||
// if subscription is found (or sent in as a parameter), then check for changes.
|
||||
// if there are any changes requested, tidy up the old subscription
|
||||
if (subscription) {
|
||||
if (doCompare) {
|
||||
// compare the current sub to the passed in parameters. Use RED.util.compareObjects against
|
||||
// only the minimal set of properties to identify if the subscription has changed
|
||||
const currentSubscription = createSubscriptionObject(subscription.topic, subscription.options, subscription.ref)
|
||||
const newSubscription = createSubscriptionObject(topic, options, ref)
|
||||
changesFound = RED.util.compareObjects(currentSubscription, newSubscription) === false
|
||||
}
|
||||
}
|
||||
|
||||
if (changesFound) {
|
||||
if (subscription.handler) {
|
||||
node._clientRemoveListeners('message', subscription.handler)
|
||||
subscription.handler = null
|
||||
}
|
||||
const _brokerConn = RED.nodes.getNode(subscription.brokerId)
|
||||
if (_brokerConn) {
|
||||
_brokerConn.unsubscribe(subscription.topic, subscription.ref, true)
|
||||
}
|
||||
}
|
||||
|
||||
// clean up the unsubscribe candidate list
|
||||
delete unsubscribeCandidates[ref]
|
||||
|
||||
// determine if this is an existing subscription
|
||||
const existingSubscription = typeof subscription === "object" && subscription !== null
|
||||
|
||||
// if existing subscription is not found or has changed, create a new subscription object
|
||||
if (existingSubscription === false || changesFound) {
|
||||
subscription = createSubscriptionObject(topic, options, ref, node.id)
|
||||
}
|
||||
|
||||
// setup remainder of subscription properties and event handling
|
||||
node.subscriptions[topic] = node.subscriptions[topic] || {};
|
||||
node.subscriptions[topic][ref] = subscription
|
||||
if (!node.subscriptionIds[topic]) {
|
||||
node.subscriptionIds[topic] = node.subid++;
|
||||
}
|
||||
options.properties = options.properties || {};
|
||||
options.properties.subscriptionIdentifier = node.subscriptionIds[topic];
|
||||
subscription.options = subscription.options || {};
|
||||
subscription.options.properties = options.properties || {};
|
||||
subscription.options.properties.subscriptionIdentifier = node.subscriptionIds[topic];
|
||||
subscription.callback = callback;
|
||||
|
||||
node.subscriptions[topic] = node.subscriptions[topic]||{};
|
||||
var sub = {
|
||||
topic:topic,
|
||||
qos:qos,
|
||||
options:options,
|
||||
handler:function(mtopic,mpayload, mpacket) {
|
||||
if(mpacket.properties && options.properties && mpacket.properties.subscriptionIdentifier && options.properties.subscriptionIdentifier && (mpacket.properties.subscriptionIdentifier !== options.properties.subscriptionIdentifier) ) {
|
||||
//do nothing as subscriptionIdentifier does not match
|
||||
} else if (matchTopic(topic,mtopic)) {
|
||||
callback(mtopic,mpayload, mpacket);
|
||||
}
|
||||
},
|
||||
ref: ref
|
||||
};
|
||||
node.subscriptions[topic][ref] = sub;
|
||||
// if the client is connected, then setup the handler and subscribe
|
||||
if (node.connected) {
|
||||
const subIdsAvailable = node.subscriptionIdentifiersAvailable()
|
||||
node._clientOn('message',sub.handler);
|
||||
// if the broker doesn't support subscription identifiers (e.g. AWS core), then don't send them
|
||||
if (options.properties && options.properties.subscriptionIdentifier && subIdsAvailable !== true) {
|
||||
delete options.properties.subscriptionIdentifier
|
||||
}
|
||||
node.client.subscribe(topic, options);
|
||||
}
|
||||
};
|
||||
|
||||
node.unsubscribe = function (topic, ref, removed) {
|
||||
ref = ref||0;
|
||||
var sub = node.subscriptions[topic];
|
||||
if (sub) {
|
||||
if (sub[ref]) {
|
||||
if(node.client) {
|
||||
node._clientRemoveListeners('message',sub[ref].handler);
|
||||
}
|
||||
delete sub[ref];
|
||||
}
|
||||
//TODO: Review. The `if(removed)` was commented out to always delete and remove subscriptions.
|
||||
// if we dont then property changes dont get applied and old subs still trigger
|
||||
//if (removed) {
|
||||
if (Object.keys(sub).length === 0) {
|
||||
delete node.subscriptions[topic];
|
||||
delete node.subscriptionIds[topic];
|
||||
if (node.connected) {
|
||||
node.client.unsubscribe(topic);
|
||||
if (!subscription.handler) {
|
||||
subscription.handler = function (mtopic, mpayload, mpacket) {
|
||||
const sops = subscription.options ? subscription.options.properties : {}
|
||||
const pops = mpacket.properties || {}
|
||||
if (subIdsAvailable && pops.subscriptionIdentifier && sops.subscriptionIdentifier && (pops.subscriptionIdentifier !== sops.subscriptionIdentifier)) {
|
||||
//do nothing as subscriptionIdentifier does not match
|
||||
} else if (matchTopic(topic, mtopic)) {
|
||||
subscription.callback && subscription.callback(mtopic, mpayload, mpacket)
|
||||
}
|
||||
}
|
||||
//}
|
||||
}
|
||||
node._clientOn('message', subscription.handler)
|
||||
// if the broker doesn't support subscription identifiers, then don't send them (AWS support)
|
||||
if (subscription.options.properties && subscription.options.properties.subscriptionIdentifier && subIdsAvailable !== true) {
|
||||
delete subscription.options.properties.subscriptionIdentifier
|
||||
}
|
||||
node.client.subscribe(topic, subscription.options)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
node.unsubscribe = function (topic, ref, removeClientSubscription) {
|
||||
ref = ref||0;
|
||||
const unsub = removeClientSubscription || node.autoUnsubscribe !== false
|
||||
const sub = node.subscriptions[topic];
|
||||
let brokerId = node.id
|
||||
if (sub) {
|
||||
if (sub[ref]) {
|
||||
brokerId = sub[ref].brokerId || brokerId
|
||||
if(node.client && sub[ref].handler) {
|
||||
node._clientRemoveListeners('message', sub[ref].handler);
|
||||
sub[ref].handler = null
|
||||
}
|
||||
if (unsub) {
|
||||
delete sub[ref]
|
||||
}
|
||||
}
|
||||
// if instructed to remove the actual MQTT client subscription
|
||||
if (unsub) {
|
||||
// if there are no more subscriptions for the topic, then remove the topic
|
||||
if (Object.keys(sub).length === 0) {
|
||||
try {
|
||||
node.client.unsubscribe(topic)
|
||||
} catch (_err) {
|
||||
// do nothing
|
||||
} finally {
|
||||
// remove unsubscribe candidate as it is now REALLY unsubscribed
|
||||
delete node.subscriptions[topic];
|
||||
delete node.subscriptionIds[topic];
|
||||
if (unsubscribeCandidates[ref]) {
|
||||
unsubscribeCandidates[ref] = unsubscribeCandidates[ref].filter(sub => sub.topic !== topic)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if instructed to not remove the client subscription, then add it to the candidate list
|
||||
// of subscriptions to be removed when the the same ref is used in a subsequent subscribe
|
||||
// and the topic has changed
|
||||
unsubscribeCandidates[ref] = unsubscribeCandidates[ref] || [];
|
||||
unsubscribeCandidates[ref].push({
|
||||
topic: topic,
|
||||
ref: ref,
|
||||
brokerId: brokerId
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
node.topicAliases = {};
|
||||
@@ -989,7 +1156,7 @@ module.exports = function(RED) {
|
||||
setStrProp(msg, options.properties, "contentType");
|
||||
setIntProp(msg, options.properties, "messageExpiryInterval", 0);
|
||||
setUserProperties(msg.userProperties, options.properties);
|
||||
setIntProp(msg, options.properties, "topicAlias", 1, node.serverProperties.topicAliasMaximum || 0);
|
||||
setIntProp(msg, options.properties, "topicAlias", 1, bsp.topicAliasMaximum || 0);
|
||||
setBoolProp(msg, options.properties, "payloadFormatIndicator");
|
||||
//FUTURE setIntProp(msg, options.properties, "subscriptionIdentifier", 1, 268435455);
|
||||
|
||||
@@ -1125,7 +1292,7 @@ module.exports = function(RED) {
|
||||
if(node.rap === "true" || node.rap === true) options.rap = true;
|
||||
else if(node.rap === "false" || node.rap === false) options.rap = false;
|
||||
}
|
||||
|
||||
node._topic = node.topic; // store the original topic incase node is later changed
|
||||
node.brokerConn.subscribe(node.topic,options,function(topic, payload, packet) {
|
||||
subscriptionHandler(node, node.datatype, topic, payload, packet);
|
||||
},node.id);
|
||||
@@ -1178,7 +1345,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
if (action === Actions.UNSUBSCRIBE) {
|
||||
subscriptions.forEach(function (sub) {
|
||||
node.brokerConn.unsubscribe(sub.topic, node.id);
|
||||
node.brokerConn.unsubscribe(sub.topic, node.id, true);
|
||||
delete node.dynamicSubs[sub.topic];
|
||||
})
|
||||
//user can access current subscriptions through the complete node is so desired
|
||||
@@ -1188,7 +1355,7 @@ module.exports = function(RED) {
|
||||
subscriptions.forEach(function (sub) {
|
||||
//always unsubscribe before subscribe to prevent multiple subs to same topic
|
||||
if (node.dynamicSubs[sub.topic]) {
|
||||
node.brokerConn.unsubscribe(sub.topic, node.id);
|
||||
node.brokerConn.unsubscribe(sub.topic, node.id, true);
|
||||
delete node.dynamicSubs[sub.topic];
|
||||
}
|
||||
|
||||
@@ -1239,7 +1406,7 @@ module.exports = function(RED) {
|
||||
});
|
||||
node.dynamicSubs = {};
|
||||
} else {
|
||||
node.brokerConn.unsubscribe(node.topic,node.id, removed);
|
||||
node.brokerConn.unsubscribe(node.topic, node.id, removed);
|
||||
}
|
||||
node.brokerConn.deregister(node, done, removed);
|
||||
node.brokerConn = null;
|
||||
|
@@ -14,15 +14,17 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
module.exports = async function(RED) {
|
||||
"use strict";
|
||||
const got = require("got");
|
||||
const { got } = await import('got')
|
||||
const {CookieJar} = require("tough-cookie");
|
||||
const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent');
|
||||
const FormData = require('form-data');
|
||||
const { v4: uuid } = require('uuid');
|
||||
const crypto = require('crypto');
|
||||
const URL = require("url").URL
|
||||
const http = require("http")
|
||||
const https = require("https")
|
||||
var mustache = require("mustache");
|
||||
var querystring = require("querystring");
|
||||
var cookie = require("cookie");
|
||||
@@ -65,16 +67,27 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
function HTTPRequest(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
checkNodeAgentPatch();
|
||||
var node = this;
|
||||
var nodeUrl = n.url;
|
||||
var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
|
||||
var nodeMethod = n.method || "GET";
|
||||
var paytoqs = false;
|
||||
var paytobody = false;
|
||||
var redirectList = [];
|
||||
var sendErrorsToCatch = n.senderr;
|
||||
const node = this;
|
||||
const nodeUrl = n.url;
|
||||
const isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
|
||||
const nodeMethod = n.method || "GET";
|
||||
let paytoqs = false;
|
||||
let paytobody = false;
|
||||
let redirectList = [];
|
||||
const sendErrorsToCatch = n.senderr;
|
||||
node.headers = n.headers || [];
|
||||
var nodeHTTPPersistent = n["persist"];
|
||||
const useKeepAlive = n["persist"];
|
||||
let agents = null
|
||||
if (useKeepAlive) {
|
||||
agents = {
|
||||
http: new http.Agent({ keepAlive: true }),
|
||||
https: new https.Agent({ keepAlive: true })
|
||||
}
|
||||
node.on('close', function () {
|
||||
agents.http.destroy()
|
||||
agents.https.destroy()
|
||||
})
|
||||
}
|
||||
if (n.tls) {
|
||||
var tlsNode = RED.nodes.getNode(n.tls);
|
||||
}
|
||||
@@ -210,24 +223,24 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
// set defaultport, else when using HttpsProxyAgent, it's defaultPort of 443 will be used :(.
|
||||
// Had to remove this to get http->https redirect to work
|
||||
// opts.defaultPort = isHttps?443:80;
|
||||
opts.timeout = node.reqTimeout;
|
||||
opts.timeout = { request: node.reqTimeout || 5000 };
|
||||
opts.throwHttpErrors = false;
|
||||
// TODO: add UI option to auto decompress. Setting to false for 1.x compatibility
|
||||
opts.decompress = false;
|
||||
opts.method = method;
|
||||
opts.retry = 0;
|
||||
opts.retry = { limit: 0 };
|
||||
opts.responseType = 'buffer';
|
||||
opts.maxRedirects = 21;
|
||||
opts.cookieJar = new CookieJar();
|
||||
opts.ignoreInvalidCookies = true;
|
||||
opts.forever = nodeHTTPPersistent;
|
||||
// opts.forever = nodeHTTPPersistent;
|
||||
if (msg.requestTimeout !== undefined) {
|
||||
if (isNaN(msg.requestTimeout)) {
|
||||
node.warn(RED._("httpin.errors.timeout-isnan"));
|
||||
} else if (msg.requestTimeout < 1) {
|
||||
node.warn(RED._("httpin.errors.timeout-isnegative"));
|
||||
} else {
|
||||
opts.timeout = msg.requestTimeout;
|
||||
opts.timeout = { request: msg.requestTimeout };
|
||||
}
|
||||
}
|
||||
const originalHeaderMap = {};
|
||||
@@ -245,9 +258,12 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
delete options.headers[h];
|
||||
}
|
||||
})
|
||||
|
||||
if (node.insecureHTTPParser) {
|
||||
options.insecureHTTPParser = true
|
||||
// Setting the property under _unixOptions as pretty
|
||||
// much the only hack available to get got to apply
|
||||
// a core http option it doesn't think we should be
|
||||
// allowed to set
|
||||
options._unixOptions = { ...options.unixOptions, insecureHTTPParser: true }
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -403,15 +419,16 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
return response
|
||||
}
|
||||
const requestUrl = new URL(response.request.requestUrl);
|
||||
const options = response.request.options;
|
||||
const options = { headers: {} }
|
||||
const normalisedHeaders = {};
|
||||
Object.keys(response.headers).forEach(k => {
|
||||
normalisedHeaders[k.toLowerCase()] = response.headers[k]
|
||||
})
|
||||
if (normalisedHeaders['www-authenticate']) {
|
||||
let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, options.method, requestUrl.pathname, normalisedHeaders['www-authenticate'])
|
||||
let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, response.request.options.method, requestUrl.pathname, normalisedHeaders['www-authenticate'])
|
||||
options.headers.Authorization = authHeader;
|
||||
}
|
||||
// response.request.options.merge(options)
|
||||
sentCreds = true;
|
||||
return retry(options);
|
||||
}
|
||||
@@ -552,12 +569,14 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
opts.agent = {
|
||||
http: new HttpProxyAgent(proxyOptions),
|
||||
https: new HttpsProxyAgent(proxyOptions)
|
||||
};
|
||||
|
||||
}
|
||||
} else {
|
||||
node.warn("Bad proxy url: "+ prox);
|
||||
}
|
||||
}
|
||||
if (useKeepAlive && !opts.agent) {
|
||||
opts.agent = agents
|
||||
}
|
||||
if (tlsNode) {
|
||||
opts.https = {};
|
||||
tlsNode.addTLSOptions(opts.https);
|
||||
@@ -614,6 +633,7 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
msg.payload = msg.payload.toString('utf8'); // txt
|
||||
|
||||
if (node.ret === "obj") {
|
||||
if (msg.statusCode == 204){msg.payload= "{}"};
|
||||
try { msg.payload = JSON.parse(msg.payload); } // obj
|
||||
catch(e) { node.warn(RED._("httpin.errors.json-error")); }
|
||||
}
|
||||
@@ -695,25 +715,43 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
});
|
||||
|
||||
const md5 = (value) => { return crypto.createHash('md5').update(value).digest('hex') }
|
||||
const sha256 = (value) => { return crypto.createHash('sha256').update(value).digest('hex') }
|
||||
const sha512 = (value) => { return crypto.createHash('sha512').update(value).digest('hex') }
|
||||
|
||||
function digestCompute(algorithm, value) {
|
||||
var lowercaseAlgorithm = ""
|
||||
if (algorithm) {
|
||||
lowercaseAlgorithm = algorithm.toLowerCase().replace(/-sess$/, '')
|
||||
}
|
||||
|
||||
if (lowercaseAlgorithm === "sha-256") {
|
||||
return sha256(value)
|
||||
} else if (lowercaseAlgorithm === "sha-512-256") {
|
||||
var hash = sha512(value)
|
||||
return hash.slice(0, 64) // Only use the first 256 bits
|
||||
} else {
|
||||
return md5(value)
|
||||
}
|
||||
}
|
||||
|
||||
function ha1Compute(algorithm, user, realm, pass, nonce, cnonce) {
|
||||
/**
|
||||
* RFC 2617: handle both MD5 and MD5-sess algorithms.
|
||||
* RFC 2617: handle both standard and -sess algorithms.
|
||||
*
|
||||
* If the algorithm directive's value is "MD5" or unspecified, then HA1 is
|
||||
* HA1=MD5(username:realm:password)
|
||||
* If the algorithm directive's value is "MD5-sess", then HA1 is
|
||||
* HA1=MD5(MD5(username:realm:password):nonce:cnonce)
|
||||
* If the algorithm directive's value ends with "-sess", then HA1 is
|
||||
* HA1=digestCompute(digestCompute(username:realm:password):nonce:cnonce)
|
||||
*
|
||||
* If the algorithm directive's value does not end with "-sess", then HA1 is
|
||||
* HA1=digestCompute(username:realm:password)
|
||||
*/
|
||||
var ha1 = md5(user + ':' + realm + ':' + pass)
|
||||
if (algorithm && algorithm.toLowerCase() === 'md5-sess') {
|
||||
return md5(ha1 + ':' + nonce + ':' + cnonce)
|
||||
var ha1 = digestCompute(algorithm, user + ':' + realm + ':' + pass)
|
||||
if (algorithm && /-sess$/i.test(algorithm)) {
|
||||
return digestCompute(algorithm, ha1 + ':' + nonce + ':' + cnonce)
|
||||
} else {
|
||||
return ha1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function buildDigestHeader(user, pass, method, path, authHeader) {
|
||||
var challenge = {}
|
||||
var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
|
||||
@@ -728,10 +766,10 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
var nc = qop && '00000001'
|
||||
var cnonce = qop && uuid().replace(/-/g, '')
|
||||
var ha1 = ha1Compute(challenge.algorithm, user, challenge.realm, pass, challenge.nonce, cnonce)
|
||||
var ha2 = md5(method + ':' + path)
|
||||
var ha2 = digestCompute(challenge.algorithm, method + ':' + path)
|
||||
var digestResponse = qop
|
||||
? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
|
||||
: md5(ha1 + ':' + challenge.nonce + ':' + ha2)
|
||||
? digestCompute(challenge.algorithm, ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
|
||||
: digestCompute(challenge.algorithm, ha1 + ':' + challenge.nonce + ':' + ha2)
|
||||
var authValues = {
|
||||
username: user,
|
||||
realm: challenge.realm,
|
||||
|
@@ -86,7 +86,7 @@ module.exports = function(RED) {
|
||||
this.topic = n.topic;
|
||||
this.stream = (!n.datamode||n.datamode=='stream'); /* stream,single*/
|
||||
this.datatype = n.datatype||'buffer'; /* buffer,utf8,base64 */
|
||||
this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r").replace("\\t","\t");
|
||||
this.newline = (n.newline||"").replace(/\\n/g,"\n").replace(/\\r/g,"\r").replace(/\\t/g,"\t");
|
||||
this.base64 = n.base64;
|
||||
this.trim = n.trim || false;
|
||||
this.server = (typeof n.server == 'boolean')?n.server:(n.server == "server");
|
||||
|
@@ -19,9 +19,9 @@ module.exports = function(RED) {
|
||||
function CSVNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.template = (n.temp || "");
|
||||
this.sep = (n.sep || ',').replace("\\t","\t").replace("\\n","\n").replace("\\r","\r");
|
||||
this.sep = (n.sep || ',').replace(/\\t/g,"\t").replace(/\\n/g,"\n").replace(/\\r/g,"\r");
|
||||
this.quo = '"';
|
||||
this.ret = (n.ret || "\n").replace("\\n","\n").replace("\\r","\r");
|
||||
this.ret = (n.ret || "\n").replace(/\\n/g,"\n").replace(/\\r/g,"\r");
|
||||
this.winflag = (this.ret === "\r\n");
|
||||
this.lineend = "\n";
|
||||
this.multi = n.multi || "one";
|
||||
|
@@ -33,8 +33,7 @@ module.exports = function(RED) {
|
||||
parseString(value, options, function (err, result) {
|
||||
if (err) { done(err); }
|
||||
else {
|
||||
value = result;
|
||||
RED.util.setMessageProperty(msg,node.property,value);
|
||||
RED.util.setMessageProperty(msg,node.property,result);
|
||||
send(msg);
|
||||
done();
|
||||
}
|
||||
|
@@ -224,7 +224,12 @@
|
||||
outputs:1,
|
||||
icon: "join.svg",
|
||||
label: function() {
|
||||
return this.name||this._("join.join");
|
||||
var nam = this.name||this._("join.join");
|
||||
if (this.mode === "custom" && !isNaN(Number(this.count))) {
|
||||
nam += " "+this.count;
|
||||
if (this.accumulate === true) { nam+= "+"; }
|
||||
}
|
||||
return nam;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
|
@@ -478,7 +478,7 @@ module.exports = function(RED) {
|
||||
var completeSend = function(partId) {
|
||||
var group = inflight[partId];
|
||||
if (group.timeout) { clearTimeout(group.timeout); }
|
||||
if ((node.accumulate !== true) || group.msg.hasOwnProperty("complete")) { delete inflight[partId]; }
|
||||
if (node.mode === 'auto' || node.accumulate !== true || group.msg.hasOwnProperty("complete")) { delete inflight[partId]; }
|
||||
if (group.type === 'array' && group.arrayLen > 1) {
|
||||
var newArray = [];
|
||||
group.payload.forEach(function(n) {
|
||||
@@ -629,6 +629,9 @@ module.exports = function(RED) {
|
||||
joinChar = node.joiner;
|
||||
if (n.count === "" && msg.hasOwnProperty('parts')) {
|
||||
targetCount = msg.parts.count || 0;
|
||||
if (msg.parts.hasOwnProperty('id')) {
|
||||
partId = msg.parts.id;
|
||||
}
|
||||
}
|
||||
if (node.build === 'object') {
|
||||
propertyKey = RED.util.getMessageProperty(msg,node.key);
|
||||
|
@@ -107,7 +107,14 @@
|
||||
outputs:1,
|
||||
icon: "batch.svg",
|
||||
label: function() {
|
||||
return this.name||this._("batch.batch");;
|
||||
var nam = this.name||this._("batch.batch");
|
||||
if (this.mode === "count" && !isNaN(Number(this.count))) {
|
||||
nam += " "+this.count;
|
||||
}
|
||||
if (this.mode === "interval" && !isNaN(Number(this.interval))) {
|
||||
nam += " "+this.interval+"s";
|
||||
}
|
||||
return nam;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name ? "node_label_italic" : "";
|
||||
|
@@ -68,9 +68,15 @@ module.exports = function(RED) {
|
||||
node.error(err,msg);
|
||||
return done();
|
||||
} else {
|
||||
filename = value;
|
||||
if (typeof value !== 'string' && value !== null && value !== undefined) {
|
||||
value = value.toString();
|
||||
}
|
||||
processMsg2(msg,nodeSend,value,done);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function processMsg2(msg,nodeSend,filename,done) {
|
||||
filename = filename || "";
|
||||
msg.filename = filename;
|
||||
var fullFilename = filename;
|
||||
@@ -275,7 +281,6 @@ module.exports = function(RED) {
|
||||
}
|
||||
RED.nodes.registerType("file",FileNode);
|
||||
|
||||
|
||||
function FileInNode(n) {
|
||||
// Read a file
|
||||
RED.nodes.createNode(this,n);
|
||||
@@ -311,9 +316,15 @@ module.exports = function(RED) {
|
||||
node.error(err,msg);
|
||||
return done();
|
||||
} else {
|
||||
filename = (value || "").replace(/\t|\r|\n/g,'');
|
||||
if (typeof value !== 'string' && value !== null && value !== undefined) {
|
||||
value = value.toString();
|
||||
}
|
||||
processMsg2(msg, nodeSend, (value || "").replace(/\t|\r|\n/g,''), nodeDone);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function processMsg2(msg, nodeSend, filename, nodeDone) {
|
||||
filename = filename || "";
|
||||
var fullFilename = filename;
|
||||
if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
|
||||
@@ -434,7 +445,8 @@ module.exports = function(RED) {
|
||||
nodeDone();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.on('close', function() {
|
||||
node.status({});
|
||||
});
|
||||
|
Reference in New Issue
Block a user