scalra
Version:
node.js framework to prototype and scale rapidly
404 lines (308 loc) • 10.6 kB
JavaScript
//
//
// conn.js
//
// Connection Management
//
// 2012-05-10 inital version
// (refactored code from aere_game_app, aere_lobby_user, aere_mjgame_user)
//
// Functions:
// isConnected(connID) // check if a connection still exists
// getConnObject(connID)
// getConnections(conn || name) // get all connections associated with a connection object or name
// getConnCount()
// createConnObject(type, conn_obj, from)
// destroyConnObject(connID)
// setSessionName(conn, name) // link a session_token with a login name
// unsetSessionName(conn) // unlink a session_token with a login name
// getSessionName(conn)
// retired Functions:
// setConnName(connID, newName, oldName)
// getConnName(connID)
// objects:
// ConnHandler(conn_module)
//-----------------------------------------
// NOTE: conn_module must implement the following public methods:
//
// 'onConnect' (a new connection is made)
// 'onDisconnect' (an existing connection is disconnected)
//
var l_timeoutWaitDispose = 1000;
//
// private methods
//
// data storing connection info, indexed by connID (uuid)
var l_conn = {};
// mapping from account name to session_token
var l_names = {};
var l_name = 'SR.Conn';
//
// public methods
//
// check if a connection still exists
exports.isConnected = function (connID) {
return l_conn.hasOwnProperty(connID);
};
// get connection object
exports.getConnObject = function (connID) {
if (l_conn.hasOwnProperty(connID))
return l_conn[connID];
else
return undefined;
};
// get all connections with the same session_token
exports.getConnections = function (conn) {
var list = [];
var token = undefined;
if (!conn) {
LOG.warn('no connection object or name specified', l_name);
return list;
}
// if connection name's provided, lookup session token directly
if (typeof conn === 'string') {
if (l_names.hasOwnProperty(conn) === false) {
LOG.warn('no connection object associated with name: ' + conn, l_name);
return list;
}
token = l_names[conn];
} else if (typeof conn === 'object' && typeof conn.session_token === 'string')
token = conn.session_token;
if (token) {
// get a list of all connections with the same session token
for (var connID in l_conn) {
if (l_conn[connID].session_token === token)
list.push(l_conn[connID]);
}
}
LOG.sys('returning ' + list.length + ' connections from same session', l_name);
return list;
};
// get a connection name from connection ID
//exports.getConnName = function (connID) {
// var name = (l_conn.hasOwnProperty(connID) ? l_conn[connID].name : undefined);
// return name;
//}
// set user account given a connection ID
//exports.setConnName = function (connID, newName, oldName) {
// // first check if connection exists
// if (l_conn.hasOwnProperty(connID) === false)
// return false;
// // if old account is provided, check if it matches with the one on record
// if (oldName && l_conn[connID].name !== oldName)
// return false;
// var conn = l_conn[connID];
// // change is successful
// conn.name = newName;
// return true;
//}
// set user's session_token to a name so we may lookup all connections associated with a given session
// this is useful when the incoming request may not have a persistent connection (such as HTTP)
exports.setSessionName = function (conn, name) {
if (!conn || typeof conn.session_token === 'undefined')
return false;
l_names[name] = conn.session_token;
return true;
};
// opposite of setSessionName
exports.unsetSessionName = function (conn) {
if (!conn || typeof conn.session_token === 'undefined')
return false;
for (var name in l_names) {
if (l_names[name] === conn.session_token)
delete l_names[name];
}
return true;
};
// get the name for current session
exports.getSessionName = function (conn) {
for (var name in l_names) {
if (l_name[name] === conn.session_token)
return name;
}
return undefined;
};
// get number of connections currently registered
exports.getConnCount = function () {
return Object.keys(l_conn).length;
};
// definition for a connection object
function Connection (type, sender, from) {
// provide default 'from' values
from = from || {};
// connection-specific UUID
this.connID = UTIL.createUUID();
// a project-specific / supplied name (can be account or app name)
this.name = '';
// sender function associated with this connection
this.connector = sender;
// store pointers on how to respond
this.type = type;
// where the connection comes from (default to nothing)
this.host = from.host || '';
this.port = from.port || 0;
// time when this connection is established
this.time = new Date();
// append cookie, if available (this is useful if the event will be relayed to another server to execute,
// cookie can then be preserved
if (from.cookie)
this.cookie = from.cookie;
}
// for sockets only currently
// TODO: make it more general?
Connection.prototype.send = function (msg) {
if (this.type === 'socket') {
LOG.sys('socket send direct triggered...', l_name);
this.connector.send(msg);
}
};
Connection.prototype.close = function () {
// TODO: should remove socket-specific tasks, or try to make it more general
if (this.type === 'socket') {
LOG.warn('socket close direct triggered...', l_name);
this.connector.end();
}
};
// get duration of the connection since its start
Connection.prototype.getDuration = function () {
return (new Date).getTime() - this.time.getTime();
};
// create a connection object
var l_createConnObject = exports.createConnObject = function (type, sender_func, from) {
// create info for the connection & create unique UUID
var conn = new Connection (type, sender_func, from);
// perform socket-specific functions ('socket' or 'sockio', 'sockjs')
// TODO: find a better way to identify persistent connections
if (type.startsWith('sock')) {
// record the connection (so it can later be retrived based on connID, or be removed when disconnect)
l_conn[conn.connID] = conn;
}
LOG.sys('[' + conn.type + '] connection created: ' + conn.connID + ' (' + conn.host + ':' + conn.port + ')', l_name);
return conn;
};
// remove a connection object
var l_destroyConnObject = exports.destroyConnObject = function (connID) {
if (l_conn.hasOwnProperty(connID)) {
LOG.sys('connection destroyed: ' + connID + ' type: ' + l_conn[connID].type, l_name);
delete l_conn[connID];
// remove session data for this connection
// NOTE: this is not symmetric (storing session should be here as well)
if (SR.State.get(connID) !== undefined) {
LOG.warn('removing session info for connID [' + connID + ']', l_name);
SR.State.delete(connID);
}
return true;
}
return false;
};
// dummy connection module
var l_conn_module = {
onConnect: function () {
},
onDisconnect: function () {
}
};
exports.ConnHandler = function (conn_module) {
// use one or the default dummy
conn_module = conn_module || l_conn_module;
// private record for all connections at this handler
var connections = {};
// add a new handler for connection/disconnection
// TODO: simplify/remove this?
this.addConnHandler = function (handler) {
var old_module = conn_module;
// replace with new one
conn_module = {
onConnect: function (conn) {
UTIL.safeCall(handler.onConnect, conn);
// call original
old_module.onConnect(conn);
},
onDisconnect: function (conn, onDone) {
UTIL.safeCall(handler.onDisconnect, conn);
// call original
old_module.onDisconnect(conn, onDone);
}
};
};
// handle new connection
this.addConnection = function (send_func, type, from) {
var conn = l_createConnObject(type, send_func, from);
// record the connection info
connections[conn.connID] = true;
// notify project-specific app
UTIL.safeCall(conn_module.onConnect, conn);
return conn;
};
//
// handle when a disconnection occurs, does the following:
// 1. checks if there are pending messages at the socket to be sent,
// 2. call custom onDisconnect()
// 3. destroy the connection object on record
//
this.removeConnection = function (socket, onDone) {
if (socket === undefined) {
LOG.error('socket undefined', l_name);
return UTIL.safeCall(onDone);
}
// close socket (may cause additional events be fired?)
if (typeof socket.end === 'function')
socket.end();
// if connection ID doesn't exist
if (socket.hasOwnProperty('connID') === false) {
LOG.error('connID not found for this socket', l_name);
return UTIL.safeCall(onDone);
}
// wait for all event processing done on this socket
SR.EventManager.waitSocketsEmpty(socket,
function () {
LOG.sys('no pending events on socket: ' + socket.connID, l_name);
// if connection record doesn't exist
if (l_conn.hasOwnProperty(socket.connID) === false) {
LOG.error('connID not found: ' + socket.connID, l_name);
return UTIL.safeCall(onDone);
}
LOG.sys('calling custom onDisconnect...', l_name);
// NOTE: we make a copy of the conn object parameters to pass to user-implemented onDisconnect
// TODO: remove this?
var conn = {};
var original = l_conn[socket.connID];
for (var key in original) {
if (typeof original[key] !== 'function')
conn[key] = original[key];
}
var callback = function () {
LOG.warn('no need to call onDone callback in onDisconnect() now, please remove it', l_name);
};
// TODO: remove the usage of custom onDisconnect
UTIL.safeCall(conn_module.onDisconnect, conn, callback);
// delete conn info
l_destroyConnObject(socket.connID);
delete connections[socket.connID];
UTIL.safeCall(onDone);
}
);
};
// close down all connections
this.removeAll = function (onDone) {
// NOTE: log may be closed b now
// if nothing to delete
if (Object.keys(connections).length === 0) {
return UTIL.safeCall(onDone);
}
var pending_count = 0;
// loop through each connection to disconnect
for (var connID in connections) {
LOG.sys('removing connID: ' + connID, l_name);
if (l_conn.hasOwnProperty(connID) && l_conn[connID].type.startsWith('sock')) {
pending_count++;
this.removeConnection(l_conn[connID].connector, function () {
pending_count--;
if (pending_count === 0)
UTIL.safeCall(onDone);
});
}
// TODO: remove other types of connections?
}
};
};