Lesson 8: Adding Real-time Functionality Using Socket.io

In previous Lessons, you learned how to build your MEAN application and how to create CRUD modules. These Lessons covered the basic functionalities of a web application; however, more and more applications require real-time communication between the server and browser. In this Lesson, you'll learn how to connect your Express and AngularJS applications in real time using the Socket.io module. Socket.io enables Node.js developers to support real-time communication using WebSockets in modern browsers and legacy fallback protocols in older browsers. In this Lesson, we'll cover the following topics:

  • Setting up the Socket.io module
  • Configuring the Express application
  • Setting up the Socket.io/Passport session
  • Wiring Socket.io routes
  • Using the Socket.io client object
  • Building a simple chat room

Introducing WebSockets

Modern web applications such as Facebook, Twitter, or Gmail are incorporating real-time capabilities, which enable the application to continuously present the user with recently updated information. Unlike traditional applications, in real-time applications the common roles of browser and server can be reversed since the server needs to update the browser with new data, regardless of the browser request state. This means that unlike the common HTTP behavior, the server won't wait for the browser's requests. Instead, it will send new data to the browser whenever this data becomes available.

This reverse approach is often called Comet, a term coined by a web developer named Alex Russel back in 2006 (the term was a word play on the AJAX term; both Comet and AJAX are common household cleaners in the US). In the past, there were several ways to implement a Comet functionality using the HTTP protocol.

The first and easiest way is XHR polling. In XHR polling, the browser makes periodic requests to the server. The server then returns an empty response unless it has new data to send back. Upon a new event, the server will return the new event data to the next polling request. While this works quite well for most browsers, this method has two problems. The most obvious one is that using this method generates a large number of requests that hit the server with no particular reason, since a lot of requests are returning empty. The second problem is that the update time depends on the request period. This means that new data will only get pushed to the browser on the next request, causing delays in updating the client state. To solve these issues, a better approach was introduced: XHR long polling.

In XHR long polling, the browser makes an XHR request to the server, but a response is not sent back unless the server has a new data. Upon an event, the server responds with the event data and the browser makes a new long polling request. This cycle enables a better management of requests, since there is only a single request per session. Furthermore, the server can update the browser immediately with new information, without having to wait for the browser's next request. Because of its stability and usability, XHR long polling has become the standard approach for real-time applications and was implemented in various ways, including Forever iFrame, multipart XHR, JSONP long polling using script tags (for cross-domain, real-time support), and the common long-living XHR.

However, all these approaches were actually hacks using the HTTP and XHR protocols in a way they were not meant to be used. With the rapid development of modern browsers and the increased adoption of the new HTML5 specifications, a new protocol emerged for implementing real-time communication: the full duplex WebSockets.

In browsers that support the WebSockets protocol, the initial connection between the server and browser is made over HTTP and is called an HTTP handshake. Once the initial connection is made, the browser and server open a single ongoing communication channel over a TCP socket. Once the socket connection is established, it enables bidirectional communication between the browser and server. This enables both parties to send and retrieve messages over a single communication channel. This also helps to lower server load, decrease message latency, and unify PUSH communication using a standalone connection.

However, WebSockets still suffer from two major problems. First and foremost is browser compatibility. The WebSockets specification is fairly new, so older browsers don't support it, and though most modern browsers now implement the protocol, a large group of users are still using these older browsers. The second problem is HTTP proxies, firewalls, and hosting providers. Since WebSockets use a different communication protocol than HTTP, a lot of these intermediaries don't support it yet and block any socket communication. As it has always been with the Web, developers are left with a fragmentation problem, which can only be solved using an abstraction library that optimizes usability by switching between protocols according to the available resources. Fortunately, a popular library called Socket.io was already developed for this purpose, and it is freely available for the Node.js developer community.

Introducing WebSockets

Introducing Socket.io

Created in 2010 by JavaScript developer, Guillermo Rauch, Socket.io aimed to abstract Node.js' real-time application development. Since then, it has evolved dramatically, released in nine major versions before being broken in its latest version into two different modules: Engine.io and Socket.io.

Previous versions of Socket.io were criticized for being unstable, since they first tried to establish the most advanced connection mechanisms and then fallback to more primitive protocols. This caused serious issues with using Socket.io in production environments and posed a threat to the adoption of Socket.io as a real-time library. To solve this, the Socket.io team redesigned it and wrapped the core functionality in a base module called Engine.io.

The idea behind Engine.io was to create a more stable real-time module, which first opens a long-polling XHR communication and then tries to upgrade the connection to a WebSockets channel. The new version of Socket.io uses the Engine.io module and provides the developer with various features such as events, rooms, and automatic connection recovery, which you would otherwise implement by yourself. In this Lesson's examples, we will use the new Socket.io 1.0, which is the first version to use the Engine.io module.

Note

Older versions of Socket.io prior to Version 1.0 are not using the new Engine.io module and therefore are much less stable in production environments.

When you include the Socket.io module, it provides you with two objects: a socket server object that is responsible for the server functionality and a socket client object that handles the browser's functionality. We'll begin by examining the server object.

The Socket.io server object

The Socket.io server object is where it all begins. You start by requiring the Socket.io module, and then use it to create a new Socket.io server instance that will interact with socket clients. The server object supports both a standalone implementation and the ability to use it in conjunction with the Express framework. The server instance then exposes a set of methods that allow you to manage the Socket.io server operations. Once the server object is initialized, it will also be responsible for serving the socket client JavaScript file for the browser.

A simple implementation of the standalone Socket.io server will look as follows:

var io = require('socket.io')();
io.on('connection', function(socket){ /* ... */ });
io.listen(3000);

This will open a Socket.io over the 3000 port and serve the socket client file at the URL http://localhost:3000/socket.io/socket.io.js. Implementing the Socket.io server in conjunction with an Express application will be a bit different:

var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
io.on('connection', function(socket){ /* ... */ });
server.listen(3000);

This time, you first use the http module of Node.js to create a server and wrap the Express application. The server object is then passed to the Socket.io module and serves both the Express application and the Socket.io server. Once the server is running, it will be available for socket clients to connect. A client trying to establish a connection with the Socket.io server will start by initiating the handshaking process.

Socket.io handshaking

When a client wants to connect the Socket.io server, it will first send a handshake HTTP request. The server will then analyze the request to gather the necessary information for ongoing communication. It will then look for configuration middleware that is registered with the server and execute it before firing the connection event. When the client is successfully connected to the server, the connection event listener is executed, exposing a new socket instance.

Once the handshaking process is over, the client is connected to the server and all communication with it is handled through the socket instance object. For example, handling a client's disconnection event will be as follows:

var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
io.on('connection', function(socket){ 
  socket.on('disconnect', function() {
    console.log('user has disconnected');
  });
});
server.listen(3000);

Notice how the socket.on() method adds an event handler to the disconnection event. Although the disconnection event is a predefined event, this approach works the same for custom events as well, as you will see in the following sections.

While the handshake mechanism is fully automatic, Socket.io does provide you with a way to intercept the handshake process using a configuration middleware.

The Socket.io configuration middleware

Although the Socket.io configuration middleware existed in previous versions, in the new version it is even simpler and allows you to manipulate socket communication before the handshake actually occurs. To create a configuration middleware, you will need to use the server's use() method, which is very similar to the Express application's use() method:

var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);

io.use(function(socket, next) {
  /* ... */

  next(null, true);
});

io.on('connection', function(socket){ 
  socket.on('disconnect', function() {
    console.log('user has disconnected');
  });
});

server.listen(3000); 

As you can see, the io.use() method callback accepts two arguments: the socket object and a next callback. The socket object is the same socket object that will be used for the connection and it holds some connection properties. One important property is the socket.request property, which represents the handshake HTTP request. In the following sections, you will use the handshake request to incorporate the Passport session with the Socket.io connection.

The next argument is a callback method that accepts two arguments: an error object and Boolean value. The next callback tells Socket.io whether or not to proceed with the handshake process, so if you pass an error object or a false value to the next method, Socket.io will not initiate the socket connection. Now that you have a basic understanding of how handshaking works, it is time to discuss the Socket.io client object.

The Socket.io client object

The Socket.io client object is responsible for the implementation of the browser socket communication with the Socket.io server. You start by including the Socket.io client JavaScript file, which is served by the Socket.io server. The Socket.io JavaScript file exposes an io() method that connects to the Socket.io server and creates the client socket object. A simple implementation of the socket client will be as follows:

<script src="/socket.io/socket.io.js"></script>

<script>
  var socket = io();
  socket.on('connect', function() {
      /* ... */
  });
</script>

Notice the default URL for the Socket.io client object. Although this can be altered, you can usually leave it like this and just include the file from the default Socket.io path. Another thing you should notice is that the io() method will automatically try to connect to the default base path when executed with no arguments; however, you can also pass a different server URL as an argument.

As you can see, the socket client is much easier to implement, so we can move on to discuss how Socket.io handles real-time communication using events.

Socket.io events

To handle the communication between the client and the server, Socket.io uses a structure that mimics the WebSockets protocol and fires events messages across the server and client objects. There are two types of events: system events, which indicate the socket connection status, and custom events, which you'll use to implement your business logic.

The system events on the socket server are as follows:

  • io.on('connection', ...): This is emitted when a new socket is connected
  • socket.on('message', ...): This is emitted when a message is sent using the socket.send() method
  • socket.on('disconnect', ...): This is emitted when the socket is disconnected

The system events on the client are as follows:

  • socket.io.on('open', ...): This is emitted when the socket client opens a connection with the server
  • socket.io.on('connect', ...): This is emitted when the socket client is connected to the server
  • socket.io.on('connect_timeout', ...): This is emitted when the socket client connection with the server is timed out
  • socket.io.on('connect_error', ...): This is emitted when the socket client fails to connect with the server
  • socket.io.on('reconnect_attempt', ...): This is emitted when the socket client tries to reconnect with the server
  • socket.io.on('reconnect', ...): This is emitted when the socket client is reconnected to the server
  • socket.io.on('reconnect_error', ...): This is emitted when the socket client fails to reconnect with the server
  • socket.io.on('reconnect_failed', ...): This is emitted when the socket client fails to reconnect with the server
  • socket.io.on('close', ...): This is emitted when the socket client closes the connection with the server

Handling events

While system events are helping us with connection management, the real magic of Socket.io relies on using custom events. In order to do so, Socket.io exposes two methods, both on the client and server objects. The first method is the on() method, which binds event handlers with events and the second method is the emit() method, which is used to fire events between the server and client objects.

An implementation of the on() method on the socket server is very simple:

var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);

io.on('connection', function(socket){ 
  socket.on('customEvent', function(customEventData) {
    /* ... */
  });
});

server.listen(3000); 

In the preceding code, you bound an event listener to the customEvent event. The event handler is being called when the socket client object emits the customEvent event. Notice how the event handler accepts the customEventData argument that is passed to the event handler from the socket client object.

An implementation of the on() method on the socket client is also straightforward:

<script src="/socket.io/socket.io.js"></script>

<script>
  var socket = io();
  socket.on('customEvent', function(customEventData) {
    /* ... */
  });
</script>

This time the event handler is being called when the socket server emits the customEvent event that sends customEventData to the socket client event handler.

Once you set your event handlers, you can use the emit() method to send events from the socket server to the socket client and vice versa.

Emitting events

On the socket server, the emit() method is used to send events to a single socket client or a group of connected socket clients. The emit() method can be called from the connected socket object, which will send the event to a single socket client, as follows:

io.on('connection', function(socket){ 
  socket.emit('customEvent', customEventData);
});

The emit() method can also be called from the io object, which will send the event to all connected socket clients, as follows:

io.on('connection', function(socket){ 
  io.emit('customEvent', customEventData);
});

Another option is to send the event to all connected socket clients except from the sender using the broadcast property, as shown in the following lines of code:

io.on('connection', function(socket){ 
  socket.broadcast.emit('customEvent', customEventData);
});

On the socket client, things are much simpler. Since the socket client is only connected to the socket server, the emit() method will only send the event to the socket server:

var socket = io();
socket.emit('customEvent', customEventData);

Although these methods allow you to switch between personal and global events, they still lack the ability to send events to a group of connected socket clients. Socket.io offers two options to group sockets together: namespaces and rooms.

Socket.io namespaces

In order to easily control socket management, Socket.io allow developers to split socket connections according to their purpose using namespaces. So instead of creating different socket servers for different connections, you can just use the same server to create different connection endpoints. This means that socket communication can be divided into groups, which will then be handled separately.

Socket.io server namespaces

To create a socket server namespace, you will need to use the socket server of() method that returns a socket namespace. Once you retain the socket namespace, you can just use it the same way you use the socket server object:

var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);

io.of('/someNamespace').on('connection', function(socket){ 
  socket.on('customEvent', function(customEventData) {
    /* ... */
  });
});

io.of('/someOtherNamespace').on('connection', function(socket){ 
  socket.on('customEvent', function(customEventData) {
    /* ... */
  });
});


server.listen(3000);

In fact, when you use the io object, Socket.io actually uses a default empty namespace as follows:

io.on('connection', function(socket){ 
/* ... */
});

The preceding lines of code are actually equivalent to this:

io.of('').on('connection', function(socket){ 
/* ... */
});

Socket.io client namespaces

On the socket client, the implementation is a little different:

<script src="/socket.io/socket.io.js"></script>

<script>
  var someSocket = io('/someNamespace');
  someSocket.on('customEvent', function(customEventData) {
    /* ... */
  });

  var someOtherSocket = io('/someOtherNamespace');
  someOtherSocket.on('customEvent', function(customEventData) {
    /* ... */
  });
</script>

As you can see, you can use multiple namespaces on the same application without much effort. However, once sockets are connected to different namespaces, you will not be able to send an event to all these namespaces at once. This means that namespaces are not very good for a more dynamic grouping logic. For this purpose, Socket.io offers a different feature called rooms.

Socket.io rooms

Socket.io rooms allow you to partition connected sockets into different groups in a dynamic way. Connected sockets can join and leave rooms, and Socket.io provides you with a clean interface to manage rooms and emit events to the subset of sockets in a room. The rooms functionality is handled solely on the socket server but can easily be exposed to the socket client.

Joining and leaving rooms

Joining a room is handled using the socket join() method, while leaving a room is handled using the leave() method. So, a simple subscription mechanism can be implemented as follows:

io.on('connection', function(socket) {
    socket.on('join', function(roomData) {
        socket.join(roomData.roomName);
    })

    socket.on('leave', function(roomData) {
        socket.leave(roomData.roomName);
    })
});

Notice that the join() and leave() methods both take the room name as the first argument.

Emitting events to rooms

To emit events to all the sockets in a room, you will need to use the in() method. So, emitting an event to all socket clients who joined a room is quite simple and can be achieved with the help of the following code snippets:

io.on('connection', function(socket){ 
	io.in('someRoom').emit('customEvent', customEventData);
});

Another option is to send the event to all connected socket clients in a room except the sender by using the broadcast property and the to() method:

io.on('connection', function(socket){ 
	socket.broadcast.to('someRoom').emit('customEvent', customEventData);
});

This pretty much covers the simple yet powerful room functionality of Socket.io. In the next section, you will learn how implement Socket.io in your MEAN application, and more importantly, how to use the Passport session to identify users in the Socket.io session. The examples in this Lesson will continue directly from those in previous Lessons, so copy the final example from Lesson 7, Creating a MEAN CRUD Module, and let's start from there.

Note

While we covered most of Socket.io features, you can learn more about Socket.io by visiting the official project page at http://socket.io/.

Installing Socket.io

Before you can use the Socket.io module, you will need to install it using npm. To do so, change your package.json file as follows:

{
  "name": "MEAN",
  "version": "0.0.9",
  "dependencies": {
    "express": "~4.8.8",
    "morgan": "~1.3.0",
    "compression": "~1.0.11",
    "body-parser": "~1.8.0",
    "method-override": "~2.2.0",
    "express-session": "~1.7.6",
    "ejs": "~1.0.0",
    "connect-flash": "~0.1.1",
    "mongoose": "~3.8.15",
    "passport": "~0.2.1",
    "passport-local": "~1.0.0",
    "passport-facebook": "~1.0.3",
    "passport-twitter": "~1.0.2",
    "passport-google-oauth": "~0.1.5",
    "socket.io": "~1.1.0"
  }
}

To install the Socket.io module, go to your application's root folder and issue the following command in your command-line tool:

$ npm install

As usual, this will install the specified version of Socket.io in your node_modules folder. When the installation process is successfully over, your will need be to configure your Express application to work in conjunction with the Socket.io module and start your socket server.

Configuring the Socket.io server

After you've installed the Socket.io module, you will need to start the socket server in conjunction with the Express application. For this, you will have to make the following changes in your config/express.js file:

var config = require('./config'),
  http = require('http'),
  socketio = require('socket.io'),
  express = require('express'),
  morgan = require('morgan'),
  compress = require('compression'),
  bodyParser = require('body-parser'),
  methodOverride = require('method-override'),
  session = require('express-session'),
  flash = require('connect-flash'),
  passport = require('passport');

module.exports = function() {
  var app = express();
   var server = http.createServer(app);
   var io = socketio.listen(server);

  if (process.env.NODE_ENV === 'development') {
    app.use(morgan('dev'));
  } else if (process.env.NODE_ENV === 'production') {
    app.use(compress());
  }

  app.use(bodyParser.urlencoded({
    extended: true
  }));
  app.use(bodyParser.json());
  app.use(methodOverride());

  app.use(session({
    saveUninitialized: true,
    resave: true,
    secret: config.sessionSecret
  }));

  app.set('views', './app/views');
  app.set('view engine', 'ejs');

  app.use(flash());
  app.use(passport.initialize());
  app.use(passport.session());

  require('../app/routes/index.server.routes.js')(app);
  require('../app/routes/users.server.routes.js')(app);
  require('../app/routes/articles.server.routes.js')(app);

  app.use(express.static('./public'));

  return server;
};

Let's go over the changes you made to your Express configuration. After including the new dependencies, you used the http core module to create a server object that wraps your Express app object. You then used the socket.io module and its listen() method to attach the Socket.io server with your server object. Finally, you returned the new server object instead of the Express application object. When the server starts, it will run your Socket.io server along with your Express application.

While you can already start using Socket.io, there is still one major problem with this implementation. Since Socket.io is a standalone module, requests that are sent to it are detached from the Express application. This means that the Express session information is not available in a socket connection. This raises a serious obstacle when dealing with your Passport authentication in the socket layer of your application. To solve this issue, you will need to configure a persistent session storage, which will allow you to share your session information between the Express application and Socket.io handshake requests.

Configuring the Socket.io session

To configure your Socket.io session to work in conjunction with your Express sessions, you have to find a way to share session information between Socket.io and Express. Since the Express session information is currently being stored in memory, Socket.io will not be able to access it properly. So, a better solution would be to store the session information in your MongoDB. Fortunately, there is node module named connect-mongo that allows you to store session information in a MongoDB instance almost seamlessly. To retrieve the Express session information, you will need some way to parse the signed session cookie information. For this purpose, you'll also install the cookie-parser module, which is used to parse the cookie header and populate the HTTP request object with cookies-related properties.

Installing the connect-mongo and cookie-parser modules

Before you can use the connect-mongo and cookie-parser modules, you will need to install it using npm. To do so, change your package.json file as follows:

{
  "name": "MEAN",
  "version": "0.0.9",
  "dependencies": {
    "express": "~4.8.8",
    "morgan": "~1.3.0",
    "compression": "~1.0.11",
    "body-parser": "~1.8.0",
    "method-override": "~2.2.0",
    "express-session": "~1.7.6",
    "ejs": "~1.0.0",
    "connect-flash": "~0.1.1",
    "mongoose": "~3.8.15",
    "passport": "~0.2.1",
    "passport-local": "~1.0.0",
    "passport-facebook": "~1.0.3",
    "passport-twitter": "~1.0.2",
    "passport-google-oauth": "~0.1.5",
    "socket.io": "~1.1.0",
    "connect-mongo": "~0.4.1",
    "cookie-parser": "~1.3.3"
  }
}

To install the new modules, go to your application's root folder and issue the following command in your command-line tool:

$ npm install

As usual, this will install the specified versions of the connect-mongo and cookie-parser modules in your node_modules folder. When the installation process is successfully over, your next step will be to configure your Express application to use connect-mongo as session storage.

Configuring the connect-mongo module

To configure your Express application to store session information using the connect-mongo module, you will have to make a few changes. First, you will need to change your config/express.js file as follows:

var config = require('./config'),
  http = require('http'),
  socketio = require('socket.io'),
  express = require('express'),
  morgan = require('morgan'),
  compress = require('compression'),
  bodyParser = require('body-parser'),
  methodOverride = require('method-override'),
  session = require('express-session'),
  MongoStore = require('connect-mongo')(session),
  flash = require('connect-flash'),
  passport = require('passport');

module.exports = function(db) {
  var app = express();
  var server = http.createServer(app);
  var io = socketio.listen(server);

  if (process.env.NODE_ENV === 'development') {
    app.use(morgan('dev'));
  } else if (process.env.NODE_ENV === 'production') {
    app.use(compress());
  }

  app.use(bodyParser.urlencoded({
    extended: true
  }));
  app.use(bodyParser.json());
  app.use(methodOverride());

  var mongoStore = new MongoStore({
    db: db.connection.db
  });

  app.use(session({
    saveUninitialized: true,
    resave: true,
    secret: config.sessionSecret,
    store: mongoStore
  }));

  app.set('views', './app/views');
  app.set('view engine', 'ejs');

  app.use(flash());
  app.use(passport.initialize());
  app.use(passport.session());

  require('../app/routes/index.server.routes.js')(app);
  require('../app/routes/users.server.routes.js')(app);
  require('../app/routes/articles.server.routes.js')(app);

  app.use(express.static('./public'));

  return server;
};

In the preceding code snippet, you configured a few things. First, you loaded the connect-mongo module, and then passed the Express session module to it. Then, you created a new connect-mongo instance and passed it your Mongoose connection object. Finally, you used the Express session store option to let the Express session module know where to store the session information.

As you can see, your Express configuration method requires a db argument. This argument is the Mongoose connection object, which will be passed to the Express configuration method from the server.js file when it requires the express.js file. So, go to your server.js file and change it as follows:

process.env.NODE_ENV = process.env.NODE_ENV || 'development';

var mongoose = require('./config/mongoose'),
  express = require('./config/express'),
  passport = require('./config/passport');

var db = mongoose();
var app = express(db);
var passport = passport();
app.listen(3000);

module.exports = app;

console.log('Server running at http://localhost:3000/');

Once the Mongoose connection is created, the server.js file will call the express.js module method and pass the Mongoose database property to it. In this way, Express will persistently store the session information in your MongoDB database so that it will be available for the Socket.io session. Next, you will need to configure your Socket.io handshake middleware to use the connect-mongo module and retrieve the Express session information.

Configuring the Socket.io session

To configure the Socket.io session, you'll need to use the Socket.io configuration middleware and retrieve your session user. Begin by creating a new file named socketio.js in your config folder to store all your Socket.io-related configurations. In your new file, add the following lines of code:

var config = require('./config'),
  cookieParser = require('cookie-parser'),
  passport = require('passport');

module.exports = function(server, io, mongoStore) {
  io.use(function(socket, next) {
    cookieParser(config.sessionSecret)(socket.request, {}, function(err) {
      var sessionId = socket.request.signedCookies['connect.sid'];

      mongoStore.get(sessionId, function(err, session) {
        socket.request.session = session;

        passport.initialize()(socket.request, {}, function() {
          passport.session()(socket.request, {}, function() {
            if (socket.request.user) {
              next(null, true);
            } else {
              next(new Error('User is not authenticated'), false);
            }
          })
        });
      });
    });
  });

  io.on('connection', function(socket) {
/* ... */
  });
};

Let's go over the new Socket.io configuration file. First, you required the necessary dependencies, and then you used the io.use() configuration method to intercept the handshake process. In your configuration function, you used the Express cookie-parser module to parse the handshake request cookie and retrieve the Express sessionId. Then, you used the connect-mongo instance to retrieve the session information from the MongoDB storage. Once you retrieved the session object, you used the passport.initialize() and passport.session() middleware to populate the session's user object according to the session information. If a user is authenticated, the handshake middleware will call the next() callback and continue with the socket initialization; otherwise, it will use the next() callback in a way that informs Socket.io that a socket connection cannot be opened. This means that only authenticated users can open a socket communication with the server and prevent unauthorized connections to your Socket.io server.

To complete your Socket.io server configuration, you will need to call the Socket.io configuration module from your express.js file. Go to your config/express.js file and paste the following line of code right before you return the server object:

require('./socketio')(server, io, mongoStore);

This will execute your Socket.io configuration method and will take care of setting the Socket.io session. Now that you have everything configured, let's see how you can use Socket.io and MEAN to easily build a simple chat.

Building a Socket.io chat

To test your Socket.io implementation, you will build a simple chat application. Your chat will be constructed from several server event handlers, but most of the implementation will take place in your AngularJS application. We'll begin with setting the server event handlers.

Setting the event handlers of the chat server

Before implementing the chat client in your AngularJS application, you'll first need to create a few server event handlers. You already have a proper application structure, so you won't implement the event handlers directly in your configuration file. Instead, it would be better to implement your chat logic by creating a new file named chat.server.controller.js inside your app/controllers folder. In your new file, paste the following lines of code:

module.exports = function(io, socket) {
  io.emit('chatMessage', {
    type: 'status',
    text: 'connected',
    created: Date.now(),
    username: socket.request.user.username
  });

  socket.on('chatMessage', function(message) {
    message.type = 'message';
    message.created = Date.now();
    message.username = socket.request.user.username;

    io.emit('chatMessage', message);
  });

  socket.on('disconnect', function() {
    io.emit('chatMessage', {
    type: 'status',
    text: 'disconnected',
    created: Date.now(),
    username: socket.request.user.username
    });
  });
};

In this file, you implemented a couple of things. First, you used the io.emit() method to inform all the connected socket clients about the newly connected user. This was done by emitting the chatMessage event, and passing a chat message object with the user information and the message text, time, and type. Since you took care of handling the user authentication in your socket server configuration, the user information is available from the socket.request.user object.

Next, you implemented the chatMessage event handler that will take care of messages sent from the socket client. The event handler will add the message type, time, and user information, and it will send the modified message object to all connected socket clients using the io.emit() method.

Our last event handler will take care of handling the disconnect system event. When a certain user is disconnected from the server, the event handler will notify all the connected socket clients about this event by using the io.emit() method. This will allow the chat view to present the disconnection information to other users.

You now have your server handlers implemented, but how will you configure the socket server to include these handlers? To do so, you will need to go back to your config/socketio.js file and slightly modify it:

var config = require('./config'),
  cookieParser = require('cookie-parser'),
  passport = require('passport');

module.exports = function(server, io, mongoStore) {
  io.use(function(socket, next) {
    cookieParser(config.sessionSecret)(socket.request, {}, function(err) {
      var sessionId = socket.request.signedCookies['connect.sid'];

      mongoStore.get(sessionId, function(err, session) {
        socket.request.session = session;

        passport.initialize()(socket.request, {}, function() {
          passport.session()(socket.request, {}, function() {
            if (socket.request.user) {
              next(null, true);
            } else {
              next(new Error('User is not authenticated'), false);
            }
          })
        });
      });
    });
  });

  io.on('connection', function(socket) {
    require('../app/controllers/chat.server.controller')(io, socket);
  });
};

Notice how the socket server connection event is used to load the chat controller. This will allow you to bind your event handlers directly with the connected socket.

Congratulations, you've successfully completed your server implementation! Next, you'll see how easy it is to implement the AngularJS chat functionality. Let's begin with the AngularJS service.

Creating the Socket service

The provided Socket.io client method is used to open a connection with the socket server and return a client instance that will be used to communicate with the server. Since it is not recommended to use global JavaScript objects, you can leverage the services singleton architecture and wrap your socket client.

Let's begin by creating the public/chat module folder. Then, create the public/chat/chat.client.module.js initialization file with the following line of code:

angular.module('chat', []);

Now, proceed to create a public/chat/services folder for your socket service. In the public/chat/services folder, create a new file named socket.client.service.js that contains the following code snippet:

angular.module('chat').service('Socket', ['Authentication', '$location', '$timeout',
  function(Authentication, $location, $timeout) {
    if (Authentication.user) {
      this.socket = io();
    } else {
      $location.path('/');
    }

    this.on = function(eventName, callback) {
      if (this.socket) {
        this.socket.on(eventName, function(data) {
          $timeout(function() {
            callback(data);
          });
        });
      }
    };

    this.emit = function(eventName, data) {
      if (this.socket) {
        this.socket.emit(eventName, data);
      }
    };

    this.removeListener = function(eventName) {
      if (this.socket) {
        this.socket.removeListener(eventName);
      }
    };
  }
]);

Let's review this code for a moment. After injecting the services, you checked whether the user is authenticated using the Authentication service. If the user is not authenticated, you redirected the request back to the home page using the $location service. Since AngularJS services are lazily loaded, the Socket service will only load when requested. This will prevent unauthenticated users from using the Socket service. If the user is authenticated, the service socket property is set by calling the io() method of Socket.io.

Next, you wrapped the socket emit(), on(), and removeListenter() methods with compatible service methods. It is worth checking the service on() method. In this method, you used a common AngularJS trick that involves the $timeout service. The problem we need to solve here is that AngularJS data binding only works for methods that are executed inside the framework. This means that unless you notify the AngularJS compiler about third-party events, it will not know about changes they cause in the data model. In our case, the socket client is a third-party library that we integrate in a service, so any events coming from the socket client might not initiate a binding process. To solve this problem, you can use the $apply and $digest methods; however, this often causes an error, since a digest cycle might already be in progress. A cleaner solution is to use $timeout trick. The $timeout service is a wrapper around the window.setTimeout() method, so calling it without the timeout argument will basically take care of the binding issue without any impact on user experience

Once you have the Socket service ready, all you have to do is implement the chat controller and chat view. Let's begin by defining the chat controller.

Creating the chat controller

The chat controller is where you implement your AngularJS chat functionality. To implement your chat controller, you'll first need to create a public/chat/controllers folder. In this folder, create a new file named chat.client.controller.js that contains the following code snippet:

angular.module('chat').controller('ChatController', ['$scope', 'Socket',
  function($scope, Socket) {
    $scope.messages = [];
  
    Socket.on('chatMessage', function(message) {
      $scope.messages.push(message);
    });
    
    $scope.sendMessage = function() {
      var message = {
        text: this.messageText,
      };
      
      Socket.emit('chatMessage', message);
            
      this.messageText = '';
    }
  
    $scope.$on('$destroy', function() {
      Socket.removeListener('chatMessage');
    })

  }
]);

In the controller, you first created a messages array and then implemented the chatMessage event listener that will add retrieved messages to this array. Next, you created a sendMessage() method that will send new messages by emitting the chatMessage event to the socket server. Finally, you used the in-built $destroy event to remove the chatMessage event listener from the socket client. The $destory event will be emitted when the controller instance is deconstructed. This is important because the event handler will still get executed unless you remove it.

Creating the chat view

The chat view will be constructed from a simple form and a list of chat messages. To implement your chat view, you'll first need to create a public/chat/views folder. In this folder, create a new file named chat.client.view.html that contains the following code snippet:

<section data-ng-controller="ChatController">
  <div data-ng-repeat="message in messages" data-ng-switch="message.type">
    <strong data-ng-switch-when='status'>
      <span data-ng-bind="message.created | date:'mediumTime'"></span>
      <span data-ng-bind="message.username"></span>
      <span>is</span>
      <span data-ng-bind="message.text"></span>
    </strong>
    <span data-ng-switch-default>
      <span data-ng-bind="message.created | date:'mediumTime'"></span>
      <span data-ng-bind="message.username"></span>
      <span>:</span>
      <span data-ng-bind="message.text"></span>
    </span>
  </div>
  <form ng-submit="sendMessage();">
    <input type="text" data-ng-model="messageText">
    <input type="submit">
  </form>
</section>

In this view, you used the ng-repeat directive to render the messages list and the ng-switch directive to distinguish between status messages and regular messages. You also used the AngularJS date filter to properly present the message time. Finally, you finished the view with a simple form that uses the ng-submit directive to invoke the sendMessage() method. Next, you will need to add a chat route to present this view.

Adding chat routes

To present the view, you will need to add a new route for it. To do so, first create the public/chat/config folder. In this folder, create a new file named chat.client.routes.js that contains the following code snippet:

angular.module('chat').config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
    when('/chat', {
      templateUrl: 'chat/views/chat.client.view.html'
    });
  }
]);

This should already be a familiar pattern, so let's proceed to finalize the chat implementation.

Finalizing the chat implementation

To finalize your chat implementation, you will need to make a few changes in your main application page and include the Socket.io client file and your new chat files. Go to the app/views/index.ejs file and make the following changes:

<!DOCTYPE html>
<html xmlns:ng="http://angularjs.org">
  <head>
    <title><%= title %></title>
  </head>
  <body>
    <section ng-view></section>
    
    <script type="text/javascript">
      window.user = <%- user || 'null' %>;
    </script>

    <script type="text/javascript" src="/socket.io/socket.io.js"></script>
    <script type="text/javascript" src="/lib/angular/angular.js"></script>
    <script type="text/javascript" src="/lib/angular-route/angular-route.js"></script>
    <script type="text/javascript" src="/lib/angular-resource/angular-resource.js"></script>

    <script type="text/javascript" src="/articles/articles.client.module.js"></script>
    <script type="text/javascript" src="/articles/controllers/articles.client.controller.js"></script>
    <script type="text/javascript" src="/articles/services/articles.client.service.js"></script>
    <script type="text/javascript" src="/articles/config/articles.client.routes.js"></script>

    <script type="text/javascript" src="/example/example.client.module.js"></script>
    <script type="text/javascript" src="/example/controllers/example.client.controller.js"></script>
    <script type="text/javascript" src="/example/config/example.client.routes.js"></script>

    <script type="text/javascript" src="/users/users.client.module.js"></script>
    <script type="text/javascript" src="/users/services/authentication.client.service.js"></script>

    <script type="text/javascript" src="/chat/chat.client.module.js"></script>
    <script type="text/javascript" src="/chat/services/socket.client.service.js"></script>
    <script type="text/javascript" src="/chat/controllers/chat.client.controller.js"></script>
    <script type="text/javascript" src="/chat/config/chat.client.routes.js"></script>
    
    <script type="text/javascript" src="/application.js"></script>
  </body>
</html>

Notice how we first added the Socket.io file. It's always a good practice to include third-party libraries before your application files. Now, you'll need to change the public/application.js file to include your new chat module:

var mainApplicationModuleName = 'mean';

var mainApplicationModule = angular.module(mainApplicationModuleName, ['ngResource', 'ngRoute', 'users', 'example', 'articles', 'chat']);

mainApplicationModule.config(['$locationProvider',
  function($locationProvider) {
    $locationProvider.hashPrefix('!');
  }
]);

if (window.location.hash === '#_=_') window.location.hash = '#!';

angular.element(document).ready(function() {
  angular.bootstrap(document, [mainApplicationModuleName]);
});

To finish up your chat implementation, change your public/example/views/example.client.view.html file and add a new chat link:

<section ng-controller="ExampleController">
  <div data-ng-show="!authentication.user">
    <a href="/signup">Signup</a>
    <a href="/signin">Signin</a>
  </div>
  <div data-ng-show="authentication.user">
    <h1>Hello <span data-ng-bind="authentication.user.fullName"></span></h1>
    <a href="/signout">Signout</a>
    <ul>
      <li><a href="/#!/chat">Chat</a></li>
      <li><a href="/#!/articles">List Articles</a></li>
      <li><a href="/#!/articles/create">Create Article</a></li>
    </ul>
  </div>
</section>

Once you are finished with these changes, your new chat example should be ready to use. Use your command-line tool and navigate to the MEAN application's root folder. Then, run your application by typing the following command:

$ node server

Once your application is running, open two different browsers and sign up with two different users. Then, navigate to http://localhost:3000/#!/chat and try sending chat messages between your two clients. You'll be able to see how chat messages are being updated in real time. Your MEAN application now supports real-time communication.

Finalizing the chat implementation
Finalizing the chat implementation
Finalizing the chat implementation
..................Content has been hidden....................

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