Sharing the session

Now we have support for multiple rooms, but it is very clumsy to enter a nickname every time we enter a room. Let us modify our system to accept the nickname once when entering the system and use it in all the rooms.

For this, let us start by modifying the landing page to add an input box to accept a nickname and add a JavaScript file to add the logic:

extends layout

block append head
  script(type='text/javascript', src='/javascripts/landing.js')

block content
  section#welcome
    div Welcome
    span
      input#nickname(type="text", 
    placeholder="Enter a nickname")
      a#startchat(class="btn") Login

Here, in the preceding code, we are adding a script entry to add landing.js and replacing the Start now button with the field to enter a name and a Login button. Next, let us take a look at landing.js:

$(function(){
   $('#startchat').click(function(){
     document.cookie = "nickname=" + $('#nickname').val() 
              + ";; path=/";
     window.location = "/rooms";
   });
});

In the previous code, we are attaching a click handler to the startchat button. In the handler, we are adding the nickname entered by the user to the cookie and redirecting the user to /rooms. We will be reading this cookie information while connecting the socket and then setting it on the socket. Before this cookie information can be accessed in the socket connection, we need to lay down some ground work to enable cookies in the Express.js application. For this, edit the app.js code by referring to the following code block:

var express = require('express')
  , routes = require('./routes')
  , http = require('http')
  , path = require('path')
  , connect = require('connect'),

var app = express();

var sessionStore = new connect.session.MemoryStore();

app.configure(function(){
  //EXISTING CODE
  app.use(express.bodyParser());
  app.use(express.cookieParser('somesuperspecialsecrethere'));
  app.use(express.session({ key: 'express.sid', 
                  store: sessionStore}));
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(path.join(__dirname, 'public')));
});

//EXISTING CODE

The first step is to add connect as a dependency in the package.json file and the require keyword in app.js. The connect keyword is used to create a session store; in this case, an in-memory session store:

var sessionStore = new connect.session.MemoryStore();

We also enable the cookieParser middleware and the session module in the express application. Express' cookieParser middleware will take a secret parameter, which will be used to encrypt the cookies. The express' session module is initialized along with passing it the key (express.sid is the key for a session) and a store where the session should be maintained. In the following code, we are passing it an in-memory store, which we created in the previous step:

  app.use(express.bodyParser());
  app.use(express.cookieParser('somesuperspecialsecrethere'));
  app.use(express.session({ key: 'express.sid', 
                  store: sessionStore}));
  app.use(express.methodOverride());
  app.use(app.router);

One important point to note about the previous code is the order of adding these two middleware components. These should be added after adding the bodyParser middleware and before adding the router middleware. If you open the browser and browse to the landing page now, you can see the cookie with the express.sid key in the browser's debugging tools under the Cookies tab. If you enter a name and click the Enter button, you will again see a new cookie, named after your nickname, being set:

var io = require('socket.io'),

exports.initialize = function (server) {
  io = io.listen(server);

  io.set('authorization', function (data, accept) {
    if (data.headers.cookie) {
      data.cookie = require('cookie').parse(data.headers.cookie);
      data.sessionID = data.cookie['express.sid'].split('.')[0];
      data.nickname = data.cookie['nickname'];
    } else {
      return accept('No cookie transmitted.', false);
    }
    accept(null, true);
  });

  var self = this;

  this.chatInfra = io.of("/chat_infra");
  this.chatInfra.on("connection", function (socket) {
    socket.on("join_room", function (room) {
      var nickname = socket.handshake.nickname;
      socket.set('nickname', nickname, function () {
        socket.emit('name_set', {'name':
       socket.handshake.nickname});
        socket.send(JSON.stringify({type:'serverMessage',
          message:'Welcome to the most interesting ' +
              'chat room on earth!'}));
         socket.join(room.name);
         var comSocket = self.chatCom.sockets[socket.id];
         comSocket.join(room.name);
         comSocket.room = room.name;
         socket.in(room.name).broadcast.emit('user_entered', 
                      {'name':nickname});
      });
    });

    //EXISTING CODE
}

The first change in the preceding code block introduces us to a new feature in socket.io; this change is shown in the following highlighted code block:

  io.set('authorization', function (data, accept) {
    if (data.headers.cookie) {
      data.cookie = require('cookie').parse(data.headers.cookie);
      data.sessionID = data.cookie['express.sid'].split('.')[0];
      data.nickname = data.cookie['nickname'];
    } else {
      return accept('No cookie transmitted.', false);
    }
    accept(null, true);
  });

In this code snippet, we are setting an authorization method for the socket. This method will get two parameters, the data that contains all the HTTP request information and the accept method callback. The authorization method is called when a socket.io connection is requested but before it is established.

We can use this method for actually performing an authorization, but in our case we will just use it to get the nickname from the cookies, as this is the only socket.io method that will have the HTTP data available with it.

We are reading the cookie headers from the HTTP data and are parsing it using the cookie module's parse method. From the cookie, we are extracting the sessionID value and the nickname and setting it to the data object. This object is available on the socket as the handshake property. Finally, we will call the accept callback, which accepts two parameters, first a message and another a Boolean variable, indicating whether the authorization was successful or not.

We will remove the set_name handler, as this handler need not be called because we already have the name with us. We will move the logic from the set_name handler to the join_room handler:

    socket.on("join_room", function (room) {
      var nickname = socket.handshake.nickname;
      socket.set('nickname', nickname, function () {
        socket.emit('name_set', {'name': nickname});
        socket.send(JSON.stringify({type:'serverMessage',
          message:'Welcome to the most interesting ' +
              'chat room on earth!'}));
         socket.join(room.name);
         var comSocket = self.chatCom.sockets[socket.id];
         comSocket.join(room.name);
         comSocket.room = room.name;
         socket.in(room.name).broadcast.emit('user_entered', 
                      {'name':nickname});
      });
    });

In the join_room handler, we will fetch the nickname from the socket.handshake map and set it as a property on the socket. On setting the nickname property, we will still trigger the name_set event so as to keep the changes on the client to a minimum:

var chatInfra = io.connect('/chat_infra'),
    chatCom = io.connect('/chat_com'),

var roomName = decodeURI((RegExp("room" + '=' + '(.+?)(&|$)').exec(location.search) || [, null])[1]);

if (roomName) {
  chatInfra.emit('join_room', {'name':roomName});

  chatInfra.on('name_set', function (data) {
  //EXISTING CODE
  });
}

As the join_room handler is the initializer for the room on the server, we will take it out of the name_set handler and directly call it during the page load. The rest of the code remains as is.

To try this code, you will have to open two different browsers or browsers in different incognito sessions as the cookies/sessions will be shared for the same browser.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset