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";
|
|
|
|
|
2017-06-13 22:01:04 +02:00
|
|
|
function sendArray(node,msg,array) {
|
2017-06-16 23:26:14 +02:00
|
|
|
for (var i = 0; i < array.length-1; i++) {
|
2017-06-13 22:01:04 +02:00
|
|
|
msg.payload = array[i];
|
2017-06-16 23:26:14 +02:00
|
|
|
msg.parts.index = node.c++;
|
|
|
|
if (node.stream !== true) { msg.parts.count = array.length; }
|
|
|
|
node.send(RED.util.cloneMessage(msg));
|
|
|
|
}
|
|
|
|
if (node.stream !== true) {
|
|
|
|
msg.payload = array[i];
|
|
|
|
msg.parts.index = node.c++;
|
2017-06-13 22:01:04 +02:00
|
|
|
msg.parts.count = array.length;
|
|
|
|
node.send(RED.util.cloneMessage(msg));
|
2017-06-16 23:26:14 +02:00
|
|
|
node.c = 0;
|
2017-06-13 22:01:04 +02:00
|
|
|
}
|
2017-06-16 23:26:14 +02:00
|
|
|
else { node.remainder = array[i]; }
|
2017-06-13 22:01:04 +02:00
|
|
|
}
|
2017-06-16 23:26:14 +02:00
|
|
|
|
2016-06-04 01:40:40 +02:00
|
|
|
function SplitNode(n) {
|
|
|
|
RED.nodes.createNode(this,n);
|
2017-06-13 22:01:04 +02:00
|
|
|
var node = this;
|
|
|
|
node.stream = n.stream;
|
|
|
|
node.spltType = n.spltType || "str";
|
2017-06-26 17:51:27 +02:00
|
|
|
node.addname = n.addname || "";
|
2017-06-05 18:04:17 +02:00
|
|
|
try {
|
2017-06-13 22:01:04 +02:00
|
|
|
if (node.spltType === "str") {
|
|
|
|
this.splt = (n.splt || "\\n").replace(/\\n/,"\n").replace(/\\r/,"\r").replace(/\\t/,"\t").replace(/\\e/,"\e").replace(/\\f/,"\f").replace(/\\0/,"\0");
|
|
|
|
} else if (node.spltType === "bin") {
|
|
|
|
var spltArray = JSON.parse(n.splt);
|
|
|
|
if (Array.isArray(spltArray)) {
|
|
|
|
this.splt = Buffer.from(spltArray);
|
|
|
|
} else {
|
|
|
|
throw new Error("not an array");
|
|
|
|
}
|
|
|
|
this.spltBuffer = spltArray;
|
|
|
|
} else if (node.spltType === "len") {
|
|
|
|
this.splt = parseInt(n.splt);
|
|
|
|
if (isNaN(this.splt) || this.splt < 1) {
|
|
|
|
throw new Error("invalid split length: "+n.splt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.arraySplt = (n.arraySplt === undefined)?1:parseInt(n.arraySplt);
|
|
|
|
if (isNaN(this.arraySplt) || this.arraySplt < 1) {
|
|
|
|
throw new Error("invalid array split length: "+n.arraySplt);
|
|
|
|
}
|
|
|
|
} catch(err) {
|
|
|
|
this.error("Invalid split property: "+err.toString());
|
|
|
|
return;
|
2017-06-05 18:04:17 +02:00
|
|
|
}
|
|
|
|
node.c = 0;
|
2017-06-27 18:10:52 +02:00
|
|
|
node.buffer = Buffer.from([]);
|
2016-06-04 01:40:40 +02:00
|
|
|
this.on("input", function(msg) {
|
|
|
|
if (msg.hasOwnProperty("payload")) {
|
|
|
|
if (msg.hasOwnProperty("parts")) { msg.parts = { parts:msg.parts }; } // push existing parts to a stack
|
|
|
|
else { msg.parts = {}; }
|
2018-01-28 22:15:59 +01:00
|
|
|
msg.parts.id = RED.util.generateId(); // generate a random id
|
|
|
|
delete msg._msgid;
|
2016-06-04 01:40:40 +02:00
|
|
|
if (typeof msg.payload === "string") { // Split String into array
|
2017-06-16 23:26:14 +02:00
|
|
|
msg.payload = (node.remainder || "") + msg.payload;
|
2016-06-04 01:40:40 +02:00
|
|
|
msg.parts.type = "string";
|
2017-06-13 22:01:04 +02:00
|
|
|
if (node.spltType === "len") {
|
|
|
|
msg.parts.ch = "";
|
2017-06-26 17:51:27 +02:00
|
|
|
msg.parts.len = node.splt;
|
2017-06-13 22:01:04 +02:00
|
|
|
var count = msg.payload.length/node.splt;
|
|
|
|
if (Math.floor(count) !== count) {
|
|
|
|
count = Math.ceil(count);
|
|
|
|
}
|
2017-06-16 23:26:14 +02:00
|
|
|
if (node.stream !== true) {
|
|
|
|
msg.parts.count = count;
|
|
|
|
node.c = 0;
|
|
|
|
}
|
2017-06-13 22:01:04 +02:00
|
|
|
var pos = 0;
|
|
|
|
var data = msg.payload;
|
2017-06-16 23:26:14 +02:00
|
|
|
for (var i=0; i<count-1; i++) {
|
2017-06-13 22:01:04 +02:00
|
|
|
msg.payload = data.substring(pos,pos+node.splt);
|
2017-06-16 23:26:14 +02:00
|
|
|
msg.parts.index = node.c++;
|
2017-06-13 22:01:04 +02:00
|
|
|
pos += node.splt;
|
|
|
|
node.send(RED.util.cloneMessage(msg));
|
|
|
|
}
|
2017-06-16 23:26:14 +02:00
|
|
|
node.remainder = data.substring(pos);
|
|
|
|
if ((node.stream !== true) || (node.remainder.length === node.splt)) {
|
|
|
|
msg.payload = node.remainder;
|
|
|
|
msg.parts.index = node.c++;
|
|
|
|
node.send(RED.util.cloneMessage(msg));
|
|
|
|
node.remainder = "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2017-06-13 22:01:04 +02:00
|
|
|
var a = [];
|
|
|
|
if (node.spltType === "bin") {
|
|
|
|
if (!node.spltBufferString) {
|
|
|
|
node.spltBufferString = node.splt.toString();
|
|
|
|
}
|
|
|
|
a = msg.payload.split(node.spltBufferString);
|
|
|
|
msg.parts.ch = node.spltBuffer; // pass the split char to other end for rejoin
|
|
|
|
} else if (node.spltType === "str") {
|
|
|
|
a = msg.payload.split(node.splt);
|
|
|
|
msg.parts.ch = node.splt; // pass the split char to other end for rejoin
|
|
|
|
}
|
|
|
|
sendArray(node,msg,a);
|
|
|
|
}
|
2017-06-16 23:26:14 +02:00
|
|
|
}
|
|
|
|
else if (Array.isArray(msg.payload)) { // then split array into messages
|
2017-06-13 22:01:04 +02:00
|
|
|
msg.parts.type = "array";
|
|
|
|
var count = msg.payload.length/node.arraySplt;
|
|
|
|
if (Math.floor(count) !== count) {
|
|
|
|
count = Math.ceil(count);
|
|
|
|
}
|
|
|
|
msg.parts.count = count;
|
|
|
|
var pos = 0;
|
|
|
|
var data = msg.payload;
|
|
|
|
msg.parts.len = node.arraySplt;
|
2017-06-16 10:21:53 +02:00
|
|
|
for (var i=0; i<count; i++) {
|
2017-06-13 22:01:04 +02:00
|
|
|
msg.payload = data.slice(pos,pos+node.arraySplt);
|
|
|
|
if (node.arraySplt === 1) {
|
|
|
|
msg.payload = msg.payload[0];
|
|
|
|
}
|
2016-06-04 01:40:40 +02:00
|
|
|
msg.parts.index = i;
|
2017-06-13 22:01:04 +02:00
|
|
|
pos += node.arraySplt;
|
2016-06-10 23:51:57 +02:00
|
|
|
node.send(RED.util.cloneMessage(msg));
|
2016-06-04 01:40:40 +02:00
|
|
|
}
|
2017-06-16 23:26:14 +02:00
|
|
|
}
|
|
|
|
else if ((typeof msg.payload === "object") && !Buffer.isBuffer(msg.payload)) {
|
2016-06-04 01:40:40 +02:00
|
|
|
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];
|
2017-06-26 17:51:27 +02:00
|
|
|
if (node.addname !== "") {
|
|
|
|
msg[node.addname] = p;
|
|
|
|
}
|
2016-06-04 01:40:40 +02:00
|
|
|
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-16 23:26:14 +02:00
|
|
|
}
|
|
|
|
else if (Buffer.isBuffer(msg.payload)) {
|
|
|
|
var len = node.buffer.length + msg.payload.length;
|
|
|
|
var buff = Buffer.concat([node.buffer, msg.payload], len);
|
2017-06-05 18:04:17 +02:00
|
|
|
msg.parts.type = "buffer";
|
2017-06-13 22:01:04 +02:00
|
|
|
if (node.spltType === "len") {
|
2017-06-16 23:26:14 +02:00
|
|
|
var count = buff.length/node.splt;
|
2017-06-13 22:01:04 +02:00
|
|
|
if (Math.floor(count) !== count) {
|
|
|
|
count = Math.ceil(count);
|
2017-06-05 18:04:17 +02:00
|
|
|
}
|
2017-06-16 23:26:14 +02:00
|
|
|
if (node.stream !== true) {
|
|
|
|
msg.parts.count = count;
|
|
|
|
node.c = 0;
|
|
|
|
}
|
2017-06-13 22:01:04 +02:00
|
|
|
var pos = 0;
|
|
|
|
msg.parts.len = node.splt;
|
2017-06-16 23:26:14 +02:00
|
|
|
for (var i=0; i<count-1; i++) {
|
|
|
|
msg.payload = buff.slice(pos,pos+node.splt);
|
|
|
|
msg.parts.index = node.c++;
|
2017-06-13 22:01:04 +02:00
|
|
|
pos += node.splt;
|
|
|
|
node.send(RED.util.cloneMessage(msg));
|
|
|
|
}
|
2017-06-16 23:26:14 +02:00
|
|
|
node.buffer = buff.slice(pos);
|
|
|
|
if ((node.stream !== true) || (node.buffer.length === node.splt)) {
|
|
|
|
msg.payload = node.buffer;
|
|
|
|
msg.parts.index = node.c++;
|
|
|
|
node.send(RED.util.cloneMessage(msg));
|
2017-06-27 18:10:52 +02:00
|
|
|
node.buffer = Buffer.from([]);
|
2017-06-16 23:26:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2017-06-13 22:01:04 +02:00
|
|
|
var count = 0;
|
|
|
|
if (node.spltType === "bin") {
|
|
|
|
msg.parts.ch = node.spltBuffer;
|
|
|
|
} else if (node.spltType === "str") {
|
|
|
|
msg.parts.ch = node.splt;
|
|
|
|
}
|
2017-06-16 23:26:14 +02:00
|
|
|
var pos = buff.indexOf(node.splt);
|
2017-06-13 22:01:04 +02:00
|
|
|
var end;
|
2017-06-16 10:21:53 +02:00
|
|
|
while (pos > -1) {
|
2017-06-13 22:01:04 +02:00
|
|
|
count++;
|
|
|
|
end = pos+node.splt.length;
|
2017-06-16 23:26:14 +02:00
|
|
|
pos = buff.indexOf(node.splt,end);
|
2017-06-13 22:01:04 +02:00
|
|
|
}
|
2017-06-24 13:09:52 +02:00
|
|
|
count++;
|
2017-06-16 23:26:14 +02:00
|
|
|
if (node.stream !== true) {
|
|
|
|
msg.parts.count = count;
|
|
|
|
node.c = 0;
|
|
|
|
}
|
2017-06-13 22:01:04 +02:00
|
|
|
var i = 0, p = 0;
|
2017-06-16 23:26:14 +02:00
|
|
|
pos = buff.indexOf(node.splt);
|
2017-06-16 10:21:53 +02:00
|
|
|
while (pos > -1) {
|
2017-06-16 23:26:14 +02:00
|
|
|
msg.payload = buff.slice(p,pos);
|
|
|
|
msg.parts.index = node.c++;
|
2017-06-13 22:01:04 +02:00
|
|
|
node.send(RED.util.cloneMessage(msg));
|
|
|
|
i++;
|
|
|
|
p = pos+node.splt.length;
|
2017-06-16 23:26:14 +02:00
|
|
|
pos = buff.indexOf(node.splt,p);
|
2017-06-13 22:01:04 +02:00
|
|
|
}
|
2017-06-16 23:26:14 +02:00
|
|
|
if ((node.stream !== true) && (p < buff.length)) {
|
|
|
|
msg.payload = buff.slice(p,buff.length);
|
|
|
|
msg.parts.index = node.c++;
|
|
|
|
msg.parts.count = node.c++;
|
2017-06-13 22:01:04 +02:00
|
|
|
node.send(RED.util.cloneMessage(msg));
|
2017-06-16 23:26:14 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
node.buffer = buff.slice(p,buff.length);
|
|
|
|
}
|
2017-06-05 18:04:17 +02:00
|
|
|
}
|
|
|
|
}
|
2016-06-04 01:40:40 +02:00
|
|
|
//else { } // otherwise drop the message.
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
RED.nodes.registerType("split",SplitNode);
|
|
|
|
|
|
|
|
|
2018-07-10 12:24:57 +02:00
|
|
|
var _max_kept_msgs_count;
|
2018-01-24 23:01:07 +01:00
|
|
|
|
2018-01-17 11:08:23 +01:00
|
|
|
function max_kept_msgs_count(node) {
|
|
|
|
if (_max_kept_msgs_count === undefined) {
|
2018-01-24 23:01:07 +01:00
|
|
|
var name = "nodeMessageBufferMaxLength";
|
2018-01-17 11:08:23 +01:00
|
|
|
if (RED.settings.hasOwnProperty(name)) {
|
|
|
|
_max_kept_msgs_count = RED.settings[name];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
_max_kept_msgs_count = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return _max_kept_msgs_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
function apply_r(exp, accum, msg, index, count) {
|
|
|
|
exp.assign("I", index);
|
|
|
|
exp.assign("N", count);
|
|
|
|
exp.assign("A", accum);
|
2018-07-10 12:24:57 +02:00
|
|
|
return new Promise((resolve,reject) => {
|
|
|
|
RED.util.evaluateJSONataExpression(exp, msg, (err, result) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
} else {
|
|
|
|
resolve(result);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2018-01-17 11:08:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function apply_f(exp, accum, count) {
|
|
|
|
exp.assign("N", count);
|
|
|
|
exp.assign("A", accum);
|
|
|
|
return RED.util.evaluateJSONataExpression(exp, {});
|
|
|
|
}
|
|
|
|
|
|
|
|
function exp_or_undefined(exp) {
|
|
|
|
if((exp === "") ||
|
|
|
|
(exp === null)) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
return exp
|
|
|
|
}
|
2018-01-24 23:01:07 +01:00
|
|
|
|
2018-07-10 12:24:57 +02:00
|
|
|
function reduceAndSendGroup(node, group) {
|
2018-01-17 11:08:23 +01:00
|
|
|
var is_right = node.reduce_right;
|
|
|
|
var flag = is_right ? -1 : 1;
|
|
|
|
var msgs = group.msgs;
|
2018-07-10 12:24:57 +02:00
|
|
|
return getInitialReduceValue(node, node.exp_init, node.exp_init_type).then(accum => {
|
|
|
|
var reduce_exp = node.reduce_exp;
|
|
|
|
var reduce_fixup = node.reduce_fixup;
|
|
|
|
var count = group.count;
|
|
|
|
msgs.sort(function(x,y) {
|
|
|
|
var ix = x.parts.index;
|
|
|
|
var iy = y.parts.index;
|
|
|
|
if (ix < iy) {return -flag;}
|
|
|
|
if (ix > iy) {return flag;}
|
|
|
|
return 0;
|
|
|
|
});
|
|
|
|
|
|
|
|
return msgs.reduce((promise, msg) => promise.then(accum => apply_r(reduce_exp, accum, msg, msg.parts.index, count)), Promise.resolve(accum))
|
|
|
|
.then(accum => {
|
|
|
|
if(reduce_fixup !== undefined) {
|
|
|
|
accum = apply_f(reduce_fixup, accum, count);
|
|
|
|
}
|
|
|
|
node.send({payload: accum});
|
|
|
|
});
|
|
|
|
}).catch(err => {
|
|
|
|
throw new Error(RED._("join.errors.invalid-expr",{error:e.message}));
|
2018-01-17 11:08:23 +01:00
|
|
|
});
|
|
|
|
}
|
2018-01-24 23:01:07 +01:00
|
|
|
|
2018-01-17 11:08:23 +01:00
|
|
|
function reduce_msg(node, msg) {
|
2018-07-10 12:24:57 +02:00
|
|
|
var promise;
|
|
|
|
if (msg.hasOwnProperty('parts')) {
|
2018-01-17 11:08:23 +01:00
|
|
|
var parts = msg.parts;
|
|
|
|
var pending = node.pending;
|
|
|
|
var pending_count = node.pending_count;
|
|
|
|
var gid = msg.parts.id;
|
2018-01-24 23:01:07 +01:00
|
|
|
var count;
|
2018-01-17 11:08:23 +01:00
|
|
|
if(!pending.hasOwnProperty(gid)) {
|
|
|
|
if(parts.hasOwnProperty('count')) {
|
|
|
|
count = msg.parts.count;
|
|
|
|
}
|
|
|
|
pending[gid] = {
|
|
|
|
count: count,
|
|
|
|
msgs: []
|
|
|
|
};
|
|
|
|
}
|
|
|
|
var group = pending[gid];
|
|
|
|
var msgs = group.msgs;
|
|
|
|
if(parts.hasOwnProperty('count') &&
|
2018-07-10 12:24:57 +02:00
|
|
|
(group.count === undefined)) {
|
2018-01-17 11:08:23 +01:00
|
|
|
group.count = count;
|
|
|
|
}
|
|
|
|
msgs.push(msg);
|
|
|
|
pending_count++;
|
2018-07-10 12:24:57 +02:00
|
|
|
var completeProcess = function() {
|
|
|
|
node.pending_count = pending_count;
|
|
|
|
var max_msgs = max_kept_msgs_count(node);
|
|
|
|
if ((max_msgs > 0) && (pending_count > max_msgs)) {
|
|
|
|
node.pending = {};
|
|
|
|
node.pending_count = 0;
|
|
|
|
var promise = Promise.reject(RED._("join.too-many"));
|
|
|
|
promise.catch(()=>{});
|
|
|
|
return promise;
|
|
|
|
}
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
2018-01-17 11:08:23 +01:00
|
|
|
if(msgs.length === group.count) {
|
|
|
|
delete pending[gid];
|
2018-07-10 12:24:57 +02:00
|
|
|
pending_count -= msgs.length;
|
|
|
|
promise = reduceAndSendGroup(node, group).then(completeProcess);
|
|
|
|
} else {
|
|
|
|
promise = completeProcess();
|
2018-01-17 11:08:23 +01:00
|
|
|
}
|
2018-07-10 12:24:57 +02:00
|
|
|
} else {
|
2018-01-17 11:08:23 +01:00
|
|
|
node.send(msg);
|
|
|
|
}
|
2018-07-10 12:24:57 +02:00
|
|
|
if (!promise) {
|
|
|
|
promise = Promise.resolve();
|
|
|
|
}
|
|
|
|
return promise;
|
2018-01-17 11:08:23 +01:00
|
|
|
}
|
|
|
|
|
2018-07-10 12:24:57 +02:00
|
|
|
function getInitialReduceValue(node, exp, exp_type) {
|
|
|
|
return new Promise((resolve,reject) => {
|
|
|
|
if(exp_type === "flow" || exp_type === "global") {
|
|
|
|
node.context()[exp_type].get(exp,(err,value) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
} else {
|
|
|
|
resolve(value);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
} else if(exp_type === "jsonata") {
|
|
|
|
var jexp = RED.util.prepareJSONataExpression(exp, node);
|
|
|
|
RED.util.evaluateJSONataExpression(jexp, {},(err,value) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
} else {
|
|
|
|
resolve(value);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return;
|
2018-01-17 11:08:23 +01:00
|
|
|
}
|
2018-07-10 12:24:57 +02:00
|
|
|
var result;
|
|
|
|
if(exp_type === "str") {
|
|
|
|
result = exp;
|
|
|
|
} else if(exp_type === "num") {
|
|
|
|
result = Number(exp);
|
|
|
|
} else if(exp_type === "bool") {
|
|
|
|
if (exp === 'true') {
|
|
|
|
result = true;
|
|
|
|
} else if (exp === 'false') {
|
|
|
|
result = false;
|
|
|
|
}
|
|
|
|
} else if ((exp_type === "bin") || (exp_type === "json")) {
|
|
|
|
result = JSON.parse(exp);
|
|
|
|
} else if(exp_type === "date") {
|
|
|
|
result = Date.now();
|
|
|
|
} else {
|
|
|
|
reject(new Error("unexpected initial value type"));
|
|
|
|
return;
|
2018-01-17 11:08:23 +01:00
|
|
|
}
|
2018-07-10 12:24:57 +02:00
|
|
|
resolve(result);
|
|
|
|
});
|
2018-01-17 11:08:23 +01:00
|
|
|
}
|
|
|
|
|
2016-06-04 01:40:40 +02:00
|
|
|
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);
|
2017-06-27 18:10:52 +02:00
|
|
|
this.joiner = n.joiner||"";
|
|
|
|
this.joinerType = n.joinerType||"str";
|
|
|
|
|
2018-01-17 11:08:23 +01:00
|
|
|
this.reduce = (this.mode === "reduce");
|
|
|
|
if (this.reduce) {
|
2018-01-29 14:30:47 +01:00
|
|
|
this.exp_init = n.reduceInit;
|
|
|
|
this.exp_init_type = n.reduceInitType;
|
2018-01-17 11:08:23 +01:00
|
|
|
var exp_reduce = n.reduceExp;
|
|
|
|
var exp_fixup = exp_or_undefined(n.reduceFixup);
|
|
|
|
this.reduce_right = n.reduceRight;
|
|
|
|
try {
|
|
|
|
this.reduce_exp = RED.util.prepareJSONataExpression(exp_reduce, this);
|
|
|
|
this.reduce_fixup = (exp_fixup !== undefined) ? RED.util.prepareJSONataExpression(exp_fixup, this) : undefined;
|
|
|
|
} catch(e) {
|
|
|
|
this.error(RED._("join.errors.invalid-expr",{error:e.message}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-27 18:10:52 +02:00
|
|
|
if (this.joinerType === "str") {
|
|
|
|
this.joiner = this.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");
|
|
|
|
} else if (this.joinerType === "bin") {
|
|
|
|
var joinArray = JSON.parse(n.joiner)
|
|
|
|
if (Array.isArray(joinArray)) {
|
|
|
|
this.joiner = Buffer.from(joinArray);
|
|
|
|
} else {
|
|
|
|
throw new Error("not an array");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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";
|
2018-01-17 11:08:23 +01:00
|
|
|
|
|
|
|
this.topics = (n.topics || []).map(function(x) { return x.topic; });
|
|
|
|
this.merge_on_change = n.mergeOnChange || false;
|
|
|
|
this.topic_counts = undefined;
|
|
|
|
this.output = n.output || "stream";
|
|
|
|
this.pending = {};
|
|
|
|
this.pending_count = 0;
|
|
|
|
|
2017-06-05 18:04:17 +02:00
|
|
|
//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]; }
|
2017-06-13 22:01:04 +02:00
|
|
|
if (group.type === 'array' && group.arrayLen > 1) {
|
|
|
|
var newArray = [];
|
|
|
|
group.payload.forEach(function(n) {
|
|
|
|
newArray = newArray.concat(n);
|
|
|
|
})
|
|
|
|
group.payload = newArray;
|
2018-07-08 17:52:30 +02:00
|
|
|
}
|
|
|
|
else if (group.type === 'buffer') {
|
2017-06-13 22:01:04 +02:00
|
|
|
var buffers = [];
|
|
|
|
var bufferLen = 0;
|
|
|
|
if (group.joinChar !== undefined) {
|
|
|
|
var joinBuffer = Buffer.from(group.joinChar);
|
2017-06-16 10:21:53 +02:00
|
|
|
for (var i=0; i<group.payload.length; i++) {
|
2017-06-13 22:01:04 +02:00
|
|
|
if (i > 0) {
|
|
|
|
buffers.push(joinBuffer);
|
|
|
|
bufferLen += joinBuffer.length;
|
|
|
|
}
|
|
|
|
buffers.push(group.payload[i]);
|
|
|
|
bufferLen += group.payload[i].length;
|
|
|
|
}
|
2018-07-08 17:52:30 +02:00
|
|
|
}
|
|
|
|
else {
|
2017-06-13 22:01:04 +02:00
|
|
|
bufferLen = group.bufferLen;
|
|
|
|
buffers = group.payload;
|
|
|
|
}
|
|
|
|
group.payload = Buffer.concat(buffers,bufferLen);
|
|
|
|
}
|
|
|
|
|
2016-06-09 12:33:40 +02:00
|
|
|
if (group.type === 'string') {
|
2017-06-27 18:10:52 +02:00
|
|
|
var groupJoinChar = group.joinChar;
|
|
|
|
if (typeof group.joinChar !== 'string') {
|
|
|
|
groupJoinChar = group.joinChar.toString();
|
|
|
|
}
|
|
|
|
RED.util.setMessageProperty(group.msg,node.property,group.payload.join(groupJoinChar));
|
2018-07-08 17:52:30 +02:00
|
|
|
}
|
|
|
|
else {
|
2017-09-28 20:09:54 +02:00
|
|
|
if (node.propertyType === 'full') {
|
|
|
|
group.msg = RED.util.cloneMessage(group.msg);
|
|
|
|
}
|
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;
|
2018-07-08 17:52:30 +02:00
|
|
|
}
|
|
|
|
else {
|
2016-06-09 12:33:40 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2018-07-10 12:24:57 +02:00
|
|
|
var pendingMessages = [];
|
|
|
|
var activeMessagePromise = null;
|
|
|
|
// In reduce mode, we must process messages fully in order otherwise
|
|
|
|
// groups may overlap and cause unexpected results. The use of JSONata
|
|
|
|
// means some async processing *might* occur if flow/global context is
|
|
|
|
// accessed.
|
|
|
|
var processReduceMessageQueue = function(msg) {
|
|
|
|
if (msg) {
|
|
|
|
// A new message has arrived - add it to the message queue
|
|
|
|
pendingMessages.push(msg);
|
|
|
|
if (activeMessagePromise !== null) {
|
|
|
|
// The node is currently processing a message, so do nothing
|
|
|
|
// more with this message
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pendingMessages.length === 0) {
|
|
|
|
// There are no more messages to process, clear the active flag
|
|
|
|
// and return
|
|
|
|
activeMessagePromise = null;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// There are more messages to process. Get the next message and
|
|
|
|
// start processing it. Recurse back in to check for any more
|
|
|
|
var nextMsg = pendingMessages.shift();
|
|
|
|
activeMessagePromise = reduce_msg(node, nextMsg)
|
|
|
|
.then(processReduceMessageQueue)
|
|
|
|
.catch((err) => {
|
|
|
|
node.error(err,nextMsg);
|
|
|
|
return processReduceMessageQueue();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
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-23 22:48:13 +02:00
|
|
|
|
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;
|
2017-06-13 22:01:04 +02:00
|
|
|
var arrayLen;
|
2017-06-05 18:04:17 +02:00
|
|
|
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;
|
2017-06-13 22:01:04 +02:00
|
|
|
arrayLen = msg.parts.len;
|
2017-06-05 18:04:17 +02:00
|
|
|
propertyIndex = msg.parts.index;
|
2016-06-10 23:51:57 +02:00
|
|
|
}
|
2018-01-17 11:08:23 +01:00
|
|
|
else if (node.mode === 'reduce') {
|
2018-07-10 12:24:57 +02:00
|
|
|
return processReduceMessageQueue(msg);
|
2018-01-17 11:08:23 +01: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;
|
2018-07-08 17:52:30 +02:00
|
|
|
if (n.count === "" && msg.hasOwnProperty('parts')) {
|
2017-06-05 18:04:17 +02:00
|
|
|
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-23 22:48:13 +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");
|
2017-06-23 22:48:13 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (msg.hasOwnProperty('complete')) {
|
|
|
|
completeSend(partId);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
node.warn("Message missing key property 'msg."+node.key+"' - cannot add to object")
|
|
|
|
}
|
2017-06-05 18:04:17 +02:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!inflight.hasOwnProperty(partId)) {
|
|
|
|
if (payloadType === 'object' || payloadType === 'merged') {
|
|
|
|
inflight[partId] = {
|
|
|
|
currentCount:0,
|
|
|
|
payload:{},
|
|
|
|
targetCount:targetCount,
|
|
|
|
type:"object",
|
2018-07-08 17:52:30 +02:00
|
|
|
msg:RED.util.cloneMessage(msg)
|
2017-06-05 18:04:17 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
else if (node.accumulate === true) {
|
|
|
|
if (msg.hasOwnProperty("reset")) { delete inflight[partId]; }
|
|
|
|
inflight[partId] = inflight[partId] || {
|
|
|
|
currentCount:0,
|
|
|
|
payload:{},
|
|
|
|
targetCount:targetCount,
|
2017-06-15 01:11:35 +02:00
|
|
|
type:payloadType,
|
2018-07-08 17:52:30 +02:00
|
|
|
msg:RED.util.cloneMessage(msg)
|
2017-06-05 18:04:17 +02:00
|
|
|
}
|
2017-06-27 18:10:52 +02:00
|
|
|
if (payloadType === 'string' || payloadType === 'array' || payloadType === 'buffer') {
|
2017-06-15 01:11:35 +02:00
|
|
|
inflight[partId].payload = [];
|
|
|
|
}
|
2017-06-05 18:04:17 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
inflight[partId] = {
|
|
|
|
currentCount:0,
|
|
|
|
payload:[],
|
|
|
|
targetCount:targetCount,
|
|
|
|
type:payloadType,
|
2018-07-08 17:52:30 +02:00
|
|
|
msg:RED.util.cloneMessage(msg)
|
2017-06-05 18:04:17 +02:00
|
|
|
};
|
|
|
|
if (payloadType === 'string') {
|
|
|
|
inflight[partId].joinChar = joinChar;
|
2017-06-13 22:01:04 +02:00
|
|
|
} else if (payloadType === 'array') {
|
|
|
|
inflight[partId].arrayLen = arrayLen;
|
|
|
|
} else if (payloadType === 'buffer') {
|
|
|
|
inflight[partId].bufferLen = 0;
|
2017-06-27 00:10:08 +02:00
|
|
|
inflight[partId].joinChar = joinChar;
|
2017-06-05 18:04:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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];
|
2017-06-13 22:01:04 +02:00
|
|
|
if (payloadType === 'buffer') {
|
|
|
|
inflight[partId].bufferLen += property.length;
|
|
|
|
}
|
2017-06-05 18:04:17 +02:00
|
|
|
if (payloadType === 'object') {
|
|
|
|
group.payload[propertyKey] = property;
|
|
|
|
group.currentCount = Object.keys(group.payload).length;
|
|
|
|
//msg.topic = node.topic || msg.topic;
|
2017-06-13 22:01:04 +02:00
|
|
|
} else if (payloadType === 'merged') {
|
2017-06-05 18:04:17 +02:00
|
|
|
if (Array.isArray(property) || typeof property !== 'object') {
|
|
|
|
if (!msg.hasOwnProperty("complete")) {
|
|
|
|
node.warn("Cannot merge non-object types");
|
|
|
|
}
|
2017-06-13 22:01:04 +02:00
|
|
|
} else {
|
2017-06-05 18:04:17 +02:00
|
|
|
for (propertyKey in property) {
|
2017-12-05 22:45:43 +01:00
|
|
|
if (property.hasOwnProperty(propertyKey) && propertyKey !== '_msgid') {
|
2017-06-05 18:04:17 +02:00
|
|
|
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++;
|
|
|
|
}
|
2017-06-13 22:01:04 +02:00
|
|
|
} else {
|
2017-06-05 18:04:17 +02:00
|
|
|
if (!isNaN(propertyIndex)) {
|
|
|
|
group.payload[propertyIndex] = property;
|
|
|
|
} else {
|
|
|
|
group.payload.push(property);
|
2016-06-11 22:43:37 +02:00
|
|
|
}
|
|
|
|
group.currentCount++;
|
|
|
|
}
|
2018-07-08 17:52:30 +02:00
|
|
|
group.msg = Object.assign(group.msg, msg);
|
2017-06-16 10:21:53 +02:00
|
|
|
var tcnt = group.targetCount;
|
|
|
|
if (msg.hasOwnProperty("parts")) { tcnt = group.targetCount || msg.parts.count; }
|
2018-01-28 22:15:59 +01:00
|
|
|
if ((tcnt > 0 && group.currentCount >= tcnt) || msg.hasOwnProperty('complete')) {
|
2017-06-05 18:04:17 +02:00
|
|
|
completeSend(partId);
|
2016-06-09 12:33:40 +02:00
|
|
|
}
|
2018-07-08 17:52:30 +02:00
|
|
|
}
|
|
|
|
catch(err) {
|
2017-06-05 18:04:17 +02:00
|
|
|
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);
|
|
|
|
}
|