"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);