Merge branch 'dev' into 3843-alternative-impl

This commit is contained in:
Nick O'Leary
2023-05-22 17:41:23 +01:00
404 changed files with 51584 additions and 12411 deletions

View File

@@ -95,45 +95,71 @@ module.exports = function(RED) {
}
this.on("input", function(msg, send, done) {
var errors = [];
var props = this.props;
const errors = [];
let 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';
props = [...props]
function evaluateProperty(doneEvaluating) {
if (props.length === 0) {
doneEvaluating()
return
}
const p = props.shift()
const property = p.p;
const value = p.v ? p.v : '';
const valueType = p.vt ? p.vt : 'str';
if (!property) return;
if (valueType === "jsonata") {
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);
if (property) {
if (valueType === "jsonata") {
if (p.v) {
try {
var exp = RED.util.prepareJSONataExpression(p.v, node);
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)
}
catch (err) {
errors.push(err.message);
} else {
try {
RED.util.evaluateNodeProperty(value, valueType, node, msg, (err, newValue) => {
if (err) {
errors.push(err.toString())
} else {
RED.util.setMessageProperty(msg,property,newValue,true);
}
evaluateProperty(doneEvaluating)
})
} catch (err) {
errors.push(err.toString());
evaluateProperty(doneEvaluating)
}
}
return;
} else {
evaluateProperty(doneEvaluating)
}
try {
RED.util.setMessageProperty(msg,property,RED.util.evaluateNodeProperty(value, valueType, this, msg),true);
} catch (err) {
errors.push(err.toString());
}
});
if (errors.length) {
done(errors.join('; '));
} else {
send(msg);
done();
}
evaluateProperty(() => {
if (errors.length) {
done(errors.join('; '));
} else {
send(msg);
done();
}
})
});
}

View File

@@ -1,6 +1,6 @@
<script type="text/html" data-template-name="complete">
<div class="form-row node-input-target-row">
<button id="node-input-complete-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button>
<button type="button" id="node-input-complete-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button>
</div>
<div class="form-row node-input-target-row node-input-target-list-row" style="position: relative; min-height: 100px">
<div style="position: absolute; top: -30px; right: 0;"><input type="text" id="node-input-complete-target-filter"></div>
@@ -18,7 +18,16 @@
color:"#c0edc0",
defaults: {
name: {value:""},
scope: {value:[], type:"*[]"},
scope: {
value: [],
type: "*[]",
validate: function (v, opt) {
if (v.length > 0) {
return true;
}
return RED._("node-red:complete.errors.scopeUndefined");
}
},
uncaught: {value:false}
},
inputs:0,

View File

@@ -4,8 +4,8 @@
<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="status.scope.group"></option>
<option value="target" data-i18n="catch.scope.selected"></options>
<option value="group" data-i18n="catch.scope.group"></option>
<option value="target" data-i18n="catch.scope.selected"></option>
</select>
</div>
<div class="form-row node-input-uncaught-row">
@@ -13,7 +13,7 @@
<label for="node-input-uncaught" style="width: auto" data-i18n="catch.label.uncaught"></label>
</div>
<div class="form-row node-input-target-row">
<button id="node-input-catch-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button>
<button type="button" id="node-input-catch-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button>
</div>
<div class="form-row node-input-target-row node-input-target-list-row" style="position: relative; min-height: 100px">
<div style="position: absolute; top: -30px; right: 0;"><input type="text" id="node-input-catch-target-filter"></div>

View File

@@ -5,11 +5,11 @@
<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"></options>
<option value="target" data-i18n="status.scope.selected"></option>
</select>
</div>
<div class="form-row node-input-target-row">
<button id="node-input-status-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button>
<button type="button" id="node-input-status-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button>
</div>
<div class="form-row node-input-target-row node-input-target-list-row" style="position: relative; min-height: 100px">
<div style="position: absolute; top: -30px; right: 0;"><input type="text" id="node-input-status-target-filter"></div>

View File

@@ -1,4 +1,3 @@
<script type="text/html" data-template-name="link in">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
@@ -272,7 +271,17 @@
color:"#ddd",//"#87D8CF",
defaults: {
name: { value: "" },
links: { value: [], type:"link in[]" },
links: {
value: [],
type: "link in[]",
validate: function (v, opt) {
if ((this.linkType === "static" && v.length > 0)
|| this.linkType === "dynamic") {
return true;
}
return RED._("node-red:link.errors.linkUndefined");
}
},
linkType: { value:"static" },
timeout: {
value: "30",

View File

@@ -164,10 +164,10 @@ module.exports = function(RED) {
if (returnNode && returnNode.returnLinkMessage) {
returnNode.returnLinkMessage(messageEvent.id, msg);
} else {
node.warn(RED._("link.error.missingReturn"))
node.warn(RED._("link.errors.missingReturn"));
}
} else {
node.warn(RED._("link.error.missingReturn"))
node.warn(RED._("link.errors.missingReturn"));
}
done();
} else if (mode === "link") {
@@ -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;

View 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>

View 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);
}

View File

@@ -17,6 +17,8 @@
display: flex;
background: var(--red-ui-tertiary-background);
padding-right: 75px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
#node-input-libs-container-row .red-ui-editableList-header > div {
flex-grow: 1;
@@ -91,21 +93,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); 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 style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 10;"><button type="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); 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 style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 10;"><button type="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); 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 style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 10;"><button type="button" id="node-finalize-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
</div>
</div>
@@ -294,7 +296,7 @@
if (val === "_custom_") {
val = $(this).val();
}
var varName = val.trim().replace(/^@/,"").replace(/@.*$/,"").replace(/[-_/].?/g, function(v) { return v[1]?v[1].toUpperCase():"" });
var varName = val.trim().replace(/^@/,"").replace(/@.*$/,"").replace(/[-_/\.].?/g, function(v) { return v[1]?v[1].toUpperCase():"" });
fvar.val(varName);
fvar.trigger("change");
@@ -451,11 +453,13 @@
tabs.activateTab("func-tab-body");
$( "#node-input-outputs" ).spinner({
min:0,
min: 0,
max: 500,
change: function(event, ui) {
var value = this.value;
if (!value.match(/^\d+$/)) { value = 1; }
else if (value < this.min) { value = this.min; }
var value = parseInt(this.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); }
}
});

View File

@@ -318,7 +318,7 @@ module.exports = function(RED) {
}
var r = node.rules[currentRule];
if (r.t === "move") {
if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1)) {
if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1) && (r.p !== r.to)) {
applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:r.p, tot:r.pt},(err,msg) => {
applyRule(msg,{t:"delete", p:r.p, pt:r.pt}, (err,msg) => {
completeApplyingRules(msg,currentRule,done);

View File

@@ -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/>

View File

@@ -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;
}

View File

@@ -21,12 +21,13 @@
<option value="javascript">JavaScript</option>
<option value="css">CSS</option>
<option value="markdown">Markdown</option>
<option value="php">PHP</option>
<option value="python">Python</option>
<option value="sql">SQL</option>
<option value="yaml">YAML</option>
<option value="text" data-i18n="template.label.none"></option>
</select>
<button id="node-template-expand-editor" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button>
<button type="button" id="node-template-expand-editor" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button>
</div>
</div>
<div class="form-row node-text-editor-row">

View File

@@ -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,19 @@ 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 +249,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 +268,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 +323,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 +390,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 +416,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);

View File

@@ -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};

View File

@@ -35,7 +35,11 @@ module.exports = function(RED) {
}
else { node.previous = {}; }
}
var value = RED.util.getMessageProperty(msg,node.property);
var value;
try {
value = RED.util.getMessageProperty(msg,node.property);
}
catch(e) { }
if (value !== undefined) {
var t = "_no_topic";
if (node.septopics) { t = topic || t; }

View File

@@ -25,7 +25,7 @@
<label class="red-ui-button" for="node-config-input-certfile"><i class="fa fa-upload"></i> <span data-i18n="tls.label.upload"></span></label>
<input class="hide" type="file" id="node-config-input-certfile">
<span id="tls-config-certname" style="width: calc(100% - 280px); overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
<button class="red-ui-button red-ui-button-small" id="tls-config-button-cert-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
<button type="button" class="red-ui-button red-ui-button-small" id="tls-config-button-cert-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
</span>
<input type="hidden" id="node-config-input-certname">
<input type="hidden" id="node-config-input-certdata">
@@ -37,7 +37,7 @@
<label class="red-ui-button" for="node-config-input-keyfile"><i class="fa fa-upload"></i> <span data-i18n="tls.label.upload"></span></label>
<input class="hide" type="file" id="node-config-input-keyfile">
<span id="tls-config-keyname" style="width: calc(100% - 280px); overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
<button class="red-ui-button red-ui-button-small" id="tls-config-button-key-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
<button type="button" class="red-ui-button red-ui-button-small" id="tls-config-button-key-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
</span>
<input type="hidden" id="node-config-input-keyname">
<input type="hidden" id="node-config-input-keydata">
@@ -53,7 +53,7 @@
<label class="red-ui-button" for="node-config-input-cafile"><i class="fa fa-upload"></i> <span data-i18n="tls.label.upload"></span></label>
<input class="hide" type="file" title=" " id="node-config-input-cafile">
<span id="tls-config-caname" style="width: calc(100% - 280px); overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
<button class="red-ui-button red-ui-button-small" id="tls-config-button-ca-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
<button type="button" class="red-ui-button red-ui-button-small" id="tls-config-button-ca-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
</span>
<input type="hidden" id="node-config-input-caname">
<input type="hidden" id="node-config-input-cadata">

View File

@@ -101,6 +101,7 @@
hostField.val(data.host);
}
},
sortable: true,
removable: true
});
if (this.noproxy) {

View File

@@ -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" >
@@ -421,7 +427,11 @@
<script type="text/javascript">
(function() {
var typedInputNoneOpt = { value: 'none', label: '', hasValue: false };
var typedInputNoneOpt = {
value: 'none',
label: RED._("node-red:mqtt.label.none"),
hasValue: false
};
var makeTypedInputOpt = function(value){
return {
value: value,
@@ -436,7 +446,11 @@
makeTypedInputOpt("text/csv"),
makeTypedInputOpt("text/html"),
makeTypedInputOpt("text/plain"),
{value:"other", label:""}
{
value: "other",
label: RED._("node-red:mqtt.label.other"),
icon: "red/images/typedInput/az.svg"
}
];
function getDefaultContentType(value) {
@@ -475,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") || !$("#node-config-input-autoUnsubscribe").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) || this.autoUnsubscribe;
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},
@@ -497,19 +517,20 @@
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},
birthRetain: {value:"false"},
birthPayload: {value:""},
birthMsg: { value: {}},
closeTopic: {value:"", validate:validateMQTTPublishTopic},
closeQos: {value:"0"},
closeRetain: {value:false},
closeRetain: {value:"false"},
closePayload: {value:""},
closeMsg: { value: {}},
willTopic: {value:"", validate:validateMQTTPublishTopic},
willQos: {value:"0"},
willRetain: {value:false},
willRetain: {value:"false"},
willPayload: {value:""},
willMsg: { value: {}},
userProps: { value: ""},
@@ -612,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);
@@ -627,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) {

View File

@@ -295,7 +295,7 @@ module.exports = function(RED) {
/* mute error - it simply isnt JSON, just leave payload as a string */
}
}
} //else {
} //else {
//leave as buffer
//}
}
@@ -357,7 +357,7 @@ module.exports = function(RED) {
return;
}
done(err);
});
});
} else {
done();
}
@@ -366,6 +366,16 @@ module.exports = function(RED) {
}
}
function updateStatus(node, allNodes) {
let setStatus = setStatusDisconnected
if(node.connecting) {
setStatus = setStatusConnecting
} else if(node.connected) {
setStatus = setStatusConnected
}
setStatus(node, allNodes)
}
function setStatusDisconnected(node, allNodes) {
if(allNodes) {
for (var id in node.users) {
@@ -459,7 +469,6 @@ module.exports = function(RED) {
if(!opts || typeof opts !== "object") {
return; //nothing to change, simply return
}
const originalBrokerURL = node.brokerurl;
//apply property changes (only if the property exists in the opts object)
setIfHasProperty(opts, node, "url", init);
@@ -468,13 +477,12 @@ module.exports = function(RED) {
setIfHasProperty(opts, node, "clientid", init);
setIfHasProperty(opts, node, "autoConnect", init);
setIfHasProperty(opts, node, "usetls", init);
setIfHasProperty(opts, node, "usews", init);
setIfHasProperty(opts, node, "verifyservercert", init);
setIfHasProperty(opts, node, "compatmode", init);
setIfHasProperty(opts, node, "protocolVersion", init);
setIfHasProperty(opts, node, "keepalive", init);
setIfHasProperty(opts, node, "cleansession", init);
setIfHasProperty(opts, node, "sessionExpiry", init);
setIfHasProperty(opts, node, "autoUnsubscribe", init);
setIfHasProperty(opts, node, "topicAliasMaximum", init);
setIfHasProperty(opts, node, "maximumPacketSize", init);
setIfHasProperty(opts, node, "receiveMaximum", init);
@@ -484,6 +492,11 @@ module.exports = function(RED) {
} else if (hasProperty(opts, "userProps")) {
node.userProperties = opts.userProps;
}
if (hasProperty(opts, "sessionExpiry")) {
node.sessionExpiryInterval = opts.sessionExpiry;
} else if (hasProperty(opts, "sessionExpiryInterval")) {
node.sessionExpiryInterval = opts.sessionExpiryInterval
}
function createLWT(topic, payload, qos, retain, v5opts, v5SubPropName) {
let message = undefined;
@@ -567,9 +580,6 @@ module.exports = function(RED) {
if (typeof node.usetls === 'undefined') {
node.usetls = false;
}
if (typeof node.usews === 'undefined') {
node.usews = false;
}
if (typeof node.verifyservercert === 'undefined') {
node.verifyservercert = false;
}
@@ -581,6 +591,9 @@ module.exports = function(RED) {
if (typeof node.cleansession === 'undefined') {
node.cleansession = true;
}
if (typeof node.autoUnsubscribe === 'undefined') {
node.autoUnsubscribe = true;
}
//use url or build a url from usetls://broker:port
if (node.url && node.brokerurl !== node.url) {
@@ -651,6 +664,7 @@ module.exports = function(RED) {
node.options.password = node.password;
node.options.keepalive = node.keepalive;
node.options.clean = node.cleansession;
node.options.autoUnsubscribe = node.autoUnsubscribe;
node.options.clientId = node.clientid || 'nodered_' + RED.util.generateId();
node.options.reconnectPeriod = RED.settings.mqttReconnectTime||5000;
delete node.options.protocolId; //V4+ default
@@ -698,16 +712,21 @@ module.exports = function(RED) {
if (Object.keys(node.users).length === 1) {
if(node.autoConnect) {
node.connect();
//update nodes status
setTimeout(function() {
updateStatus(node, true)
}, 1)
}
}
};
node.deregister = function(mqttNode,done) {
node.deregister = function(mqttNode, done, autoDisconnect) {
delete node.users[mqttNode.id];
if (!node.closing && node.connected && Object.keys(node.users).length === 0) {
node.disconnect();
if (autoDisconnect && !node.closing && node.connected && Object.keys(node.users).length === 0) {
node.disconnect(done);
} else {
done();
}
done();
};
node.canConnect = function() {
return !node.connected && !node.connecting;
@@ -782,7 +801,9 @@ module.exports = function(RED) {
// Send any birth message
if (node.birthMessage) {
node.publish(node.birthMessage);
setTimeout(() => {
node.publish(node.birthMessage);
}, 1);
}
});
node._clientOn("reconnect", function() {
@@ -839,7 +860,7 @@ module.exports = function(RED) {
let waitEnd = (client, ms) => {
return new Promise( (resolve, reject) => {
node.closing = true;
if(!client) {
if(!client) {
resolve();
} else {
const t = setTimeout(() => {
@@ -991,14 +1012,21 @@ module.exports = function(RED) {
}
if (topicOK) {
node.client.publish(msg.topic, msg.payload, options, function(err) {
done && done(err);
return
});
node.client.publish(msg.topic, msg.payload, options, function (err) {
if (done) {
done(err)
} else if(err) {
node.error(err, msg)
}
})
} else {
const error = new Error(RED._("mqtt.errors.invalid-topic"));
error.warn = true;
done(error);
const error = new Error(RED._("mqtt.errors.invalid-topic"))
error.warn = true
if (done) {
done(error)
} else {
node.warn(error, msg)
}
}
}
};
@@ -1011,7 +1039,7 @@ module.exports = function(RED) {
/**
* Add event handlers to the MQTT.js client and track them so that
* we do not remove any handlers that the MQTT client uses internally.
* we do not remove any handlers that the MQTT client uses internally.
* Use {@link node._clientRemoveListeners `node._clientRemoveListeners`} to remove handlers
* @param {string} event The name of the event
* @param {function} handler The handler for this event
@@ -1019,11 +1047,11 @@ module.exports = function(RED) {
node._clientOn = function(event, handler) {
node.clientListeners.push({event, handler})
node.client.on(event, handler)
}
}
/**
* Remove event handlers from the MQTT.js client & only the events
* that we attached in {@link node._clientOn `node._clientOn`}.
* Remove event handlers from the MQTT.js client & only the events
* that we attached in {@link node._clientOn `node._clientOn`}.
* * If `event` is omitted, then all events matching `handler` are removed
* * If `handler` is omitted, then all events named `event` are removed
* * If both parameters are omitted, then all events are removed
@@ -1205,14 +1233,18 @@ module.exports = function(RED) {
node.on('close', function(removed, done) {
if (node.brokerConn) {
if(node.isDynamic) {
Object.keys(node.dynamicSubs).forEach(function (topic) {
node.brokerConn.unsubscribe(topic, node.id, removed);
});
node.dynamicSubs = {};
if (node.brokerConn.options.autoUnsubscribe) {
Object.keys(node.dynamicSubs).forEach(function (topic) {
node.brokerConn.unsubscribe(topic, node.id, removed);
});
node.dynamicSubs = {};
}
} else {
node.brokerConn.unsubscribe(node.topic,node.id, removed);
if (node.brokerConn.options.autoUnsubscribe) {
node.brokerConn.unsubscribe(node.topic, node.id, removed);
}
}
node.brokerConn.deregister(node, done);
node.brokerConn.deregister(node, done, removed);
node.brokerConn = null;
} else {
done();
@@ -1275,9 +1307,9 @@ module.exports = function(RED) {
node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"});
}
node.brokerConn.register(node);
node.on('close', function(done) {
node.on('close', function(removed, done) {
if (node.brokerConn) {
node.brokerConn.deregister(node,done);
node.brokerConn.deregister(node, done, removed)
node.brokerConn = null;
} else {
done();

View File

@@ -227,6 +227,7 @@
}
});
},
sortable: true,
removable: true
});

View File

@@ -46,7 +46,7 @@ module.exports = function(RED) {
isText = true;
} else if (parsedType.type !== "application") {
isText = false;
} else if ((parsedType.subtype !== "octet-stream")
} else if ((parsedType.subtype !== "octet-stream")
&& (parsedType.subtype !== "cbor")
&& (parsedType.subtype !== "x-protobuf")) {
checkUTF = true;
@@ -200,6 +200,15 @@ module.exports = function(RED) {
this.callback = function(req,res) {
var msgid = RED.util.generateId();
res._msgid = msgid;
// Since Node 15, req.headers are lazily computed and the property
// marked as non-enumerable.
// That means it doesn't show up in the Debug sidebar.
// This redefines the property causing it to be evaluated *and*
// marked as enumerable again.
Object.defineProperty(req, 'headers', {
value: req.headers,
enumerable: true
})
if (node.method.match(/^(post|delete|put|options|patch)$/)) {
node.send({_msgid:msgid,req:req,res:createResponseWrapper(node,res),payload:req.body});
} else if (node.method == "get") {
@@ -282,7 +291,7 @@ module.exports = function(RED) {
RED.nodes.createNode(this,n);
var node = this;
this.headers = n.headers||{};
this.statusCode = n.statusCode;
this.statusCode = parseInt(n.statusCode);
this.on("input",function(msg,_send,done) {
if (msg.res) {
var headers = RED.util.cloneMessage(node.headers);
@@ -323,7 +332,7 @@ module.exports = function(RED) {
}
}
}
var statusCode = node.statusCode || msg.statusCode || 200;
var statusCode = node.statusCode || parseInt(msg.statusCode) || 200;
if (typeof msg.payload == "object" && !Buffer.isBuffer(msg.payload)) {
msg.res._res.status(statusCode).jsonp(msg.payload);
} else {

View File

@@ -91,6 +91,11 @@
<label for="node-input-senderr" style="width: auto" data-i18n="httpin.senderr"></label>
</div>
<div class="form-row">
<input type="checkbox" id="node-input-insecureHTTPParser" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-insecureHTTPParser", style="width: auto;" data-i18n="httpin.insecureHTTPParser"></label>
</div>
<div class="form-row">
<label for="node-input-ret"><i class="fa fa-arrow-left"></i> <span data-i18n="httpin.label.return"></span></label>
@@ -227,6 +232,7 @@
persist: {value:false},
proxy: {type:"http proxy",required: false,
label:RED._("node-red:httpin.proxy-config") },
insecureHTTPParser: {value: false},
authType: {value: ""},
senderr: {value: false},
headers: { value: [] }
@@ -338,6 +344,12 @@
} else {
$("#node-input-useProxy").prop("checked", false);
}
if (node.insecureHTTPParser) {
$("node-intput-insecureHTTPParser").prop("checked", true)
} else {
$("node-intput-insecureHTTPParser").prop("checked", false)
}
updateProxyOptions();
$("#node-input-useProxy").on("click", function() {
updateProxyOptions();
@@ -405,6 +417,7 @@
});
},
sortable: true,
removable: true
});
if (node.headers) {

View File

@@ -14,9 +14,9 @@
* 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');
@@ -86,6 +86,7 @@ in your Node-RED user directory (${RED.settings.userDir}).
if (n.paytoqs === true || n.paytoqs === "query") { paytoqs = true; }
else if (n.paytoqs === "body") { paytobody = true; }
node.insecureHTTPParser = n.insecureHTTPParser
var prox, noprox;
if (process.env.http_proxy) { prox = process.env.http_proxy; }
@@ -209,24 +210,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 = {};
@@ -244,6 +245,13 @@ in your Node-RED user directory (${RED.settings.userDir}).
delete options.headers[h];
}
})
if (node.insecureHTTPParser) {
// 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 }
}
}
],
beforeRedirect: [
@@ -398,15 +406,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);
}
@@ -430,6 +439,10 @@ in your Node-RED user directory (${RED.settings.userDir}).
formData.append(opt, val);
} else if (typeof val === 'object' && val.hasOwnProperty('value')) {
formData.append(opt,val.value,val.options || {});
} else if (Array.isArray(val)) {
for (var i=0; i<val.length; i++) {
formData.append(opt, val[i])
}
} else {
formData.append(opt,JSON.stringify(val));
}
@@ -690,25 +703,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
@@ -723,10 +754,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,

View File

@@ -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");

View File

@@ -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";
@@ -110,7 +110,12 @@ module.exports = function(RED) {
if (msg.payload[s].hasOwnProperty(p)) {
/* istanbul ignore else */
if (typeof msg.payload[s][p] !== "object") {
var q = "" + msg.payload[s][p];
// Fix to honour include null values flag
//if (typeof msg.payload[s][p] !== "object" || (node.include_null_values === true && msg.payload[s][p] === null)) {
var q = "";
if (msg.payload[s][p] !== undefined) {
q += msg.payload[s][p];
}
if (q.indexOf(node.quo) !== -1) { // add double quotes if any quotes
q = q.replace(/"/g, '""');
ou += node.quo + q + node.quo + node.sep;
@@ -130,9 +135,15 @@ module.exports = function(RED) {
ou += node.sep;
}
else {
var p = RED.util.ensureString(RED.util.getMessageProperty(msg,"payload["+s+"]['"+template[t]+"']"));
var tt = template[t];
if (template[t].indexOf('"') >=0 ) { tt = "'"+tt+"'"; }
else { tt = '"'+tt+'"'; }
var p = RED.util.getMessageProperty(msg,'payload["'+s+'"]['+tt+']');
/* istanbul ignore else */
if (p === "undefined") { p = ""; }
if (p === undefined) { p = ""; }
// fix to honour include null values flag
//if (p === null && node.include_null_values !== true) { p = "";}
p = RED.util.ensureString(p);
if (p.indexOf(node.quo) !== -1) { // add double quotes if any quotes
p = p.replace(/"/g, '""');
ou += node.quo + p + node.quo + node.sep;

View File

@@ -33,7 +33,13 @@ module.exports = function(RED) {
parseString(value, options, function (err, result) {
if (err) { done(err); }
else {
value = result;
// TODO: With xml2js@0.5.0, they return an object with
// a null prototype. This could cause unexpected
// issues. So for now, we have to reconstruct
// the object with a proper prototype.
// Once https://github.com/Leonidas-from-XIV/node-xml2js/pull/674
// is merged, we can revisit and hopefully remove this hack
value = fixObj(result)
RED.util.setMessageProperty(msg,node.property,value);
send(msg);
done();
@@ -46,4 +52,18 @@ module.exports = function(RED) {
});
}
RED.nodes.registerType("xml",XMLNode);
function fixObj(obj) {
const res = {}
const keys = Object.keys(obj)
keys.forEach(k => {
if (typeof obj[k] === 'object' && obj[k]) {
res[k] = fixObj(obj[k])
} else {
res[k] = obj[k]
}
})
return res
}
}

View File

@@ -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":"";

View File

@@ -251,7 +251,9 @@ module.exports = function(RED) {
}
else {
node.buffer = buff.slice(p,buff.length);
node.pendingDones.push(done);
if (node.buffer.length > 0) {
node.pendingDones.push(done);
}
}
if (node.buffer.length == 0) {
done();
@@ -476,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) {

View File

@@ -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" : "";

0
packages/node_modules/@node-red/nodes/core/storage/10-file.html vendored Executable file → Normal file
View File

View File

@@ -117,7 +117,9 @@ 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 aflg = true;
if (msg.hasOwnProperty("parts") && msg.parts.type === "string" && (msg.parts.count === msg.parts.index + 1)) { aflg = false; }
if ((node.appendNewline) && (!Buffer.isBuffer(data)) && aflg) { data += os.EOL; }
var buf;
if (node.encoding === "setbymsg") {
buf = encode(data, msg.encoding || "none");
@@ -314,7 +316,6 @@ module.exports = function(RED) {
});
filename = filename || "";
var fullFilename = filename;
var filePath = "";
if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename));
}