WebSockets Chat EM

This example implements a chat service. The server is built with the EM-Websocket gem and uses the channel functionality of the ruby event machine (see EventMachine::Channel ).

Gemfile
source "http://rubygems.org"

gem "haml"
gem 'em-websocket'
gem 'sinatra'
Terminal
ruby server.rb

websocketsChatEM/server.rb
require 'em-websocket'
require 'json'
require 'sinatra/base'

def timestamp
  Time.now.strftime("%H:%M:%S")
end

EventMachine.run {
  @channel = EM::Channel.new
  @users = {}
  @messages = []

  EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
      
    ws.onopen {
      #Subscribe the new user to the channel with the callback function for the push action
      new_user = @channel.subscribe { |msg| ws.send msg }
      
      #Add the new user to the user list
      @users[ws.object_id] = new_user
      
      #Push the last messages to the user
      @messages.each do |message|
        ws.send message
      end
      
      #Broadcast the notification to all users
      @channel.push  ({
        "nickname" => '', 
        "message" => "New user joined. #{@users.length} users in chat", 
        "timestamp" => timestamp }.to_json)
   }

    ws.onmessage { |msg|
      
      #Add the timestamp to the message
      message = JSON.parse(msg).merge( {'timestamp' => timestamp}).to_json
      
      #append the message at the end of the queue
      @messages << message
      @messages.shift if @messages.length > 10

      #Broadcast the message to all users connected to the channel
      @channel.push message
    }

    ws.onclose { 
      @channel.unsubscribe(@users[ws.object_id])
      @users.delete(ws.object_id)

      #Broadcast the notification to all users
      @channel.push ({
        "nickname" => '', 
        "message" => "One user left. #{@users.length} users in chat", 
        "timestamp" => timestamp}.to_json)
    }
  end

  #Run a Sinatra server for serving index.html
  class App < Sinatra::Base
    set :public_folder, settings.root
    
    get '/' do
      send_file 'index.html'
    end
  end
  App.run!
websocketsChatEM/index.html.haml
!!! 5
%html
  %head
    %meta(charset="utf-8")
    %meta(content="IE=edge,chrome=1" http-equiv="X-UA-Compatible")
    %meta(name="viewport" content="width=device-width, initial-scale=1")  
    %link(rel="stylesheet" href="http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.css")
    %script(type="text/javascript" src="http://code.jquery.com/jquery-1.6.4.min.js")
    %script(type="text/javascript" src="http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.js")
    %title
      WebSockets Chat
  %body
    :javascript
      var WebSocket = window.WebSocket || window.MozWebSocket;

      $(function() {
        var socket = new WebSocket("ws://" + location.hostname + ":8080");
        
        $('#send').click(function() {
          var msg = {nickname: $('#nickname').val(), message: $('#message').val()};
          socket.send( JSON.stringify(msg) );
          $('#message').val("");
        })  
        socket.onopen = function(event) { 
          $('#console').prepend('<p>CONNECTED</p>');
        };
        socket.onmessage = function(event) {
          var msg = $.parseJSON(event.data);
          
          $('#console').prepend('<p>' + msg.timestamp + ' <strong>' + msg.nickname + '</strong>: ' + msg.message + '</p>');
        }
        socket.onerror = function(event) {
          $('#console').prepend('<p>ERROR: ' + event.data + '</p>');
        }
      });

    %div(data-role="page")
      %div(data-role = "header")
        %h1 Simple Chat
      %input#nickname(type="text" value="" placeholder="Nickname")
      %input#message(type='text' value="" placeholder="Start chat here")
      %a#send(data-role = 'button')
        Send
      
      #console