actionhero
Version:
actionhero.js is a multi-transport API Server with integrated cluster capabilities and delayed tasks
260 lines (228 loc) • 7.74 kB
JavaScript
var primus = require('primus');
var UglifyJS = require('uglify-js');
var fs = require('fs');
var path = require('path');
var util = require('util');
var browser_fingerprint = require('browser_fingerprint');
var initialize = function(api, options, next){
//////////
// INIT //
//////////
var type = 'websocket'
var attributes = {
canChat: true,
logConnections: true,
logExits: true,
sendWelcomeMessage: true,
verbs: [
'quit',
'exit',
'documentation',
'roomAdd',
'roomLeave',
'roomView',
'detailsView',
'say'
]
}
var server = new api.genericServer(type, options, attributes);
//////////////////////
// REQUIRED METHODS //
//////////////////////
server.start = function(next){
var webserver = api.servers.servers.web;
server.server = new primus(webserver.server, api.config.servers.websocket.server);
server.server.on('connection', function(rawConnection){
handleConnection(rawConnection);
});
server.server.on('disconnection', function(rawConnection){
handleDisconnection(rawConnection);
});
api.log('webSockets bound to ' + webserver.options.bindIP + ':' + webserver.options.port, 'debug');
server.active = true;
server.writeClientJS();
next();
}
server.stop = function(next){
server.active = false;
if( api.config.servers.websocket.destroyClientsOnShutdown === true ){
server.connections().forEach(function(connection){
connection.destroy();
});
}
process.nextTick(function(){
next();
});
}
server.sendMessage = function(connection, message, messageCount){
if(message.error && message.error instanceof Error){
try{
message.error = String( message.error.message );
}catch(e){ }
}
if(!message.context){ message.context = 'response'; }
if(!messageCount){ messageCount = connection.messageCount; }
if(message.context === 'response' && !message.messageCount){ message.messageCount = messageCount; }
connection.rawConnection.write(message);
}
server.sendFile = function(connection, error, fileStream, mime, length){
var content = '';
var response = {
error : error,
content : null,
mime : mime,
length : length
};
try{
if(!error){
fileStream.on('data', function(d){ content+= d; });
fileStream.on('end', function(){
response.content = content;
server.sendMessage(connection, response, connection.messageCount);
});
}else{
server.sendMessage(connection, response, connection.messageCount);
}
}catch(e){
api.log(e, 'warning');
server.sendMessage(connection, response, connection.messageCount);
}
};
server.goodbye = function(connection){
connection.rawConnection.end();
};
////////////
// EVENTS //
////////////
server.on('connection', function(connection){
connection.rawConnection.on('data', function(data){
handleData(connection, data);
});
});
server.on('actionComplete', function(data){
if(data.toRender !== false){
data.connection.response.messageCount = data.messageCount;
server.sendMessage(data.connection, data.response, data.messageCount)
}
});
////////////
// CLIENT //
////////////
server.compileActionheroClientJS = function(){
var ahClientSource = fs.readFileSync(__dirname + '/../client/actionheroClient.js').toString();
var url = api.config.servers.websocket.clientUrl;
ahClientSource = ahClientSource.replace(/%%URL%%/g, url);
var defaults = {}
for(var i in api.config.servers.websocket.client){
defaults[i] = api.config.servers.websocket.client[i]
}
defaults.url = url;
var defaultsString = util.inspect(defaults);
defaultsString = defaultsString.replace('\'window.location.origin\'', 'window.location.origin');
ahClientSource = ahClientSource.replace('%%DEFAULTS%%', 'return ' + defaultsString);
return ahClientSource;
}
server.renderClientJS = function(minimize){
if(!minimize){ minimize = false; }
var libSource = api.servers.servers.websocket.server.library();
var ahClientSource = server.compileActionheroClientJS();
ahClientSource =
';;;\r\n' +
'(function(exports){ \r\n' +
ahClientSource +
'\r\n' +
'exports.ActionheroClient = ActionheroClient; \r\n' +
'exports.actionheroClient = actionheroClient; \r\n' +
'})(typeof exports === \'undefined\' ? window : exports);' ;
if(minimize){
return UglifyJS.minify(libSource + '\r\n\r\n\r\n' + ahClientSource, {fromString: true}).code;
}else{
return (libSource + '\r\n\r\n\r\n' + ahClientSource);
}
}
server.writeClientJS = function(){
if(!api.config.general.paths.public || api.config.general.paths.public.length === 0){
return;
}
if(api.config.servers.websocket.clientJsPath && api.config.servers.websocket.clientJsName){
var base = path.normalize(
api.config.general.paths.public[0] +
path.sep +
api.config.servers.websocket.clientJsPath +
path.sep +
api.config.servers.websocket.clientJsName
);
try{
fs.writeFileSync(base + '.js', server.renderClientJS(false));
api.log('wrote ' + base + '.js', 'debug');
fs.writeFileSync(base + '.min.js', server.renderClientJS(true));
api.log('wrote ' + base + '.min.js', 'debug');
}catch(e){
api.log('Cannot write client-side JS for websocket server:', 'warning');
api.log(e, 'warning');
throw e;
}
}
}
/////////////
// HELPERS //
/////////////
var handleConnection = function(rawConnection){
var parsedCookies = browser_fingerprint.parseCookies(rawConnection);
var fingerprint = parsedCookies[api.config.servers.web.fingerprintOptions.cookieKey];
server.buildConnection({
rawConnection : rawConnection,
remoteAddress : rawConnection.address.ip,
remotePort : rawConnection.address.port,
fingerprint : fingerprint,
});
}
var handleDisconnection = function(rawConnection){
for(var i in server.connections()){
if(server.connections()[i] && rawConnection.id === server.connections()[i].rawConnection.id){
server.connections()[i].destroy();
break;
}
}
}
var handleData = function(connection, data){
var verb = data.event;
delete data.event;
connection.messageCount++;
connection.params = {};
if(verb === 'action'){
for(var v in data.params){
connection.params[v] = data.params[v];
}
connection.error = null;
connection.response = {};
server.processAction(connection);
} else if(verb === 'file'){
connection.params = {
file: data.file
}
server.processFile(connection);
} else {
var words = [];
var message;
if(data.room){
words.push(data.room);
delete data.room;
}
for(var i in data){ words.push(data[i]); }
connection.verbs(verb, words, function(error, data){
if(!error){
message = {status: 'OK', context: 'response', data: data};
server.sendMessage(connection, message);
} else {
message = {status: error, context: 'response', data: data}
server.sendMessage(connection, message);
}
});
}
}
next(server);
}
/////////////////////////////////////////////////////////////////////
// exports
exports.initialize = initialize;