diff --git a/examples/chat-cluster/README.md b/examples/chat-cluster/README.md
new file mode 100644
index 0000000000..4d8c26aa7c
--- /dev/null
+++ b/examples/chat-cluster/README.md
@@ -0,0 +1,44 @@
+
+# Socket.IO Chat
+
+A simple chat demo for socket.io
+
+## How to use
+
+
+### Starting Redis
+This example uses Redis for communication between sockets that may be connected on different slave processes of the Node application.
+
+To install `redis-server` follow http://redis.io/topics/quickstart
+
+Run redis with the default configuration. It will run on localhost port 6379. This runs Redis in the foreground so keep this Terminal window open.
+
+```
+$ redis-server
+```
+
+### Running the example application
+
+```
+$ cd socket.io
+$ npm install
+$ cd examples/chat-cluster
+$ npm install
+$ node .
+```
+
+And point your browser to `http://localhost:3000`. Optionally, specify
+a port by supplying the `PORT` env variable.
+
+## Cluster Chat Features
+
+- Forks the node process multiple times and routes requests to one of the slave processes of Node.
+
+
+## Basic Chat Features (See other example)
+
+- Multiple users can join a chat room by each entering a unique username
+on website load.
+- Users can type chat messages to the chat room.
+- A notification is sent to all users when a user joins or leaves
+the chatroom.
diff --git a/examples/chat-cluster/index.js b/examples/chat-cluster/index.js
new file mode 100644
index 0000000000..b39a1ccc67
--- /dev/null
+++ b/examples/chat-cluster/index.js
@@ -0,0 +1,152 @@
+// Setup basic express server
+var express = require('express');
+var app = express();
+// var server = require('http').createServer(app);
+var http = require('http');
+var port = process.env.PORT || 3000
+
+// Required for cluster
+var redisAdapter = require('socket.io-redis');
+var sticky = require('sticky-session');
+var redis = require('redis');
+var cluster = require('cluster');
+
+// number of slaves
+var workers = process.env.WORKERS || 5; // use 5 slaves if no number of works is specified
+
+
+///////////////////////////////////
+// Server
+//
+// Configure sticky sessions to ensure requests go to the same
+// child in the cluster.
+// See : https://github.com/indutny/sticky-session
+// NOTE: Sticky sessions are based on a hash of the IP address.
+// This means multiple web browsers or tabs on the same machine
+// will always hit the same slave.
+//
+///////////////////////////////////
+
+sticky(workers, function() {
+
+ // This code will be executed only in slave workers
+ var server = http.createServer(app);
+
+ var io = require('../..')(server);
+ //var io = require('socket.io')(server);
+
+ // configure socket.io to use redis adapter
+ addRedisAdapter(io);
+
+ // configure socket.io to respond to certain events
+ addIOEventHandlers(io);
+
+ return server;
+
+}).listen(port, function() {
+
+ // this code is executed in both slaves and master
+ console.log('server started on port '+port+'. process id = '+process.pid);
+
+});
+
+
+///////////////////////////////////
+// Routing
+///////////////////////////////////
+
+app.use(express.static(__dirname + '/public'));
+
+
+///////////////////////////////////
+// Redis Adapter
+///////////////////////////////////
+
+function addRedisAdapter(io) {
+ var redisUrl = process.env.REDISTOGO_URL || 'redis://127.0.0.1:6379';
+ var redisOptions = require('parse-redis-url')(redis).parse(redisUrl);
+ var pub = redis.createClient(redisOptions.port, redisOptions.host, {
+ detect_buffers: true,
+ auth_pass: redisOptions.password
+ });
+ var sub = redis.createClient(redisOptions.port, redisOptions.host, {
+ detect_buffers: true,
+ auth_pass: redisOptions.password
+ });
+
+ io.adapter(redisAdapter({
+ pubClient: pub,
+ subClient: sub
+ }));
+ console.log('Redis adapter started with url: ' + redisUrl);
+};
+
+///////////////////////////////////
+// Chatroom Handlers
+///////////////////////////////////
+
+// usernames which are currently connected to the chat
+// var usernames = {};
+// var numUsers = 0;
+
+function addIOEventHandlers(io) {
+
+ io.on('connection', function (socket) {
+
+ // var addedUser = false;
+ console.log('Connection made. socket.id='+socket.id+' . pid = '+process.pid);
+
+ // when the client emits 'new message', this listens and executes
+ socket.on('new message', function (data) {
+ // we tell the client to execute 'new message'
+
+ console.log('emitting message: "'+data+'". socket.id='+socket.id+' . pid = '+process.pid);
+ socket.broadcast.emit('new message', {
+ username: socket.username,
+ message: data
+ });
+ });
+
+ // when the client emits 'add user', this listens and executes
+ socket.on('add user', function (username) {
+ // we store the username in the socket session for this client
+ socket.username = username;
+ // add the client's username to the global list
+ // usernames[username] = username;
+ // ++numUsers;
+ // addedUser = true;
+ socket.emit('login', { // todo
+ numUsers: 420
+ });
+ // echo globally (all clients) that a person has connected
+ socket.broadcast.emit('user joined', { // todo
+ username: socket.username,
+ numUsers: 421
+ });
+ });
+
+ // when the client emits 'typing', we broadcast it to others
+ socket.on('typing', function () {
+ socket.broadcast.emit('typing', {
+ username: socket.username
+ });
+ });
+
+ // when the client emits 'stop typing', we broadcast it to others
+ socket.on('stop typing', function () {
+ socket.broadcast.emit('stop typing', {
+ username: socket.username
+ });
+ });
+
+ // when the user disconnects.. perform this
+ socket.on('disconnect', function () {
+ socket.broadcast.emit('user left', { // todo
+ username: socket.username,
+ numUsers: 422
+ });
+ });
+
+ });
+
+};
\ No newline at end of file
diff --git a/examples/chat-cluster/package.json b/examples/chat-cluster/package.json
new file mode 100644
index 0000000000..ca0de5b243
--- /dev/null
+++ b/examples/chat-cluster/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "socket.io-chat",
+ "version": "0.0.0",
+ "description": "A simple chat client using socket.io",
+ "main": "index.js",
+ "author": "Grant Timmerman",
+ "private": true,
+ "license": "BSD",
+ "dependencies": {
+ "express": "3.4.8",
+ "parse-redis-url": "0.0.1",
+ "redis": "^0.12.1",
+ "socket.io-redis": "^0.1.3",
+ "sticky-session": "^0.1.0"
+ }
+}
\ No newline at end of file
diff --git a/examples/chat-cluster/public/index.html b/examples/chat-cluster/public/index.html
new file mode 100644
index 0000000000..9b2043d73f
--- /dev/null
+++ b/examples/chat-cluster/public/index.html
@@ -0,0 +1,28 @@
+
+
+
+
+ Socket.IO Chat Example
+
+
+
+
+
+
+
+
+
+
+
+
+
What's your nickname?
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/chat-cluster/public/main.js b/examples/chat-cluster/public/main.js
new file mode 100644
index 0000000000..cff3d74a67
--- /dev/null
+++ b/examples/chat-cluster/public/main.js
@@ -0,0 +1,266 @@
+$(function() {
+ var FADE_TIME = 150; // ms
+ var TYPING_TIMER_LENGTH = 400; // ms
+ var COLORS = [
+ '#e21400', '#91580f', '#f8a700', '#f78b00',
+ '#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
+ '#3b88eb', '#3824aa', '#a700ff', '#d300e7'
+ ];
+
+ // Initialize varibles
+ var $window = $(window);
+ var $usernameInput = $('.usernameInput'); // Input for username
+ var $messages = $('.messages'); // Messages area
+ var $inputMessage = $('.inputMessage'); // Input message input box
+
+ var $loginPage = $('.login.page'); // The login page
+ var $chatPage = $('.chat.page'); // The chatroom page
+
+ // Prompt for setting a username
+ var username;
+ var connected = false;
+ var typing = false;
+ var lastTypingTime;
+ var $currentInput = $usernameInput.focus();
+
+ var socket = io();
+
+ function addParticipantsMessage (data) {
+ var message = '';
+ if (data.numUsers === 1) {
+ message += "there's 1 participant";
+ } else {
+ message += "there are " + data.numUsers + " participants";
+ }
+ log(message);
+ }
+
+ // Sets the client's username
+ function setUsername () {
+ username = cleanInput($usernameInput.val().trim());
+
+ // If the username is valid
+ if (username) {
+ $loginPage.fadeOut();
+ $chatPage.show();
+ $loginPage.off('click');
+ $currentInput = $inputMessage.focus();
+
+ // Tell the server your username
+ socket.emit('add user', username);
+ }
+ }
+
+ // Sends a chat message
+ function sendMessage () {
+ var message = $inputMessage.val();
+ // Prevent markup from being injected into the message
+ message = cleanInput(message);
+ // if there is a non-empty message and a socket connection
+ if (message && connected) {
+ $inputMessage.val('');
+ addChatMessage({
+ username: username,
+ message: message
+ });
+ // tell server to execute 'new message' and send along one parameter
+ socket.emit('new message', message);
+ }
+ }
+
+ // Log a message
+ function log (message, options) {
+ var $el = $('
').addClass('log').text(message);
+ addMessageElement($el, options);
+ }
+
+ // Adds the visual chat message to the message list
+ function addChatMessage (data, options) {
+ // Don't fade the message in if there is an 'X was typing'
+ var $typingMessages = getTypingMessages(data);
+ options = options || {};
+ if ($typingMessages.length !== 0) {
+ options.fade = false;
+ $typingMessages.remove();
+ }
+
+ var $usernameDiv = $('')
+ .text(data.username)
+ .css('color', getUsernameColor(data.username));
+ var $messageBodyDiv = $('')
+ .text(data.message);
+
+ var typingClass = data.typing ? 'typing' : '';
+ var $messageDiv = $('')
+ .data('username', data.username)
+ .addClass(typingClass)
+ .append($usernameDiv, $messageBodyDiv);
+
+ addMessageElement($messageDiv, options);
+ }
+
+ // Adds the visual chat typing message
+ function addChatTyping (data) {
+ data.typing = true;
+ data.message = 'is typing';
+ addChatMessage(data);
+ }
+
+ // Removes the visual chat typing message
+ function removeChatTyping (data) {
+ getTypingMessages(data).fadeOut(function () {
+ $(this).remove();
+ });
+ }
+
+ // Adds a message element to the messages and scrolls to the bottom
+ // el - The element to add as a message
+ // options.fade - If the element should fade-in (default = true)
+ // options.prepend - If the element should prepend
+ // all other messages (default = false)
+ function addMessageElement (el, options) {
+ var $el = $(el);
+
+ // Setup default options
+ if (!options) {
+ options = {};
+ }
+ if (typeof options.fade === 'undefined') {
+ options.fade = true;
+ }
+ if (typeof options.prepend === 'undefined') {
+ options.prepend = false;
+ }
+
+ // Apply options
+ if (options.fade) {
+ $el.hide().fadeIn(FADE_TIME);
+ }
+ if (options.prepend) {
+ $messages.prepend($el);
+ } else {
+ $messages.append($el);
+ }
+ $messages[0].scrollTop = $messages[0].scrollHeight;
+ }
+
+ // Prevents input from having injected markup
+ function cleanInput (input) {
+ return $('').text(input).text();
+ }
+
+ // Updates the typing event
+ function updateTyping () {
+ if (connected) {
+ if (!typing) {
+ typing = true;
+ socket.emit('typing');
+ }
+ lastTypingTime = (new Date()).getTime();
+
+ setTimeout(function () {
+ var typingTimer = (new Date()).getTime();
+ var timeDiff = typingTimer - lastTypingTime;
+ if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
+ socket.emit('stop typing');
+ typing = false;
+ }
+ }, TYPING_TIMER_LENGTH);
+ }
+ }
+
+ // Gets the 'X is typing' messages of a user
+ function getTypingMessages (data) {
+ return $('.typing.message').filter(function (i) {
+ return $(this).data('username') === data.username;
+ });
+ }
+
+ // Gets the color of a username through our hash function
+ function getUsernameColor (username) {
+ // Compute hash code
+ var hash = 7;
+ for (var i = 0; i < username.length; i++) {
+ hash = username.charCodeAt(i) + (hash << 5) - hash;
+ }
+ // Calculate color
+ var index = Math.abs(hash % COLORS.length);
+ return COLORS[index];
+ }
+
+ // Keyboard events
+
+ $window.keydown(function (event) {
+ // Auto-focus the current input when a key is typed
+ if (!(event.ctrlKey || event.metaKey || event.altKey)) {
+ $currentInput.focus();
+ }
+ // When the client hits ENTER on their keyboard
+ if (event.which === 13) {
+ if (username) {
+ sendMessage();
+ socket.emit('stop typing');
+ typing = false;
+ } else {
+ setUsername();
+ }
+ }
+ });
+
+ $inputMessage.on('input', function() {
+ updateTyping();
+ });
+
+ // Click events
+
+ // Focus input when clicking anywhere on login page
+ $loginPage.click(function () {
+ $currentInput.focus();
+ });
+
+ // Focus input when clicking on the message input's border
+ $inputMessage.click(function () {
+ $inputMessage.focus();
+ });
+
+ // Socket events
+
+ // Whenever the server emits 'login', log the login message
+ socket.on('login', function (data) {
+ connected = true;
+ // Display the welcome message
+ var message = "Welcome to Socket.IO Chat – ";
+ log(message, {
+ prepend: true
+ });
+ addParticipantsMessage(data);
+ });
+
+ // Whenever the server emits 'new message', update the chat body
+ socket.on('new message', function (data) {
+ addChatMessage(data);
+ });
+
+ // Whenever the server emits 'user joined', log it in the chat body
+ socket.on('user joined', function (data) {
+ log(data.username + ' joined');
+ addParticipantsMessage(data);
+ });
+
+ // Whenever the server emits 'user left', log it in the chat body
+ socket.on('user left', function (data) {
+ log(data.username + ' left');
+ addParticipantsMessage(data);
+ removeChatTyping(data);
+ });
+
+ // Whenever the server emits 'typing', show the typing message
+ socket.on('typing', function (data) {
+ addChatTyping(data);
+ });
+
+ // Whenever the server emits 'stop typing', kill the typing message
+ socket.on('stop typing', function (data) {
+ removeChatTyping(data);
+ });
+});
diff --git a/examples/chat-cluster/public/style.css b/examples/chat-cluster/public/style.css
new file mode 100644
index 0000000000..62cbe093ed
--- /dev/null
+++ b/examples/chat-cluster/public/style.css
@@ -0,0 +1,150 @@
+/* Fix user-agent */
+
+* {
+ box-sizing: border-box;
+}
+
+html {
+ font-weight: 300;
+ -webkit-font-smoothing: antialiased;
+}
+
+html, input {
+ font-family:
+ "HelveticaNeue-Light",
+ "Helvetica Neue Light",
+ "Helvetica Neue",
+ Helvetica,
+ Arial,
+ "Lucida Grande",
+ sans-serif;
+}
+
+html, body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+ul {
+ list-style: none;
+ word-wrap: break-word;
+}
+
+/* Pages */
+
+.pages {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ width: 100%;
+}
+
+.page {
+ height: 100%;
+ position: absolute;
+ width: 100%;
+}
+
+/* Login Page */
+
+.login.page {
+ background-color: #000;
+}
+
+.login.page .form {
+ height: 100px;
+ margin-top: -100px;
+ position: absolute;
+
+ text-align: center;
+ top: 50%;
+ width: 100%;
+}
+
+.login.page .form .usernameInput {
+ background-color: transparent;
+ border: none;
+ border-bottom: 2px solid #fff;
+ outline: none;
+ padding-bottom: 15px;
+ text-align: center;
+ width: 400px;
+}
+
+.login.page .title {
+ font-size: 200%;
+}
+
+.login.page .usernameInput {
+ font-size: 200%;
+ letter-spacing: 3px;
+}
+
+.login.page .title, .login.page .usernameInput {
+ color: #fff;
+ font-weight: 100;
+}
+
+/* Chat page */
+
+.chat.page {
+ display: none;
+}
+
+/* Font */
+
+.messages {
+ font-size: 150%;
+}
+
+.inputMessage {
+ font-size: 100%;
+}
+
+.log {
+ color: gray;
+ font-size: 70%;
+ margin: 5px;
+ text-align: center;
+}
+
+/* Messages */
+
+.chatArea {
+ height: 100%;
+ padding-bottom: 60px;
+}
+
+.messages {
+ height: 100%;
+ margin: 0;
+ overflow-y: scroll;
+ padding: 10px 20px 10px 20px;
+}
+
+.message.typing .messageBody {
+ color: gray;
+}
+
+.username {
+ float: left;
+ font-weight: 700;
+ overflow: hidden;
+ padding-right: 15px;
+ text-align: right;
+}
+
+/* Input */
+
+.inputMessage {
+ border: 10px solid #000;
+ bottom: 0;
+ height: 60px;
+ left: 0;
+ outline: none;
+ padding-left: 10px;
+ position: absolute;
+ right: 0;
+ width: 100%;
+}