Of the many life lessons I have learned creating nodechat.js, one of the most poignant was that people like to leave their browsers (and tabs) open for a long time. This should not have been a surprise to me–I often have tabs open for weeks in the vain hope that I will get back to whatever I was doing on that page someday. This phenomena meant that users would log into nodechat, click away to another tab (or restart their browser, or suspend their PC), only to revist the nodechat tab later and find that the chat appeared frozen. The same issue occured everytime I pushed a new server update: all clients would be cut off until the user refreshed the page. Clearly this was a subpar user exeperience. This is how I fixed it.
There are three main parts to my eventual solution:
- Socket.IO reconnection
- client versioning
- pushed client updates
Using the same techniques used in nodechat.js, we will write a simple node.js + socket.io application that displays it’s version number and can be remotely updated. If you are unfamiliar with Node.js or Socket.IO, you might want to checkout the original nodechat tutorial first.
Setup
Before the fun starts, we need to perform a little setup. I tried to keep this project as simple as possible, so please excuse any and all bad practices you see.
First, create a project directory. Within it, install these modules using npm:
npm install express
npm install socket.io
npm install jade
Then create the following directory structure and empty files:
project/
-server.js
-views/
--index.jade
-public/
--js/
---jquery-1.5.1.js
server.js will contain all of our server-side application code. index.jade will server up the client-side application code and the minimal layout. jQuery is included to make our life easier.
The contents of server.js should be as follows. Explanations are in-line:
//Include our various dependencies for this demo and set up socket.io to work with express
var express = require('express')
, app = express.createServer()
, io = require('socket.io')
, socket = io.listen(app)
, version = '1.1.awesome';
//Tell express where to find our jQuery library
app.use(express.static('./public'));
//Setup express to render index.jade to any client requesting '/'.
app.get('/', function (req, res) {
res.render('index.jade', {
locals: { version: version } //Send the version variable we defined above to the template
, layout: false //Tells jade we aren't going to use a layout.jade base template
});
});
//Make express hang out and listen for incoming connections
app.listen(8000);
The contents of index.jade should be as follows:
!!! 5
html(lang='en')
head
title fzysqr.com client updating demo
script(type='text/javascript', src='/js/jquery-1.5.1.js')
script(type='text/javascript', src='/socket.io/socket.io.js')
script
$(document).ready(function () {
//Crockford compliance! Define all the need variables here
var socket;
//- Setup the socket library. The rememberTransport and tryTransportsOnConnectTimeout
//- are neccessary because the defaults will cause some browsers to degrade to
//- crappy transports when they are disconnected (think suspended laptop) and will
//- subsequently stick on said crappy transport even after the network is up again.
socket = new io.Socket(null, {
rememberTransport: false
, tryTransportsOnConnectTimeout: false
});
//- Simple connect event so we can see that we are connecting
socket.on('connect', function () {
console.log('Connected! Oh hai!');
});
//- Start the connection
socket.connect();
});
body
p Hello fzysqr.com reader!
br
br
| This is version:
//- Render the version variable
h1 #{version}
Once you have all the pieces in place, run node server.js and point your browser to http://localhost:8000/. You should see something like:

Now we are ready to have some fun!
Reconnectionism
As web applications have become richer, user expectations of a traditional thick client software like experience has increased. You simply expect Gmail to reconnect when you open your laptop. Likewise, if nodechat detects that a connection is disrupted, it will display a notice to the user and attempt to reconnect every 30 seconds until it succeeds (or the user closes the tab). Let’s modify our new app to do the same.
All of the required changes are in index.jade. Here are the deltas:
....
//Crockford compliance! Define all the need variables here
var socket, connected, trying, tryConnect;
....
//- Log our connection even and hide the disconnectMessage element in case
//- we are reconnecting after an interuption
socket.on('connect', function () {
console.log('Connected! Oh hai!');
$('#disconnectMessage').hide();
});
//- This method checks to see if we are connected, if not, tell socket.io to connect
//- and reset a timeout to check again in 30 seconds.
tryConnect = function () {
if (!connected) {
console.log('Trying to reconnect...');
socket.connect();
clearTimeout(trying);
trying = setTimeout(tryConnect, 30000);
}
};
//- Handle disconnection by displaying a notice to the user and
//- setting a timer to try connecting in 500ms.
socket.on('disconnect', function () {
console.log('Disconnected. Oh noes!');
connected = false;
trying = setTimeout(tryConnect, 500);
$('#disconnectMessage').show();
});
....
We define a handler for the disconnect event from the socket object (which is an EventEmitter) and use a timer to try to reconnect the socket.io connection repeatedly until it succeeds. Go ahead and fire it up again (node server.js) and go back to http://localhost:8000/. Initially, you should see the same thing as before:

Then if you kill the node server with ctrl-c, you should see this:

Then if you restart the node server and wait patiently (up to 30 seconds), the client should reconnect again and you should see this again:

Client updates
Once nodechat was able to reconnect clients, I noticed a big spike in channel retention. People were able to stay connected/reconnected for weeks at a time. They were also able to reconnect after I shipped a new version of nodechat. This introduce a whole new problem. What happens if we deploy a breaking change to the code? The clients reconnect and promptly crash. Not good UX. Even if I am careful to maintain backwards compatibility, I still want to make sure that long running clients see improvements and bug fixes in the client-side code. To handle this, clients listen for a special upgrade message from the server and react accordingly.
This time we have some changes in server.js. Once again, deltas only:
//Handle the connect event for the socket.io server in order to send out the
//current codebase version to any connecting clients.
socket.on('connection', function(client) {
client.send({
event: 'version'
, data: version
});
});
Then, in index.jade, we add a new variable to track the version and a handler for incoming socket messages on the client. If we received the version update message, we force a refresh:
....
//Crockford compliance! Define all the need variables here
var socket, connected, trying, tryConnect
, version = '#{version}'; //Keep track of the code version at the time this page was rendered
....
//- Handle the message event to see if we are getting a version message from the server
//- If we do, check it's contents to make sure we are on the latest and greatest. If we
//- are out of date, force a reload to get the latest code.
socket.on('message', function (msg) {
if (msg && msg.event === 'version' && version !== msg.data) {
setTimeout(function() {
window.location.reload();
}, 5000);
}
});
....
That’s it! All the pieces are in place. Fire up the server and try it out. When we first connect to http://localhost:8000/, you expect to see the same screen as before:

Then kill the node server and you should see this:

Now, go into server.js and update the version to something else:
, version = '1.2.awesomer';
Save the file and start up the node server again. Switch back to the web browser and wait. Do NOT reload the browser. You should first see the 1.1 version reconnect:

Then, after another view seconds, the browser should reload the whole page and display:

Wrapping up
The full source for this tutorial can be found here on github. The three different tags represent the three different stages in this tutorial.
This type of client versioning is going to be old hat for anyone who has done serious work in real time web applications, but it was not immediately obvious to me when I first dove into node.js and Socket.IO. Having the ability to pull all my clients along when I deploy a new version has made it easier to push bug fixes and feature to nodechat without dropping the whole channel off-line every time.
As always, I welcome any comments, questions, or suggestions. Find me on twitter (@jdslatts) or drop by nodechat.