#!/usr/bin/env rackup -s thin # # async_chat.ru # raggi/thin # # Created by James Tucker on 2008-06-19. # Copyright 2008 James Tucker . # Uncomment if appropriate for you.. EM.epoll # EM.kqueue # bug on OS X in 0.12? class DeferrableBody include EventMachine::Deferrable def initialize @queue = [] end def schedule_dequeue return unless @body_callback EventMachine::next_tick do next unless body = @queue.shift body.each do |chunk| @body_callback.call(chunk) end schedule_dequeue unless @queue.empty? end end def call(body) @queue << body schedule_dequeue end def each &blk @body_callback = blk schedule_dequeue end end class Chat module UserBody attr_accessor :username end def initialize @users = {} end def render_page [] << <<-EOPAGE Async Chat
Your first message will become your nickname! Users: #{@users.map{|k,u|u.username}.join(', ')}
EOPAGE end def register_user(user_id, renderer) body = create_user(user_id) body.call render_page body.errback { delete_user user_id } body.callback { delete_user user_id } EventMachine::next_tick do renderer.call [200, {'Content-Type' => 'text/html'}, body] end end def new_message(user_id, message) return unless @users[user_id] if @users[user_id].username == :anonymous username = unique_username(message) log "User: #{user_id} is #{username}" @users[user_id].username = message message = "-> #{username} signed on." end username ||= @users[user_id].username log "User: #{username} sent: #{message}" @users.each do |id, body| EventMachine::next_tick { body.call [js_message(username, message)] } end end private def unique_username(name) name.concat('_') while @users.any? { |id,u| name == u.username } name end def log(str) print str, "\n" end def add_user(id, body) @users[id] = body end def delete_user(id) message = "User: #{id} - #{@users[id].username if @users[id]} disconnected." log message new_message(id, message) @users.delete id end def js_message(username, message) %() end def create_user(id) message = "User: #{id} connected." log message new_message(id, message) body = DeferrableBody.new body.extend UserBody body.username = :anonymous add_user(id, body) body end end class AsyncChat AsyncResponse = [-1, {}, []].freeze AjaxResponse = [200, {}, []].freeze def initialize @chat = Chat.new end def call(env) request = Rack::Request.new(env) # TODO - cookie me, baby user_id = request.env['REMOTE_ADDR'] if request.xhr? message = request['message'] @chat.new_message(user_id, Rack::Utils.escape_html(message)) AjaxResponse else renderer = request.env['async.callback'] @chat.register_user(user_id, renderer) AsyncResponse end end end run AsyncChat.new