mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge branch 'dev' into pr_3438
This commit is contained in:
5
packages/node_modules/@node-red/nodes/core/common/05-junction.html
vendored
Normal file
5
packages/node_modules/@node-red/nodes/core/common/05-junction.html
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<!--
|
||||
05-junction.html
|
||||
This file exists so that the runtime loads the Junction node into the registry,
|
||||
but it is empty so it doesn't appear in the editor palette
|
||||
-->
|
12
packages/node_modules/@node-red/nodes/core/common/05-junction.js
vendored
Normal file
12
packages/node_modules/@node-red/nodes/core/common/05-junction.js
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
function JunctionNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.on("input",function(msg, send, done) {
|
||||
send(msg);
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("junction",JunctionNode);
|
||||
}
|
@@ -389,13 +389,6 @@
|
||||
var id = $("#inject-time-type-select").val();
|
||||
$(".inject-time-row").hide();
|
||||
$("#inject-time-row-"+id).show();
|
||||
if ((id == "none") || (id == "interval") || (id == "interval-time")) {
|
||||
$("#node-once").show();
|
||||
}
|
||||
else {
|
||||
$("#node-once").hide();
|
||||
$("#node-input-once").prop('checked', false);
|
||||
}
|
||||
|
||||
// Scroll down
|
||||
var scrollDiv = $("#dialog-form").parent();
|
||||
|
@@ -74,7 +74,7 @@
|
||||
RED.nodes.registerType('debug',{
|
||||
category: 'common',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
name: {value:"_DEFAULT_"},
|
||||
active: {value:true},
|
||||
tosidebar: {value:true},
|
||||
console: {value:false},
|
||||
@@ -160,6 +160,10 @@
|
||||
},
|
||||
messageSourceClick: function(sourceId, aliasId, path) {
|
||||
// Get all of the nodes that could have logged this message
|
||||
if (RED.nodes.workspace(sourceId)) {
|
||||
RED.view.reveal(sourceId);
|
||||
return
|
||||
}
|
||||
var candidateNodes = [RED.nodes.node(sourceId)]
|
||||
if (path) {
|
||||
for (var i=2;i<path.length;i++) {
|
||||
@@ -235,10 +239,11 @@
|
||||
// sourceNode should be the top-level node - one that is on a flow.
|
||||
var sourceNode;
|
||||
var pathParts;
|
||||
var pathHierarchy;
|
||||
if (o.path) {
|
||||
// Path is a `/`-separated list of ids that identifies the
|
||||
// complete parentage of the node that generated this message.
|
||||
// flow-id/subflow-A-instance/subflow-A-type/subflow-B-instance/subflow-B-type/node-id
|
||||
// flow-id/subflow-A-instance/subflow-B-instance
|
||||
|
||||
// If it has one id, that is a top level flow
|
||||
// each subsequent id is the instance id of a subflow node
|
||||
@@ -251,11 +256,41 @@
|
||||
// Highlight the subflow instance node.
|
||||
sourceNode = RED.nodes.node(pathParts[1]);
|
||||
}
|
||||
pathHierarchy = pathParts.map((id,index) => {
|
||||
if (index === 0) {
|
||||
return {
|
||||
id: id,
|
||||
label: RED.nodes.workspace(id).label
|
||||
}
|
||||
} else {
|
||||
var instanceNode = RED.nodes.node(id)
|
||||
return {
|
||||
id: id,
|
||||
label: (instanceNode.name || RED.nodes.subflow(instanceNode.type.substring(8)).name)
|
||||
}
|
||||
}
|
||||
})
|
||||
if (pathParts.length === 1) {
|
||||
pathHierarchy.push({
|
||||
id: o.id,
|
||||
label: sourceNode.name || sourceNode.type+":"+sourceNode.id
|
||||
})
|
||||
}
|
||||
if (o._alias) {
|
||||
let aliasNode = RED.nodes.node(o._alias)
|
||||
if (aliasNode) {
|
||||
pathHierarchy.push({
|
||||
id: o._alias,
|
||||
label: aliasNode.name || aliasNode.type+":"+aliasNode.id
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is probably redundant...
|
||||
sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z);
|
||||
}
|
||||
if (sourceNode) {
|
||||
var sourceFlow = RED.nodes.workspace(sourceNode.z)
|
||||
o._source = {
|
||||
id:sourceNode.id,
|
||||
z:sourceNode.z,
|
||||
@@ -266,7 +301,9 @@
|
||||
// the top-level subflow instance node.
|
||||
// This means the node's name is displayed in the sidebar.
|
||||
_alias:o._alias,
|
||||
path: pathParts
|
||||
flowName: sourceFlow?(sourceFlow.label||sourceNode.z):sourceNode.z,
|
||||
path: pathParts,
|
||||
pathHierarchy: pathHierarchy
|
||||
};
|
||||
}
|
||||
RED.debug.handleDebugMessage(o);
|
||||
@@ -507,6 +544,12 @@
|
||||
$("#node-input-complete").val($("#node-input-typed-complete").typedInput('value'));
|
||||
}
|
||||
$("#node-input-statusVal").val($("#node-input-typed-status").typedInput('value'));
|
||||
},
|
||||
onadd: function() {
|
||||
if (this.name === '_DEFAULT_') {
|
||||
this.name = ''
|
||||
RED.actions.invoke("core:generate-node-names", this)
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
@@ -108,7 +108,9 @@ module.exports = function(RED) {
|
||||
}
|
||||
})
|
||||
this.on("input", function(msg, send, done) {
|
||||
if (hasOwnProperty.call(msg, "status") && hasOwnProperty.call(msg.status, "source") && hasOwnProperty.call(msg.status.source, "id") && (msg.status.source.id === node.id)) {
|
||||
if (hasOwnProperty.call(msg, "status") && msg.status &&
|
||||
hasOwnProperty.call(msg.status, "source") && msg.status.source &&
|
||||
hasOwnProperty.call(msg.status.source, "id") && (msg.status.source.id === node.id)) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
@@ -129,7 +131,8 @@ module.exports = function(RED) {
|
||||
fill = "red";
|
||||
st = msg.error.message;
|
||||
}
|
||||
if (hasOwnProperty.call(msg, "status")) {
|
||||
if (hasOwnProperty.call(msg, "status") &&
|
||||
msg.status) {
|
||||
fill = msg.status.fill || "grey";
|
||||
shape = msg.status.shape || "ring";
|
||||
st = msg.status.text || "";
|
||||
|
@@ -32,14 +32,23 @@
|
||||
<label for="node-input-timeout"><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 style="position:relative; height: 30px; text-align: right;"><div style="display:inline-block"><input type="text" id="node-input-link-target-filter"></div></div>
|
||||
<div class="form-row node-input-link-row"></div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-linkType" data-i18n="link.linkCallType"></label>
|
||||
<select id="node-input-linkType" style="width: 70%">
|
||||
<option value="static" data-i18n="link.staticLinkCall"></option>
|
||||
<option value="dynamic" data-i18n="link.dynamicLinkCall"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="link-call-target-tree" style="position:relative; height: 30px; text-align: right;">
|
||||
<div style="display:inline-block"><input type="text" id="node-input-link-target-filter"></div>
|
||||
</div>
|
||||
<div class="form-row node-input-link-row link-call-target-tree"></div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
|
||||
var treeList;
|
||||
let treeList;
|
||||
|
||||
function onEditPrepare(node,targetType) {
|
||||
if (!node.links) {
|
||||
@@ -47,7 +56,7 @@
|
||||
}
|
||||
node.oldLinks = [];
|
||||
|
||||
var activeSubflow = RED.nodes.subflow(node.z);
|
||||
const activeSubflow = RED.nodes.subflow(node.z);
|
||||
|
||||
treeList = $("<div>")
|
||||
.css({width: "100%", height: "100%"})
|
||||
@@ -67,10 +76,10 @@
|
||||
RED.view.redraw();
|
||||
}
|
||||
});
|
||||
var candidateNodes = RED.nodes.filterNodes({type:targetType});
|
||||
var candidateNodesCount = 0;
|
||||
const candidateNodes = RED.nodes.filterNodes({type:targetType});
|
||||
let candidateNodesCount = 0;
|
||||
|
||||
var search = $("#node-input-link-target-filter").searchBox({
|
||||
const search = $("#node-input-link-target-filter").searchBox({
|
||||
style: "compact",
|
||||
delay: 300,
|
||||
change: function() {
|
||||
@@ -79,7 +88,7 @@
|
||||
treeList.treeList("filter", null);
|
||||
search.searchBox("count","");
|
||||
} else {
|
||||
var count = treeList.treeList("filter", function(item) {
|
||||
const count = treeList.treeList("filter", function(item) {
|
||||
return item.label.toLowerCase().indexOf(val) > -1 || (item.node && item.node.type.toLowerCase().indexOf(val) > -1)
|
||||
});
|
||||
search.searchBox("count",count+" / "+candidateNodesCount);
|
||||
@@ -87,25 +96,27 @@
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var flows = [];
|
||||
var flowMap = {};
|
||||
const flows = [];
|
||||
const flowMap = {};
|
||||
|
||||
if (activeSubflow) {
|
||||
flowMap[activeSubflow.id] = {
|
||||
id: activeSubflow.id,
|
||||
class: 'red-ui-palette-header',
|
||||
label: "Subflow : "+(activeSubflow.name || activeSubflow.id),
|
||||
label: "Subflow : " + (activeSubflow.name || activeSubflow.id),
|
||||
expanded: true,
|
||||
children: []
|
||||
};
|
||||
flows.push(flowMap[activeSubflow.id])
|
||||
} else {
|
||||
RED.nodes.eachWorkspace(function(ws) {
|
||||
}
|
||||
if (!activeSubflow || node.type === "link call") {
|
||||
// Only "Link Call" can look outside of its own subflow
|
||||
// Link In and Link Out nodes outside of a subflow should be ignored
|
||||
RED.nodes.eachWorkspace(function (ws) {
|
||||
flowMap[ws.id] = {
|
||||
id: ws.id,
|
||||
class: 'red-ui-palette-header',
|
||||
label: (ws.label || ws.id)+(node.z===ws.id ? " *":""),
|
||||
label: (ws.label || ws.id) + (node.z === ws.id ? " *" : ""),
|
||||
expanded: true,
|
||||
children: []
|
||||
}
|
||||
@@ -113,22 +124,21 @@
|
||||
})
|
||||
}
|
||||
|
||||
candidateNodes.forEach(function(n) {
|
||||
candidateNodes.forEach(function (n) {
|
||||
if (flowMap[n.z]) {
|
||||
if (targetType === "link out" && n.mode === 'return') {
|
||||
// Link In nodes looking for Link Out nodes should not
|
||||
// include return-mode nodes.
|
||||
return
|
||||
return;
|
||||
}
|
||||
var isChecked = false;
|
||||
isChecked = (node.links.indexOf(n.id) !== -1) || (n.links||[]).indexOf(node.id) !== -1;
|
||||
const isChecked = (node.links.indexOf(n.id) !== -1) || (n.links || []).indexOf(node.id) !== -1;
|
||||
if (isChecked) {
|
||||
node.oldLinks.push(n.id);
|
||||
}
|
||||
flowMap[n.z].children.push({
|
||||
id: n.id,
|
||||
node: n,
|
||||
label: n.name||n.id,
|
||||
label: n.name || n.id,
|
||||
selected: isChecked,
|
||||
checkbox: node.type !== "link call",
|
||||
radio: node.type === "link call"
|
||||
@@ -136,8 +146,8 @@
|
||||
candidateNodesCount++;
|
||||
}
|
||||
});
|
||||
flows = flows.filter(function(f) { return f.children.length > 0 })
|
||||
treeList.treeList('data',flows);
|
||||
const flowsFiltered = flows.filter(function(f) { return f.children.length > 0 })
|
||||
treeList.treeList('data',flowsFiltered);
|
||||
setTimeout(function() {
|
||||
treeList.treeList('show',node.z);
|
||||
},100);
|
||||
@@ -209,6 +219,10 @@
|
||||
}
|
||||
|
||||
function onAdd() {
|
||||
if (this.name === '_DEFAULT_') {
|
||||
this.name = ''
|
||||
RED.actions.invoke("core:generate-node-names", this)
|
||||
}
|
||||
for (var i=0;i<this.links.length;i++) {
|
||||
var n = RED.nodes.node(this.links[i]);
|
||||
if (n && n.links.indexOf(this.id) === -1) {
|
||||
@@ -221,7 +235,7 @@
|
||||
category: 'common',
|
||||
color:"#ddd",//"#87D8CF",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
name: { value: "_DEFAULT_" },
|
||||
links: { value: [], type:"link out[]" }
|
||||
},
|
||||
inputs:0,
|
||||
@@ -257,12 +271,14 @@
|
||||
category: 'common',
|
||||
color:"#ddd",//"#87D8CF",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
links: { value: [], type:"link in[]"},
|
||||
timeout: { value: "30",
|
||||
label: RED._("node-red:link.timeout"),
|
||||
validate:RED.validators.number(true)
|
||||
}
|
||||
name: { value: "" },
|
||||
links: { value: [], type:"link in[]" },
|
||||
linkType: { value:"static" },
|
||||
timeout: {
|
||||
value: "30",
|
||||
label: RED._("node-red:link.timeout"),
|
||||
validate:RED.validators.number(true)
|
||||
}
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
@@ -274,7 +290,9 @@
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
if (this.links.length > 0) {
|
||||
if (this.linkType === "dynamic") {
|
||||
return this._("link.dynamicLinkLabel");
|
||||
} else if (this.links.length > 0) {
|
||||
var targetNode = RED.nodes.node(this.links[0]);
|
||||
return targetNode && (targetNode.name || this._("link.linkCall"));
|
||||
}
|
||||
@@ -284,6 +302,22 @@
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
console.log("link call oneditprepare")
|
||||
const updateVisibility = function() {
|
||||
const static = $('#node-input-linkType').val() !== "dynamic";
|
||||
if(static) {
|
||||
$("div.link-call-target-tree").show();
|
||||
} else {
|
||||
$("div.link-call-target-tree").hide();
|
||||
}
|
||||
}
|
||||
$("#node-input-linkType").on("change",function(d){
|
||||
updateVisibility();
|
||||
});
|
||||
if (["static","dynamic"].indexOf(this.linkType) < 0) {
|
||||
$("#node-input-linkType").val('static');
|
||||
}
|
||||
updateVisibility();
|
||||
onEditPrepare(this,"link in");
|
||||
},
|
||||
oneditsave: function() {
|
||||
@@ -296,9 +330,9 @@
|
||||
category: 'common',
|
||||
color:"#ddd",//"#87D8CF",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
name: { value:"_DEFAULT_" },
|
||||
mode: { value: "link" },// link || return
|
||||
links: { value: [], type:"link in[]"}
|
||||
links: { value: [], type:"link in[]" }
|
||||
},
|
||||
align:"right",
|
||||
inputs:1,
|
||||
|
@@ -14,10 +14,119 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
/**
|
||||
* @typedef LinkTarget
|
||||
* @type {object}
|
||||
* @property {string} id - ID of the target node.
|
||||
* @property {string} name - Name of target Node
|
||||
* @property {number} flowId - ID of flow where the target node exists
|
||||
* @property {string} flowName - Name of flow where the target node exists
|
||||
* @property {boolean} isSubFlow - True if the link-in node exists in a subflow instance
|
||||
*/
|
||||
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
const crypto = require("crypto");
|
||||
const targetCache = (function () {
|
||||
const registry = { id: {}, name: {} };
|
||||
function getIndex(/** @type {[LinkTarget]}*/ targets, id) {
|
||||
for (let index = 0; index < (targets || []).length; index++) {
|
||||
const element = targets[index];
|
||||
if (element.id === id) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
/**
|
||||
* Generate a target object from a node
|
||||
* @param {LinkInNode} node
|
||||
* @returns {LinkTarget} a link target object
|
||||
*/
|
||||
function generateTarget(node) {
|
||||
const isSubFlow = node._flow.TYPE === "subflow";
|
||||
return {
|
||||
id: node.id,
|
||||
name: node.name || node.id,
|
||||
flowId: node._flow.flow.id,
|
||||
flowName: isSubFlow ? node._flow.subflowDef.name : node._flow.flow.label,
|
||||
isSubFlow: isSubFlow
|
||||
}
|
||||
}
|
||||
return {
|
||||
/**
|
||||
* Get a list of targets registerd to this name
|
||||
* @param {string} name Name of the target
|
||||
* @param {boolean} [excludeSubflows] set `true` to exclude
|
||||
* @returns {[LinkTarget]} Targets registerd to this name.
|
||||
*/
|
||||
getTargets(name, excludeSubflows) {
|
||||
const targets = registry.name[name] || [];
|
||||
if (excludeSubflows) {
|
||||
return targets.filter(e => e.isSubFlow != true);
|
||||
}
|
||||
return targets;
|
||||
},
|
||||
/**
|
||||
* Get a single target by registered name.
|
||||
* To restrict to a single flow, include the `flowId`
|
||||
* If there is no targets OR more than one target, null is returned
|
||||
* @param {string} name Name of the node
|
||||
* @param {string} [flowId]
|
||||
* @returns {LinkTarget} target
|
||||
*/
|
||||
getTarget(name, flowId) {
|
||||
/** @type {[LinkTarget]}*/
|
||||
let possibleTargets = this.getTargets(name);
|
||||
/** @type {LinkTarget}*/
|
||||
let target;
|
||||
if (possibleTargets.length && flowId) {
|
||||
possibleTargets = possibleTargets.filter(e => e.flowId == flowId);
|
||||
}
|
||||
if (possibleTargets.length === 1) {
|
||||
target = possibleTargets[0];
|
||||
}
|
||||
return target;
|
||||
},
|
||||
/**
|
||||
* Get a target by node ID
|
||||
* @param {string} nodeId ID of the node
|
||||
* @returns {LinkTarget} target
|
||||
*/
|
||||
getTargetById(nodeId) {
|
||||
return registry.id[nodeId];
|
||||
},
|
||||
register(/** @type {LinkInNode} */ node) {
|
||||
const target = generateTarget(node);
|
||||
const tByName = this.getTarget(target.name, target.flowId);
|
||||
if (!tByName || tByName.id !== target.id) {
|
||||
registry.name[target.name] = registry.name[target.name] || [];
|
||||
registry.name[target.name].push(target)
|
||||
}
|
||||
registry.id[target.id] = target;
|
||||
return target;
|
||||
},
|
||||
remove(node) {
|
||||
const target = generateTarget(node);
|
||||
const tn = this.getTarget(target.name, target.flowId);
|
||||
if (tn) {
|
||||
const targs = this.getTargets(tn.name);
|
||||
const idx = getIndex(targs, tn.id);
|
||||
if (idx > -1) {
|
||||
targs.splice(idx, 1);
|
||||
}
|
||||
if (targs.length === 0) {
|
||||
delete registry.name[tn.name];
|
||||
}
|
||||
}
|
||||
delete registry.id[target.id];
|
||||
},
|
||||
clear() {
|
||||
registry = { id: {}, name: {} };
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
function LinkInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
@@ -27,12 +136,14 @@ module.exports = function(RED) {
|
||||
msg._event = n.event;
|
||||
node.receive(msg);
|
||||
}
|
||||
targetCache.register(node);
|
||||
RED.events.on(event,handler);
|
||||
this.on("input", function(msg, send, done) {
|
||||
send(msg);
|
||||
done();
|
||||
});
|
||||
this.on("close",function() {
|
||||
targetCache.remove(node);
|
||||
RED.events.removeListener(event,handler);
|
||||
});
|
||||
}
|
||||
@@ -74,31 +185,69 @@ module.exports = function(RED) {
|
||||
function LinkCallNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
const node = this;
|
||||
const target = n.links[0];
|
||||
const staticTarget = typeof n.links === "string" ? n.links : n.links[0];
|
||||
const linkType = n.linkType;
|
||||
const messageEvents = {};
|
||||
let timeout = parseFloat(n.timeout || "30")*1000;
|
||||
|
||||
let timeout = parseFloat(n.timeout || "30") * 1000;
|
||||
if (isNaN(timeout)) {
|
||||
timeout = 30000;
|
||||
}
|
||||
function getTargetNode(msg) {
|
||||
const dynamicMode = linkType === "dynamic";
|
||||
const target = dynamicMode ? msg.target : staticTarget
|
||||
|
||||
this.on("input", function(msg, send, done) {
|
||||
msg._linkSource = msg._linkSource || [];
|
||||
const messageEvent = {
|
||||
id: crypto.randomBytes(14).toString('hex'),
|
||||
node: node.id,
|
||||
////1st see if the target is a direct node id
|
||||
let foundNode;
|
||||
if (targetCache.getTargetById(target)) {
|
||||
foundNode = RED.nodes.getNode(target)
|
||||
}
|
||||
messageEvents[messageEvent.id] = {
|
||||
msg: RED.util.cloneMessage(msg),
|
||||
send,
|
||||
done,
|
||||
ts: setTimeout(function() {
|
||||
timeoutMessage(messageEvent.id)
|
||||
}, timeout )
|
||||
};
|
||||
msg._linkSource.push(messageEvent);
|
||||
var targetNode = RED.nodes.getNode(target);
|
||||
if (targetNode) {
|
||||
targetNode.receive(msg);
|
||||
if (target && !foundNode && dynamicMode) {
|
||||
//next, look in **this flow only** for the node
|
||||
let cachedTarget = targetCache.getTarget(target, node._flow.flow.id);
|
||||
if (!cachedTarget) {
|
||||
//single target node not found in registry!
|
||||
//get all possible targets from regular flows (exclude subflow instances)
|
||||
const possibleTargets = targetCache.getTargets(target, true);
|
||||
if (possibleTargets.length === 1) {
|
||||
//only 1 link-in found with this name - good, lets use it
|
||||
cachedTarget = possibleTargets[0];
|
||||
} else if (possibleTargets.length > 1) {
|
||||
//more than 1 link-in has this name, raise an error
|
||||
throw new Error(`Multiple link-in nodes named '${target}' found`);
|
||||
}
|
||||
}
|
||||
if (cachedTarget) {
|
||||
foundNode = RED.nodes.getNode(cachedTarget.id);
|
||||
}
|
||||
}
|
||||
if (foundNode instanceof LinkInNode) {
|
||||
return foundNode;
|
||||
}
|
||||
throw new Error(`target link-in node '${target || ""}' not found`);
|
||||
}
|
||||
this.on("input", function (msg, send, done) {
|
||||
try {
|
||||
const targetNode = getTargetNode(msg);
|
||||
if (targetNode instanceof LinkInNode) {
|
||||
msg._linkSource = msg._linkSource || [];
|
||||
const messageEvent = {
|
||||
id: crypto.randomBytes(14).toString('hex'),
|
||||
node: node.id,
|
||||
}
|
||||
messageEvents[messageEvent.id] = {
|
||||
msg: RED.util.cloneMessage(msg),
|
||||
send,
|
||||
done,
|
||||
ts: setTimeout(function () {
|
||||
timeoutMessage(messageEvent.id)
|
||||
}, timeout)
|
||||
};
|
||||
msg._linkSource.push(messageEvent);
|
||||
targetNode.receive(msg);
|
||||
}
|
||||
} catch (error) {
|
||||
node.error(error, msg);
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -581,12 +581,45 @@ RED.debug = (function() {
|
||||
var metaRow = $('<div class="red-ui-debug-msg-meta"></div>').appendTo(msg);
|
||||
$('<span class="red-ui-debug-msg-date">'+ getTimestamp()+'</span>').appendTo(metaRow);
|
||||
if (sourceNode) {
|
||||
$('<a>',{href:"#",class:"red-ui-debug-msg-name"}).text('node: '+(o.name||sourceNode.name||sourceNode.id))
|
||||
|
||||
var nodeLink = $('<a>',{href:"#",class:"red-ui-debug-msg-name"}).text("node: "+(o.name||sourceNode.name||sourceNode.id))
|
||||
.appendTo(metaRow)
|
||||
.on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
config.messageSourceClick(sourceNode.id, sourceNode._alias, sourceNode.path);
|
||||
});
|
||||
|
||||
if (sourceNode.pathHierarchy) {
|
||||
RED.popover.create({
|
||||
tooltip: true,
|
||||
target:nodeLink,
|
||||
trigger: "hover",
|
||||
size: "small",
|
||||
direction: "bottom",
|
||||
interactive: true,
|
||||
content: function() {
|
||||
const content = $("<div>")
|
||||
sourceNode.pathHierarchy.forEach((pathPart,idx) => {
|
||||
const link = $("<a>", {href:"#" ,style:'display: block'})
|
||||
.css({
|
||||
paddingLeft:((idx*10)+((idx === sourceNode.pathHierarchy.length - 1)?10:0))+"px",
|
||||
paddingRight:'2px'
|
||||
})
|
||||
.text(pathPart.label)
|
||||
.appendTo(content)
|
||||
.on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
config.messageSourceClick(pathPart.id);
|
||||
})
|
||||
if (idx < sourceNode.pathHierarchy.length - 1) {
|
||||
$('<i class="fa fa-angle-down" style="margin-right: 3px"></i>').prependTo(link)
|
||||
}
|
||||
})
|
||||
return content
|
||||
},
|
||||
delay: { show: 50, hide: 150 }
|
||||
});
|
||||
}
|
||||
} else if (name) {
|
||||
$('<span class="red-ui-debug-msg-name">'+name+'</span>').appendTo(metaRow);
|
||||
}
|
||||
|
@@ -355,7 +355,7 @@
|
||||
color:"#fdd0a2",
|
||||
category: 'function',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
name: {value:"_DEFAULT_"},
|
||||
func: {value:"\nreturn msg;"},
|
||||
outputs: {value:1},
|
||||
noerr: {value:0,required:true,
|
||||
@@ -413,7 +413,7 @@
|
||||
$("#func-tabs-content").children().hide();
|
||||
$("#" + tab.id).show();
|
||||
let editor = $("#" + tab.id).find('.monaco-editor').first();
|
||||
if(editor.length) {
|
||||
if(editor.length) {
|
||||
if(that.editor.nodered && that.editor.type == "monaco") {
|
||||
that.editor.nodered.refreshModuleLibs(getLibsList());
|
||||
}
|
||||
@@ -619,6 +619,12 @@
|
||||
this.finalizeEditor.resize();
|
||||
|
||||
$("#node-input-libs-container").css("height", (height - 192)+"px");
|
||||
},
|
||||
onadd: function() {
|
||||
if (this.name === '_DEFAULT_') {
|
||||
this.name = ''
|
||||
RED.actions.invoke("core:generate-node-names", this)
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
@@ -55,6 +55,7 @@ module.exports = function(RED) {
|
||||
catch(e) { return false;}
|
||||
}
|
||||
else if (b === "null") { return a === null; }
|
||||
else if (b === "number") { return typeof a === b && !isNaN(a) }
|
||||
else { return typeof a === b && !Array.isArray(a) && !Buffer.isBuffer(a) && a !== null; }
|
||||
},
|
||||
'head': function(a, b, c, d, parts) {
|
||||
|
@@ -122,22 +122,26 @@
|
||||
}},
|
||||
timeoutUnits: {value:"seconds"},
|
||||
rate: {
|
||||
value:"1", required:true,
|
||||
value:"1",
|
||||
required:true,
|
||||
label:RED._("node-red:delay.label.rate"),
|
||||
validate:function(v,opt) {
|
||||
if (RED.validators.number(v) && (v >= 0)) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:delay.errors.invalid-rate");
|
||||
}},
|
||||
}
|
||||
},
|
||||
nbRateUnits: {
|
||||
value:"1", required:false,
|
||||
value:"1",
|
||||
required:false,
|
||||
validate:function(v,opt) {
|
||||
if (RED.validators.number(v) && (v >= 0)) {
|
||||
if (v === undefined || (RED.validators.number(v) && (v >= 0))) {
|
||||
return true;
|
||||
}
|
||||
return RED._("node-red:delay.errors.invalid-rate-unit");
|
||||
}},
|
||||
}
|
||||
},
|
||||
rateUnits: {value: "second"},
|
||||
randomFirst: {
|
||||
value:"1", required:true,
|
||||
|
@@ -65,6 +65,11 @@
|
||||
/* opacity: 0.3;
|
||||
pointer-events: none; */
|
||||
}
|
||||
.form-row.form-row-mqtt-datatype-tip > .form-tips {
|
||||
width: calc(70% - 18px);
|
||||
display: inline-block;
|
||||
margin-top: -8px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -121,6 +126,7 @@
|
||||
<div class="form-row">
|
||||
<label for="node-input-datatype"><i class="fa fa-sign-out"></i> <span data-i18n="mqtt.label.output"></span></label>
|
||||
<select id="node-input-datatype" style="width:70%;">
|
||||
<option value="auto-detect" data-i18n="mqtt.output.auto-detect"></option>
|
||||
<option value="auto" data-i18n="mqtt.output.auto"></option>
|
||||
<option value="buffer" data-i18n="mqtt.output.buffer"></option>
|
||||
<option value="utf8" data-i18n="mqtt.output.string"></option>
|
||||
@@ -128,6 +134,10 @@
|
||||
<option value="base64" data-i18n="mqtt.output.base64"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row form-row-mqtt-datatype-tip">
|
||||
<label> </label>
|
||||
<div class="form-tips" id="mqtt-in-datatype-depreciated-tip"><span data-i18n="mqtt.label.auto-mode-depreciated"></span></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
@@ -765,9 +775,8 @@
|
||||
}
|
||||
},
|
||||
qos: {value: "2"},
|
||||
datatype: {value:"auto",required:true},
|
||||
broker: {type:"mqtt-broker", required:true,
|
||||
label:RED._("node-red:mqtt.label.broker")},
|
||||
datatype: {value:"auto-detect",required:true},
|
||||
broker: {type:"mqtt-broker", required:true, label:RED._("node-red:mqtt.label.broker")},
|
||||
// subscriptionIdentifier: {value:0},
|
||||
nl: {value:false},
|
||||
rap: {value:true},
|
||||
@@ -803,6 +812,16 @@
|
||||
$("div.form-row-mqtt5").toggleClass("form-row-mqtt5-active",!!v5);
|
||||
$("div.form-row.form-row-mqtt-static").toggleClass("form-row-mqtt-static-disabled", !!dynamic)
|
||||
}
|
||||
|
||||
$("#node-input-datatype").on("change", function() {
|
||||
if($(this).val() === "auto") {
|
||||
$(".form-row.form-row-mqtt-datatype-tip").show();
|
||||
} else {
|
||||
$(".form-row.form-row-mqtt-datatype-tip").hide();
|
||||
}
|
||||
})
|
||||
$("#node-input-datatype").trigger("change");
|
||||
|
||||
$("#node-input-broker").on("change",function(d){
|
||||
updateVisibility();
|
||||
});
|
||||
@@ -823,7 +842,7 @@
|
||||
$("#node-input-qos").val("2");
|
||||
}
|
||||
if (this.datatype === undefined) {
|
||||
$("#node-input-datatype").val("auto");
|
||||
$("#node-input-datatype").val("auto-detect");
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
|
@@ -20,7 +20,30 @@ module.exports = function(RED) {
|
||||
var isUtf8 = require('is-utf8');
|
||||
var HttpsProxyAgent = require('https-proxy-agent');
|
||||
var url = require('url');
|
||||
|
||||
const knownMediaTypes = {
|
||||
"text/css":"string",
|
||||
"text/html":"string",
|
||||
"text/plain":"string",
|
||||
"text/html":"string",
|
||||
"application/json":"json",
|
||||
"application/octet-stream":"buffer",
|
||||
"application/pdf":"buffer",
|
||||
"application/x-gtar":"buffer",
|
||||
"application/x-gzip":"buffer",
|
||||
"application/x-tar":"buffer",
|
||||
"application/xml":"string",
|
||||
"application/zip":"buffer",
|
||||
"audio/aac":"buffer",
|
||||
"audio/ac3":"buffer",
|
||||
"audio/basic":"buffer",
|
||||
"audio/mp4":"buffer",
|
||||
"audio/ogg":"buffer",
|
||||
"image/bmp":"buffer",
|
||||
"image/gif":"buffer",
|
||||
"image/jpeg":"buffer",
|
||||
"image/tiff":"buffer",
|
||||
"image/png":"buffer",
|
||||
}
|
||||
//#region "Supporting functions"
|
||||
function matchTopic(ts,t) {
|
||||
if (ts == "#") {
|
||||
@@ -103,7 +126,7 @@ module.exports = function(RED) {
|
||||
if(src[propName] === "true" || src[propName] === true) {
|
||||
dst[propName] = true;
|
||||
} else if(src[propName] === "false" || src[propName] === false) {
|
||||
dst[propName] = true;
|
||||
dst[propName] = false;
|
||||
}
|
||||
} else {
|
||||
if(def != undefined) dst[propName] = def;
|
||||
@@ -188,24 +211,7 @@ module.exports = function(RED) {
|
||||
*/
|
||||
function subscriptionHandler(node, datatype ,topic, payload, packet) {
|
||||
const v5 = node.brokerConn.options && node.brokerConn.options.protocolVersion == 5;
|
||||
|
||||
if (datatype === "buffer") {
|
||||
// payload = payload;
|
||||
} else if (datatype === "base64") {
|
||||
payload = payload.toString('base64');
|
||||
} else if (datatype === "utf8") {
|
||||
payload = payload.toString('utf8');
|
||||
} else if (datatype === "json") {
|
||||
if (isUtf8(payload)) {
|
||||
payload = payload.toString();
|
||||
try { payload = JSON.parse(payload); }
|
||||
catch(e) { node.error(RED._("mqtt.errors.invalid-json-parse"),{payload:payload, topic:topic, qos:packet.qos, retain:packet.retain}); return; }
|
||||
}
|
||||
else { node.error((RED._("mqtt.errors.invalid-json-string")),{payload:payload, topic:topic, qos:packet.qos, retain:packet.retain}); return; }
|
||||
} else {
|
||||
if (isUtf8(payload)) { payload = payload.toString(); }
|
||||
}
|
||||
var msg = {topic:topic, payload:payload, qos:packet.qos, retain:packet.retain};
|
||||
var msg = {topic:topic, payload:null, qos:packet.qos, retain:packet.retain};
|
||||
if(v5 && packet.properties) {
|
||||
setStrProp(packet.properties, msg, "responseTopic");
|
||||
setBufferProp(packet.properties, msg, "correlationData");
|
||||
@@ -215,6 +221,76 @@ module.exports = function(RED) {
|
||||
setStrProp(packet.properties, msg, "reasonString");
|
||||
setUserProperties(packet.properties.userProperties, msg);
|
||||
}
|
||||
const v5isUtf8 = v5 ? msg.payloadFormatIndicator === true : null;
|
||||
const v5HasMediaType = v5 ? !!msg.contentType : null;
|
||||
const v5MediaTypeLC = v5 ? (msg.contentType + "").toLowerCase() : null;
|
||||
|
||||
if (datatype === "buffer") {
|
||||
// payload = payload;
|
||||
} else if (datatype === "base64") {
|
||||
payload = payload.toString('base64');
|
||||
} else if (datatype === "utf8") {
|
||||
payload = payload.toString('utf8');
|
||||
} else if (datatype === "json") {
|
||||
if (v5isUtf8 || isUtf8(payload)) {
|
||||
try {
|
||||
payload = JSON.parse(payload.toString());
|
||||
} catch (e) {
|
||||
node.error(RED._("mqtt.errors.invalid-json-parse"), { payload: payload, topic: topic, qos: packet.qos, retain: packet.retain }); return;
|
||||
}
|
||||
} else {
|
||||
node.error((RED._("mqtt.errors.invalid-json-string")), { payload: payload, topic: topic, qos: packet.qos, retain: packet.retain }); return;
|
||||
}
|
||||
} else {
|
||||
//"auto" (legacy) or "auto-detect" (new default)
|
||||
if (v5isUtf8 || v5HasMediaType) {
|
||||
const outputType = knownMediaTypes[v5MediaTypeLC]
|
||||
switch (outputType) {
|
||||
case "string":
|
||||
payload = payload.toString();
|
||||
break;
|
||||
case "buffer":
|
||||
//no change
|
||||
break;
|
||||
case "json":
|
||||
try {
|
||||
//since v5 type states this should be JSON, parse it & error out if NOT JSON
|
||||
payload = payload.toString()
|
||||
const obj = JSON.parse(payload);
|
||||
if (datatype === "auto-detect") {
|
||||
payload = obj; //as mode is "auto-detect", return the parsed JSON
|
||||
}
|
||||
} catch (e) {
|
||||
node.error(RED._("mqtt.errors.invalid-json-parse"), { payload: payload, topic: topic, qos: packet.qos, retain: packet.retain }); return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (v5isUtf8 || isUtf8(payload)) {
|
||||
payload = payload.toString(); //auto String
|
||||
if (datatype === "auto-detect") {
|
||||
try {
|
||||
payload = JSON.parse(payload); //auto to parsed object (attempt)
|
||||
} catch (e) {
|
||||
/* mute error - it simply isnt JSON, just leave payload as a string */
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (isUtf8(payload)) {
|
||||
payload = payload.toString(); //auto String
|
||||
if (datatype === "auto-detect") {
|
||||
try {
|
||||
payload = JSON.parse(payload);
|
||||
} catch (e) {
|
||||
/* mute error - it simply isnt JSON, just leave payload as a string */
|
||||
}
|
||||
}
|
||||
} //else {
|
||||
//leave as buffer
|
||||
//}
|
||||
}
|
||||
msg.payload = payload;
|
||||
if ((node.brokerConn.broker === "localhost")||(node.brokerConn.broker === "127.0.0.1")) {
|
||||
msg._topic = topic;
|
||||
}
|
||||
@@ -465,7 +541,7 @@ module.exports = function(RED) {
|
||||
};
|
||||
if(hasProperty(opts, "willTopic")) {
|
||||
//will v5 properties must be set in the "properties" sub object
|
||||
node.options.will = createLWT(opts.willTopic, opts.willPayload, opts.willQos, opts.willRetain, opts.willMsg, "properies");
|
||||
node.options.will = createLWT(opts.willTopic, opts.willPayload, opts.willQos, opts.willRetain, opts.willMsg, "properties");
|
||||
};
|
||||
} else {
|
||||
//update options
|
||||
@@ -637,24 +713,8 @@ module.exports = function(RED) {
|
||||
|
||||
node.deregister = function(mqttNode,done) {
|
||||
delete node.users[mqttNode.id];
|
||||
if (node.closing) {
|
||||
return done();
|
||||
}
|
||||
if (Object.keys(node.users).length === 0) {
|
||||
if (node.client && node.client.connected) {
|
||||
// Send close message
|
||||
if (node.closeMessage) {
|
||||
node.publish(node.closeMessage,function(err) {
|
||||
node.client.end(done);
|
||||
});
|
||||
} else {
|
||||
node.client.end(done);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
if (node.client) { node.client.end(); }
|
||||
return done();
|
||||
}
|
||||
if (!node.closing && node.connected && Object.keys(node.users).length === 0) {
|
||||
node.disconnect();
|
||||
}
|
||||
done();
|
||||
};
|
||||
@@ -663,6 +723,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
node.connect = function (callback) {
|
||||
if (node.canConnect()) {
|
||||
node.closing = false;
|
||||
node.connecting = true;
|
||||
setStatusConnecting(node, true);
|
||||
try {
|
||||
@@ -672,6 +733,7 @@ module.exports = function(RED) {
|
||||
let callbackDone = false; //prevent re-connects causing node.client.on('connect' firing callback multiple times
|
||||
// Register successful connect or reconnect handler
|
||||
node.client.on('connect', function (connack) {
|
||||
node.closing = false;
|
||||
node.connecting = false;
|
||||
node.connected = true;
|
||||
if(!callbackDone && typeof callback == "function") {
|
||||
@@ -740,6 +802,7 @@ module.exports = function(RED) {
|
||||
reasonCode: rc,
|
||||
reasonString: rs
|
||||
}
|
||||
node.connected = false;
|
||||
node.log(RED._("mqtt.state.broker-disconnected", details));
|
||||
setStatusDisconnected(node, true);
|
||||
});
|
||||
@@ -764,25 +827,31 @@ module.exports = function(RED) {
|
||||
}
|
||||
};
|
||||
node.disconnect = function (callback) {
|
||||
const _callback = function () {
|
||||
const _callback = function (resetNodeConnectedState) {
|
||||
setStatusDisconnected(node, true);
|
||||
node.connecting = false;
|
||||
node.connected = false;
|
||||
if(resetNodeConnectedState) {
|
||||
node.closing = true;
|
||||
node.connecting = false;
|
||||
node.connected = false;
|
||||
}
|
||||
callback && typeof callback == "function" && callback();
|
||||
};
|
||||
|
||||
if(node.client) {
|
||||
if(node.client.connected && node.closeMessage) {
|
||||
node.publish(node.closeMessage, function (err) {
|
||||
node.client.end(_callback);
|
||||
});
|
||||
} else if(node.client.connected || node.client.reconnecting) {
|
||||
node.client.end(_callback);
|
||||
} else if(node.client.disconnecting || node.client.connected === false) {
|
||||
_callback();
|
||||
}
|
||||
if(node.closing) {
|
||||
return _callback(false);
|
||||
}
|
||||
var endCallBack = function endCallBack() {
|
||||
}
|
||||
if(node.connected && node.closeMessage) {
|
||||
node.publish(node.closeMessage, function (err) {
|
||||
node.client.end(endCallBack);
|
||||
_callback(true);
|
||||
});
|
||||
} else if(node.connected) {
|
||||
node.client.end(endCallBack);
|
||||
_callback(true);
|
||||
} else {
|
||||
_callback();
|
||||
_callback(false);
|
||||
}
|
||||
}
|
||||
node.subscriptionIds = {};
|
||||
@@ -1074,6 +1143,8 @@ module.exports = function(RED) {
|
||||
node.brokerConn.unsubscribe(node.topic,node.id, removed);
|
||||
}
|
||||
node.brokerConn.deregister(node, done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -1134,7 +1205,11 @@ module.exports = function(RED) {
|
||||
}
|
||||
node.brokerConn.register(node);
|
||||
node.on('close', function(done) {
|
||||
node.brokerConn.deregister(node,done);
|
||||
if (node.brokerConn) {
|
||||
node.brokerConn.deregister(node,done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
node.error(RED._("mqtt.errors.missing-config"));
|
||||
|
@@ -100,14 +100,107 @@
|
||||
<option value="obj" data-i18n="httpin.json"></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-row form-tips" id="tip-json" hidden><span data-i18n="httpin.tip.req"></span></div>
|
||||
|
||||
<div class="form-row" style="margin-bottom:0;">
|
||||
<label><i class="fa fa-list"></i> <span data-i18n="httpin.label.headers"></span></label>
|
||||
</div>
|
||||
<div class="form-row node-input-headers-container-row">
|
||||
<ol id="node-input-headers-container"></ol>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-tips" id="tip-json" hidden><span data-i18n="httpin.tip.req"></span></div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
const headerTypes = [
|
||||
{ value: "Accept", label: "Accept", hasValue: false },
|
||||
{ value: "Accept-Encoding", label: "Accept-Encoding", hasValue: false },
|
||||
{ value: "Accept-Language", label: "Accept-Language", hasValue: false },
|
||||
{ value: "Authorization", label: "Authorization", hasValue: false },
|
||||
{ value: "Content-Type", label: "Content-Type", hasValue: false },
|
||||
{ value: "Cache-Control", label: "Cache-Control", hasValue: false },
|
||||
{ value: "User-Agent", label: "User-Agent", hasValue: false },
|
||||
{ value: "Location", label: "Location", hasValue: false },
|
||||
{ value: "other", label: "other", hasValue: true, icon: "red/images/typedInput/az.png" },
|
||||
{ value: "msg", label: "msg.", hasValue: true },
|
||||
]
|
||||
const headerOptions = {};
|
||||
const defaultOptions = [
|
||||
{ value: "other", label: "other", hasValue: true, icon: "red/images/typedInput/az.png" },
|
||||
{ value: "msg", label: "msg.", hasValue: true },
|
||||
];
|
||||
headerOptions["accept"] = [
|
||||
{ value: "text/plain", label: "text/plain", hasValue: false },
|
||||
{ value: "text/html", label: "text/html", hasValue: false },
|
||||
{ value: "application/json", label: "application/json", hasValue: false },
|
||||
{ value: "application/xml", label: "application/xml", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
headerOptions["accept-encoding"] = [
|
||||
{ value: "gzip", label: "gzip", hasValue: false },
|
||||
{ value: "deflate", label: "deflate", hasValue: false },
|
||||
{ value: "compress", label: "compress", hasValue: false },
|
||||
{ value: "br", label: "br", hasValue: false },
|
||||
{ value: "gzip, deflate", label: "gzip, deflate", hasValue: false },
|
||||
{ value: "gzip, deflate, br", label: "gzip, deflate, br", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
headerOptions["accept-language"] = [
|
||||
{ value: "*", label: "*", hasValue: false },
|
||||
{ value: "en-GB, en-US, en;q=0.9", label: "en-GB, en-US, en;q=0.9", hasValue: false },
|
||||
{ value: "de-AT, de-DE;q=0.9, en;q=0.5", label: "de-AT, de-DE;q=0.9, en;q=0.5", hasValue: false },
|
||||
{ value: "es-mx,es,en;q=0.5", label: "es-mx,es,en;q=0.5", hasValue: false },
|
||||
{ value: "fr-CH, fr;q=0.9, en;q=0.8", label: "fr-CH, fr;q=0.9, en;q=0.8", hasValue: false },
|
||||
{ value: "zh-CN, zh-TW; q = 0.9, zh-HK; q = 0.8, zh; q = 0.7, en; q = 0.6", label: "zh-CN, zh-TW; q = 0.9, zh-HK; q = 0.8, zh; q = 0.7, en; q = 0.6", hasValue: false },
|
||||
{ value: "ja-JP, jp", label: "ja-JP, jp", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
headerOptions["content-type"] = [
|
||||
{ value: "text/css", label: "text/css", hasValue: false },
|
||||
{ value: "text/plain", label: "text/plain", hasValue: false },
|
||||
{ value: "text/html", label: "text/html", hasValue: false },
|
||||
{ value: "application/json", label: "application/json", hasValue: false },
|
||||
{ value: "application/octet-stream", label: "application/octet-stream", hasValue: false },
|
||||
{ value: "application/pdf", label: "application/pdf", hasValue: false },
|
||||
{ value: "application/xml", label: "application/xml", hasValue: false },
|
||||
{ value: "application/zip", label: "application/zip", hasValue: false },
|
||||
{ value: "multipart/form-data", label: "multipart/form-data", hasValue: false },
|
||||
{ value: "audio/aac", label: "audio/aac", hasValue: false },
|
||||
{ value: "audio/ac3", label: "audio/ac3", hasValue: false },
|
||||
{ value: "audio/basic", label: "audio/basic", hasValue: false },
|
||||
{ value: "audio/mp4", label: "audio/mp4", hasValue: false },
|
||||
{ value: "audio/ogg", label: "audio/ogg", hasValue: false },
|
||||
{ value: "image/bmp", label: "image/bmp", hasValue: false },
|
||||
{ value: "image/gif", label: "image/gif", hasValue: false },
|
||||
{ value: "image/jpeg", label: "image/jpeg", hasValue: false },
|
||||
{ value: "image/png", label: "image/png", hasValue: false },
|
||||
{ value: "image/tiff", label: "image/tiff", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
headerOptions["cache-control"] = [
|
||||
{ value: "max-age=0", label: "max-age=0", hasValue: false },
|
||||
{ value: "max-age=86400", label: "max-age=86400", hasValue: false },
|
||||
{ value: "no-cache", label: "no-cache", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
|
||||
headerOptions["user-agent"] = [
|
||||
{ value: "Mozilla/5.0", label: "Mozilla/5.0", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
|
||||
function getHeaderOptions(headerName) {
|
||||
const lc = (headerName || "").toLowerCase();
|
||||
let opts = headerOptions[lc];
|
||||
return opts || defaultOptions;
|
||||
}
|
||||
|
||||
RED.nodes.registerType('http request',{
|
||||
category: 'network',
|
||||
color:"rgb(231, 231, 174)",
|
||||
@@ -133,7 +226,8 @@
|
||||
proxy: {type:"http proxy",required: false,
|
||||
label:RED._("node-red:httpin.proxy-config") },
|
||||
authType: {value: ""},
|
||||
senderr: {value: false}
|
||||
senderr: {value: false},
|
||||
headers: { value: [] }
|
||||
},
|
||||
credentials: {
|
||||
user: {type:"text"},
|
||||
@@ -156,6 +250,7 @@
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
const node = this;
|
||||
$("#node-input-useAuth").on("change", function() {
|
||||
if ($(this).is(":checked")) {
|
||||
$(".node-input-useAuth-row").show();
|
||||
@@ -169,9 +264,10 @@
|
||||
$('#node-input-user').val('');
|
||||
$('#node-input-password').val('');
|
||||
}
|
||||
RED.tray.resize();
|
||||
});
|
||||
$("#node-input-authType-select").on("change", function() {
|
||||
var val = $(this).val();
|
||||
const val = $(this).val();
|
||||
$("#node-input-authType").val(val);
|
||||
if (val === "basic" || val === "digest") {
|
||||
$(".node-input-basic-row").show();
|
||||
@@ -183,6 +279,7 @@
|
||||
$('#node-span-token').show();
|
||||
$('#node-input-user').val('');
|
||||
}
|
||||
RED.tray.resize();
|
||||
});
|
||||
$("#node-input-method").on("change", function() {
|
||||
if ($(this).val() == "GET") {
|
||||
@@ -190,17 +287,18 @@
|
||||
} else {
|
||||
$(".node-input-paytoqs-row").hide();
|
||||
}
|
||||
RED.tray.resize();
|
||||
});
|
||||
if (this.paytoqs === true || this.paytoqs == "query") {
|
||||
if (node.paytoqs === true || node.paytoqs == "query") {
|
||||
$("#node-input-paytoqs").val("query");
|
||||
} else if (this.paytoqs === "body") {
|
||||
} else if (node.paytoqs === "body") {
|
||||
$("#node-input-paytoqs").val("body");
|
||||
} else {
|
||||
$("#node-input-paytoqs").val("ignore");
|
||||
}
|
||||
if (this.authType) {
|
||||
if (node.authType) {
|
||||
$('#node-input-useAuth').prop('checked', true);
|
||||
$("#node-input-authType-select").val(this.authType);
|
||||
$("#node-input-authType-select").val(node.authType);
|
||||
$("#node-input-authType-select").change();
|
||||
} else {
|
||||
$('#node-input-useAuth').prop('checked', false);
|
||||
@@ -213,8 +311,9 @@
|
||||
} else {
|
||||
$("#node-row-tls").hide();
|
||||
}
|
||||
RED.tray.resize();
|
||||
}
|
||||
if (this.tls) {
|
||||
if (node.tls) {
|
||||
$('#node-input-usetls').prop('checked', true);
|
||||
} else {
|
||||
$('#node-input-usetls').prop('checked', false);
|
||||
@@ -230,8 +329,9 @@
|
||||
} else {
|
||||
$("#node-input-useProxy-row").hide();
|
||||
}
|
||||
RED.tray.resize();
|
||||
}
|
||||
if (this.proxy) {
|
||||
if (node.proxy) {
|
||||
$("#node-input-useProxy").prop("checked", true);
|
||||
} else {
|
||||
$("#node-input-useProxy").prop("checked", false);
|
||||
@@ -247,7 +347,70 @@
|
||||
} else {
|
||||
$("#tip-json").hide();
|
||||
}
|
||||
RED.tray.resize();
|
||||
});
|
||||
const hasMatch = function (arr, value) {
|
||||
return arr.some(function (ht) {
|
||||
return ht.value === value
|
||||
});
|
||||
}
|
||||
const headerList = $("#node-input-headers-container").css('min-height', '150px').css('min-width', '450px').editableList({
|
||||
addItem: function (container, i, header) {
|
||||
const row = $('<div/>').css({
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
display: 'flex'
|
||||
}).appendTo(container);
|
||||
const propertNameCell = $('<div/>').css({ 'flex-grow': 1 }).appendTo(row);
|
||||
const propertyName = $('<input/>', { class: "node-input-header-name", type: "text", style: "width: 100%" })
|
||||
.appendTo(propertNameCell)
|
||||
.typedInput({ types: headerTypes });
|
||||
|
||||
const propertyValueCell = $('<div/>').css({ 'flex-grow': 1, 'margin-left': '10px' }).appendTo(row);
|
||||
const propertyValue = $('<input/>', { class: "node-input-header-value", type: "text", style: "width: 100%" })
|
||||
.appendTo(propertyValueCell)
|
||||
.typedInput({
|
||||
types: getHeaderOptions(header.keyType)
|
||||
});
|
||||
|
||||
const setup = function(_header) {
|
||||
const headerTypeIsAPreset = function(h) {return hasMatch(headerTypes, h) };
|
||||
const headerValueIsAPreset = function(h, v) {return hasMatch(getHeaderOptions(h), v) };
|
||||
const {keyType, keyValue, valueType, valueValue} = header;
|
||||
if(keyType == "msg" || keyType == "other") {
|
||||
propertyName.typedInput('type', keyType);
|
||||
propertyName.typedInput('value', keyValue);
|
||||
} else if (headerTypeIsAPreset(keyType)) {
|
||||
propertyName.typedInput('type', keyType);
|
||||
} else {
|
||||
propertyName.typedInput('type', "other");
|
||||
propertyName.typedInput('value', keyValue);
|
||||
}
|
||||
if(valueType == "msg" || valueType == "other") {
|
||||
propertyValue.typedInput('type', valueType);
|
||||
propertyValue.typedInput('value', valueValue);
|
||||
} else if (headerValueIsAPreset(propertyName.typedInput('type'), valueType)) {
|
||||
propertyValue.typedInput('type', valueType);
|
||||
} else {
|
||||
propertyValue.typedInput('type', "other");
|
||||
propertyValue.typedInput('value', valueValue);
|
||||
}
|
||||
}
|
||||
setup(header);
|
||||
|
||||
propertyName.on('change', function (event) {
|
||||
propertyValue.typedInput('types', getHeaderOptions(propertyName.typedInput('type')));
|
||||
});
|
||||
|
||||
},
|
||||
removable: true
|
||||
});
|
||||
if (node.headers) {
|
||||
for (let index = 0; index < node.headers.length; index++) {
|
||||
const element = node.headers[index];
|
||||
headerList.editableList('addItem', node.headers[index]);
|
||||
}
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
if (!$("#node-input-usetls").is(':checked')) {
|
||||
@@ -256,6 +419,36 @@
|
||||
if (!$("#node-input-useProxy").is(":checked")) {
|
||||
$("#node-input-proxy").val("_ADD_");
|
||||
}
|
||||
const headers = $("#node-input-headers-container").editableList('items');
|
||||
const node = this;
|
||||
node.headers = [];
|
||||
headers.each(function(i) {
|
||||
const header = $(this);
|
||||
const keyType = header.find(".node-input-header-name").typedInput('type');
|
||||
const keyValue = header.find(".node-input-header-name").typedInput('value');
|
||||
const valueType = header.find(".node-input-header-value").typedInput('type');
|
||||
const valueValue = header.find(".node-input-header-value").typedInput('value');
|
||||
if (keyType !== '' || keyType === 'other' || keyType === 'msg') {
|
||||
node.headers.push({
|
||||
keyType, keyValue, valueType, valueValue
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
const dlg = $("#dialog-form");
|
||||
const expandRow = dlg.find('.node-input-headers-container-row');
|
||||
let height = dlg.height() - 5;
|
||||
if(expandRow && expandRow.length){
|
||||
const siblingRows = dlg.find('> .form-row:not(.node-input-headers-container-row)');
|
||||
for (let i = 0; i < siblingRows.size(); i++) {
|
||||
const cr = $(siblingRows[i]);
|
||||
if(cr.is(":visible"))
|
||||
height -= cr.outerHeight(true);
|
||||
}
|
||||
$("#node-input-headers-container").editableList('height',height);
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
@@ -73,7 +73,7 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
var paytobody = false;
|
||||
var redirectList = [];
|
||||
var sendErrorsToCatch = n.senderr;
|
||||
|
||||
node.headers = n.headers || [];
|
||||
var nodeHTTPPersistent = n["persist"];
|
||||
if (n.tls) {
|
||||
var tlsNode = RED.nodes.getNode(n.tls);
|
||||
@@ -105,6 +105,37 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
timingLog = RED.settings.httpRequestTimingLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Case insensitive header value update util function
|
||||
* @param {object} headersObject The opt.headers object to update
|
||||
* @param {string} name The header name
|
||||
* @param {string} value The header value to set (if blank, header is removed)
|
||||
*/
|
||||
const updateHeader = function(headersObject, name, value ) {
|
||||
const hn = name.toLowerCase();
|
||||
const keys = Object.keys(headersObject);
|
||||
const matchingKeys = keys.filter(e => e.toLowerCase() == hn)
|
||||
const updateKey = (k,v) => {
|
||||
delete headersObject[k]; //delete incase of case change
|
||||
if(v) { headersObject[name] = v } //re-add with requested name & value
|
||||
}
|
||||
if(matchingKeys.length == 0) {
|
||||
updateKey(name, value)
|
||||
} else {
|
||||
matchingKeys.forEach(k => {
|
||||
updateKey(k, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {Object} headersObject
|
||||
* @param {string} name
|
||||
* @return {any} value
|
||||
*/
|
||||
const getHeaderValue = (headersObject, name) => {
|
||||
const asLowercase = name.toLowercase();
|
||||
return headersObject[Object.keys(headersObject).find(k => k.toLowerCase() === asLowercase)];
|
||||
}
|
||||
this.on("input",function(msg,nodeSend,nodeDone) {
|
||||
checkNodeAgentPatch();
|
||||
//reset redirectList on each request
|
||||
@@ -183,7 +214,6 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
// TODO: add UI option to auto decompress. Setting to false for 1.x compatibility
|
||||
opts.decompress = false;
|
||||
opts.method = method;
|
||||
opts.headers = {};
|
||||
opts.retry = 0;
|
||||
opts.responseType = 'buffer';
|
||||
opts.maxRedirects = 21;
|
||||
@@ -229,34 +259,85 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
]
|
||||
}
|
||||
|
||||
var ctSet = "Content-Type"; // set default camel case
|
||||
var clSet = "Content-Length";
|
||||
let ctSet = "Content-Type"; // set default camel case
|
||||
let clSet = "Content-Length";
|
||||
const normaliseKnownHeader = function (name) {
|
||||
const _name = name.toLowerCase();
|
||||
// only normalise the known headers used later in this
|
||||
// function. Otherwise leave them alone.
|
||||
switch (_name) {
|
||||
case "content-type":
|
||||
ctSet = name;
|
||||
name = _name;
|
||||
break;
|
||||
case "content-length":
|
||||
clSet = name;
|
||||
name = _name;
|
||||
break;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
opts.headers = {};
|
||||
//add msg.headers
|
||||
//NOTE: ui headers will take precidence over msg.headers
|
||||
if (msg.headers) {
|
||||
if (msg.headers.hasOwnProperty('x-node-red-request-node')) {
|
||||
var headerHash = msg.headers['x-node-red-request-node'];
|
||||
const headerHash = msg.headers['x-node-red-request-node'];
|
||||
delete msg.headers['x-node-red-request-node'];
|
||||
var hash = hashSum(msg.headers);
|
||||
const hash = hashSum(msg.headers);
|
||||
if (hash === headerHash) {
|
||||
delete msg.headers;
|
||||
}
|
||||
}
|
||||
if (msg.headers) {
|
||||
for (var v in msg.headers) {
|
||||
if (msg.headers.hasOwnProperty(v)) {
|
||||
var name = v.toLowerCase();
|
||||
if (name !== "content-type" && name !== "content-length") {
|
||||
// only normalise the known headers used later in this
|
||||
// function. Otherwise leave them alone.
|
||||
name = v;
|
||||
}
|
||||
else if (name === 'content-type') { ctSet = v; }
|
||||
else { clSet = v; }
|
||||
opts.headers[name] = msg.headers[v];
|
||||
}
|
||||
for (let hn in msg.headers) {
|
||||
const name = normaliseKnownHeader(hn);
|
||||
updateHeader(opts.headers, name, msg.headers[hn]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//add/remove/update headers from UI.
|
||||
if (node.headers.length) {
|
||||
for (let index = 0; index < node.headers.length; index++) {
|
||||
const header = node.headers[index];
|
||||
let headerName, headerValue;
|
||||
if (header.keyType === "other") {
|
||||
headerName = header.keyValue
|
||||
} else if (header.keyType === "msg") {
|
||||
RED.util.evaluateNodeProperty(header.keyValue, header.keyType, node, msg, (err, value) => {
|
||||
if (err) {
|
||||
//ignore header
|
||||
} else {
|
||||
headerName = value;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
headerName = header.keyType
|
||||
}
|
||||
if (!headerName) {
|
||||
continue; //skip if header name is empyy
|
||||
}
|
||||
if (header.valueType === "other") {
|
||||
headerValue = header.valueValue
|
||||
} else if (header.valueType === "msg") {
|
||||
RED.util.evaluateNodeProperty(header.valueValue, header.valueType, node, msg, (err, value) => {
|
||||
if (err) {
|
||||
//ignore header
|
||||
} else {
|
||||
headerValue = value;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
headerValue = header.valueType
|
||||
}
|
||||
const hn = normaliseKnownHeader(headerName);
|
||||
updateHeader(opts.headers, hn, headerValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (msg.hasOwnProperty('followRedirects')) {
|
||||
opts.followRedirect = !!msg.followRedirects;
|
||||
}
|
||||
|
@@ -35,8 +35,6 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
var listenerNodes = {};
|
||||
var activeListenerNodes = 0;
|
||||
|
||||
|
||||
// A node red node that sets up a local websocket server
|
||||
function WebSocketListenerNode(n) {
|
||||
@@ -166,7 +164,6 @@ module.exports = function(RED) {
|
||||
}
|
||||
|
||||
if (node.isServer) {
|
||||
activeListenerNodes++;
|
||||
if (!serverUpgradeAdded) {
|
||||
RED.server.on('upgrade', handleServerUpgrade);
|
||||
serverUpgradeAdded = true
|
||||
@@ -210,7 +207,7 @@ module.exports = function(RED) {
|
||||
startconn(); // start outbound connection
|
||||
}
|
||||
|
||||
node.on("close", function() {
|
||||
node.on("close", function(done) {
|
||||
if (node.heartbeatInterval) {
|
||||
clearInterval(node.heartbeatInterval);
|
||||
}
|
||||
@@ -218,19 +215,25 @@ module.exports = function(RED) {
|
||||
delete listenerNodes[node.fullPath];
|
||||
node.server.close();
|
||||
node._inputNodes = [];
|
||||
activeListenerNodes--;
|
||||
// if (activeListenerNodes === 0 && serverUpgradeAdded) {
|
||||
// RED.server.removeListener('upgrade', handleServerUpgrade);
|
||||
// serverUpgradeAdded = false;
|
||||
// }
|
||||
}
|
||||
else {
|
||||
node.closing = true;
|
||||
node.server.close();
|
||||
if (node.tout) {
|
||||
clearTimeout(node.tout);
|
||||
node.tout = null;
|
||||
}
|
||||
//wait 20*50 (1000ms max) for ws to close.
|
||||
//call done when readyState === ws.CLOSED (or 1000ms, whichever comes fist)
|
||||
const closeMonitorInterval = 20;
|
||||
let closeMonitorCount = 50;
|
||||
let si = setInterval(() => {
|
||||
if(node.server.readyState === ws.CLOSED || closeMonitorCount <= 0) {
|
||||
if (node.tout) {
|
||||
clearTimeout(node.tout);
|
||||
node.tout = null;
|
||||
}
|
||||
clearInterval(si);
|
||||
return done();
|
||||
}
|
||||
closeMonitorCount--;
|
||||
}, closeMonitorInterval);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -50,7 +50,8 @@
|
||||
</div>
|
||||
|
||||
<div id="node-row-newline" class="form-row hidden" style="padding-left:110px;">
|
||||
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional">
|
||||
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional"><br/>
|
||||
<input type="checkbox" id="node-input-trim" style="display:inline-block; width:auto; vertical-align:top;"> <span data-i18n="tcpin.label.reattach"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
@@ -87,6 +88,7 @@
|
||||
datatype:{value:"buffer"},
|
||||
newline:{value:""},
|
||||
topic: {value:""},
|
||||
trim: {value:false},
|
||||
base64: {/*deprecated*/ value:false, required:true},
|
||||
tls: {type:"tls-config", value:'', required:false,
|
||||
label:RED._("node-red:httpin.tls-config") }
|
||||
@@ -315,7 +317,8 @@
|
||||
<span id="node-units"></span>
|
||||
</div>
|
||||
<div id="node-row-newline" class="form-row hidden" style="padding-left:162px;">
|
||||
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional">
|
||||
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional"><br/>
|
||||
<input type="checkbox" id="node-input-trim" style="display:inline-block; width:auto; vertical-align:top;"> <span data-i18n="tcpin.label.reattach"></span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
@@ -339,8 +342,8 @@
|
||||
ret: {value:"buffer"},
|
||||
splitc: {value:"0", required:true},
|
||||
newline: {value:""},
|
||||
tls: {type:"tls-config", value:'', required:false,
|
||||
label:RED._("node-red:httpin.tls-config") }
|
||||
trim: {value:false},
|
||||
tls: {type:"tls-config", value:'', required:false, label:RED._("node-red:httpin.tls-config")}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
|
@@ -88,6 +88,7 @@ module.exports = function(RED) {
|
||||
this.datatype = n.datatype||'buffer'; /* buffer,utf8,base64 */
|
||||
this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r").replace("\\t","\t");
|
||||
this.base64 = n.base64;
|
||||
this.trim = n.trim || false;
|
||||
this.server = (typeof n.server == 'boolean')?n.server:(n.server == "server");
|
||||
this.closing = false;
|
||||
this.connected = false;
|
||||
@@ -135,7 +136,8 @@ module.exports = function(RED) {
|
||||
buffer = buffer+data;
|
||||
var parts = buffer.split(node.newline);
|
||||
for (var i = 0; i<parts.length-1; i+=1) {
|
||||
msg = {topic:node.topic, payload:parts[i] + node.newline.trimEnd()};
|
||||
msg = {topic:node.topic, payload:parts[i]};
|
||||
if (node.trim == true) { msg.payload += node.newline; }
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
@@ -229,7 +231,8 @@ module.exports = function(RED) {
|
||||
buffer = buffer+data;
|
||||
var parts = buffer.split(node.newline);
|
||||
for (var i = 0; i<parts.length-1; i+=1) {
|
||||
msg = {topic:node.topic, payload:parts[i] + node.newline.trimEnd(), ip:socket.remoteAddress, port:socket.remotePort};
|
||||
msg = {topic:node.topic, payload:parts[i], ip:socket.remoteAddress, port:socket.remotePort};
|
||||
if (node.trim == true) { msg.payload += node.newline; }
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
@@ -518,6 +521,7 @@ module.exports = function(RED) {
|
||||
this.out = n.out;
|
||||
this.ret = n.ret || "buffer";
|
||||
this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r").replace("\\t","\t");
|
||||
this.trim = n.trim || false;
|
||||
this.splitc = n.splitc;
|
||||
if (n.tls) {
|
||||
var tlsNode = RED.nodes.getNode(n.tls);
|
||||
@@ -653,7 +657,8 @@ module.exports = function(RED) {
|
||||
let parts = chunk.split(node.newline);
|
||||
for (var p=0; p<parts.length-1; p+=1) {
|
||||
let m = RED.util.cloneMessage(msg);
|
||||
m.payload = parts[p] + node.newline.trimEnd();
|
||||
m.payload = parts[p];
|
||||
if (node.trim == true) { m.payload += node.newline; }
|
||||
nodeSend(m);
|
||||
}
|
||||
chunk = parts[parts.length-1];
|
||||
|
@@ -21,7 +21,7 @@
|
||||
<label style="width:100%;"><span data-i18n="json.label.o2j"></span></label>
|
||||
</div>
|
||||
<div class="form-row node-json-to-json-options" style="padding-left: 20px;">
|
||||
<input style="width:20px; vertical-align:top; margin-right: 5px;" type="checkbox" id="node-input-pretty"><label style="width: auto;" for="node-input-pretty" data-i18n="json.label.pretty"></span>
|
||||
<input style="width:20px; vertical-align:top; margin-right: 5px;" type="checkbox" id="node-input-pretty"><label style="width: auto;" for="node-input-pretty" data-i18n="json.label.pretty"></label>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
|
@@ -47,7 +47,7 @@
|
||||
<div class="form-row">
|
||||
<input type="checkbox" id="node-input-allowEmptySequence" style="margin-left:20px; margin-right: 10px; vertical-align:top; width:auto;">
|
||||
<label for="node-input-allowEmptySequence" style="width:auto;" data-i18n="batch.interval.empty"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="node-row-msg-concat">
|
||||
|
Reference in New Issue
Block a user