Part 2: Real-Time Browser to Browser Communication, Adding Application Specific Events

In the previous part of this article, I wrote an introduction to Node.js and Socket.Io; presenting a basic server and client communication model. In this entry I am writing an extension to the Server and Client JavaScript objects, to allow for Application Specific Events. This provides the basic framework to writing an application that routes messages between two or more browsers.

Application Specific Events occur at an existing socket level, rather than at the listening socket level. These define the application communication protocol.

Below are the changes required for extending the previous Server and Client objects to support such events.

Server side: Handle client side events, and return responses

Here are a few short steps to follow for extending the Server object, to allow for configurable event handlers.

Step 1: Update the constructor documentation, to reflect configuration object changes.

/**
 * Messaging server.
 * @class Provides server functionality.
 * @constructor
 * @param {Object} config Server configuration object.
 * Supported keys are: port (listening port number), socket (socket listener configuration object),
 * optional 'scope' object used for maintaining a custom scope reference,
 * optional 'connectionHandler' connection handler,
 * and an events object providing event handler functionality.
 */

Step 2: Add a function used for registering the events and their handlers, allowing for a custom scope.

/**
 * Method used for attaching socket events and their handlers.
 * NOTE: Event names and functions are stored in the config.events object.
 * NOTE: The scope of each event handler is bound to the configured 'scope' object
 * @param {Object} socket Socket object.
 * @function
 */
Server.prototype.attachSocketEvents = function( socket ) {
        var me = this;
        Object.keys( this._config.events ).forEach( function( eventName ) {
                socket.on( eventName, function( data ) {
                        // Determine which scope to bind the event handler to
                        var scope = typeof me._config.scope !== "undefined" ? me._config.scope : me;

                        // Call the Application Specific Event handler, passing in the data and socket objects
                        me._config.events[eventName].bind( scope )( data, socket );
                } );
        } );
}

Step 3: Update the init function, to trigger a call to the previous function, and register the events

/**
 * Method used for preparing the server listeners, and attaching event handlers.
 * @function
 */
Server.prototype.init = function() {
        // Load required libraries.
        this.loadLibraries();

        // Open port for incoming connections
        this._httpServer.listen( this._config.port );

        // Attach a Socket.Io connection handler
        // This handler in turn will attach application specific event handlers.
        this._socketIo.sockets.on( 'connection', function ( socket ) {
                // Attach the Application Specific Event handlers
                this.attachSocketEvents( socket );

                // Call a custom connection event handler, if configured
                if ( typeof this._config.connectionHandler !== "undefined" ) {
                        this._config.connectionHandler.bind( this )( socket );
                }
        }.bind( this ) );
}

Step 4: Update the server instance, to make use of the new functionality.

// Create server
new Server( {
        port: 10000 // Listening port
        ,socket: { // Socket configuration
                log: false // Disable loggings
        }
        // Add event handlers
        ,events: {
                // Example clientEvent
                clientEvent: function( data, socket ) {
                        console.log( "Client from: " + socket.handshake.address.address + ", says: " + data.text );
                        // Emit another event, unknown to the client
                        socket.emit( 'unknownEvent', {} );
                }
        }
        // Connection handler
        ,connectionHandler: function( socket ) {
                console.log( 'Connection from: ' + socket.handshake.address.address );
                // Emit an example serverEvent
                socket.emit( 'serverEvent', { text: 'Hello Client.' } );
        }
 } ).init();

Client side: Emit events, and handle responses

Here are the quick steps to follow for updating the Client object, to allow emitting events and handling responses.

Step 1: Update the constructor documentation, to reflect the new configuration options.

/**
 * Messaging client.
 * @class Provides client functionality.
 * @constructor
 * @param {Object} config Client configuration object. Supported keys are: port (listening port number), host (URL), optional scope object and an events object, defining application specific events.
 */

Step 2: Create an emit function, that wraps the socket’s emit function.

/**
 * Method used for emitting an event. Wrapper for the socket's 'emit' function.
 * @function
 * @param {String} eventName Event name.
 * @param {Object} data Event data object.
 */
Client.prototype.emit = function( eventName, data ) {
        this._socket.emit( eventName, data );
}

Step 3: Create a function used for attaching event handlers.

/**
 * Method used for attaching socket events and their handlers.
 * NOTE: Event names and functions are stored in the config.events object.
 * NOTE: The scope of each event handler is bound to the configured 'scope' object.
 * @function
 */
Client.prototype.attachSocketEvents = function() {
        for ( var eventName in this._config.events ) {
                // Determine which scope to bind the event handler to
                var scope = typeof this._config.scope !== "undefined" ? this._config.scope : this;

                // Bind the event handler
                // NOTE: Unlike the Server attachSocketEvents function, this function binds the event handler, without passing the extra socket data.
                this._socket.on( eventName, this._config.events[eventName].bind( scope ) );
        }
}

Step 4: Update the init function to make use of the above function.

/**
 * Method used for initiating a connection, and attaching event listeners.
 * @function
 */
Client.prototype.init = function() {
        // Create connection
        this._socket = io.connect( this._config.host + ':' + this._config.port );

        // Attach the Application Specific Event handlers
        this.attachSocketEvents();
}

Step 5: Update the client instance to make use of the new functionality.

var Example = new Client( {
        port: 10000
        ,host: 'http://localhost'
        ,scope: Example
        // Example event handlers, not bound to any scope
        ,events: {
                // Sample connection event
                connect: function() {
                        // Emit an example 'clientEvent'
                        Example.emit( 'clientEvent', {
                                text: 'Hello Server.'
                        } );
                }
                // Example serverEvent
                ,serverEvent: function( data ) {
                        console.log( data.text );
                        // Emit a response, to this event
                        this.emit( 'clientEvent', { text: 'How are you?' } );
                }
        }
} );

Step 5: Initialise the object.

// Initialise the server
Example.init();

At the current state, the Server object can successfully handle new connections, create the sockets and attach/emit events. The Client object can emit events, and handle replies. In the next entry, I will present how to route messages between multiple clients, and how to create a friendly user interface using ExtJS, to allow users to exchange messages.