UNPKG

msgflo

Version:

Polyglot FBP runtime based on message queues

344 lines (331 loc) 12.3 kB
// 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;