summaryrefslogblamecommitdiff
path: root/index.js
blob: 5df3ccf84c0211d5a7e703e2efaed2cd9f9d9e2e (plain) (tree)
1
             
















                                                               
                                                 










                                                                               



                                                                                               



                                    
                                       
               



                                                                     



                                        
 

                                      
                                                                 

                        
           
   



                                                             
                                                                        
                                         
                                            



























                                                                                                                              
     




                                                    
                      
                  
       




                                                          

               








                                                  
                                               







                                                  
       





                                                                                        
         




















                                                                                
       


























                                                     

       







                                    
                               
                                
     
                                         
   
 
 
                              
                    
"use strict";
/* Deps */
var http = require('http')
  , path = require('path')
  , connect = require('connect')
  , express = require('express')
  , app = express()
  , logger = require('logger').createLogger('manavis.log')
  , cookieParser = express.cookieParser('your secret sauce')
  , sessionStore = new connect.middleware.session.MemoryStore()
  ;

app.configure(function () {
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(cookieParser);
  app.use(express.session({ store: sessionStore }));
  app.use(app.router);
  app.use(express.static(__dirname + "/public"));
});

var server = http.createServer(app)
  , io = require('socket.io').listen(server)
  , SessionSockets = require('session.socket.io')
  , sessionSockets = new SessionSockets(io, sessionStore, cookieParser)
  ;
/* End deps */

/* Only one level is logged, and numerical timestamps are easier to compare. */
logger.format = function(level, date, message) {
  return JSON.stringify({
    date: +date,
    data: JSON.parse(message) /* TODO: Get rid of encoding/decoding hack (write own logger?) */
  });
}

/* Number of sessions we've seen. */
var count = 0;
/* nid -> { nick, filters, channel } */
var users = {};
/* nid -> socket */
var sockets = {};
/* FIXME: Workaround to prevent logout propagating during ghosting */
var ghosting = false;
/* Auto-incrementing room count */
var channelCount = 0;
/* Channel ID -> { usernum, filters } */
var channels = {};

var logServerAction = entityLogger(0);

sessionSockets.on('connection', function (err, socket, session) {
  if (!session) {
    socket.disconnect();
    return;
  }
  /*
   * Don't do anything until they send a login message.
   * Later versions might also check a protocol version here.
   */
  /* Someone new connected. Restore or initialise their session data. */
  session.nid = session.nid || (++count);
  var logAction = entityLogger(session.nid);
  session.nick = session.nick || null;
  session.save();
  if (session.nid in sockets) {
    /* Ghost the old session */
    ghosting = true;
    sockets[session.nid].disconnect();
    ghosting = false;
  }
  sockets[session.nid] = socket;
  /* New user! */
  logAction("CONNECT", { "ip": socket.handshake.address.address, "proxied-ip": socket.handshake.headers['x-forwarded-for'] });
  users[session.nid] = { nick: session.nick, filters: {} };
  /* Let them know of their data. */
  socket.emit('selflogin', {
    id: session.nid,
    nick: session.nick
  });
  /* Let everyone else know that someone connected. */
  socket.broadcast.emit('login', {
    id: session.nid,
    nick: session.nick
  });
  /* Send the new user the userlist. */
  socket.emit('users', { users: users });
  /* Set up various handlers for the new socket. */
  socket.on('nick', function (d) {
    if (!(typeof(d) == "object" && "nick" in d)) {
      return;
    }
    /* TODO Collision checking? */
    users[session.nid].nick = session.nick = d.nick;
    session.save();
    logAction("NICK", d.nick);
    io.sockets.emit('nickset', {
      id: session.nid,
      nick: d.nick
    });
  });
  socket.on('join', function(d) {
    var channel;
    if (d != null) {
      if (!(typeof(d) == "number" && d <= channelCount)) {
        return;
      }
      /* Join an existing channel */
      channel = d;
    } else {
      /* Automagically create a new channel */
      channel = ++channelCount;
    }
    logAction("JOIN", users[session.nid].channel);
    if ("channel" in users[session.nid]) {
      /* Leave any channel we're in */
      socket.leave(users[session.nid].channel);
    }
    /* Inform socket.io about the channel join */
    socket.join(channel);
    /* Let everyone know about the channel join */
    users[session.nid].channel = channel;
    io.sockets.emit('join', {
      id: session.nid,
      channel: channel
    });
    /* Update the channel information */
    if (channel in channels) {
      /* This channel already exists. Inform the joining user of the current filters. */
      socket.emit('filterset', {
        id: 0, /* Server */
        filters: channels[channel].filters
      });
      ++channels[channel].usernum;
    } else {
      /* This channel didn't already exist, so create it and set the filters. */
      channels[channel] = {
        usernum: 1,
        filters: users[session.nid].filters
      };
    }
  });
  socket.on('part', function() {
    if (!users[session.nid].channel) {
      return;
    }
    logAction("PART");
    socket.leave(users[session.nid].channel);
    if (!--channels[users[session.nid].channel].usernum) {
      delete channels[users[session.nid].channel];
    }
    delete users[session.nid].channel;
    io.sockets.emit('part', {
      id: session.nid
    });
  });
  socket.on('filter', function(d) {
    if (!(typeof(d) == "object" && "filters" in d)) {
      return;
    }
    users[session.nid].filters = d.filters;
    logAction("FILTER", d.filters);
    var channel = users[session.nid].channel;
    if (!channel) {
      return;
    }
    channels[channel].filters = d.filters;
    socket.broadcast.to(channel).emit('filterset', {
      id: session.nid,
      filters: d.filters
    });
  });
  socket.on('disconnect', function() {
    if (ghosting) {
      logAction("GHOSTED");
      return;
    }
    logAction("DISCONNECT");
    delete sockets[session.nid];
    delete users[session.nid];
    socket.broadcast.emit('logout', {
      id: session.nid
    });
  });
});

function entityLogger(id) {
  return function(action, content) {
    var message = {
      user:id,
      action:action
    };
    if (arguments.length > 1) {
      message.content = content;
    }
    logger.info(JSON.stringify(message));
  }
}

logServerAction(0, "STARTUP");
server.listen(3000);