msgflo
Version:
Polyglot FBP runtime based on message queues
344 lines (331 loc) • 12.3 kB
JavaScript
// Implementation of the FBP protocol
// http://noflojs.org/documentation/protocol
var EventEmitter, Protocol, async, debug, defaultGraph, fbpComponentFromMsgflo, fbpPort, handleGraphMessage, handleMessage, serializeErr;
debug = require('debug')('msgflo:fbp');
EventEmitter = require('events').EventEmitter;
async = require('async');
fbpPort = function(port) {
var m;
m = {
id: port.id,
type: port.type || "any",
description: port.description || "",
schema: port.schema, // optional
addressable: false,
required: false // TODO: implement
};
return m;
};
fbpComponentFromMsgflo = function(name, component) {
var info;
if (component.definition) {
// full info available
info = {
name: name,
description: component.definition.label || component.cmd || "",
icon: component.definition.icon || '',
subgraph: false,
inPorts: component.definition.inports.map(fbpPort),
outPorts: component.definition.outports.map(fbpPort)
};
} else {
// just inifial info
info = {
name: name,
description: component.cmd,
icon: '',
subgraph: false,
inPorts: [],
outPorts: []
};
}
return info;
};
// JSON serialization of Error objects is empty
serializeErr = function(err) {
return {
message: err.message
};
};
defaultGraph = 'default/main';
handleMessage = function(proto, sub, cmd, payload, ctx) {
var component, components, i, info, len, name, options, p, ref, ref1, ref2, runtime, sendMainGraphSource, subscribeEdge;
debug('RECV:', sub, cmd, payload);
if (sub === 'runtime' && cmd === 'getruntime') {
runtime = {
id: proto.coordinator.options.runtimeId,
type: 'msgflo',
version: '0.7',
capabilities: ['protocol:runtime', 'protocol:component', 'protocol:graph', 'protocol:network', 'component:getsource', 'component:setsource'],
graph: defaultGraph
};
options = proto.coordinator.library.options;
if (((ref = options.config) != null ? ref.namespace : void 0) != null) {
runtime.namespace = options.config.namespace;
}
if (((ref1 = options.config) != null ? ref1.repository : void 0) != null) {
runtime.repository = options.config.repository;
}
return proto.transport.send('runtime', 'runtime', runtime, ctx);
} else if (sub === 'runtime' && cmd === 'packet') {
return proto.coordinator.sendToExportedPort(payload.port, payload.payload, function(err) {
if (err) {
return proto.transport.send('runtime', 'error', serializeErr(err), ctx);
}
return proto.transport.send('runtime', 'packetsent', {
port: payload.port,
event: payload.event,
graph: payload.graph,
payload: payload.payload
}, ctx);
});
// Component
} else if (sub === 'component' && cmd === 'list') {
debug('attempting to list components');
components = [];
ref2 = proto.coordinator.library.components;
for (name in ref2) {
component = ref2[name];
info = fbpComponentFromMsgflo(name, component);
components.push(info);
}
for (i = 0, len = components.length; i < len; i++) {
info = components[i];
proto.transport.send('component', 'component', info, ctx);
}
proto.transport.send('component', 'componentsready', components.length, ctx);
return debug('sent components', components.length);
} else if (sub === 'component' && cmd === 'getsource') {
sendMainGraphSource = function() {
var graph, resp;
graph = proto.coordinator.serializeGraph('main');
resp = {
code: JSON.stringify(graph),
name: 'main',
library: 'default',
language: 'json'
};
return proto.transport.send('component', 'source', resp, ctx);
};
if (payload.name === defaultGraph) {
// Main graph. Ref https://github.com/noflo/noflo-ui/issues/390
return setTimeout(sendMainGraphSource, 0);
} else {
// Regular component
return proto.coordinator.getComponentSource(payload.name, function(err, source) {
if (err) {
proto.transport.send('component', 'error', serializeErr(err), ctx);
return;
}
return proto.transport.send('component', 'source', source, ctx);
});
}
} else if (sub === 'component' && cmd === 'source') {
p = payload;
return proto.coordinator.addComponent(p.name, p.language, p.code, function(err) {
if (err) {
return proto.transport.send('component', 'error', serializeErr(err), ctx);
}
return proto.transport.sendAll('component', 'source', payload);
});
// Network
} else if (sub === 'network' && cmd === 'start') {
return proto.coordinator.startNetwork(payload.graph, function(err) {
if (err) {
return proto.transport.sendAll('network', 'error', serializeErr(err));
}
return proto.transport.sendAll('network', 'started', {
running: true,
started: true,
graph: payload.graph,
time: new Date()
});
});
} else if (sub === 'network' && cmd === 'stop') {
return proto.coordinator.stopNetwork(payload.graph, function(err) {
if (err) {
return proto.transport.sendAll('network', 'error', serializeErr(err));
}
return proto.transport.sendAll('network', 'stopped', {
running: false,
started: true,
graph: payload.graph,
time: new Date()
});
});
} else if (sub === 'network' && cmd === 'getstatus') {
return proto.transport.sendAll('network', 'status', {
running: proto.coordinator.started,
started: proto.coordinator.started,
graph: payload.graph,
time: new Date()
});
} else if (sub === 'network' && cmd === 'edges') {
debug('network:edges', payload.edges.length);
subscribeEdge = function(edge, cb) {
return proto.coordinator.subscribeConnection(edge.src.node, edge.src.port, edge.tgt.node, edge.tgt.port, function(err) {
return cb(err);
});
};
return proto.coordinator.clearSubscriptions(function(err) {
if (err) {
return proto.transport.sendAll('network', 'error', serializeErr(err));
}
return async.map(payload.edges, subscribeEdge, function(err) {
if (err) {
return proto.transport.sendAll('network', 'error', serializeErr(err));
}
return proto.transport.sendAll('network', 'edges', payload);
});
});
// Graph
} else if (sub === 'graph') {
return handleGraphMessage(proto, cmd, payload, ctx);
} else {
return debug('Unhandled FBP protocol message: ', sub, cmd);
}
};
handleGraphMessage = function(proto, cmd, payload, ctx) {
var graph, p;
graph = payload.graph;
if (cmd === 'clear') {
// FIXME: support multiple graphs
return proto.coordinator.clearGraph(payload.id, function(err) {
if (err) {
return proto.transport.send('graph', 'error', serializeErr(err), ctx);
}
return proto.transport.sendAll('graph', 'clear', payload);
});
} else if (cmd === 'addnode') {
return proto.coordinator.startParticipant(payload.id, payload.component, payload.metadata, function(err) {
if (err) {
return proto.transport.send('graph', 'error', serializeErr(err), ctx);
}
return proto.transport.sendAll('graph', 'addnode', payload);
});
} else if (cmd === 'removenode') {
return proto.coordinator.stopParticipant(payload.id, payload.component, function(err) {
if (err) {
return proto.transport.send('graph', 'error', serializeErr(err), ctx);
}
return proto.transport.sendAll('graph', 'removenode', payload);
});
} else if (cmd === 'changenode') {
return proto.coordinator.updateNodeMetadata(payload.id, payload.metadata, function(err) {
if (err) {
return proto.transport.send('graph', 'error', serializeErr(err), ctx);
}
return proto.transport.sendAll('graph', 'changenode', payload);
});
// Connections
} else if (cmd === 'addedge') {
debug('addedge', payload);
p = payload;
return proto.coordinator.connect(p.src.node, p.src.port, p.tgt.node, p.tgt.port, p.metadata, function(err) {
if (err) {
return proto.transport.send('graph', 'error', serializeErr(err), ctx);
}
return proto.transport.sendAll('graph', 'addedge', payload);
});
} else if (cmd === 'removeedge') {
p = payload;
return proto.coordinator.disconnect(p.src.node, p.src.port, p.tgt.node, p.tgt.port, function(err) {
if (err) {
return proto.transport.send('graph', 'error', serializeErr(err), ctx);
}
return proto.transport.sendAll('graph', 'removeedge', payload);
});
} else if (cmd === 'changeedge') {
p = payload;
return proto.coordinator.updateEdge(p.src.node, p.src.port, p.tgt.node, p.tgt.port, p.metadata, function(err) {
if (err) {
return proto.transport.send('graph', 'error', serializeErr(err), ctx);
}
return proto.transport.sendAll('graph', 'changeedge', payload);
});
// IIPs
} else if (cmd === 'addinitial') {
return proto.coordinator.addInitial(payload.tgt.node, payload.tgt.port, payload.src.data, payload.metadata, function(err) {
if (err) {
return proto.transport.send('graph', 'error', serializeErr(err), ctx);
}
return proto.transport.sendAll('graph', 'addinitial', payload);
});
} else if (cmd === 'removeinitial') {
proto.coordinator.removeInitial(payload.tgt.node, payload.tgt.port);
return proto.transport.sendAll('graph', 'removeinitial', payload);
// exported ports
} else if (cmd === 'addinport') {
return proto.coordinator.exportPort('inport', payload.public, payload.node, payload.port, payload.metadata, function(err) {
if (err) {
return proto.transport.send('graph', 'error', serializeErr(err), ctx);
}
return proto.transport.sendAll('graph', 'addinport', payload);
});
} else if (cmd === 'addoutport') {
return proto.coordinator.exportPort('outport', payload.public, payload.node, payload.port, payload.metadata, function(err) {
if (err) {
return proto.transport.send('graph', 'error', serializeErr(err), ctx);
}
return proto.transport.sendAll('graph', 'addoutport', payload);
});
} else {
return debug('Unhandled FBP protocol message: ', 'graph', cmd);
}
};
Protocol = class Protocol {
constructor(transport, coordinator) {
this.transport = transport;
this.coordinator = coordinator;
if (!this.coordinator) {
throw Error('Protocol');
}
this.coordinator.on('exported-port-data', (port, data, graph) => {
return this.transport.sendAll('runtime', 'packet', {
port: port,
event: 'data',
payload: data,
graph: graph
});
});
this.transport.on('message', (protocol, command, payload, ctx) => {
return handleMessage(this, protocol, command, payload, ctx);
});
this.coordinator.library.on('components-changed', (names, allComponents) => {
var component, i, info, len, name, results;
debug('components-changed', names);
results = [];
for (i = 0, len = names.length; i < len; i++) {
name = names[i];
component = allComponents[name];
info = fbpComponentFromMsgflo(name, component);
results.push(this.transport.sendAll('component', 'component', info));
}
return results;
});
this.coordinator.on('connection-data', (conn, data) => {
var from, fromPort, id, msg, to, toPort;
from = conn.src.node;
fromPort = conn.src.port;
to = conn.tgt.node;
toPort = conn.tgt.port;
debug('on data', from, fromPort, data);
id = `${from}() ${fromPort.toUpperCase()} -> ${toPort.toUpperCase()} ${to}()`;
msg = {
id: id, // FIXME: https://github.com/noflo/noflo-ui/issues/293
graph: conn.graph || defaultGraph,
src: {
node: from,
port: fromPort
},
tgt: {
node: to,
port: toPort
},
data: data
};
return this.transport.sendAll('network', 'data', msg);
});
}
};
exports.Protocol = Protocol;