UNPKG

msgflo

Version:

Polyglot FBP runtime based on message queues

227 lines (202 loc) 6.48 kB
var EventEmitter, Runtime, WebSocketServer, WebSocketTransport, common, coordinator, debug, http, protocol, querystring, saveGraphFile, send, transport; protocol = require('./protocol'); coordinator = require('./coordinator'); common = require('./common'); querystring = require('querystring'); transport = require('msgflo-nodejs').transport; debug = require('debug')('msgflo:runtime'); http = require('http'); EventEmitter = require('events').EventEmitter; WebSocketServer = require('websocket').server; send = function(connection, msg) { return connection.sendUTF(JSON.stringify(msg)); }; WebSocketTransport = class WebSocketTransport extends EventEmitter { constructor(server) { var handleMessage, ws; super(); this.server = server; this.connections = []; ws = new WebSocketServer({ httpServer: this.server }); handleMessage = (message, connection) => { var e, msg; if (message.type !== 'utf8') { return; } try { msg = JSON.parse(message.utf8Data); } catch (error) { e = error; return null; } return this.emitMessage(msg, connection); }; ws.on('request', (request) => { var connection, subProtocol; subProtocol = request.requestedProtocols.indexOf("noflo") !== -1 ? "noflo" : null; connection = request.accept(subProtocol, request.origin); this.connections.push(connection); connection.on('message', function(message) { return handleMessage(message, connection); }); return connection.on('close', () => { var connIndex; connIndex = this.connections.indexOf(connection); if (connIndex === -1) { return; } return this.connections.splice(connIndex, 1); }); }); } send(protocol, command, payload, ctx) { var connection, msg; if (command === 'error') { console.error(`${protocol}:error`, payload.message); } connection = ctx; msg = { protocol: protocol, command: command, payload: payload }; debug('SEND', msg); return send(connection, msg); } sendAll(protocol, command, payload) { var connection, i, len, msg, ref, results; if (command === 'error') { console.error(`${protocol}:error`, payload.message); } msg = { protocol: protocol, command: command, payload: payload }; debug('SENDALL', this.connections.length, msg); ref = this.connections; results = []; for (i = 0, len = ref.length; i < len; i++) { connection = ref[i]; results.push(send(connection, msg)); } return results; } emitMessage(msg, ctx) { return this.emit('message', msg.protocol, msg.command, msg.payload, ctx); } }; // atomic saveGraphFile = function(graph, filepath, callback) { var fs, json, temppath; fs = require('fs'); temppath = filepath + `.msgflo-autosave-${Date.now()}`; json = JSON.stringify(graph, null, 2); return fs.open(temppath, 'wx', function(err, fd) { if (err) { return callback(err); } return fs.write(fd, json, function(err) { if (err) { return callback(err); } return fs.fsync(fd, function(err) { if (err) { return callback(err); } return fs.rename(temppath, filepath, function(err) { return fs.unlink(temppath, function(e) { return callback(err); }); }); }); }); }); }; Runtime = class Runtime { constructor(options) { this.options = options; this.server = null; this.transport = null; this.protocol = null; this.broker = transport.getBroker(this.options.broker); this.coordinator = new coordinator.Coordinator(this.broker, this.options); this.saveGraph = common.debounce(() => { var graph; debug('saving graph changes', this.options.graph); graph = this.coordinator.serializeGraph('main'); return saveGraphFile(graph, this.options.graph, function(err) { if (err) { return console.log("ERROR: Failed to save graph file", err); } }); }, 500); if (this.options.graph && this.options.autoSave) { debug('enabling autosave'); this.coordinator.on('graph-changed', () => { return this.saveGraph(); }); } } start(callback) { this.server = http.createServer(); this.transport = new WebSocketTransport(this.server); this.protocol = new protocol.Protocol(this.transport, this.coordinator); this.server.on('request', (request, response) => { return this.serveFrontpage(request, response); }); return this.server.listen(this.options.port, (err) => { if (err) { return callback(err); } return this.coordinator.start((err) => { var onLoaded; if (err) { return callback(err); } onLoaded = (err) => { return callback(err, this.address(), this.liveUrl()); }; if (this.options.graph) { return this.coordinator.loadGraphFile(this.options.graph, this.options, onLoaded); } else { return onLoaded(null); } }); }); } stop(callback) { return this.coordinator.stop((stopErr) => { return this.server.close(function(closeErr) { if (stopErr) { return callback(stopErr); } if (closeErr) { return callback(closeErr); } return callback(null); }); }); } address() { var address, scheme; scheme = 'ws://'; return address = scheme + this.options.host + ':' + this.options.port; } liveUrl() { var params, url; params = querystring.escape(`protocol=websocket&address=${this.address()}&id=${this.options.runtimeId}`); return url = `${this.options.ide}#runtime/endpoint?${params}`; } serveFrontpage(req, res) { var html; html = `<a id="flowhub_url">Open in Flowhub</a>\n<script>\n var addr = window.location.origin.replace("http://", "ws://");\n addr = addr.replace("https://", "ws://");\n var ide = "${this.options.ide}";\n var url = ide+"/#runtime/endpoint?protocol=websocket&address="+encodeURIComponent(addr);\n var a = document.getElementById("flowhub_url");\n a.href = url;\n</script>`; res.statusCode = 200; res.setHeader("Content-Type", "text/html"); res.write(html); return res.end(); } }; exports.Runtime = Runtime;