2016-06-04 01:40:40 +02:00
|
|
|
/**
|
2017-01-11 16:24:33 +01:00
|
|
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
2016-06-04 01:40:40 +02:00
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
**/
|
|
|
|
|
|
|
|
module.exports = function(RED) {
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
function SplitNode(n) {
|
|
|
|
RED.nodes.createNode(this,n);
|
2016-06-09 12:33:40 +02:00
|
|
|
this.splt = (n.splt || "\\n").replace(/\\n/,"\n").replace(/\\r/,"\r").replace(/\\t/,"\t").replace(/\\e/,"\e").replace(/\\f/,"\f").replace(/\\0/,"\0");
|
2017-06-05 18:04:17 +02:00
|
|
|
try {
|
|
|
|
var s = JSON.parse(n.splt.trim());
|
|
|
|
if (Array.isArray(s)) { this.splt = new Buffer.from(s); }
|
|
|
|
}
|
|
|
|
catch (e) {}
|
2016-06-04 01:40:40 +02:00
|
|
|
var node = this;
|
2017-06-05 18:04:17 +02:00
|
|
|
node.c = 0;
|
|
|
|
node.buffer = new Buffer([]);
|
2016-06-04 01:40:40 +02:00
|
|
|
this.on("input", function(msg) {
|
|
|
|
if (msg.hasOwnProperty("payload")) {
|
|
|
|
var a = msg.payload;
|
|
|
|
if (msg.hasOwnProperty("parts")) { msg.parts = { parts:msg.parts }; } // push existing parts to a stack
|
|
|
|
else { msg.parts = {}; }
|
|
|
|
msg.parts.id = msg._msgid; // use the existing _msgid by default.
|
|
|
|
if (typeof msg.payload === "string") { // Split String into array
|
2016-06-09 12:33:40 +02:00
|
|
|
a = msg.payload.split(node.splt);
|
2016-06-04 01:40:40 +02:00
|
|
|
msg.parts.ch = node.splt; // pass the split char to other end for rejoin
|
|
|
|
msg.parts.type = "string";
|
|
|
|
}
|
|
|
|
if (Array.isArray(a)) { // then split array into messages
|
|
|
|
msg.parts.type = msg.parts.type || "array"; // if it wasn't a string in the first place
|
|
|
|
for (var i = 0; i < a.length; i++) {
|
|
|
|
msg.payload = a[i];
|
|
|
|
msg.parts.index = i;
|
|
|
|
msg.parts.count = a.length;
|
2017-06-05 18:04:17 +02:00
|
|
|
//if (i === a.length-1) { msg.complete = true; }
|
2016-06-10 23:51:57 +02:00
|
|
|
node.send(RED.util.cloneMessage(msg));
|
2016-06-04 01:40:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ((typeof msg.payload === "object") && !Buffer.isBuffer(msg.payload)) {
|
|
|
|
var j = 0;
|
|
|
|
var l = Object.keys(msg.payload).length;
|
|
|
|
var pay = msg.payload;
|
|
|
|
msg.parts.type = "object";
|
|
|
|
for (var p in pay) {
|
|
|
|
if (pay.hasOwnProperty(p)) {
|
|
|
|
msg.payload = pay[p];
|
|
|
|
msg.parts.key = p;
|
|
|
|
msg.parts.index = j;
|
|
|
|
msg.parts.count = l;
|
2016-06-10 23:51:57 +02:00
|
|
|
node.send(RED.util.cloneMessage(msg));
|
2016-06-04 01:40:40 +02:00
|
|
|
j += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-06-05 18:04:17 +02:00
|
|
|
// Handle Buffer objects.... with overlaps to handle partial stream like
|
|
|
|
else if (Buffer.isBuffer(msg.payload)) {
|
|
|
|
var len = node.buffer.length + msg.payload.length;
|
|
|
|
var buff = Buffer.concat([node.buffer, msg.payload], len);
|
|
|
|
var pos = buff.indexOf(node.splt);
|
|
|
|
msg.parts.type = "buffer";
|
|
|
|
msg.parts.ch = node.splt; // pass the split to other end for rejoin (maybe)
|
|
|
|
while (pos !== -1) {
|
|
|
|
msg.payload = buff.slice(0,pos);
|
|
|
|
msg.parts.index = node.c;
|
|
|
|
node.c += 1;
|
|
|
|
buff = buff.slice(pos + node.splt.length);
|
|
|
|
if (buff.length === 0) {
|
|
|
|
msg.parts.count = node.c;
|
|
|
|
node.c = 0; //reset the count if no remainder.
|
|
|
|
}
|
|
|
|
node.send(RED.util.cloneMessage(msg));
|
|
|
|
pos = buff.indexOf(node.splt);
|
|
|
|
}
|
|
|
|
// save the remainder to use as start of next time round
|
|
|
|
node.buffer = buff;
|
|
|
|
}
|
2016-06-04 01:40:40 +02:00
|
|
|
//else { } // otherwise drop the message.
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
RED.nodes.registerType("split",SplitNode);
|
|
|
|
|
|
|
|
|
|
|
|
function JoinNode(n) {
|
|
|
|
RED.nodes.createNode(this,n);
|
2016-06-09 12:33:40 +02:00
|
|
|
this.mode = n.mode||"auto";
|
2016-06-06 00:32:03 +02:00
|
|
|
this.property = n.property||"payload";
|
|
|
|
this.propertyType = n.propertyType||"msg";
|
2016-06-10 23:51:57 +02:00
|
|
|
if (this.propertyType === 'full') {
|
|
|
|
this.property = "payload";
|
|
|
|
}
|
2016-06-06 00:32:03 +02:00
|
|
|
this.key = n.key||"topic";
|
2016-06-10 23:51:57 +02:00
|
|
|
this.timer = (this.mode === "auto") ? 0 : Number(n.timeout || 0)*1000;
|
2016-06-04 01:40:40 +02:00
|
|
|
this.count = Number(n.count || 0);
|
2016-06-09 12:33:40 +02:00
|
|
|
this.joiner = (n.joiner||"").replace(/\\n/g,"\n").replace(/\\r/g,"\r").replace(/\\t/g,"\t").replace(/\\e/g,"\e").replace(/\\f/g,"\f").replace(/\\0/g,"\0");
|
2016-06-04 01:40:40 +02:00
|
|
|
this.build = n.build || "array";
|
2017-06-05 18:04:17 +02:00
|
|
|
this.accumulate = n.accumulate || "false";
|
|
|
|
//this.topic = n.topic;
|
2016-06-04 01:40:40 +02:00
|
|
|
var node = this;
|
|
|
|
var inflight = {};
|
|
|
|
|
2016-06-09 12:33:40 +02:00
|
|
|
var completeSend = function(partId) {
|
|
|
|
var group = inflight[partId];
|
|
|
|
clearTimeout(group.timeout);
|
2017-06-05 18:04:17 +02:00
|
|
|
if ((node.accumulate !== true) || group.msg.hasOwnProperty("complete")) { delete inflight[partId]; }
|
2016-06-09 12:33:40 +02:00
|
|
|
if (group.type === 'string') {
|
2016-06-10 23:51:57 +02:00
|
|
|
RED.util.setMessageProperty(group.msg,node.property,group.payload.join(group.joinChar));
|
2016-06-09 12:33:40 +02:00
|
|
|
} else {
|
2016-06-10 23:51:57 +02:00
|
|
|
RED.util.setMessageProperty(group.msg,node.property,group.payload);
|
2016-06-04 01:40:40 +02:00
|
|
|
}
|
2016-06-09 12:33:40 +02:00
|
|
|
if (group.msg.hasOwnProperty('parts') && group.msg.parts.hasOwnProperty('parts')) {
|
2016-06-13 18:43:22 +02:00
|
|
|
group.msg.parts = group.msg.parts.parts;
|
2016-06-09 12:33:40 +02:00
|
|
|
} else {
|
|
|
|
delete group.msg.parts;
|
2016-06-04 01:40:40 +02:00
|
|
|
}
|
2017-06-05 18:04:17 +02:00
|
|
|
delete group.msg.complete;
|
2016-06-09 12:33:40 +02:00
|
|
|
node.send(group.msg);
|
2016-06-04 01:40:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
this.on("input", function(msg) {
|
2017-06-05 18:04:17 +02:00
|
|
|
try {
|
|
|
|
var property;
|
|
|
|
if (node.mode === 'auto' && (!msg.hasOwnProperty("parts")||!msg.parts.hasOwnProperty("id"))) {
|
|
|
|
node.warn("Message missing msg.parts property - cannot join in 'auto' mode")
|
2016-06-06 00:32:03 +02:00
|
|
|
return;
|
|
|
|
}
|
2017-06-05 18:04:17 +02:00
|
|
|
if (node.propertyType == "full") {
|
|
|
|
property = msg;
|
2016-06-09 12:33:40 +02:00
|
|
|
}
|
2017-06-05 18:04:17 +02:00
|
|
|
else {
|
|
|
|
try {
|
|
|
|
property = RED.util.getMessageProperty(msg,node.property);
|
|
|
|
} catch(err) {
|
|
|
|
node.warn("Message property "+node.property+" not found");
|
|
|
|
return;
|
|
|
|
}
|
2016-06-09 12:33:40 +02:00
|
|
|
}
|
2017-06-05 18:04:17 +02:00
|
|
|
|
|
|
|
var partId;
|
|
|
|
var payloadType;
|
|
|
|
var propertyKey;
|
|
|
|
var targetCount;
|
|
|
|
var joinChar;
|
|
|
|
var propertyIndex;
|
2016-06-10 23:51:57 +02:00
|
|
|
if (node.mode === "auto") {
|
2017-06-05 18:04:17 +02:00
|
|
|
// Use msg.parts to identify all of the group information
|
|
|
|
partId = msg.parts.id;
|
|
|
|
payloadType = msg.parts.type;
|
|
|
|
targetCount = msg.parts.count;
|
|
|
|
joinChar = msg.parts.ch;
|
|
|
|
propertyKey = msg.parts.key;
|
|
|
|
propertyIndex = msg.parts.index;
|
2016-06-10 23:51:57 +02:00
|
|
|
}
|
2017-06-05 18:04:17 +02:00
|
|
|
else {
|
|
|
|
// Use the node configuration to identify all of the group information
|
|
|
|
partId = "_";
|
|
|
|
payloadType = node.build;
|
|
|
|
targetCount = node.count;
|
|
|
|
joinChar = node.joiner;
|
|
|
|
if (targetCount === 0 && msg.hasOwnProperty('parts')) {
|
|
|
|
targetCount = msg.parts.count || 0;
|
|
|
|
}
|
|
|
|
if (node.build === 'object') {
|
|
|
|
propertyKey = RED.util.getMessageProperty(msg,node.key);
|
2016-06-04 01:40:40 +02:00
|
|
|
}
|
2016-06-06 00:32:03 +02:00
|
|
|
}
|
2017-06-05 18:04:17 +02:00
|
|
|
if ((payloadType === 'object') && (propertyKey === null || propertyKey === undefined || propertyKey === "")) {
|
|
|
|
if (node.mode === "auto") {
|
|
|
|
node.warn("Message missing 'msg.parts.key' property - cannot add to object");
|
|
|
|
} else {
|
|
|
|
node.warn("Message missing key property 'msg."+node.key+"' - cannot add to object")
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!inflight.hasOwnProperty(partId)) {
|
|
|
|
if (payloadType === 'object' || payloadType === 'merged') {
|
|
|
|
inflight[partId] = {
|
|
|
|
currentCount:0,
|
|
|
|
payload:{},
|
|
|
|
targetCount:targetCount,
|
|
|
|
type:"object",
|
|
|
|
msg:msg
|
|
|
|
};
|
|
|
|
}
|
|
|
|
else if (node.accumulate === true) {
|
|
|
|
if (msg.hasOwnProperty("reset")) { delete inflight[partId]; }
|
|
|
|
inflight[partId] = inflight[partId] || {
|
|
|
|
currentCount:0,
|
|
|
|
payload:{},
|
|
|
|
targetCount:targetCount,
|
|
|
|
type:"object",
|
|
|
|
msg:msg
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
inflight[partId] = {
|
|
|
|
currentCount:0,
|
|
|
|
payload:[],
|
|
|
|
targetCount:targetCount,
|
|
|
|
type:payloadType,
|
|
|
|
joinChar: joinChar,
|
|
|
|
msg:msg
|
|
|
|
};
|
|
|
|
if (payloadType === 'string') {
|
|
|
|
inflight[partId].joinChar = joinChar;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (node.timer > 0) {
|
|
|
|
inflight[partId].timeout = setTimeout(function() {
|
|
|
|
completeSend(partId)
|
|
|
|
}, node.timer)
|
|
|
|
}
|
2016-06-04 01:40:40 +02:00
|
|
|
}
|
2016-06-09 12:33:40 +02:00
|
|
|
|
2017-06-05 18:04:17 +02:00
|
|
|
var group = inflight[partId];
|
|
|
|
if (payloadType === 'object') {
|
|
|
|
group.payload[propertyKey] = property;
|
|
|
|
group.currentCount = Object.keys(group.payload).length;
|
|
|
|
//msg.topic = node.topic || msg.topic;
|
|
|
|
}
|
|
|
|
else if (payloadType === 'merged') {
|
|
|
|
if (Array.isArray(property) || typeof property !== 'object') {
|
|
|
|
if (!msg.hasOwnProperty("complete")) {
|
|
|
|
node.warn("Cannot merge non-object types");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
for (propertyKey in property) {
|
|
|
|
if (property.hasOwnProperty(propertyKey)) {
|
|
|
|
group.payload[propertyKey] = property[propertyKey];
|
|
|
|
}
|
2016-06-11 22:43:37 +02:00
|
|
|
}
|
2017-06-05 18:04:17 +02:00
|
|
|
group.currentCount = Object.keys(group.payload).length;
|
|
|
|
//group.currentCount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (!isNaN(propertyIndex)) {
|
|
|
|
group.payload[propertyIndex] = property;
|
|
|
|
} else {
|
|
|
|
group.payload.push(property);
|
2016-06-11 22:43:37 +02:00
|
|
|
}
|
|
|
|
group.currentCount++;
|
|
|
|
}
|
2017-06-05 18:04:17 +02:00
|
|
|
// TODO: currently reuse the last received - add option to pick first received
|
|
|
|
group.msg = msg;
|
|
|
|
if (group.currentCount >= group.targetCount || msg.hasOwnProperty('complete')) {
|
|
|
|
completeSend(partId);
|
2016-06-09 12:33:40 +02:00
|
|
|
}
|
2017-06-05 18:04:17 +02:00
|
|
|
} catch(err) {
|
|
|
|
console.log(err.stack);
|
2016-06-09 12:33:40 +02:00
|
|
|
}
|
2016-06-04 01:40:40 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
this.on("close", function() {
|
|
|
|
for (var i in inflight) {
|
2016-06-09 12:33:40 +02:00
|
|
|
if (inflight.hasOwnProperty(i)) {
|
|
|
|
clearTimeout(inflight[i].timeout);
|
|
|
|
}
|
2016-06-04 01:40:40 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
RED.nodes.registerType("join",JoinNode);
|
|
|
|
}
|