total4
Version:
Total.js framework v4
2,051 lines (1,636 loc) • 44.9 kB
JavaScript
// Total.js FlowStream
// The MIT License
// Copyright 2021-2023 (c) Peter Širka <petersirka@gmail.com>
'use strict';
const BLACKLISTID = { paused: 1, groups: 1, tabs: 1 };
const REG_ARGS = /\{{1,2}[a-z0-9_.-\s]+\}{1,2}/gi;
const D = '__';
function Message() {
this.ismessage = true;
this.cloned = 0;
}
Message.prototype = {
get user() {
return this.controller ? this.controller.user : null;
},
get session() {
return this.controller ? this.controller.session : null;
},
get sessionid() {
return this.controller && this.controller ? this.controller.req.sessionid : null;
},
get language() {
return (this.controller ? this.controller.language : '') || '';
},
get ip() {
return this.controller ? this.controller.ip : null;
},
get req() {
return this.controller ? this.controller.req : null;
},
get res() {
return this.controller ? this.controller.res : null;
},
get params() {
return this.controller ? this.controller.params : null;
},
get files() {
return this.controller ? this.controller.files : null;
},
get body() {
return this.controller ? this.controller.body : null;
},
get query() {
return this.controller ? this.controller.query : null;
},
get headers() {
return this.controller && this.controller.req ? this.controller.req.headers : null;
},
get ua() {
return this.controller && this.controller.req ? this.controller.req.ua : null;
}
};
var MP = Message.prototype;
MP.emit = function(name, a, b, c, d, e, f, g) {
var self = this;
if (!self.$events)
return self;
var evt = self.$events[name];
if (evt) {
var clean = false;
for (var e of evt) {
if (e.once)
clean = true;
e.fn.call(self, a, b, c, d, e, f, g);
}
if (clean) {
var index = 0;
while (true) {
if (!evt[index])
break;
if (evt[index].once)
evt.splice(index, 1);
else
index++;
}
self.$events[name] = evt.length ? evt : undefined;
}
}
return self;
};
MP.resume = function() {
sendmessage(this.to, this, this.$emitevent, true);
};
MP.emit2 = function(name, a, b, c, d, e, f, g) {
var self = this;
if (!self.$events)
return self;
var evt = self.$events[name];
if (evt) {
var clean = false;
for (var e of evt) {
if (e.cloned < self.cloned) {
if (e.once)
clean = true;
e.fn.call(self, a, b, c, d, e, f, g);
}
}
if (clean) {
var index = 0;
while (true) {
var e = evt[index];
if (!e)
break;
if (e.cloned < self.cloned) {
if (e.once)
evt.splice(index, 1);
else
index++;
} else
index++;
}
self.$events[name] = evt.length ? evt : undefined;
}
}
return self;
};
MP.on = function(name, fn, once) {
var self = this;
if (!self.$events)
self.$events = {};
var obj = { cloned: self.cloned, fn: fn, once: once };
if (self.$events[name])
self.$events[name].push(obj);
else
self.$events[name] = [obj];
return self;
};
MP.once = function(name, fn) {
return this.on(name, fn, true);
};
MP.off = MP.removeListener = MP.removeAllListeners = function(name, fn) {
var self = this;
if (!name) {
delete self.$events;
return;
}
if (self.$events) {
var evt = self.$events[name];
if (evt) {
if (fn) {
evt = evt.remove(n => n.fn === fn);
self.$events[name] = evt.length ? evt : undefined;
} else
delete self.$events[name];
}
}
return self;
};
MP.clone = function() {
var self = this;
var obj = new Message();
obj.previd = self.id;
obj.$events = self.$events;
obj.duration = self.duration;
obj.repo = self.repo;
obj.vars = self.vars;
obj.main = self.main;
obj.refs = self.refs;
obj.count = self.count;
obj.data = self.data;
obj.used = self.used;
obj.processed = 0;
obj.controller = self.controller;
obj.cloned = self.cloned + 1;
obj.$timeoutidtotal = self.$timeoutidtotal;
obj.color = self.color;
if (obj.refs.pending)
obj.refs.pending++;
else
obj.refs.pending = 1;
// additional custom variables
obj.uid = self.uid;
obj.reference = self.reference;
obj.ref = self.ref;
if (obj.$events && obj.$events.timeout) {
var index = 0;
while (true) {
var e = obj.$events.timeout[index];
if (e) {
if ((e.cloned + 1) < obj.cloned)
obj.$events.timeout.splice(index, 1);
else
index++;
} else
break;
}
}
if (self.$timeoutid) {
clearTimeout(self.$timeoutid);
self.$timeoutid = null;
}
return obj;
};
MP.status = function(a, b, c, d) {
this.instance.status(a, b, c, d);
return this;
};
MP.dashboard = function(a, b, c, d) {
this.instance.dashboard(a, b, c, d);
return this;
};
MP.debug = function(a, b, c, d) {
this.instance.debug(a, b, c, d);
return this;
};
MP.throw = function(a, b, c, d) {
this.error = a;
this.instance.throw(a, b, c, d);
return this;
};
function variables(str, data, encoding) {
if (typeof(str) === 'object') {
var obj = {};
for (var key in str) {
var val = str[key];
if (typeof(val) === 'string')
obj[key] = variables.call(this, val, data, encoding);
else
obj[key] = val;
}
return obj;
}
if (typeof(str) !== 'string' || str.indexOf('{') === -1)
return str;
var main = this.main ? this.main : this;
if (data == null || data == true)
data = this;
return str.replace(REG_ARGS, function(text) {
var l = text[1] === '{' ? 2 : 1;
var key = text.substring(l, text.length - l).trim();
var val = null;
if (main.variables)
val = main.variables[key];
if (!val && main.variables2)
val = main.variables2[key];
if (!val && main.secrets)
val = main.secrets[key];
if (!val && key === 'hostname') {
val = (main.$schema.origin || '') + (main.$schema.proxypath || '');
if (val[val.length - 1] === '/')
val = val.substring(0, val.length - 1);
}
var customencoding = typeof(encoding) === 'function';
if (!val && data != null && typeof(data) === 'object') {
var nested = key.indexOf('.') !== -1;
val = nested ? U.get(data, key) : data[key];
}
if (customencoding) {
val = encoding(val, key);
} else {
if (encoding !== 'json') {
if (val instanceof Date)
val = val.format();
}
switch (encoding) {
case 'urlencoded':
case 'url':
val = encodeURIComponent(val);
break;
case 'json':
val = JSON.stringify(val);
break;
case 'querify':
val = QUERIFY(val).substring(1);
break;
}
}
return val == null ? text : val;
});
}
MP.replace = MP.variables = function(str, data, encoding) {
return variables.call(this, str, data, encoding);
};
function timeouthandler(msg) {
msg.error = 408;
msg.$events.timeout && msg.emit('timeout', msg);
msg.$events.timeout2 && msg.emit('timeout2', msg);
msg.end();
}
MP.send = function(outputindex, data, clonedata) {
var self = this;
if (clonedata == null)
clonedata = self.main.cloning;
if (self.isdestroyed || self.main.paused || (self.instance && self.instance.isdestroyed)) {
if (!self.isdestroyed)
self.destroy();
return 0;
}
var outputs;
var count = 0;
if (outputindex == null) {
if (self.instance.connections) {
for (var key in self.instance.connections)
count += self.send(key);
}
if (!count)
self.destroy();
return count;
}
var meta = self.main.meta;
var now = Date.now();
outputs = self.instance.connections ? (self.instance.connections[outputindex] || EMPTYARRAY) : EMPTYARRAY;
if (self.processed === 0) {
self.processed = 1;
self.main.stats.pending--;
if (self.main.stats.pending < 0)
self.main.stats.pending = 0;
self.instance.stats.pending--;
if (self.instance.stats.pending < 0)
self.instance.stats.pending = 0;
self.instance.stats.output++;
self.instance.stats.duration = now - self.ts;
}
if (!self.main.$can(false, self.instance.id, outputindex)) {
self.destroy();
return count;
}
var tid = self.toid + D + outputindex + (self.color || '');
if (self.main.stats.traffic[tid]) {
self.main.stats.traffic[tid]++;
} else {
self.main.stats.traffic[tid] = 1;
self.main.stats.traffic.priority.push(tid);
}
if (self.transformation === '1')
self.transformation = '_' + tid;
for (var i = 0; i < outputs.length; i++) {
var output = outputs[i];
if (output.disabled || output.paused)
continue;
var schema = meta.flow[output.id];
if (schema && (schema.message || schema['message_' + output.index]) && schema.component && schema.ready && self.main.$can(true, output.id, output.index)) {
var next = meta.components[schema.component];
if (next && next.connected && !next.isdestroyed && !next.disabled) {
if (output.color && self.color && self.color !== output.color)
continue;
var inputindex = output.index;
var message = self.clone();
if (data != undefined)
message.data = data;
if (clonedata && message.data && typeof(message.data) === 'object')
message.data = message.data instanceof Buffer ? Buffer.from(message.data) : U.clone(message.data);
message.used++;
message.instance = schema;
message.from = self.to;
message.fromid = self.toid;
message.fromcomponent = self.instance.component;
message.to = schema;
message.toid = output.id;
message.output = outputindex;
message.input = message.index = inputindex;
message.tocomponent = schema.component;
message.cache = schema.cache;
message.ts = now;
message.color = output.color;
if (self.$timeout)
message.$timeoutid = setTimeout(timeouthandler, self.$timeout, message);
schema.stats.input++;
schema.stats.pending++;
self.main.stats.messages++;
self.main.stats.pending++;
self.main.mm++;
if (self.$events) {
self.$events.next && self.emit2('next', self, message);
self.$events.something && self.emit2('something', self, message);
self.$events.message && self.emit('message', message);
}
setImmediate(sendmessage, schema, message, true);
count++;
}
}
}
if (count) {
self.refs.pending--;
if (self.refs.pending < 0)
self.refs.pending = 0;
} else
self.destroy();
return count;
};
MP.rewrite = function(data) {
this.data = data;
return this;
};
MP.totaltimeout = function(callback, time) {
if (time == null)
time = callback;
else
this.on('timeout2', callback);
this.$timeoutidtotal && clearTimeout(this.$timeoutidtotal);
this.$timeoutidtotal = setTimeout(timeouthandler, time, this);
return this;
};
MP.timeout = function(callback, time) {
if (time == null)
time = callback;
else
this.on('timeout', callback);
this.$timeout = time;
return this;
};
MP.end = MP.destroy = function() {
var self = this;
if (self.isdestroyed)
return;
if (self.processed === 0) {
self.processed = 1;
self.main.stats.pending--;
if (self.main.stats.pending < 0)
self.main.stats.pending = 0;
self.instance.stats.pending--;
if (self.instance.stats.pending < 0)
self.instance.stats.pending = 0;
self.instance.stats.duration = Date.now() - self.ts;
self.instance.stats.destroyed++;
}
if (self.$timeoutid) {
clearTimeout(self.$timeoutid);
self.$timeoutid = null;
}
if (self.$timeoutidtotal) {
clearTimeout(self.$timeoutidtotal);
self.$timeoutidtotal = null;
}
if (self.refs.pending) {
self.refs.pending--;
if (self.refs.pending < 0)
self.refs.pending = 0;
}
if (self.$events) {
self.$events.something && self.emit('something', self);
self.$events.terminate && self.emit('terminate', self);
}
if (!self.refs.pending) {
if (self.$events) {
self.$events.end && self.emit('end', self);
self.$events.destroy && self.emit('destroy', self);
}
if (self.main.$events)
self.main.$events.end && self.main.emit('end', self);
}
self.isdestroyed = true;
self.repo = null;
self.main = null;
self.middleware = null;
self.from = null;
self.to = null;
self.data = null;
self.instance = null;
self.duration = null;
self.ts = null;
self.$events = null;
};
function FlowStream(name, errorhandler) {
var t = this;
t.strict = true;
t.loading = 0;
t.cloning = true;
t.error = errorhandler || console.error;
t.id = t.name = name;
t.uid = Date.now().toString(36) + 'X';
t.meta = {};
t.meta.components = {};
t.meta.flow = {};
t.meta.cache = {};
t.logger = [];
t.middleware = [];
t.stats = { messages: 0, pending: 0, traffic: { priority: [] }, mm: 0, minutes: 0 };
t.paused = false;
t.mm = 0;
new U.EventEmitter2(t);
t.$counter = 1;
t.$interval = setInterval(t => t.service(t.$counter++), 5000, t);
}
var FP = FlowStream.prototype;
FP.service = function(counter) {
var t = this;
var is = t.mm;
if (counter % 12 === 0) {
t.stats.minutes++;
t.stats.mm = t.mm;
t.mm = 0;
for (let key in t.meta.flow) {
let com = t.meta.flow[key];
com.service && com.service(t.stats.minutes);
}
if (t.stats.traffic.priority.length)
is = 1;
}
t.onstats && t.onstats(t.stats);
t.$events.stats && t.emit('stats', t.stats);
if (is)
t.stats.traffic = { priority: [] };
};
FP.pause = function(is) {
var self = this;
self.paused = is;
for (let m in self.meta.flow) {
let instance = self.meta.flow[m];
if (instance && instance.pause)
instance.pause(is);
}
return self;
};
FP.register = function(name, declaration, config, callback, extend) {
var self = this;
var type = typeof(declaration);
if (type === 'string') {
if (!declaration) {
let e = new Error('Invalid component declaration');
callback && callback(e);
self.error(e, 'register', name);
return;
}
try {
declaration = new Function('instance', declaration);
} catch (e) {
callback && callback(e);
self.error(e, 'register', name);
return;
}
}
var cache;
var prev = self.meta.components[name];
if (prev) {
cache = prev.cache;
prev.connected = false;
prev.disabled = true;
prev.destroy = null;
prev.disconnect && prev.disconnect();
}
var curr = { id: name, main: self, connected: true, disabled: false, cache: cache || {}, config: config || {}, stats: {}, ui: {}, iscomponent: true };
if (extend) {
try {
var cacheid = name;
if (type === 'object') {
for (let key in declaration)
curr[key] = declaration[key];
} else
declaration(curr, F.require);
curr.id = cacheid;
} catch (e) {
self.error(e, 'register', name);
callback && callback(e);
return;
}
} else
curr.make = type === 'object' ? declaration.make : declaration;
curr.config = U.clone(curr.config || curr.options);
var errors = new ErrorBuilder();
var done = function() {
self.inc(-1);
self.meta.components[name] = curr;
self.onregister && self.onregister(curr);
self.$events.register && self.emit('register', name, curr);
curr.install && !prev && curr.install.call(curr, curr);
for (var key in self.meta.flow) {
if (!BLACKLISTID[key]) {
var f = self.meta.flow[key];
if (f.component === curr.id)
self.initcomponent(key, curr);
}
}
self.clean();
callback && callback(errors.length ? errors : null);
};
self.inc(1);
if (curr.npm && curr.npm.length) {
curr.npm.wait(function(name, next) {
NPMINSTALL(name, function(err) {
if (err) {
self.error(err, 'npm');
errors.push(err);
}
next();
});
}, done);
} else
setImmediate(done);
return curr;
};
FP.destroy = function() {
var self = this;
clearInterval(self.$interval);
self.$interval = null;
self.inc(1);
self.unload(function() {
self.inc(-1);
self.emit('destroy');
self.meta = null;
self.$events = null;
delete F.flows[self.name];
});
};
FP.inc = function(num) {
var self = this;
if (num === 0)
self.loading = 0;
else if (num === -1)
self.loading--;
else
self.loading++;
// Assurance
if (self.loading < 0)
self.loading = 0;
return self;
};
FP.cleanforce = function() {
var self = this;
if (self.cleantimeout) {
clearTimeout(self.cleantimeout);
self.cleantimeout = null;
}
if (!self.meta)
return self;
for (var key in self.meta.flow) {
if (!BLACKLISTID[key]) {
var instance = self.meta.flow[key];
if (instance.connections) {
for (var key2 in instance.connections) {
var conns = instance.connections[key2];
var rem = {};
for (var conn of conns) {
if (conn) {
var target = self.meta.flow[conn.id];
if (target) {
var com = self.meta.components[target.component];
if (com) {
if (self.strict) {
if (target.inputs) {
if (!target.inputs.findItem('id', conn.index))
rem[conn.id] = 1;
} else if (!com.inputs || !com.inputs.findItem('id', conn.index))
rem[conn.id] = 1;
}
} else
rem[conn.id] = 1;
} else
rem[conn.id] = 1;
}
}
var arr = conns.remove(c => c == null || rem[c.id] === 1);
if (arr.length)
instance.connections[key2] = arr;
else
delete instance.connections[key2];
}
}
}
}
var paused = self.meta.flow.paused;
if (paused) {
for (var key in paused) {
var arr = key.split(D);
// arr[0] type
// arr[1] id
// arr[2] index
if (!self.meta.flow[arr[1]])
delete paused[key];
}
}
var fn = key => self.meta.flow[key] == null;
self.logger = self.logger.remove(fn);
self.middleware = self.middleware.remove(fn);
return self;
};
FP.unregister = function(name, callback) {
var self = this;
if (name == null) {
Object.keys(self.meta.components).wait(function(key, next) {
self.unregister(key, next);
}, callback);
return self;
}
var curr = self.meta.components[name];
if (curr) {
self.onunregister && self.onunregister(curr);
self.$events.unregister && self.emit('unregister', name, curr);
self.inc(1);
Object.keys(self.meta.flow).wait(function(key, next) {
var instance = self.meta.flow[key];
if (instance) {
if (instance.component === name) {
instance.ready = false;
try {
instance.isdestroyed = true;
self.ondisconnect && self.ondisconnect(instance);
self.$events.disconnect && self.emit('disconnect', instance);
instance.close && instance.close.call(instance, true);
instance.destroy && instance.destroy.call(instance);
} catch (e) {
self.onerror.call(instance, e, 'instance_close', key);
}
delete self.meta.flow[key];
}
} else
delete self.meta.flow[key];
next();
}, function() {
self.inc(-1);
curr.connected = false;
curr.disabled = true;
curr.uninstall && curr.uninstall.call(curr, curr);
curr.destroy = null;
curr.cache = null;
delete self.meta.components[name];
callback && callback();
self.clean();
});
} else if (callback)
callback();
return self;
};
FP.clean = function() {
var self = this;
if (!self.loading) {
self.cleantimeout && clearTimeout(self.cleantimeout);
self.cleantimeout = setTimeout(() => self.cleanforce(), 1000);
}
return self;
};
/*
FP.ondisconnect = function(instance) {
};
FP.onconnect = function(instance) {
};
FP.onregister = function(component) {
};
FP.onunregister = function(component) {
};
FP.onreconfigure = function(instance, init) {
};
*/
FP.ondashboard = function(a, b, c, d) {
// this == instance
this.main.$events.dashboard && this.main.emit('dashboard', this, a, b, c, d);
};
FP.onstatus = function(a, b, c, d) {
// this == instance
this.main.$events.status && this.main.emit('status', this, a, b, c, d);
};
FP.onerror = function(a, b, c, d) {
// this == instance
this.main.$events.error && this.main.emit('error', this, a, b, c, d);
};
FP.ondebug = function(a, b, c, d) {
// this == instance
this.main.$events.debug && this.main.emit('debug', this, a, b, c, d);
};
function newlogger(callback) {
var self = this;
self.$logger = callback;
var index = self.main.logger.indexOf(self.id);
if (callback) {
if (index === -1)
self.main.logger.push(self.id);
} else {
if (index !== -1)
self.main.logger.splice(index, 1);
}
return self;
}
function newmiddleware(callback) {
var self = this;
self.$middleware = callback;
var index = self.main.middleware.indexOf(self.id);
if (callback) {
if (index === -1)
self.main.middleware.push(self.id);
} else {
if (index !== -1)
self.main.middleware.splice(index, 1);
}
return self;
}
function newmessage(data) {
var self = this;
var msg = new Message();
msg.refs = { pending: 1 };
msg.repo = {};
msg.vars = {};
msg.to = msg.instance = self;
msg.toid = self.id;
msg.tocomponent = self.component;
msg.data = data instanceof Message ? data.data : data;
msg.cloned = 0;
msg.count = 0;
msg.instance = self;
msg.duration = msg.ts = Date.now();
msg.used = 1;
msg.main = self instanceof FlowStream ? self : self.main;
msg.processed = 0;
return msg;
}
// New transform message
function newtransform(output, data, callback) {
if (typeof(data) === 'function') {
callback = data;
data = undefined;
}
var self = this;
var msg = newmessage.call(self, data);
msg.on('destroy', function($) {
if (callback) {
var tmp = msg.transformation;
if (tmp && tmp !== '1') {
if (self.main.stats.traffic[tmp]) {
self.main.stats.traffic[tmp]++;
} else {
self.main.stats.traffic[tmp] = 1;
self.main.stats.traffic.priority.push(tmp);
}
}
callback($);
callback = null;
}
});
msg.transformation = '1';
msg.send(output);
//setImmediate(() => msg.send(output));
//return msg;
}
FP.ontrigger = function(outputindex, data, controller, events) {
// this == instance
var schema = this;
var self = schema.main;
var count = 0;
if (self.paused)
return count;
if (schema && schema.ready && schema.component && schema.connections) {
var instance = self.meta.components[schema.component];
if (instance && instance.connected && !instance.disabled && self.$can(false, schema.id, outputindex)) {
var conn = schema.connections[outputindex];
if (conn && conn.length) {
var ts = Date.now();
for (var i = 0; i < conn.length; i++) {
var m = conn[i];
var target = self.meta.flow[m.id];
if (!target || (!target.message && !target['message_' + m.index]) || !self.$can(true, m.id, m.index))
continue;
var com = self.meta.components[target.component];
if (!com)
continue;
if (target.isdestroyed || (data && data.instance && data.instance.isdestroyed))
continue;
var ismessage = data instanceof Message;
if (ismessage && m.color && data.color && data.color !== m.color)
continue;
var message = ismessage ? data.clone() : new Message();
if (ismessage) {
if (data.isdestroyed)
return 0;
if (data.processed === 0) {
data.processed = 1;
data.main.stats.pending--;
if (data.main.stats.pending < 0)
data.main.stats.pending = 0;
if (data.instance) {
data.instance.stats.pending--;
if (data.instance.stats.pending < 0)
data.instance.stats.pending = 0;
data.instance.stats.output++;
data.instance.stats.duration = ts - self.ts;
}
}
} else {
message.refs = { pending: 1 };
message.$events = events || {};
message.repo = {};
message.vars = {};
message.data = data;
message.duration = message.ts = ts;
message.used = 1;
}
if (i && (self.cloning != false) && message.data && typeof(message.data) === 'object')
message.data = message.data instanceof Buffer ? Buffer.from(message.data) : U.clone(message.data);
message.main = self;
message.controller = controller;
message.instance = target;
message.color = m.color;
message.from = schema;
message.fromid = schema.id;
message.fromcomponent = schema.component;
message.output = outputindex;
message.to = message.instance = target;
message.toid = m.id;
message.input = message.index = m.index;
message.tocomponent = target.component;
message.cache = target.cache;
message.processed = 0;
target.stats.pending++;
target.stats.input++;
schema.stats.output++;
message.main.stats.pending++;
message.main.stats.messages++;
message.main.mm++;
message.id = message.main.uid + message.main.stats.messages;
message.count = message.main.stats.messages;
if (message.fromid && !count) {
var tid = message.fromid + D + message.output + (message.color || '');
if (message.main.stats.traffic[tid])
message.main.stats.traffic[tid]++;
else {
message.main.stats.traffic[tid] = 1;
message.main.stats.traffic.priority.push(tid);
}
}
if (ismessage && data.$timeout)
message.$timeoutid = setTimeout(timeouthandler, data.$timeout, message);
if (ismessage) {
data.next && data.emit2('next', data, message);
data.something && data.emit2('something', data, message);
data.message && data.emit('message', message);
}
count++;
setImmediate(sendmessage, target, message, true);
}
}
}
}
return count;
};
FP.reconfigure = function(id, config, rewrite) {
var self = this;
var instance = self.meta.flow[id];
if (instance && !instance.isdestroyed) {
if (rewrite)
instance.config = config;
else
U.extend(instance.config, config);
instance.configure && instance.configure(instance.config);
self.onreconfigure && self.onreconfigure(instance);
self.$events.configure && self.emit('configure', instance);
}
return !!instance;
};
FP.unload = function(callback) {
var self = this;
var keys = Object.keys(self.meta.flow);
keys.wait(function(key, next) {
var current = self.meta.flow[key];
if (current) {
current.isdestroyed = true;
self.ondisconnect && self.ondisconnect(current);
try {
current.close && current.close.call(current, true);
current.destroy && current.destroy.call(current);
} catch(e) {
self.onerror.call(current, e, 'instance_close', key);
}
}
delete self.meta.flow[key];
next();
}, function() {
// uninstall components
self.unregister(null, callback);
});
return self;
};
FP.loadvariables = function(variables, type = 'variables') {
// @type {String} variables (default), variables2, secrets
var self = this;
if (JSON.stringify(self[type]) !== JSON.stringify(variables)) {
self[type] = variables;
for (let key in self.meta.flow) {
let instance = self.meta.flow[key];
instance[type] && instance[type](self[type]);
instance.vary && instance.vary(type);
}
}
return self;
};
FP.load = function(components, design, callback, asfile) {
var self = this;
if (self.loading) {
setTimeout(() => self.load(components, design, callback), 200);
return self;
}
self.loading = 10000;
self.unload(function() {
var keys = Object.keys(components);
var error = new ErrorBuilder();
keys.wait(function(key, next) {
var body = components[key];
if (typeof(body) === 'string' && body.indexOf('<script ') !== -1) {
self.add(key, body, function(err) {
err && error.push(err);
next();
}, asfile);
} else {
error.push('Invalid component: ' + key);
next();
}
}, function() {
// Loads design
self.inc(0);
self.use(design, function(err) {
self.inc(0);
err && error.push(err);
self.clean();
callback && callback(err);
});
});
});
return self;
};
FP.replace = variables;
FP.rewrite = function(data, callback) {
var self = this;
if (self.loading) {
setTimeout(() => self.replace(data, callback), 200);
return self;
}
self.loading = 100000;
var keys = Object.keys(data.components);
var error = new ErrorBuilder();
var processed = {};
keys.wait(function(key, next) {
var body = data.components[key];
processed[key] = 1;
if (typeof(body) === 'string' && body.indexOf('<script ') !== -1) {
self.add(key, body, function(err) {
err && error.push(err);
next();
}, data.asfiles);
} else {
error.push('Invalid component: ' + key);
next();
}
}, function() {
// Removed non-exist components
Object.keys(self.meta.components).wait(function(key, next) {
if (processed[key])
next();
else
self.unregister(key, next);
}, function() {
// Loads design
self.inc(0);
self.use(data.design, function(err) {
if (data.variables)
self.loadvariables(U.clone(data.variables));
self.inc(0);
err && error.push(err);
self.clean();
callback && callback(err);
});
});
});
return self;
};
FP.insert = function(schema, callback) {
var self = this;
if (callback)
self._use(schema, callback, null, true);
else
return new Promise((resolve, reject) => self._use(schema, (err, res) => err ? reject(err) : resolve(res), null, true));
};
FP.remove = function(keys, callback) {
var self = this;
if (callback)
self._remove(keys, callback);
else
return new Promise((resolve, reject) => self._remove(keys, (err, res) => err ? reject(err) : resolve(res), null, true));
};
FP._remove = function(keys, callback) {
var self = this;
if (!(keys instanceof Array))
keys = Object.keys(keys);
for (var key of keys) {
if (BLACKLISTID[key]) {
delete self.meta.flow[key];
continue;
}
var instance = self.meta.flow[key];
if (instance) {
instance.ready = false;
self.ondisconnect && self.ondisconnect(instance);
self.$events.disconnect && self.emit('disconnect', instance);
try {
instance.close && instance.close.call(instance, true);
instance.destroy && instance.destroy.call(instance);
} catch (e) {
self.onerror.call(instance, e, 'instance_close', key);
}
delete self.meta.flow[key];
}
}
self.clean();
callback && callback();
};
function use(self, schema, callback, reinit, insert) {
self._use(schema, callback, reinit, insert);
}
FP.use = function(schema, callback, reinit) {
var self = this;
if (callback)
self._use(schema, callback, reinit);
else
return new Promise((resolve, reject) => self._use(schema, (err, res) => err ? reject(err) : resolve(res), reinit));
};
FP._use = function(schema, callback, reinit, insert) {
var self = this;
if (self.loading) {
setTimeout(use, 200, self, schema, callback, reinit, insert);
return self;
}
if (typeof(schema) === 'string')
schema = schema.parseJSON(true);
else
schema = U.clone(schema);
if (typeof(callback) === 'boolean') {
var tmp = reinit;
reinit = callback;
callback = tmp;
}
// schema.COMPONENT_ID.component = 'condition';
// schema.COMPONENT_ID.config = {};
// schema.COMPONENT_ID.connections = { '0': [{ id: 'COMPONENT_ID', index: '2' }] }
var err = new ErrorBuilder();
if (schema) {
var keys = Object.keys(schema);
var ts = Date.now();
if (!insert) {
if (self.meta.flow.paused)
delete self.meta.flow.paused;
if (self.meta.flow.groups)
delete self.meta.flow.groups;
if (self.meta.flow.tabs)
delete self.meta.flow.tabs;
}
self.inc(1);
keys.wait(function(key, next) {
if (BLACKLISTID[key]) {
self.meta.flow[key] = schema[key];
next();
return;
}
var current = self.meta.flow[key];
var instance = schema[key];
var component = instance.component ? self.meta.components[instance.component] : null;
// Component not found
if (!component) {
err.push(key, '"' + instance.component + '" component not found.');
if (current) {
current.isdestroyed = true;
self.ondisconnect && self.ondisconnect(current);
try {
current.close && current.close.call(current, true);
current.destroy && current.destroy.call(current);
} catch (e) {
self.onerror.call(current, e, 'instance_close', key);
}
}
delete self.meta.flow[key];
next();
return;
}
var fi = self.meta.flow[key];
if (!fi || reinit) {
self.meta.flow[key] = instance;
var tmp = self.initcomponent(key, component);
if (tmp) {
tmp.ts = ts;
tmp.newbie = true;
}
} else {
fi.connections = instance.connections;
fi.x = instance.x;
fi.y = instance.y;
fi.offset = instance.offset;
fi.size = instance.size;
fi.tab = instance.tab;
fi.ts = ts;
if (JSON.stringify(fi.config) !== JSON.stringify(instance.config)) {
U.extend(fi.config, instance.config);
fi.configure && fi.configure(fi.config);
self.onreconfigure && self.onreconfigure(fi, true);
self.$events.configure && self.emit('configure', fi);
}
}
next();
}, function() {
if (!insert) {
for (var key in self.meta.flow) {
if (!BLACKLISTID[key]) {
var instance = self.meta.flow[key];
if (instance.ts !== ts) {
instance.ready = false;
instance.isdestroyed = true;
self.ondisconnect && self.ondisconnect(instance);
self.$events.disconnect && self.emit('disconnect', instance);
try {
instance.close && instance.close.call(instance, true);
instance.destroy && instance.destroy.call(instance);
} catch (e) {
self.onerror.call(instance, e, 'instance_close', key);
}
delete self.meta.flow[key];
}
}
}
}
for (var key in self.meta.flow) {
var instance = self.meta.flow[key];
if (instance.newbie) {
if (instance.init) {
try {
instance.init();
} catch (e) {
self.onerror.call(instance, e, 'instance_init', key);
}
}
instance.newbie = false;
}
if (instance.refresh) {
try {
instance.refresh();
} catch (e) {
self.onerror.call(instance, e, 'instance_refresh', key);
}
}
}
self.inc(-1);
self.cleanforce();
self.$events.schema && self.emit('schema', self.meta.flow);
callback && callback(err.length ? err : null);
});
} else {
err.push('schema', 'Flow schema is invalid.');
self.error(err, 'use');
callback && callback(err);
}
return self;
};
FP.initcomponent = function(key, component) {
var self = this;
var instance = self.meta.flow[key];
if (instance.ready) {
// Closes old instance
instance.ready = false;
try {
self.ondisconnect && self.ondisconnect(instance);
self.$events.disconnect && self.emit('disconnect', instance);
instance.close && instance.close.call(instance);
} catch (e) {
self.onerror.call(instance, e, 'instance_close', key);
}
}
instance.isinstance = true;
instance.stats = { pending: 0, input: 0, output: 0, duration: 0, destroyed: 0 };
instance.cache = {};
instance.id = key;
instance.module = component;
instance.ready = false;
if (instance.options) {
instance.config = instance.options;
delete instance.options;
}
var tmp = component.config;
if (tmp)
instance.config = instance.config ? U.extend(U.clone(tmp), instance.config) : U.clone(tmp);
if (!instance.config)
instance.config = {};
instance.main = self;
instance.dashboard = self.ondashboard;
instance.status = self.onstatus;
instance.debug = self.ondebug;
instance.throw = self.onerror;
instance.send = self.ontrigger;
instance.newmessage = newmessage;
instance.logger = newlogger;
instance.middleware = newmiddleware;
instance.transform = newtransform;
instance.replace = variables;
instance.instances = self.meta.flow;
instance.components = self.meta.components;
self.onconnect && self.onconnect(instance);
self.$events.connect && self.emit('connect', instance);
try {
component.make && component.make.call(instance, instance, instance.config);
} catch (e) {
self.error(e, 'instance_make', instance);
return;
}
if (instance.open) {
instance.open.call(instance, (function(instance) {
return function() {
if (instance) {
instance.ready = true;
delete instance.open;
}
};
})(instance));
} else
instance.ready = true;
// Notifies about the pause state
if (self.paused && instance.pause)
instance.pause(true);
self.meta.flow[key] = instance;
return instance;
};
function sendmessage(instance, message, event) {
if (instance.isdestroyed || message.isdestroyed || instance.main.paused) {
message.destroy();
return;
}
if (message.middleware === undefined && instance.main.middleware.length) {
message.middleware = instance.main.middleware.slice(0);
if (message.middleware.length)
message.$emitevent = event;
else
message.middleware = null;
}
if (message.middleware && message.middleware.length) {
var mid = message.middleware.shift();
if (mid) {
var tmp = instance.main.meta.flow[mid];
if (tmp && tmp.$middleware) {
// Executes middleware
tmp.$middleware(message);
} else {
// Maybe another middleware
sendmessage(instance, message, event);
}
return;
}
}
// Logger
if (instance.main.logger.length) {
var main = instance.main;
for (var key of main.logger) {
var tmp = main.meta.flow[key];
if (tmp && tmp.$logger)
tmp.$logger(message);
}
}
if (event) {
message.$events && message.$events.message && message.emit('message', message);
message.main.$events && message.main.$events.message && message.main.emit('message', message);
}
try {
var is = false;
var key = 'message_' + message.input;
if (instance[key]) {
is = true;
instance[key](message);
}
if (instance.message) {
is = true;
instance.message(message);
}
if (!is)
message.destroy();
} catch (e) {
instance.main.error(e, 'instance_message', message);
message.destroy();
}
}
FP.$can = function(isinput, id, index) {
var self = this;
if (self.paused)
return false;
if (!self.meta.flow.paused)
return true;
var key = (isinput ? 'input' : 'output') + D + id + D + index;
if (!self.meta.flow.paused[key])
return true;
};
function trigger(self, path, data, controller, events) {
self.trigger(path, data, controller, events);
}
// path = ID__INPUTINDEX
FP.trigger = function(path, data, controller, events) {
var self = this;
if (self.loading) {
setTimeout(trigger, 200, self, path, data, controller, events);
return;
}
if (self.paused)
return;
path = path.split(D);
var inputindex = path.length === 1 ? 0 : path[1];
var schema = self.meta.flow[path[0]];
if (schema && schema.ready && schema.component && (schema.message || schema['message_' + inputindex])) {
var instance = self.meta.components[schema.component];
if (instance && instance.connected && !instance.disabled && self.$can(true, path[0], path[1])) {
var ismessage = data instanceof Message;
var ts = Date.now();
var message = ismessage ? data.clone(false) : new Message();
if (ismessage) {
if (data.processed === 0) {
data.processed = 1;
data.main.stats.pending--;
if (data.main.stats.pending < 0)
data.main.stats.pending = 0;
data.instance.stats.pending--;
if (data.instance.stats.pending < 0)
data.instance.stats.pending = 0;
data.instance.stats.output++;
data.instance.stats.duration = ts - self.ts;
}
} else {
message.refs = { pending: 1 };
message.$events = events || {};
message.repo = {};
message.data = data;
message.duration = message.ts = ts;
message.used = 1;
}
message.controller = controller;
message.instance = schema;
message.main = self;
message.to = schema;
message.toid = path[0];
message.input = message.index = inputindex;
message.tocomponent = instance.id;
message.cache = instance.cache;
message.processed = 0;
schema.stats.input++;
schema.stats.pending++;
message.main.stats.pending++;
message.main.stats.messages++;
message.main.mm++;
message.id = message.main.uid + message.main.stats.messages;
message.count = message.main.stats.messages;
if (message.fromid) {
var tid = message.fromid + D + message.output + (message.color || '');
if (message.main.stats.traffic[tid])
message.main.stats.traffic[tid]++;
else {
message.main.stats.traffic[tid] = 1;
message.main.stats.traffic.priority.push(tid);
}
} else {
message.from = null;
message.fromid = null;
message.fromcomponent = null;
message.output = null;
}
setImmediate(sendmessage, schema, message, true);
return message;
}
}
};
FP.trigger2 = function(path, data, controller) {
var self = this;
if (self.paused)
return;
var events = {};
var obj;
path = path.split(D);
var counter = 0;
for (var key in self.meta.flow) {
var flow = self.meta.flow[key];
if (flow.component === path[0])
obj = self.trigger(key + D + (path.length === 1 ? 0 : path[1]), data, controller, events, counter++);
}
return obj;
};
FP.clear = function() {
var self = this;
self.meta.flow = {};
return self;
};
FP.make = function(fn) {
var self = this;
fn.call(self, self);
return self;
};
FP.find = function(id) {
return this.meta.flow[id];
};
FP.send = function(path, body) {
var self = this;
if (!self.paused && self.meta && self.meta.flow) {
path = path.split(D);
var instance = self.meta.flow[path[0]];
if (instance)
instance.send(path[1], body);
return !!instance;
}
};
FP.add = function(name, body, callback, asfile) {
var self = this;
var meta = body.parseComponent({ readme: '<readme>', settings: '<settings>', css: '<style>', be: '<script total>', be2: '<script node>', js: '<script>', html: '<body>', schema: '<schema>', template: '<template>' });
var node = (meta.be || meta.be2 || '').trim().replace(/\n\t/g, '\n');
if (!meta.be && !meta.be2) {
var e = new Error('Invalid component content');
self.error(e, 'add', name);
callback && callback(e);
return;
}
meta.id = name;
meta.checksum = HASH(node).toString(36);
var component = self.meta.components[name];
if (component && component.ui && component.ui.checksum === meta.checksum) {
component.ui = meta;
component.ts = Date.now();
callback && callback();
} else {
var fn;
if (asfile) {
var filename = F.path.tmp(self.id + '_' + meta.id) + '.js';
F.Fs.writeFile(filename, node, function(err) {
if (err) {
callback && callback(err);
return;
}
try {
fn = require(filename);
delete meta.be;
delete meta.be2;
component = self.register(meta.id, fn, null, callback, true);
if (component) {
component.ui = meta;
component.ui.raw = body;
}
} catch (e) {
self.error(e, 'add', name);
callback && callback(e);
}
});
return;
}
try {
fn = new Function('exports', 'require', node);
} catch (e) {
self.error(e, 'add', name);
callback && callback(e);
return null;
}
delete meta.be;
delete meta.be2;
component = self.register(meta.id, fn, null, callback, true);
if (component)
component.ui = meta;
else
return null;
}
component.ui.raw = body;
return component;
};
FP.instances = function() {
var self = this;
var arr = [];
for (var key in self.meta.flow) {
if (!BLACKLISTID[key]) {
var instance = self.meta.flow[key];
if (instance.ready)
arr.push(instance);
}
}
return arr;
};
FP.export_instance = function(id) {
var self = this;
var instance = self.meta.flow[id];
if (instance) {
if (BLACKLISTID[id])
return U.clone(instance);
var tmp = {};
tmp.x = instance.x;
tmp.y = instance.y;
tmp.size = instance.size;
tmp.offset = instance.offset;
tmp.stats = U.clone(instance.stats);
tmp.connections = U.clone(instance.connections);
tmp.id = instance.id;
tmp.config = U.clone(instance.config);
tmp.component = instance.component;
tmp.connected = true;
tmp.note = instance.note;
tmp.tab = instance.tab;
tmp.reference = instance.reference;
tmp.meta = instance.meta;
if (instance.outputs)
tmp.outputs = instance.outputs;
if (instance.inputs)
tmp.inputs = instance.inputs;
return tmp;
}
};
FP.export_component = function(id) {
var self = this;
var com = self.meta.components[id];
if (com) {
var obj = {};
obj.id = com.id;
obj.name = com.name;
obj.title = com.title;
obj.meta = com.meta;
obj.type = com.type;
obj.css = com.ui.css;
obj.js = com.ui.js;
obj.icon = com.icon;
obj.color = com.ui.color;
obj.config = com.config;
obj.html = com.ui.html;
obj.readme = com.ui.readme;
obj.template = com.ui.template;
obj.settings = com.ui.settings;
obj.inputs = com.inputs;
obj.outputs = com.outputs;
obj.group = com.group;
obj.version = com.version;
obj.author = com.author;
obj.permissions = com.permissions;
return obj;
}
};
FP.export = function(type) {
var self = this;
if (type === 'components')
return self.components(true);
var output = {};
for (var key in self.meta.flow)
output[key] = self.export_instance(key);
return output;
};
FP.components = function(prepare_export) {
var self = this;
var arr = [];
for (var key in self.meta.components) {
if (prepare_export)
arr.push(self.export_component(key));
else
arr.push(self.meta.components[key]);
}
return arr;
};
exports.create = function(id, errorhandler) {
let flowstream = new FlowStream(id, errorhandler);
F.flows[id] = flowstream;
return flowstream;
};