Permalink
Browse files

presence channels, private channels, and examples. TODO: tests and us…

…e redis to keep track of presence so it scales across slanger instances
  • Loading branch information...
1 parent 50f2227 commit 89b962dcbc8e879223f9de750c3b8b8702f22040 @stevegraham committed Sep 27, 2011
View
@@ -1,2 +1,5 @@
.DS_Store
*.swp
+*.swn
+*.swo
+*.swp
View
@@ -1,24 +1,27 @@
#!/usr/bin/env ruby -Ku
require 'optparse'
-require 'bundler'
-Bundler.require
+require 'bundler/setup'
require 'eventmachine'
options = {
api_host: '0.0.0.0', api_port: '4567', websocket_host: '0.0.0.0',
websocket_port: '8080', debug: false, redis_address: 'redis://0.0.0.0:6379/0'
}
-optparse = OptionParser.new do |opts|
+OptionParser.new do |opts|
opts.on '-h', '--help', 'Display this screen' do
exit
end
- opts.on '-k', '--key APP_KEY', "Pusher application key" do |k|
+ opts.on '-k', '--app_key APP_KEY', "Pusher application key" do |k|
options[:app_key] = k
end
+ opts.on '-s', '--secret SECRET', "Pusher application secret" do |k|
+ options[:secret] = k
+ end
+
opts.on '-r', '--redis_address URL', "Address to bind to (Default: redis://127.0.0.1:6379/0)" do |h|
options[:redis_address] = h
end
@@ -36,17 +39,30 @@ optparse = OptionParser.new do |opts|
end
end.parse!
-raise RuntimeError.new '--key APP_KEY is a required argument. Use your Pusher key.' unless options[:app_key]
+%w<app_key secret>.each do |parameter|
+ raise RuntimeError.new "--#{parameter} STRING is a required argument. Use your Pusher #{parameter}." unless options[parameter.to_sym]
+end
EM.run do
File.tap { |f| require f.expand_path(f.join(f.dirname(__FILE__),'..', 'slanger.rb')) }
+ Slanger::Config.load options
+ Slanger::Service.run
- Slanger::Service.run options
-
- puts "\n"
- puts '*' * 72
- puts "* Slanger API server listening on port #{options[:api_port]}".ljust(71) + "*"
- puts "* Slanger WebSocket server listening on port #{options[:websocket_port]}".ljust(71) + "*"
- puts '*' * 72
+ puts "\x1b[2J\x1b[H"
puts "\n"
+ puts " .d8888b. 888 "
+ puts " d88P Y88b 888 "
+ puts " Y88b. 888 "
+ puts ' "Y888b. 888 8888b. 88888b. .d88b. .d88b. 888d888 '
+ puts ' "Y88b. 888 "88b 888 "88b d88P"88b d8P Y8b 888P" '
+ puts ' "888 888 .d888888 888 888 888 888 88888888 888 '
+ puts " Y88b d88P 888 888 888 888 888 Y88b 888 Y8b. 888 "
+ puts ' "Y8888P" 888 "Y888888 888 888 "Y88888 "Y8888 888 '
+ puts " 888 "
+ puts " Y8b d88P "
+ puts ' "Y88P" '
+ puts "\n" * 2
+
+ puts "Slanger API server listening on port #{options[:api_port]}"
+ puts "Slanger WebSocket server listening on port #{options[:websocket_port]}"
end
View
@@ -0,0 +1,56 @@
+require 'sinatra'
+require 'haml'
+require 'pusher'
+require 'json'
+require 'digest/md5'
+require 'thin'
+
+set :views, File.dirname(__FILE__) + '/templates'
+set :port, 3000
+
+enable :sessions
+
+Pusher.host = '0.0.0.0'
+Pusher.port = 4567
+Pusher.app_id = 'your-pusher-app-id'
+Pusher.secret = 'your-pusher-secret'
+Pusher.key = '765ec374ae0a69f4ce44'
+
+get '/' do
+ @channel = "MY_CHANNEL"
+ haml :index
+end
+
+get '/chat' do
+ @channel = "presence-channel"
+ if session[:current_user]
+ haml :chat_room
+ else
+ haml :chat_lobby
+ end
+end
+
+post '/chat' do
+ if session[:current_user]
+ Pusher['presence-channel'].trigger_async('chat_message', {
+ sender: session[:current_user], body: params['message']
+ })
+ request.xhr? ? status(201) : redirect('/chat')
+ else
+ status 403
+ end
+end
+
+post '/identify' do
+ session[:current_user] = params['handle']
+ redirect request.referer
+end
+
+post '/pusher/auth' do
+ Pusher[params['channel_name']].authenticate(params['socket_id'], {
+ user_id: Digest::MD5.hexdigest(session[:current_user]),
+ user_info: {
+ name: session[:current_user]
+ }
+ }).to_json
+end
View
@@ -0,0 +1,62 @@
+/* @override http://127.0.0.1:3000/screen.css */
+
+* {
+ margin: 0;
+ padding: 0;
+}
+
+body, html {
+ height: 100%;
+ font: 90% "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
+ overflow: hidden;
+}
+
+#presence {
+ width: 300px;
+ position: absolute;
+ top: 0;
+ right: 0;
+ border-left: 1px solid #ddd;
+ z-index: -1;
+}
+
+#presence li {
+ margin: 5px 10px;
+}
+
+#main {
+ height: 100%;
+}
+
+#message {
+ width: 600px;
+}
+
+input {
+
+}
+
+#main form {
+ vertical-align: bottom;
+ position: absolute;
+ bottom: 0;
+ padding: 10px;
+ background-color: #333;
+ width: 100%;
+}
+
+#messages li {
+ margin: 5px 7px;
+ overflow: scroll;
+}
+
+#messages li.info {
+ color: #999;
+}
+
+ol {
+ list-style-type: none;
+ height: 100%;
+}
+
+
View
@@ -0,0 +1,55 @@
+require 'sinatra'
+require 'haml'
+require 'pusher'
+require 'json'
+require 'digest/md5'
+require 'thin'
+
+set :views, File.dirname(__FILE__) + '/templates'
+set :port, 3001
+
+enable :sessions
+
+Pusher.app_id = '8792'
+Pusher.key = '5ad8640c9b11e84cc60a'
markburns
markburns Apr 26, 2012 Collaborator

Should this or even this file even be here?

stevegraham
stevegraham Apr 26, 2012 Owner

not really, but i also don't care. :)

+Pusher.secret = '11a872de453861e042a9'
+
+get '/' do
+ @channel = "MY_CHANNEL"
+ haml :index
+end
+
+get '/chat' do
+ @use_pusher = true
+ @channel = "presence-channel"
+ if session[:current_user]
+ haml :chat_room
+ else
+ haml :chat_lobby
+ end
+end
+
+post '/chat' do
+ if session[:current_user]
+ Pusher['presence-channel'].trigger_async('chat_message', {
+ sender: session[:current_user], body: params['message']
+ })
+ request.xhr? ? status(201) : redirect('/chat')
+ else
+ status 403
+ end
+end
+
+post '/identify' do
+ session[:current_user] = params['handle']
+ redirect request.referer
+end
+
+post '/pusher/auth' do
+ Pusher[params['channel_name']].authenticate(params['socket_id'], {
+ user_id: Digest::MD5.hexdigest(session[:current_user]),
+ user_info: {
+ name: session[:current_user]
+ }
+ }).to_json
+end
@@ -0,0 +1,15 @@
+!!!
+%html
+ %head
+ %title Chat Lobby
+ %body
+ %h1 Slanger Chat
+ %p
+ Share those special slanger moments that always bring a smile to your face.
+
+ %form{ action: '/identify', method: 'POST'}
+ %label{ for: 'handle' }
+ Handle
+ %input{ type: 'text', id: 'handle', name: 'handle' }
+ %input{ type: 'submit' }
+
@@ -0,0 +1,51 @@
+!!!
+%html
+ %head
+ %title Chat Room
+ %meta{ 'http-equiv' => 'Content-Type', content: 'text/html', charset: 'utf-8' }
+ %link{ rel: 'stylesheet', href: 'screen.css', type: 'text/css' }
+
+ %body
+ %div{ id: 'main'}
+ %ol{ id: 'messages' }
+ %form{ action: '/chat', method: 'POST' }
+ %input{ type: 'text', id: 'message', name: 'message' }
+ %input{ type: 'submit', value: 'Send' }
+ %div{ id: 'sidebar' }
+ %ol{ id: 'presence' }
+ %script{src: "http://js.pusherapp.com/1.8/pusher.min.js"}
+ %script{src: "http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"}
+ :javascript
+ Pusher.host = "#{@use_pusher ? 'ws.pusherapp.com' : '0.0.0.0' }"
+ Pusher.ws_port = "#{@use_pusher ? 80 : 8080}"
+ Pusher.log = function(data) {
+ console.log('\t\t', data);
+ };
+ var pusher = new Pusher('#{Pusher.key}');
+ var myChannel = pusher.subscribe('presence-channel');
+ myChannel.bind('chat_message', function(msg) {
+ $('#messages').append("<li><strong>" + msg['sender'] + "</strong>: " + msg['body'] + "</li>")
+ })
+
+ myChannel.bind('pusher:subscription_succeeded', function(members) {
+ members.each(function(member) {
+ $('#presence').append($('<li>' + member.info.name + '</li>').attr('id', member.id))
+ })
+ })
+
+ myChannel.bind('pusher:member_added', function(member) {
+ $('#messages').append($('<li class="info">**** ' + member.info.name + ' entered the chat ****</li>'))
+ $('#presence').append($('<li>' + member.info.name + '</li>').attr('id', member.id))
+ })
+
+ myChannel.bind('pusher:member_removed', function(member) {
+ $('#messages').append($('<li class="info">**** ' + member.info.name + ' left the chat ****</li>'))
+ $('#' + member.id).remove()
+ })
+
+
+ $('#main form').submit(function(ev) {
+ $.post('/chat', { message: $('#message').val() })
+ $('#message').val(null)
+ return false
+ })
@@ -0,0 +1,16 @@
+!!!
+%html
+ %head
+ %title Websocket Test
+ %body
+ %script{src: "http://js.pusherapp.com/1.8/pusher.min.js"}
+ :javascript
+ //Pusher.host = "0.0.0.0"
+ //Pusher.ws_port = "8080"
+ Pusher.log = function(data) {
+ console.log('\t\t', data);
+ };
+ var pusher = new Pusher('5ad8640c9b11e84cc60a');
+ pusher.bind('pusher:error', function(data) { alert(data['message']) })
+ var myChannel = pusher.subscribe('#{@channel}');
+ myChannel.bind('an_event', function(data) { alert('WOWEEWOWEEWOWEEE!') })
View
@@ -17,4 +17,4 @@
myChannel.bind('an_event', function(data) { alert('WOWEEWOWEEWOWEEE!') })
</script>
</body>
-</html>
+</html>
@@ -21,7 +21,7 @@ class ApiServer < Sinatra::Base
# authenticate request. exclude 'channel_id' and 'app_id', these are added the the params
# by the pusher client lib after computing HMAC
Signature::Request.new('POST', env['PATH_INFO'], params.except('channel_id', 'app_id')).
- authenticate { |key| Signature::Token.new key, lookup_secret[key] }
+ authenticate { |key| Signature::Token.new key, Slanger::Config.secret }
f = Fiber.current
Slanger::Redis.publish(params[:channel_id], payload).tap do |r|
@@ -37,10 +37,6 @@ def payload
}
Hash[payload.reject { |k,v| v.nil? }].to_json
end
-
- def lookup_secret
- Hash.new "your-pusher-secret"
- end
end
end
View
@@ -0,0 +1,24 @@
+module Slanger
+ module Config
+ def load(opts={})
+ options.update opts
+ end
+
+ def [](key)
+ options[key]
+ end
+
+ def options
+ @options ||= {
+ api_host: '0.0.0.0', api_port: '4567', websocket_host: '0.0.0.0',
+ websocket_port: '8080', debug: false, redis_address: 'redis://0.0.0.0:6379/0'
+ }
+ end
+
+ def method_missing(meth, *args, &blk)
+ @options[meth]
+ end
+
+ extend self
+ end
+end
Oops, something went wrong.

0 comments on commit 89b962d

Please sign in to comment.