pusher-client-merman 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
@@ -0,0 +1,23 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ pusher-client (0.2.1)
5
+ addressable (~> 2.3.1)
6
+ libwebsocket (= 0.1.0)
7
+ ruby-hmac (~> 0.4.0)
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ addressable (2.3.1)
13
+ bacon (1.1.0)
14
+ libwebsocket (0.1.0)
15
+ ruby-hmac (0.4.0)
16
+
17
+ PLATFORMS
18
+ java
19
+ ruby
20
+
21
+ DEPENDENCIES
22
+ bacon
23
+ pusher-client!
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Logan Koester
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,88 @@
1
+ = pusher-client (Ruby)
2
+
3
+ pusher-client is a ruby gem for consuming WebSockets from the Pusher[http://pusherapp.com] web service.
4
+
5
+ The connection to Pusher can optionally be maintained in its own thread (see Asynchronous Usage).
6
+
7
+ This gem no longer depends on em-http, and is compatible with jruby since 0.2.
8
+
9
+ When binding to a global event, note that you still must be subscribed to the channels the event
10
+ may be sent on. You can't just bind a global event without subscribing to any channels and call it a day.
11
+
12
+ == Installation
13
+ gem install pusher-client
14
+ == Single-Threaded Usage
15
+ The application will pause at socket.connect and handle events from Pusher as they happen.
16
+
17
+ require 'pusher-client'
18
+ PusherClient.logger = Logger.new(STDOUT)
19
+ options = {:secret => 'YOUR_APPLICATION_SECRET'}
20
+ socket = PusherClient::Socket.new(YOUR_APPLICATION_KEY, options)
21
+
22
+ # Subscribe to two channels
23
+ socket.subscribe('channel1')
24
+ socket.subscribe('channel2')
25
+
26
+ # Subscribe to presence channel
27
+ socket.subscribe('presence-channel3', USER_ID)
28
+
29
+ # Subscribe to private channel
30
+ socket.subscribe('private-channel4', USER_ID)
31
+
32
+ # Bind to a global event (can occur on either channel1 or channel2)
33
+ socket.bind('globalevent') do |data|
34
+ puts data
35
+ end
36
+
37
+ # Bind to a channel event (can only occur on channel1)
38
+ socket['channel1'].bind('channelevent') do |data|
39
+ puts data
40
+ end
41
+
42
+ socket.connect
43
+
44
+ == Asynchronous Usage
45
+ The socket will remain open in the background as long as your main application thread is running,
46
+ and you can continue to subscribe/unsubscribe to channels and bind new events.
47
+
48
+ require 'pusher-client'
49
+ PusherClient.logger = Logger.new(STDOUT)
50
+ socket = PusherClient::Socket.new(YOUR_APPLICATION_KEY)
51
+ socket.connect(true) # Connect asynchronously
52
+
53
+ # Subscribe to two channels
54
+ socket.subscribe('channel1')
55
+ socket.subscribe('channel2')
56
+
57
+ # Bind to a global event (can occur on either channel1 or channel2)
58
+ socket.bind('globalevent') do |data|
59
+ puts data
60
+ end
61
+
62
+ # Bind to a channel event (can only occur on channel1)
63
+ socket['channel1'].bind('channelevent') do |data|
64
+ puts data
65
+ end
66
+
67
+ loop do
68
+ sleep(1) # Keep your main thread running
69
+ end
70
+
71
+ For further documentation, read the source & test suite. Some features of the JavaScript client
72
+ are not yet implemented.
73
+
74
+ == Contributing to pusher-client
75
+
76
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
77
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
78
+ * Fork the project
79
+ * Start a feature/bugfix branch
80
+ * Commit and push until you are happy with your contribution
81
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
82
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
83
+
84
+ == Copyright
85
+
86
+ Copyright (c) 2010 Logan Koester. See LICENSE.txt for
87
+ further details.
88
+
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "pusher-client"
16
+ gem.homepage = "http://github.com/pusher/pusher-ruby-client"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Ruby client for consuming WebSockets from http://pusher.com}
19
+ gem.description = %Q{Ruby client for consuming WebSockets from http://pusher.com}
20
+ gem.email = "logan@logankoester.com"
21
+ gem.authors = ["Logan Koester", "Phil Leggetter"]
22
+ end
23
+ Jeweler::RubygemsDotOrgTasks.new
24
+
25
+ require 'rake/testtask'
26
+ Rake::TestTask.new(:test) do |test|
27
+ test.libs << 'lib' << 'test'
28
+ test.pattern = 'test/**/*_test.rb'
29
+ test.verbose = true
30
+ end
31
+
32
+ require 'rcov/rcovtask'
33
+ Rcov::RcovTask.new do |test|
34
+ test.libs << 'test'
35
+ test.pattern = 'test/**/*_test.rb'
36
+ test.verbose = true
37
+ end
38
+
39
+ task :default => :test
40
+
41
+ require 'rake/rdoctask'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "pusher-client #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.2-alpha
@@ -0,0 +1,20 @@
1
+ # Usage: $ PUSHER_KEY=YOURKEY ruby examples/hello_pusher.rb
2
+
3
+ require 'rubygems'
4
+ require './lib/pusher-client.rb'
5
+ require 'pp'
6
+
7
+ APP_KEY = ENV['PUSHER_KEY'] # || "YOUR_APPLICATION_KEY"
8
+
9
+ PusherClient.logger = Logger.new(STDOUT)
10
+ socket = PusherClient::Socket.new(APP_KEY)
11
+
12
+ # Subscribe to a channel
13
+ socket.subscribe('hellopusher')
14
+
15
+ # Bind to a channel event
16
+ socket['hellopusher'].bind('hello') do |data|
17
+ pp data
18
+ end
19
+
20
+ socket.connect
@@ -0,0 +1,23 @@
1
+ # Usage: $ PUSHER_KEY=YOURKEY ruby examples/hello_pusher.rb
2
+
3
+ require 'rubygems'
4
+ require './lib/pusher-client.rb'
5
+ require 'pp'
6
+
7
+ APP_KEY = ENV['PUSHER_KEY'] # || "YOUR_APPLICATION_KEY"
8
+
9
+ PusherClient.logger = Logger.new('/dev/null')
10
+ socket = PusherClient::Socket.new(APP_KEY)
11
+ socket.connect(true)
12
+
13
+ # Subscribe to a channel
14
+ socket.subscribe('hellopusher')
15
+
16
+ # Bind to a channel event
17
+ socket['hellopusher'].bind('hello') do |data|
18
+ pp data
19
+ end
20
+
21
+ loop do
22
+ sleep 1
23
+ end
@@ -0,0 +1,24 @@
1
+ autoload :Logger, 'logger'
2
+
3
+ module PusherClient
4
+ HOST = 'ws.pusherapp.com'
5
+ WS_PORT = 80
6
+ WSS_PORT = 443
7
+
8
+ @logger = Logger.new(STDOUT)
9
+
10
+ def self.logger
11
+ @logger
12
+ end
13
+
14
+ def self.logger=(logger)
15
+ @logger = logger
16
+ end
17
+ end
18
+
19
+ Thread.abort_on_exception = true
20
+
21
+ require File.dirname(__FILE__) + '/pusher-client/websocket.rb'
22
+ require File.dirname(__FILE__) + '/pusher-client/socket.rb'
23
+ require File.dirname(__FILE__) + '/pusher-client/channel.rb'
24
+ require File.dirname(__FILE__) + '/pusher-client/channels.rb'
@@ -0,0 +1,54 @@
1
+ module PusherClient
2
+
3
+ class Channel
4
+ attr_accessor :global, :subscribed
5
+ attr_reader :name, :callbacks, :global_callbacks
6
+
7
+ def initialize(channel_name)
8
+ @name = channel_name
9
+ @global = false
10
+ @callbacks = {}
11
+ @global_callbacks = {}
12
+ @subscribed = false
13
+ end
14
+
15
+ def bind(event_name, &callback)
16
+ @callbacks[event_name] = callbacks[event_name] || []
17
+ @callbacks[event_name] << callback
18
+ return self
19
+ end
20
+
21
+ def dispatch_with_all(event_name, data)
22
+ dispatch(event_name, data)
23
+ dispatch_global_callbacks(event_name, data)
24
+ end
25
+
26
+ def dispatch(event_name, data)
27
+ PusherClient.logger.debug "Dispatching callbacks for #{event_name}"
28
+ if @callbacks[event_name]
29
+ @callbacks[event_name].each do |callback|
30
+ callback.call(data)
31
+ end
32
+ else
33
+ PusherClient.logger.debug "No callbacks to dispatch for #{event_name}"
34
+ end
35
+ end
36
+
37
+ def dispatch_global_callbacks(event_name, data)
38
+ if @global_callbacks[event_name]
39
+ PusherClient.logger.debug "Dispatching global callbacks for #{event_name}"
40
+ @global_callbacks[event_name].each do |callback|
41
+ callback.call(data)
42
+ end
43
+ else
44
+ PusherClient.logger.debug "No global callbacks to dispatch for #{event_name}"
45
+ end
46
+ end
47
+
48
+ def acknowledge_subscription(data)
49
+ @subscribed = true
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,38 @@
1
+ module PusherClient
2
+ class Channels
3
+
4
+ attr_reader :channels
5
+
6
+ def initialize
7
+ @channels = {}
8
+ end
9
+
10
+ def add(channel_name)
11
+ unless @channels[channel_name]
12
+ @channels[channel_name] = Channel.new(channel_name)
13
+ end
14
+ @channels[channel_name]
15
+ end
16
+
17
+ def find(channel_name)
18
+ @channels[channel_name]
19
+ end
20
+
21
+ def remove(channel_name)
22
+ @channels.delete(channel_name)
23
+ @channels
24
+ end
25
+
26
+ def empty?
27
+ @channels.empty?
28
+ end
29
+
30
+ def size
31
+ @channels.size
32
+ end
33
+
34
+ alias :<< :add
35
+ alias :[] :find
36
+
37
+ end
38
+ end
@@ -0,0 +1,199 @@
1
+ require 'json'
2
+ require 'hmac-sha2'
3
+ require 'digest/md5'
4
+
5
+ module PusherClient
6
+ class Socket
7
+
8
+ # Mimick the JavaScript client
9
+ CLIENT_ID = 'pusher-ruby-client'
10
+ VERSION = '1.7.1'
11
+
12
+ attr_accessor :encrypted, :secure
13
+ attr_reader :path, :connected, :channels, :global_channel, :socket_id
14
+
15
+ def initialize(application_key, options={})
16
+ raise ArgumentError if (!application_key.is_a?(String) || application_key.size < 1)
17
+
18
+ @path = "/app/#{application_key}?client=#{CLIENT_ID}&version=#{VERSION}"
19
+ @key = application_key
20
+ @secret = options[:secret]
21
+ @socket_id = nil
22
+ @channels = Channels.new
23
+ @global_channel = Channel.new('pusher_global_channel')
24
+ @global_channel.global = true
25
+ @secure = false
26
+ @connected = false
27
+ @encrypted = options[:encrypted] || false
28
+
29
+ bind('pusher:connection_established') do |data|
30
+ socket = JSON.parse(data)
31
+ @connected = true
32
+ @socket_id = socket['socket_id']
33
+ subscribe_all
34
+ end
35
+
36
+ bind('pusher:connection_disconnected') do |data|
37
+ @channels.channels.each { |c| c.disconnect }
38
+ end
39
+
40
+ bind('pusher:error') do |data|
41
+ PusherClient.logger.fatal("Pusher : error : #{data.inspect}")
42
+ end
43
+ end
44
+
45
+ def connect(async = false)
46
+ if @encrypted || @secure
47
+ url = "wss://#{HOST}:#{WSS_PORT}#{@path}"
48
+ else
49
+ url = "ws://#{HOST}:#{WS_PORT}#{@path}"
50
+ end
51
+ PusherClient.logger.debug("Pusher : connecting : #{url}")
52
+
53
+ @connection_thread = Thread.new {
54
+ options = {:ssl => @encrypted || @secure}
55
+ @connection = WebSocket.new(url, options)
56
+ PusherClient.logger.debug "Websocket connected"
57
+ loop do
58
+ msg = @connection.receive[0]
59
+ params = parser(msg)
60
+ next if (params['socket_id'] && params['socket_id'] == self.socket_id)
61
+ event_name = params['event']
62
+ event_data = params['data']
63
+ channel_name = params['channel']
64
+ send_local_event(event_name, event_data, channel_name)
65
+ end
66
+ }
67
+
68
+ @connection_thread.run
69
+ @connection_thread.join unless async
70
+ return self
71
+ end
72
+
73
+ def disconnect
74
+ if @connected
75
+ PusherClient.logger.debug "Pusher : disconnecting"
76
+ @connection.close
77
+ @connection_thread.kill if @connection_thread
78
+ @connected = false
79
+ else
80
+ PusherClient.logger.warn "Disconnect attempted... not connected"
81
+ end
82
+ end
83
+
84
+ def subscribe(channel_name, user_id = nil)
85
+ @user_data = {:user_id => user_id}.to_json unless user_id.nil?
86
+
87
+ channel = @channels << channel_name
88
+ if @connected
89
+ authorize(channel, method(:authorize_callback))
90
+ end
91
+ return channel
92
+ end
93
+
94
+ def unsubscribe(channel_name)
95
+ channel = @channels.remove channel_name
96
+ if @connected
97
+ send_event('pusher:unsubscribe', {
98
+ 'channel' => channel_name
99
+ })
100
+ end
101
+ return channel
102
+ end
103
+
104
+ def bind(event_name, &callback)
105
+ @global_channel.bind(event_name, &callback)
106
+ return self
107
+ end
108
+
109
+ def [](channel_name)
110
+ if @channels[channel_name]
111
+ @channels[channel_name]
112
+ else
113
+ @channels << channel_name
114
+ end
115
+ end
116
+
117
+ def subscribe_all
118
+ @channels.channels.clone.each{ |k,v|
119
+ subscribe(k)
120
+ }
121
+ end
122
+
123
+ #auth for private and presence
124
+ def authorize(channel, callback)
125
+ if is_private_channel(channel.name)
126
+ auth_data = get_private_auth(channel)
127
+ elsif is_presence_channel(channel.name)
128
+ auth_data = get_presence_auth(channel)
129
+ channel_data = @user_data
130
+ end
131
+ # could both be nil if didn't require auth
132
+ callback.call(channel, auth_data, channel_data)
133
+ end
134
+
135
+ def authorize_callback(channel, auth_data, channel_data)
136
+ send_event('pusher:subscribe', {
137
+ 'channel' => channel.name,
138
+ 'auth' => auth_data,
139
+ 'channel_data' => channel_data
140
+ })
141
+ channel.acknowledge_subscription(nil)
142
+ end
143
+
144
+ def is_private_channel(channel_name)
145
+ channel_name.match(/^private-/)
146
+ end
147
+
148
+ def is_presence_channel(channel_name)
149
+ channel_name.match(/^presence-/)
150
+ end
151
+
152
+ def get_private_auth(channel)
153
+ string_to_sign = @socket_id + ':' + channel.name
154
+ signature = HMAC::SHA256.hexdigest(@secret, string_to_sign)
155
+ return "#{@key}:#{signature}"
156
+ end
157
+
158
+ def get_presence_auth(channel)
159
+ string_to_sign = @socket_id + ':' + channel.name + ':' + @user_data
160
+ signature = HMAC::SHA256.hexdigest(@secret, string_to_sign)
161
+ return "#{@key}:#{signature}"
162
+ end
163
+
164
+
165
+ # For compatibility with JavaScript client API
166
+ alias :subscribeAll :subscribe_all
167
+
168
+ def send_event(event_name, data)
169
+ payload = {'event' => event_name, 'data' => data}.to_json
170
+ @connection.send(payload)
171
+ PusherClient.logger.debug("Pusher : sending event : #{payload}")
172
+ end
173
+
174
+ protected
175
+
176
+ def send_local_event(event_name, event_data, channel_name)
177
+ if (channel_name)
178
+ channel = @channels[channel_name]
179
+ if (channel)
180
+ channel.dispatch_with_all(event_name, event_data)
181
+ end
182
+ end
183
+
184
+ @global_channel.dispatch_with_all(event_name, event_data)
185
+ PusherClient.logger.debug("Pusher : event received : channel: #{channel_name}; event: #{event_name}")
186
+ end
187
+
188
+ def parser(data)
189
+ begin
190
+ return JSON.parse(data)
191
+ rescue => err
192
+ PusherClient.logger.warn(err)
193
+ PusherClient.logger.warn("Pusher : data attribute not valid JSON - you may wish to implement your own Pusher::Client.parser")
194
+ return data
195
+ end
196
+ end
197
+ end
198
+
199
+ end
@@ -0,0 +1,84 @@
1
+ require 'rubygems'
2
+ require 'socket'
3
+ require 'libwebsocket'
4
+ require 'openssl'
5
+
6
+ module PusherClient
7
+ class WebSocket
8
+
9
+ def initialize(url, params = {})
10
+ @hs ||= LibWebSocket::OpeningHandshake::Client.new(:url => url, :version => params[:version])
11
+ @frame ||= LibWebSocket::Frame.new
12
+
13
+ @socket = TCPSocket.new(@hs.url.host, @hs.url.port || 80)
14
+
15
+ if params[:ssl] == true
16
+
17
+ ctx = OpenSSL::SSL::SSLContext.new
18
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
19
+ # http://curl.haxx.se/ca/cacert.pem
20
+ ctx.ca_file = path_to_cert()
21
+
22
+ ssl_sock = OpenSSL::SSL::SSLSocket.new(@socket, ctx)
23
+ ssl_sock.sync_close = true
24
+ ssl_sock.connect
25
+
26
+ @socket = ssl_sock
27
+
28
+ end
29
+
30
+ @socket.write(@hs.to_s)
31
+ @socket.flush
32
+
33
+ loop do
34
+ data = @socket.getc
35
+ next if data.nil?
36
+
37
+ result = @hs.parse(data.chr)
38
+
39
+ raise @hs.error unless result
40
+
41
+ if @hs.done?
42
+ @handshaked = true
43
+ break
44
+ end
45
+ end
46
+ end
47
+
48
+ def path_to_cert
49
+ File.join(File.dirname(File.expand_path(__FILE__)), '../../certs/cacert.pem')
50
+ end
51
+
52
+ def send(data)
53
+ raise "no handshake!" unless @handshaked
54
+
55
+ data = @frame.new(data).to_s
56
+ @socket.write data
57
+ @socket.flush
58
+ end
59
+
60
+ def receive
61
+ raise "no handshake!" unless @handshaked
62
+
63
+ data = @socket.gets("\xff")
64
+ @frame.append(data)
65
+
66
+ messages = []
67
+ while message = @frame.next
68
+ messages << message
69
+ end
70
+ messages
71
+ end
72
+
73
+ def socket
74
+ @socket
75
+ end
76
+
77
+ def close
78
+ @socket.close
79
+ end
80
+
81
+ end
82
+ end
83
+
84
+
@@ -0,0 +1,73 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{pusher-client-merman}
8
+ s.version = "0.2.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Logan Koester"]
12
+ s.date = %q{2011-01-07}
13
+ s.description = %q{Ruby client for consuming WebSockets from http://pusherapp.com}
14
+ s.email = %q{logan@logankoester.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE.txt",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "examples/hello_pusher.rb",
28
+ "examples/hello_pusher_async.rb",
29
+ "lib/pusher-client.rb",
30
+ "lib/pusher-client/channel.rb",
31
+ "lib/pusher-client/channels.rb",
32
+ "lib/pusher-client/socket.rb",
33
+ "lib/pusher-client/websocket.rb",
34
+ "pusher-client.gemspec",
35
+ "test/pusherclient_test.rb",
36
+ "test/test.watchr",
37
+ "test/teststrap.rb"
38
+ ]
39
+ s.homepage = %q{http://github.com/logankoester/pusher-client}
40
+ s.licenses = ["MIT"]
41
+ s.require_paths = ["lib"]
42
+ s.rubygems_version = %q{1.3.7}
43
+ s.summary = %q{Ruby client for consuming WebSockets from http://pusherapp.com}
44
+ s.test_files = [
45
+ "examples/hello_pusher.rb",
46
+ "examples/hello_pusher_async.rb",
47
+ "test/pusherclient_test.rb",
48
+ "test/teststrap.rb"
49
+ ]
50
+
51
+ if s.respond_to? :specification_version then
52
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
53
+ s.specification_version = 3
54
+
55
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
56
+ s.add_runtime_dependency(%q<libwebsocket>, ["0.1.0"])
57
+ s.add_runtime_dependency(%q<ruby-hmac>, ["~> 0.4.0"])
58
+ s.add_runtime_dependency(%q<addressable>, ["~> 2.3.1"])
59
+ s.add_development_dependency(%q<bacon>, [">= 0"])
60
+ else
61
+ s.add_dependency(%q<libwebsocket>, ["0.1.0"])
62
+ s.add_dependency(%q<ruby-hmac>, ["~> 0.4.0"])
63
+ s.add_dependency(%q<addressable>, ["~> 2.3.1"])
64
+ s.add_dependency(%q<bacon>, [">= 0"])
65
+ end
66
+ else
67
+ s.add_dependency(%q<libwebsocket>, ["0.1.0"])
68
+ s.add_dependency(%q<addressable>, ["~> 2.3.1"])
69
+ s.add_dependency(%q<ruby-hmac>, ["~> 0.4.0"])
70
+ s.add_dependency(%q<bacon>, [">= 0"])
71
+ end
72
+ end
73
+
@@ -0,0 +1,161 @@
1
+ require File.dirname(File.expand_path(__FILE__)) + '/teststrap.rb'
2
+ require 'logger'
3
+
4
+ describe "A PusherClient::Channels collection" do
5
+ before do
6
+ @channels = PusherClient::Channels.new
7
+ end
8
+
9
+ it "should initialize empty" do
10
+ @channels.empty?.should.equal(true)
11
+ @channels.size.should.equal 0
12
+ end
13
+
14
+ it "should instantiate new channels added to it by name" do
15
+ @channels << 'TestChannel'
16
+ @channels.find('TestChannel').class.should.equal(PusherClient::Channel)
17
+ end
18
+
19
+ it "should allow removal of channels by name" do
20
+ @channels << 'TestChannel'
21
+ @channels['TestChannel'].class.should.equal(PusherClient::Channel)
22
+ @channels.remove('TestChannel')
23
+ @channels.empty?.should.equal(true)
24
+ end
25
+
26
+ it "should not allow two channels of the same name" do
27
+ @channels << 'TestChannel'
28
+ @channels << 'TestChannel'
29
+ @channels.size.should.equal 1
30
+ end
31
+
32
+ end
33
+
34
+ describe "A PusherClient::Channel" do
35
+ before do
36
+ @channels = PusherClient::Channels.new
37
+ @channel = @channels << "TestChannel"
38
+ end
39
+
40
+ it 'should not be subscribed by default' do
41
+ @channel.subscribed.should.equal false
42
+ end
43
+
44
+ it 'should not be global by default' do
45
+ @channel.global.should.equal false
46
+ end
47
+
48
+ it 'can have procs bound to an event' do
49
+ @channel.bind('TestEvent') {}
50
+ @channel.callbacks.size.should.equal 1
51
+ end
52
+
53
+ it 'should run callbacks when an event is dispatched' do
54
+
55
+ @channel.bind('TestEvent') do
56
+ PusherClient.logger.test "Local callback running"
57
+ end
58
+
59
+ @channel.dispatch('TestEvent', {})
60
+ PusherClient.logger.test_messages.should.include?("Local callback running")
61
+ end
62
+
63
+ end
64
+
65
+ describe "A PusherClient::Socket" do
66
+ before do
67
+ @socket = PusherClient::Socket.new(TEST_APP_KEY)
68
+ end
69
+
70
+ it 'should not connect when instantiated' do
71
+ @socket.connected.should.equal false
72
+ end
73
+
74
+ it 'should raise ArgumentError if TEST_APP_KEY is not a nonempty string' do
75
+ lambda {
76
+ @broken_socket = PusherClient::Socket.new('')
77
+ }.should.raise(ArgumentError)
78
+ lambda {
79
+ @broken_socket = PusherClient::Socket.new(555)
80
+ }.should.raise(ArgumentError)
81
+ end
82
+
83
+ describe "...when connected" do
84
+ before do
85
+ @socket.connect
86
+ end
87
+
88
+ it 'should know its connected' do
89
+ @socket.connected.should.equal true
90
+ end
91
+
92
+ it 'should know its socket_id' do
93
+ @socket.socket_id.should.equal '123abc'
94
+ end
95
+
96
+ it 'should not be subscribed to its global channel' do
97
+ @socket.global_channel.subscribed.should.equal false
98
+ end
99
+
100
+ it 'should subscribe to a channel' do
101
+ @channel = @socket.subscribe('testchannel')
102
+ @socket.channels['testchannel'].should.equal @channel
103
+ @channel.subscribed.should.equal true
104
+ end
105
+
106
+ it 'should unsubscribe from a channel' do
107
+ @channel = @socket.unsubscribe('testchannel')
108
+ PusherClient.logger.test_messages.last.should.include?('pusher:unsubscribe')
109
+ @socket.channels['testchannel'].should.equal nil
110
+ end
111
+
112
+ it 'should allow binding of global events' do
113
+ @socket.bind('testevent') { |data| PusherClient.logger.test("testchannel received #{data}") }
114
+ @socket.global_channel.callbacks.has_key?('testevent').should.equal true
115
+ end
116
+
117
+ it 'should trigger callbacks for global events' do
118
+ @socket.bind('globalevent') { |data| PusherClient.logger.test("Global event!") }
119
+ @socket.global_channel.callbacks.has_key?('globalevent').should.equal true
120
+
121
+ @socket.simulate_received('globalevent', 'some data', '')
122
+ PusherClient.logger.test_messages.last.should.include?('Global event!')
123
+ end
124
+
125
+ it 'should kill the connection thread when disconnect is called' do
126
+ @socket.disconnect
127
+ Thread.list.size.should.equal 1
128
+ end
129
+
130
+ it 'should not be connected after disconnecting' do
131
+ @socket.disconnect
132
+ @socket.connected.should.equal false
133
+ end
134
+
135
+ describe "when subscribed to a channel" do
136
+ before do
137
+ @channel = @socket.subscribe('testchannel')
138
+ end
139
+
140
+ it 'should allow binding of callbacks for the subscribed channel' do
141
+ @socket['testchannel'].bind('testevent') { |data| PusherClient.logger.test(data) }
142
+ @socket['testchannel'].callbacks.has_key?('testevent').should.equal true
143
+ end
144
+
145
+ it "should trigger channel callbacks when a message is received" do
146
+ # Bind 2 events for the channel
147
+ @socket['testchannel'].bind('coming') { |data| PusherClient.logger.test(data) }
148
+ @socket['testchannel'].bind('going') { |data| PusherClient.logger.test(data) }
149
+
150
+ # Simulate the first event
151
+ @socket.simulate_received('coming', 'Hello!', 'testchannel')
152
+ PusherClient.logger.test_messages.last.should.include?('Hello!')
153
+
154
+ # Simulate the second event
155
+ @socket.simulate_received('going', 'Goodbye!', 'testchannel')
156
+ PusherClient.logger.test_messages.last.should.include?('Goodbye!')
157
+ end
158
+
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,63 @@
1
+ ENV["WATCHR"] = "1"
2
+ system 'clear'
3
+ puts "Watchr: Ready! :-)"
4
+
5
+ def notify_send(result)
6
+ #title = "Watchr Test Results"
7
+ if result.include?("FAILED") or result.include?("ERROR")
8
+ title = "FAIL"
9
+ image = "~/.autotest_images/fail.png"
10
+ message = "One or more tests have failed"
11
+ else
12
+ title = "PASS"
13
+ image = "~/.autotest_images/pass.png"
14
+ message = "All tests pass"
15
+ end
16
+
17
+ options = "-c Watchr --icon '#{File.expand_path(image)}' '#{title}' '#{message}' --urgency=critical"
18
+ system %(notify-send #{options} &)
19
+ end
20
+
21
+ def run(cmd)
22
+ puts(cmd)
23
+ `#{cmd}`
24
+ end
25
+
26
+ def run_all_tests
27
+ system('clear')
28
+ result = run "bacon test/*_test.rb"
29
+ notify_send result
30
+ puts result
31
+ end
32
+
33
+ def run_suite
34
+ run_all_tests
35
+ end
36
+
37
+ watch('test/teststrap\.rb') { run_all_tests }
38
+ watch('test/factories\.rb') { run_all_tests }
39
+ watch('test/.*_test.*\.rb') { run_all_tests }
40
+ watch('lib/*\.rb') { run_all_tests }
41
+ watch('lib/.*/*\.rb') { run_all_tests }
42
+
43
+ # Ctrl-\
44
+ Signal.trap 'QUIT' do
45
+ puts " --- Running all tests ---\n\n"
46
+ run_all_tests
47
+ end
48
+
49
+ @interrupted = false
50
+
51
+ # Ctrl-C
52
+ Signal.trap 'INT' do
53
+ if @interrupted then
54
+ @wants_to_quit = true
55
+ abort("\n")
56
+ else
57
+ puts "Interrupt a second time to quit"
58
+ @interrupted = true
59
+ Kernel.sleep 1.5
60
+ # raise Interrupt, nil # let the run loop catch it
61
+ run_suite
62
+ end
63
+ end
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'bacon'
11
+
12
+ require File.dirname(__FILE__) + '/../lib/pusher-client.rb'
13
+
14
+ TEST_APP_KEY = "TEST_APP_KEY"
15
+
16
+ module PusherClient
17
+ class TestLogger < Logger
18
+ attr_reader :test_messages
19
+
20
+ def initialize(logdev, shift_age = 0, shift_size = 1048576)
21
+ @test_messages = []
22
+ super
23
+ end
24
+ def test(msg)
25
+ @test_messages << msg
26
+ debug msg
27
+ end
28
+ end
29
+
30
+ class Socket
31
+ # Simulate a connection being established
32
+ def connect(async = false)
33
+ @connection_thread = Thread.new do
34
+ @connection = TestConnection.new
35
+ @global_channel.dispatch('pusher:connection_established', {'socket_id' => '123abc'})
36
+ end
37
+ @connection_thread.run
38
+ @connection_thread.join unless async
39
+ return self
40
+ end
41
+
42
+ def simulate_received(event_name, event_data, channel_name)
43
+ send_local_event(event_name, event_data, channel_name)
44
+ end
45
+ end
46
+
47
+ class TestConnection
48
+ def send(payload)
49
+ PusherClient.logger.test("SEND: #{payload}")
50
+ end
51
+
52
+ def close
53
+ end
54
+ end
55
+
56
+ PusherClient.logger = TestLogger.new('test.log')
57
+
58
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pusher-client-merman
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Logan Koester
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-01-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: libwebsocket
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.1.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - '='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.1.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: ruby-hmac
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.4.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.4.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: addressable
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 2.3.1
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.3.1
62
+ - !ruby/object:Gem::Dependency
63
+ name: bacon
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Ruby client for consuming WebSockets from http://pusherapp.com
79
+ email: logan@logankoester.com
80
+ executables: []
81
+ extensions: []
82
+ extra_rdoc_files:
83
+ - LICENSE.txt
84
+ - README.rdoc
85
+ files:
86
+ - .document
87
+ - Gemfile
88
+ - Gemfile.lock
89
+ - LICENSE.txt
90
+ - README.rdoc
91
+ - Rakefile
92
+ - VERSION
93
+ - examples/hello_pusher.rb
94
+ - examples/hello_pusher_async.rb
95
+ - lib/pusher-client.rb
96
+ - lib/pusher-client/channel.rb
97
+ - lib/pusher-client/channels.rb
98
+ - lib/pusher-client/socket.rb
99
+ - lib/pusher-client/websocket.rb
100
+ - pusher-client.gemspec
101
+ - test/pusherclient_test.rb
102
+ - test/test.watchr
103
+ - test/teststrap.rb
104
+ homepage: http://github.com/logankoester/pusher-client
105
+ licenses:
106
+ - MIT
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ! '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ! '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 1.8.23
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: Ruby client for consuming WebSockets from http://pusherapp.com
129
+ test_files:
130
+ - examples/hello_pusher.rb
131
+ - examples/hello_pusher_async.rb
132
+ - test/pusherclient_test.rb
133
+ - test/teststrap.rb