Socket.IO

We can send information to our servers when we submit user info or words but how do we get the server to update us without requesting updates manually? We use Socket.IO to enable real-time two-way event-based communication. Documentation for Socket.IO is available at socket.io/docs. We install it by executing the following code:

npm install --save socket.io

Socket handshaking, user join

First, we require socket.io and our game in socket.js:

var socketIO = require('socket.io'),
var Game = require('./app/controllers/game'),

Authorization takes place during handshaking, which is when the socket connection is established. Without handshaking, we would not know which socket connection belongs to which Express session. As given in the following code:

module.exports = function(server) {
  var io = socketIO(server, {transports: ['websocket']});

  io.use(function(socket, next) {var handshakeData = socket.request;
    console.log('socket handshaking', handshakeData._query.username);
    socket.user = handshakeData._query.username;

    Game.join(socket.user)
    .then(function(users) {console.log('game joined successfully', socket.user);
      socket.broadcast.emit('join', users);
      next();
    })
    .catch(function(err) {console.log('game joined fail', socket.user);
      next(err);
    });
  });

};

The io.use() method lets you give the Socket.IO server functions to run after a socket is created.

The request sent from the client (consisting of a URL and name) will be stored in handshakeData. The console will output the username and make sure that the sockets are handshaking.

Next, it will assign the username to socket.user so that it can be passed in to the join() function. The socket will call the Game.join() function and if the user is able to join, a console message will be displayed with the message game joined successfully and the name of the user.

The Socket.broadcast.emit method sends the message to all other clients except the newly created connection telling them that a new user has joined.

If the user was not successfully created (that is, there were two users with the same name) the error will be sent to the catch method and the console will log that the user was not able to join the game. Then, next(err) will send the error message back to the connecting client, so that on the client side we can show a pop-up message telling the user that the name is being used.

Adding and pushing updates to clients

With Socket.IO, you can send and receive any events you want as well as any data you want in the JSON format.

There are three additional socket events (after connecting) that we're going to need for our game: disconnect, chain (chain new word to last), and game status.

In socket.js, add these three socket events:

 io.sockets.on('connection', function(socket) {console.log('client connected via socket', socket.user);

    socket.on('disconnect', function() {console.log('socket user', socket.user, 'leave'),
      Game.leave(socket.user)
      .then(function(users) {socket.broadcast.emit('leave', users);
      });
    });

    socket.on('chain', function(word, responseFunc) {console.log('socket chain', word);
      Game.chain(socket.user, word)
      .then(function(stat) {console.log('successful to chain', stat);
        if (responseFunc) {responseFunc({status: 200,
            resp: stat
          });
        }
        console.log('broadcasting from', socket.user, stat);
        socket.broadcast.emit('stat', stat);
      })
      .catch(function(err) {console.log('fail to chain', err);
        if (responseFunc) {responseFunc(err);
        }
      });
    });

    socket.on('game', function(query, responseFunc) {console.log('socket stat', socket.user);
      Game.state()
      .then(function(game) {console.log('socket stat end', game);
        if (responseFunc) {responseFunc(game);
        }
      });
    });

    socket.on('error', function(err) {console.error('error', err);
    });
  });

The first socket event connection we subscribe to will be triggered when a user establishes a socket connection with the server. Once a client is connected, we log that event and display their name on to the console so that we know who is connected.

The second event disconnect will be triggered when users are disconnected from the server. It happens when they leave the game or the network connection is broken. Once this event is triggered, we broadcast to all other sockets that the user has left (via socket.broadcast.emit) so that the other users can see that the disconnected user is no longer in the list of active players.

The last two socket events, chain and game, are game actions.

The chain takes in the user's submitted word and calls the Game.chain() function; if it succeeds, then it logs that the chain was successful and broadcasts the status to all other users.

The game responds with the latest game status.

Launch Socket.IO applications

To launch our game, let's create a launch script called www, and place it under the bin folder. This is our code for ./bin/www as given in the following:

#!/usr/bin/env node
var app = require('../app'),
var socket = require('../socket'),

app.set('port', process.env.PORT || 3000);

var server = app.listen(app.get('port'), function() {console.log('Express server listening on port ' + server.address().port);
});
socket(server);

The first line tells shell which interpreter should be used to execute this script. Here, we tell shell that the interpreter is node. Then, we can launch the server locally with the following command:

./bin/www

Next, in bin/www, we will set up an Express application listening on a port, which is defined in the environment variable or 3000 if nothing is there.

We then bind socket to our HTTP server, which is created by our Express application. Since the Socket.IO server needs to be attached to an HTTP server, we pass the server object to the socket function, where we initialize the socket server.

So now we have the launch script in place. If we launch the server locally, we will see the following message printed to the console:

$ ./bin/www
connecting db...
Express server listening on port 3000
db connected

Test Socket.IO applications with the Socket.IO client

We will write the JavaScript for the client-side frontend application, which we will test our game with.

You can find the JavaScript file under public/javascripts/app.js and the view in app/views/index.jade. We will not be covering frontend components such as jade and stylus/css in this book.

We begin by setting up our game with all our variables, which are classes in the index.jade file that we will refer to. We also initialize our game with the init() function, which will be described in the next code block:

$(function() {var game = new Game({$viewLogin: $('.view-login'),$viewGame: $('.view-game'),$username: $('.username'),$wordInput: $('.word-input'),$word: $('.word'),$bywho: $('.bywho'),$users: $('.users'),});
});

var Game = function(views) {this.views = views;

  this.init();
};

The Game.prototype adds functions to our Game method in app/controllers/game.js. We will break this up into several smaller code blocks to show the client-side logic that we're working with.

The init() function begins by bringing the username input box into focus, and then when the submit button is pressed, obtain the value of the user input and assign it to the variable username.

We then send the user name to the join() function listed as follows, in the next code block.

We also set up a function that will take the input from the submit button for chain (which is where you input the word you would like to chain with the current word), store it in the chain variable, and then send it to the chain function (discussed later) and clear out the text input box.

Game.prototype = {init: function() {var me = this;

    this.views.$username.focus();

    this.views.$viewLogin.submit(function() {var username = me.views.$username.val();me.join(username);
      return false;
    });

    this.views.$viewGame.submit(function() {var word = me.views.$wordInput.val();
      me.chain(word);
      me.views.$chain.val(''),
      return false;
    });
  };

The login UI will look like this:

Test Socket.IO applications with the Socket.IO client

When a user submits a user name, it is passed on to the join function, which first establishes a socket connection and then calls User.join() (covered earlier) on the server (game.js) and initializes a socket handshake (with the configuration to only use WebSocket as the transport) with the submitted username and a URL that consists of /?username= + username.

When the connection is established, the socket emits the game status and users list (updateStat() and updateUsers() functions, which we will discuss later) and calls the showGameView() function.

The showGameView() function (see the following code block) hides the login form, displays the view-game form where you can input a word to chain, and focuses on the chain input box.

 join: function(username) {var socket = io.connect('/?username=' + username, {transports: ['websocket'],
    });

    this.socket = socket;

    var me = this;
    this.socket.on('connect', function() {console.log('connect'),
      me.socket.emit('game', null, function(game) {console.log('stat', game);
        me.updateStat(game.stat);
        me.updateUsers(game.users);
      });

      me.showGameView();
    });

    this.socket.on('join', function(users) {me.updateUsers(users);
    });

    this.socket.on('leave', function(users) {me.updateUsers(users);
    });

    this.socket.on('stat', function(stat) {me.updateStat(stat);
    });
  },

  showGameView: function() {this.views.$viewLogin.hide();
    this.views.$viewGame.show();
    this.views.$wordInput.focus();
  },

When a user joins or leaves the game, it's passed to the socket server (game.js) join or leave functions, and the client-side updateUsers() function is called.

The updateUsers() function takes the array of users that was returned by the server and maps it to get the usernames that are displayed as a list.

Similarly, when a stat call is made to the server, updateStat() method gets called, which receives the current word (stat.word) from the server and displays it.

Additionally, the input box will contain the last letter of that word as a placeholder and the user who submitted the current word will be displayed by accessing the user array and popping out the last user.

  updateStat: function(stat) {this.views.$word.html(stat.word);

    this.views.$wordInput.attr('placeholder', stat.word.substr(-1));
    this.views.$bywho.html('current word updated by: ' + stat.used.pop().user);
  },

  updateUsers: function(users) {this.views.$users.html(users.map(function(user) {return '<li>' + user + '</li>';
    }).join(''));
  },

The chain function given in the following alerts a user if they try to submit without entering a word; it then sends a call to the server's chain function, the input word, and the callback function, which will output the data received from the server (which is the response word and used array).

Looking in the server's socket code (socket.js line 47), if a callback is present, and the function was successful, then a status of 200 is sent back.

If the client side receives a status of 200, then it will call the updateStat() function with data.resp, which is the stat object containing the word and used words; otherwise, if no data was received from the server or the chain was unsuccessful and a status code that is not 200 is sent back, the user will see an alert telling them that their input word doesn't chain with the current word.

 chain: function(word) {if (!word) {return alert('Please input a word'),
    }

    var me = this;
    this.socket.emit('chain', word, function(data) {console.log('chain', data);
      if (!data || data.status !== 200) {return alert('Your word "' + word + ' can't chain with current word.'),
      }

      me.updateStat(data.resp);
    });
  }
};
Test Socket.IO applications with the Socket.IO client

Debug Socket.IO with Chrome Developer Tools

To debug Socket.IO, we want to know what socket request we send to our server, what the request arguments are, and what the broadcast messages look like. Chrome has a built-in powerful WebSocket debugging tool; let's see how to use it.

To open Chrome Developer Tools, go to the menu, select View | Developer | Developer Tools. You can also right click on the page, and select Inspect Element.

From the Developer tools, select the Network panel.

Debug Socket.IO with Chrome Developer Tools

Now when we go back to the page and join the game, we will see a Socket.IO request in the Network panel of the Chrome Developer Tools. The request URL is ws://127.0.01:3000/socket.io/?username=marc&EIO=2&transport=websocket and Status Code is 101 Switching Protocols, meaning we passed the handshaking and established a socket connection with the server.

Now, click on the Frames tab on the right-hand side panel. We will see some messages there in the table. The white rows are the messages our client sent to the server and the green rows are the messages that the server sent to the client.

Debug Socket.IO with Chrome Developer Tools

Let's take a look at each row and understand what happened in the game.

0{"sid":"XNhi9CiZ-rbgbS5VAAAC","upgrades":[],"pingInterval":25000,"pingTimeout":60000}:. After the connection is established, the server returned some configuration to the client such as the socket session id (sid), pingInterval, and pingTimeout.

420["game",null]: The client sent a socket request to get the latest game status.

430[{"users":["leo"],"stat":{"word":"what","_id":"54cec37c0ffeb2cca1778ae6","__v":0,"used":[{"word":"what","user":"admin"}]}}]: The server responded with the latest game status, which shows that the current word is what.

421["chain","tomorrow"]: The client sent a request to chain the current word what with tomorrow.

431[{"status":200,"resp":{"__v":0,"_id":"54cec37c0ffeb2cca1778ae6","word":"tomorrow","used":[{"word":"what","user":"admin"},{"user":"leo","word":"tomorrow"}]}}]: The server accepted the request and returned the updated game status. So now, the current word is tomorrow

42["join",["leo","marc"]]: marc joined the game. Now we have leo and marc in the game.

42["stat",{"__v":0,"_id":"54cec37c0ffeb2cca1778ae6","word":"we","used":[{"word":"what","user":"admin"},{"user":"leo","word":"tomorrow"},{"user":"marc","word":"we"}]}]: Here marc chained the current word tomorrow with we. So the server pushed the game status to the client.

42["join",["leo","marc","geoffrey"]]: geoffrey joined the game. Now we have three players in the game: leo, marc, and geoffrey.

42["leave",["leo","geoffrey"]]: marc left the game, leo and geoffrey are still in the game.

Now you've had a chance to actually test the game developed for this app and can see how the different aspects intertwine.

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

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