msgflo
Version:
Polyglot FBP runtime based on message queues
227 lines (202 loc) • 6.48 kB
JavaScript
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;