Skip to content

Instantly share code, notes, and snippets.

@seanhess
Created January 10, 2013 16:57
Show Gist options
  • Save seanhess/4503752 to your computer and use it in GitHub Desktop.
Save seanhess/4503752 to your computer and use it in GitHub Desktop.
// creating global parameters and start
// listening to 'port', we are creating an express
// server and then we are binding it with socket.io
var express = require('express'),
app = express(),
util = require('util'),
server = require('http').createServer(app),
io = require('socket.io').listen(server),
port = 7890,
routes = require('./routes'),
testing = require('./testing'),
auth = require('./auth'),
Models = require('./models'),
Config = require('./config'),
Game = Models.Game,
Room = Models.Room,
Comment = Models.Comment,
// hash object to save clients data,
// { socketid: { clientid, nickname }, socketid: { ... } }
chatClients = new Object(),
Manager = new Object({Clients : {}, Games:{}, Rooms:{}}),
ROOMMAX = Config.MaxRoomUsers,
RECENTCHATS = Config.RecentChatCount;
server.listen(port);
/* COMMAND LINE INTERFACE */
var arg1 = process.argv[2];
console.log('CLI ARG = '+arg1);
if(arg1 === 'rebuild'){
models.synchronize(function(success){
if(success){
testing.createFixtures();
}else{
console.log('database error not synched');
}
});
}
/** EXPRESS CONFIG **/
app.use(express.bodyParser());
app.use("/styles", express.static(__dirname + '/public/styles'));
app.use("/scripts", express.static(__dirname + '/public/scripts'));
app.use("/images", express.static(__dirname + '/public/images'));
/** ROUTING */
// authentication endpoints
app.get('/auth/register', auth.getRegister);
app.post('/auth/register', auth.postRegister);
app.get('/auth/login', auth.getLogin);
app.post('/auth/login', auth.postLogin);
// web pages
app.get('/', routes.homepage);
// accessing models
app.get('/api/games', routes.getGames);
app.post('/api/games', routes.postGames);
app.get('/api/teams', routes.getTeams);
app.post('/api/teams', routes.postTeams);
//test suite
app.get('/testing/debug', testing.getDebug);
/* IO Bindings Events */
io.set('log level', 2);
io.set('transports', [ 'websocket', 'xhr-polling' ]);
io.sockets.on('connection', function(socket){
socket.on( 'connect', function(data) { connect (socket, data); });
socket.on( 'chatmessage', function(data) { chatmessage (socket, data); });
socket.on( 'subscribe', function(data) { subscribe (socket, data); });
socket.on( 'unsubscribe', function(data) { unsubscribe (socket, data); });
socket.on( 'disconnect', function(data) { disconnect (socket); });
});
// create a client for the socket
function connect(socket, data){
// data = { gameId, isHome, apiKey}
auth.validateToken(data, function(userData){
if(userData){
console.log('******* NEW AUTHENTICATED USER ********');
socket.emit('log',{msg : 'User Authenticated'});
// remove apiKey
delete data.apiKey;
data.username = userData.username;
data.userId = userData.id;
// Global list of all Clients
Manager.Clients[socket.id] = data;
subscribeNewUser(socket,data, function(roomGuid){
if(roomGuid){
socket.emit('log',{msg :'Game & Room Prepared'});
getRecentChats(roomGuid, RECENTCHATS, function(chats){
if(chats){
socket.emit('ready',{ roomGuid : roomGuid, username : data.username,
isHome : data.isHome, recentChats : chats });
}else{
socket.emit('ready',{ roomGuid : roomGuid, username : data.username,
isHome : data.isHome, recentChats : []});
}
});
}else{
console.log('not able to subscribeNewUser()');
}
});
}else{
socket.emit('error',{code:1, msg:"invalid token unable to connect"});
console.log("invalid username + token");
}
});
}
function subscribeNewUser(socket, user, fn){
joinGame(data.gameId, function(roomId) {
subscribe(socket, data, roomId);
})
}
function joinGame(gameId, fn) {
// GAMES
findOrCreateGame(data.gameId, function(success){
if(success){
// ROOMS
findOrCreateRoom(data.gameId, function(roomGuid){
if(!roomGuid) return fn(false)
// SUBSCRIBE
console.log('**** SUBSCRIBE '+data.username+' to '+roomGuid+'****');
fn(roomGuid);
});
}else{
console.log('Error : Game not created or found');
fn(false);
}
});
}
//------------------------ GAMES --------------------------------
// should return that a game was created and is ready for users
function findOrCreateGame(gameId,fn){
console.log('** GAME **');
// FIND game exists in the manager
gameExists(gameId,function(exists){
if(exists){
console.log('-- FOUND GAME');
fn(true);
// CREATE game does not exist in Manager.Games create it insert
}else{
console.log('-- CREATE NEW GAME');
createGame(gameId, fn);
}
});
}
function createGame(gameId, fn){
// pull the game out of the database
Game.find(gameId)
.success(function(game){
if (!game) return fn(null, false);
// if the database finds one and returns an object
// init rooms object inside of Manager.Games.rooms
game.rooms = {};
Manager.Games[game.id] = game;
fn(null, true);
})
.error(fn)
;
}
function gameExists(gameId,fn){
var game = Manager.Games[gameId];
fn(game)
//if(game){ fn(true); }
//else{ fn(false); }
}
function doX(cb) {
doSomething(function(err, data) {
if (err) return cb (err)
// more code
})
}
function doSomething(cb) {
cb(new Error("something bad happened")
cb(null, "some data")
}
// ------------------------ ROOMS --------------------------------
// make sure Room is prepared and return the roomGUID
function findOrCreateRoom(gameId,fn){
console.log('** ROOM **');
roomExists(gameId,function(exists){
if(exists){
console.log('-- ROOM EXISTS');
roomAvailable(gameId,function(available){
if(available){
console.log('---- FOUND ROOM');
// already checked that their is room so find the last and jump in
findLastRoomForGame(gameId,function(roomGuid){
if(roomGuid){
fn(roomGuid);
}else{
console.log('Error : Last room not found');
fn(false);
}
});
}else{
console.log('---- BRAND NEW ROOM CREATE');
createRoom(gameId,function(roomGuid){
if(roomGuid){
fn(roomGuid);
}else{
console.log('failed to create room');
fn(false);
}
});
}
});
}else{
console.log('-- NO ROOM EXISTS');
createRoom(gameId,function(roomGuid){
if(roomGuid){
console.log('---- CREATE ROOM');
fn(roomGuid);
}else{
console.log('failed to create room');
fn(false);
}
});
}
});
}
function roomExists(gameId,fn){
var rooms = Manager.Games[gameId].rooms;
var roomCount = Object.keys(rooms).length;
if(roomCount === 0 || roomCount === undefined){
fn(false);
}else{
fn(true);
}
}
function roomAvailable(gameId,fn){
findLastRoomForGame(gameId,function(roomGuid){
if(roomGuid){
var count = Manager.Games[gameId].rooms[roomGuid].userCount;
if(count < ROOMMAX){
console.log(count +' of '+ROOMMAX+' users in room');
fn(true);
}else{
console.log('---- NEW ROOM NEEDED!!!!!!!!');
fn(false);
}
}else{
console.log('Error : room Availability not found');
fn(false);
}
});
}
function createRoom(gameId,fn){
var newRoom = {
roomId : 0,
userCount: 0,
awayCount : 0,
homeCount : 0,
totalUsage: 0,
totalComments: 0,
users:[]
};
var roomGuid = generateRoomGuid();
Room.build({roomGuid : roomGuid, gameId : gameId})
.save()
.success(function(room){
if(room){
newRoom.roomId = room.id;
Manager.Games[gameId].rooms[roomGuid] = newRoom;
// store gameId
newRoom.gameId = gameId;
Manager.Rooms[roomGuid] = newRoom
fn(roomGuid);
}else{
fn(false);
}
});
}
function lastRoomForGame(gameId,fn){
var rooms = Manager.Games[gameId].rooms;
var keys = Object.keys(rooms);
var roomCount = keys.length;
var lastRoomKey = keys[roomCount-1];
//fn(lastRoomKey);
return lastRoomKey
}
// when a client disconnect, unsubscribe him from
// the rooms he subscribed to
function disconnect(socket){
console.log('disconnect()');
// Identify the user
var user = Manager.Clients[socket.id];
console.log(user);
var roomUsers = Manager.Games[user.gameId].rooms[user.roomGuid].users;
console.log('roomUsers');
console.log(roomUsers);
for (var i = 0; i < roomUsers.length; i++) {
var roomUser = roomUsers[i];
// TODO make this a stronger comparison
if(roomUser.username == user.username && roomUser.userId == user.userId){
console.log(roomUser);
Manager.Games[user.gameId].rooms[user.roomGuid].users.splice(i,1);
unsubscribe(socket,user);
}
};
// unsubscribe from the rooms
// for(var room in rooms){
// if(room && rooms[room]){
// unsubscribe(socket, { room: room.replace('/','') });
// }
// }
// client was unsubscribed from the rooms,
// now we can delete him from the hash object
delete Manager.Clients[socket.id];
}
// receive chat message from a client and
// send it to the relevant room
function chatmessage(socket, data){
//data = { msg,'Hello',room: '0764ec245e794adbfa81', username: 'nwalter', isHome: false }
// find the game id from the Manager.Rooms Hashtable
var room = Manager.Rooms[data.room];
var gameId = room.gameId;
// increment the totalComments in the game
Manager.Games[gameId].rooms[data.room].totalComments++;
// saves the message but doesn't block the thread
findIdByUsername(data.username ,function(userId){
data.userId = userId;
saveChatMessage(data,gameId);
});
socket.emit('log',{msg:data.username+': '+data.msg});
console.log( 'chatMessage = '+data.username +': '+ data.msg +" in "+ data.room+" is home "+data.isHome);
socket.broadcast.to(data.room).emit('chatmessage', { username: data.username, msg: data.msg, isHome: data.isHome});
}
function findIdByUsername(username,fn){
User.find({where : {username : username}})
.success(function(user){
fn(user.id);
})
.error(function(user){
});
}
function saveChatMessage(data, gameId){
console.log('saveChatMessage()');
console.log(data)
var room = Manager.Rooms[data.room];
var roomId = room.roomId;
var comment = {
msg : data.msg,
userId : data.userId,
roomId : roomId,
username : data.username,
isHome : data.isHome
};
Comment.build(comment)
.save()
.success(function(savedComment){
})
.error(function(error){
console.log('comment was not saved');
console.log(error);
}
);
}
// Room already prepared
// subscribe a client to a room
function subscribe(socket, data){
// increment the home or awayCount in the room
if(data.isHome === true){
Manager.Games[data.gameId].rooms[data.roomGuid].homeCount++;
}else{
Manager.Games[data.gameId].rooms[data.roomGuid].awayCount++;
}
// incrementing the number number of userCount in the room
Manager.Games[data.gameId].rooms[data.roomGuid].userCount++;
Manager.Games[data.gameId].rooms[data.roomGuid].totalUsage++;
// adding a new user to the specific game
Manager.Games[data.gameId].rooms[data.roomGuid]
.users.push(
{
userId : data.userId,
username : data.username,
isHome : data.isHome
}
);
console.log(Manager.Games[data.gameId].rooms[data.roomGuid]);
// PULL OUT GAMEId NOW?
socket.join(data.roomGuid);
socket.emit('log',{msg:'subscribe() - '+data.username+' to room '+data.roomGuid});
// update all other clients about the online presence
updatePresence(data.roomGuid, data, socket, 'online');
// send to the client a list of all subscribed clients in this room
// socket.emit('roomclients', { room: data.roomGuid, clients: getClientsInRoom(socket.id, data.room) });
}
// unsubscribe a client from a room, this can be
// occured when a client disconnected from the server
// or he subscribed to another room
function unsubscribe(socket, data){
console.log('unsubscribe()');
console.log(data);
// update all other clients about the offline
// presence
var room = Manager.Rooms[data.roomGuid];
var gameId = room.gameId;
if(data.isHome === true){
Manager.Games[gameId].rooms[data.roomGuid].homeCount--;
}else{
Manager.Games[gameId].rooms[data.roomGuid].awayCount--;
}
Manager.Games[gameId].rooms[data.roomGuid].userCount--;
updatePresence(data.roomGuid, data, socket, 'offline');
// remove the client from socket.io room
socket.leave(data.roomGuid);
// if this client was the only one in that room
// we are updating all clients about that the
// room is destroyed
// if(!countClientsInRoom(data.room)){
// // with 'io.sockets' we can contact all the
// // clients that connected to the server
// io.sockets.emit('removeroom', { room: data.room });
// }
}
// 'io.sockets.manager.rooms' is an object that holds
// the active room names as a key, returning array of
// room names
function getRooms(){
return Object.keys(Manager.rooms);
}
// get array of clients in a room
function getClientsInRoom(socketId, room){
// get array of socket ids in this room
var socketIds = io.sockets.manager.rooms['/' + room];
var clients = [];
if(socketIds && socketIds.length > 0){
socketsCount = socketIds.length;
// push every client to the result array
for(var i = 0, len = socketIds.length; i < len; i++){
// check if the socket is not the requesting
// socket
if(socketIds[i] != socketId){
clients.push(chatClients[socketIds[i]]);
}
}
}
return clients;
}
// get the amount of clients in aroom
function countClientsInRoom(room){
// 'io.sockets.manager.rooms' is an object that holds
// the active room names as a key and an array of
// all subscribed client socket ids
if(io.sockets.manager.rooms['/' + room]){
return io.sockets.manager.rooms['/' + room].length;
}
return 0;
}
// updating all other clients when a client goes
// online or offline.
function updatePresence(room, data, socket, state){
console.log('updatePresence() room = '+room+' state = '+state);
// socket.io may add a trailing '/' to the
// room name so we are clearing it
// room = room.replace('/','');
// by using 'socket.broadcast' we can send/emit
// a message/event to all other clients except
// the sender himself
socket.broadcast.to(room).emit('presence',
{
username : data.username,
client: Manager.Clients[socket.id],
state: state,
room: data.roomGuid
});
}
function getRecentChats(roomGuid, numChats ,fn){
console.log('getRecentChats()');
room = Manager.Rooms[roomGuid];
Comment.findAll({where: {roomId : room.roomId}, limit: numChats, order : 'id DESC'})
.success(function(comments){
if(comments){
var CommentList = [];
for (var i = 0; i < comments.length; i++) {
var comment = comments[i]
CommentList.push(comment);
}
CommentList.reverse();
fn(CommentList);
}else{
fn(false);
}
});
}
// unique id generator
function generateRoomGuid(){
var S4 = function () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return (S4() + S4() + S4() + S4() + S4());
}
// show a message in console
console.log('Chat server is running and listening to port %d...', port);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment