pusher-client-nc 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,54 @@
1
+ module PusherClient
2
+ class Subscription
3
+ attr_accessor :global, :subscribed
4
+ attr_reader :channel, :user_data, :callbacks, :global_callbacks
5
+
6
+ def initialize(channel_name, user_data)
7
+ @channel = channel_name
8
+ @user_data = user_data
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,46 @@
1
+ module PusherClient
2
+ class Subscriptions
3
+ attr_reader :subscriptions
4
+
5
+ def initialize
6
+ @subscriptions = []
7
+ end
8
+
9
+ def add(channel_name, user_data)
10
+ unless find(channel_name, user_data)
11
+ @subscriptions << Subscription.new(channel_name, user_data)
12
+ end
13
+ find(channel_name, user_data)
14
+ end
15
+
16
+ def find_all(channel_name)
17
+ @subscriptions.select {|s| s.channel == channel_name }
18
+ end
19
+
20
+ def find_for_bind(channel_name)
21
+ @subscriptions.detect {|s| s.channel == channel_name }
22
+ end
23
+
24
+ def find(channel_name, user_data)
25
+ @subscriptions.detect { |s| s.channel == channel_name && s.user_data == user_data }
26
+ end
27
+
28
+ def remove(channel_name, user_data)
29
+ subscription = find(channel_name, user_data)
30
+ @subscriptions.delete(subscription)
31
+ @subscriptions
32
+ end
33
+
34
+ def empty?
35
+ @subscriptions.empty?
36
+ end
37
+
38
+ def size
39
+ @subscriptions.size
40
+ end
41
+
42
+ alias :<< :add
43
+ alias :[] :find_for_bind
44
+
45
+ end
46
+ end
@@ -0,0 +1,62 @@
1
+ require 'rubygems'
2
+ require 'socket'
3
+ require 'libwebsocket'
4
+
5
+ module PusherClient
6
+ class WebSocket
7
+
8
+ def initialize(url, params = {})
9
+ @hs ||= LibWebSocket::OpeningHandshake::Client.new(:url => url, :version => params[:version])
10
+ @frame ||= LibWebSocket::Frame.new
11
+
12
+ @socket = TCPSocket.new(@hs.url.host, @hs.url.port || 80)
13
+
14
+ @socket.write(@hs.to_s)
15
+ @socket.flush
16
+
17
+ loop do
18
+ data = @socket.getc
19
+ next if data.nil?
20
+
21
+ result = @hs.parse(data.chr)
22
+
23
+ raise @hs.error unless result
24
+
25
+ if @hs.done?
26
+ @handshaked = true
27
+ break
28
+ end
29
+ end
30
+ end
31
+
32
+ def send(data)
33
+ raise "no handshake!" unless @handshaked
34
+
35
+ data = @frame.new(data).to_s
36
+ @socket.write data
37
+ @socket.flush
38
+ end
39
+
40
+ def receive
41
+ raise "no handshake!" unless @handshaked
42
+
43
+ data = @socket.gets("\xff")
44
+ @frame.append(data)
45
+
46
+ messages = []
47
+ while message = @frame.next
48
+ messages << message
49
+ end
50
+ messages
51
+ end
52
+
53
+ def socket
54
+ @socket
55
+ end
56
+
57
+ def close
58
+ @socket.close
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,85 @@
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-nc}
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, Neil Cauldwell"]
12
+ s.date = %q{2012-05-28}
13
+ s.description = %q{Ruby client for consuming WebSockets from http://pusherapp.com}
14
+ s.email = %q{support@nur.ph}
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/subscribe.rb",
28
+ "examples/subscribe_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/subscription.rb",
34
+ "lib/pusher-client/subscriptions.rb",
35
+ "lib/pusher-client/websocket.rb",
36
+ "pusher-client-nc.gemspec",
37
+ "pusher-client.gemspec",
38
+ "test/pusherclient_test.rb",
39
+ "test/test.watchr",
40
+ "test/teststrap.rb"
41
+ ]
42
+ s.homepage = %q{http://github.com/logankoester/pusher-client}
43
+ s.licenses = ["MIT"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.3.7}
46
+ s.summary = %q{Ruby client for consuming WebSockets from http://pusherapp.com}
47
+ s.test_files = [
48
+ "examples/subscribe.rb",
49
+ "examples/subscribe_async.rb",
50
+ "test/pusherclient_test.rb",
51
+ "test/teststrap.rb"
52
+ ]
53
+
54
+ if s.respond_to? :specification_version then
55
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
56
+ s.specification_version = 3
57
+
58
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
59
+ s.add_runtime_dependency(%q<libwebsocket>, ["~> 0.1.3"])
60
+ s.add_development_dependency(%q<json>, [">= 0"])
61
+ s.add_development_dependency(%q<ruby-hmac>, [">= 0"])
62
+ s.add_development_dependency(%q<bacon>, [">= 0"])
63
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
64
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
65
+ s.add_development_dependency(%q<rcov>, [">= 0"])
66
+ else
67
+ s.add_dependency(%q<libwebsocket>, ["~> 0.1.3"])
68
+ s.add_dependency(%q<json>, [">= 0"])
69
+ s.add_dependency(%q<ruby-hmac>, [">= 0"])
70
+ s.add_dependency(%q<bacon>, [">= 0"])
71
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
72
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
73
+ s.add_dependency(%q<rcov>, [">= 0"])
74
+ end
75
+ else
76
+ s.add_dependency(%q<libwebsocket>, ["~> 0.1.3"])
77
+ s.add_dependency(%q<json>, [">= 0"])
78
+ s.add_dependency(%q<ruby-hmac>, [">= 0"])
79
+ s.add_dependency(%q<bacon>, [">= 0"])
80
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
81
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
82
+ s.add_dependency(%q<rcov>, [">= 0"])
83
+ end
84
+ end
85
+
@@ -0,0 +1,84 @@
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}
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{2012-05-28}
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/subscribe.rb",
28
+ "examples/subscribe_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/subscription.rb",
34
+ "lib/pusher-client/subscriptions.rb",
35
+ "lib/pusher-client/websocket.rb",
36
+ "pusher-client.gemspec",
37
+ "test/pusherclient_test.rb",
38
+ "test/test.watchr",
39
+ "test/teststrap.rb"
40
+ ]
41
+ s.homepage = %q{http://github.com/logankoester/pusher-client}
42
+ s.licenses = ["MIT"]
43
+ s.require_paths = ["lib"]
44
+ s.rubygems_version = %q{1.3.7}
45
+ s.summary = %q{Ruby client for consuming WebSockets from http://pusherapp.com}
46
+ s.test_files = [
47
+ "examples/subscribe.rb",
48
+ "examples/subscribe_async.rb",
49
+ "test/pusherclient_test.rb",
50
+ "test/teststrap.rb"
51
+ ]
52
+
53
+ if s.respond_to? :specification_version then
54
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
58
+ s.add_runtime_dependency(%q<libwebsocket>, ["~> 0.1.3"])
59
+ s.add_development_dependency(%q<json>, [">= 0"])
60
+ s.add_development_dependency(%q<ruby-hmac>, [">= 0"])
61
+ s.add_development_dependency(%q<bacon>, [">= 0"])
62
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
63
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
64
+ s.add_development_dependency(%q<rcov>, [">= 0"])
65
+ else
66
+ s.add_dependency(%q<libwebsocket>, ["~> 0.1.3"])
67
+ s.add_dependency(%q<json>, [">= 0"])
68
+ s.add_dependency(%q<ruby-hmac>, [">= 0"])
69
+ s.add_dependency(%q<bacon>, [">= 0"])
70
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
71
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
72
+ s.add_dependency(%q<rcov>, [">= 0"])
73
+ end
74
+ else
75
+ s.add_dependency(%q<libwebsocket>, ["~> 0.1.3"])
76
+ s.add_dependency(%q<json>, [">= 0"])
77
+ s.add_dependency(%q<ruby-hmac>, [">= 0"])
78
+ s.add_dependency(%q<bacon>, [">= 0"])
79
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
80
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
81
+ s.add_dependency(%q<rcov>, [">= 0"])
82
+ end
83
+ end
84
+
@@ -0,0 +1,246 @@
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
+ @channel.bind('TestEvent') do
55
+ PusherClient.logger.test "Local callback running"
56
+ end
57
+
58
+ @channel.dispatch('TestEvent', {})
59
+ PusherClient.logger.test_messages.should.include?("Local callback running")
60
+ end
61
+ end
62
+
63
+ describe "A PusherClient::Subscriptions collection" do
64
+ before do
65
+ @subscriptions = PusherClient::Subscriptions.new
66
+ end
67
+
68
+ it "should initialize empty" do
69
+ @subscriptions.empty?.should.equal(true)
70
+ @subscriptions.size.should.equal 0
71
+ end
72
+
73
+ it "should instantiate new subscriptions added to it by channel and user_data" do
74
+ @subscriptions.add('TestChannel', 'user_id_1')
75
+ @subscription = @subscriptions.find('TestChannel', 'user_id_1')
76
+ @subscription.class.should.equal(PusherClient::Subscription)
77
+ end
78
+
79
+ it "should be able to find all subscriptions with a channel_name" do
80
+ @subscriptions.add('TestChannel', 'user_id_1')
81
+ @subscriptions.add('TestChannel', 'user_id_2')
82
+ @subs = @subscriptions.find_all('TestChannel')
83
+ @subs.size.should.equal 2
84
+ @subs.each { |s| s.channel.should.equal('TestChannel') }
85
+ end
86
+
87
+ it "should be able to find a unique subscription by channel_name and user_data" do
88
+ @subscriptions.add('TestChannel', 'user_id_1')
89
+ @subscription = @subscriptions.find('TestChannel', 'user_id_1')
90
+ @subscription.channel.should.equal('TestChannel')
91
+ @subscription.user_data.should.equal('user_id_1')
92
+ end
93
+
94
+ it "should allow removal of subscriptions by channel and user_data" do
95
+ @subscriptions.add('TestChannel', 'user_id_1')
96
+ @subscription = @subscriptions.find('TestChannel', 'user_id_1')
97
+ @subscription.class.should.equal(PusherClient::Subscription)
98
+ @subscriptions.remove('TestChannel', 'user_id_1')
99
+ @subscriptions.empty?.should.equal(true)
100
+ end
101
+
102
+ it "should not allow two subscriptions of the same channel and user" do
103
+ @subscriptions.add('TestChannel', 'user_id_1')
104
+ @subscriptions.add('TestChannel', 'user_id_1')
105
+ @subscriptions.add('TestChannel', 'user_id_2')
106
+ @subscriptions.size.should.equal 2
107
+ end
108
+ end
109
+
110
+ describe "A PusherClient::Subscription" do
111
+ before do
112
+ @subscriptions = PusherClient::Subscriptions.new
113
+ @subscription = @subscriptions.add("TestChannel", "user_id_1")
114
+ end
115
+
116
+ it 'should have a channel and a channel_name' do
117
+ @subscription.channel.should.equal "TestChannel"
118
+ end
119
+
120
+ it 'should have user_data' do
121
+ @subscription.user_data.should.equal "user_id_1"
122
+ end
123
+
124
+ it 'should not be subscribed by default' do
125
+ @subscription.subscribed.should.equal false
126
+ end
127
+
128
+ it 'should not be global by default' do
129
+ @subscription.global.should.equal false
130
+ end
131
+
132
+ it 'can have procs bound to an event' do
133
+ @subscription.bind('TestEvent') {}
134
+ @subscription.callbacks.size.should.equal 1
135
+ end
136
+
137
+ it 'should run callbacks when an event is dispatched' do
138
+ @subscription.bind('TestEvent') do
139
+ PusherClient.logger.test "Local callback running"
140
+ end
141
+
142
+ @subscription.dispatch('TestEvent', {})
143
+ PusherClient.logger.test_messages.should.include?("Local callback running")
144
+ end
145
+ end
146
+
147
+ describe "A PusherClient::Socket" do
148
+ before do
149
+ @socket = PusherClient::Socket.new(TEST_APP_KEY)
150
+ end
151
+
152
+ it 'should not connect when instantiated' do
153
+ @socket.connected.should.equal false
154
+ end
155
+
156
+ it 'should raise ArgumentError if TEST_APP_KEY is not a nonempty string' do
157
+ lambda {
158
+ @broken_socket = PusherClient::Socket.new('')
159
+ }.should.raise(ArgumentError)
160
+ lambda {
161
+ @broken_socket = PusherClient::Socket.new(555)
162
+ }.should.raise(ArgumentError)
163
+ end
164
+
165
+ describe "...when connected" do
166
+ before do
167
+ @socket.connect
168
+ end
169
+
170
+ it 'should know its connected' do
171
+ @socket.connected.should.equal true
172
+ end
173
+
174
+ it 'should know its socket_id' do
175
+ @socket.socket_id.should.equal '123abc'
176
+ end
177
+
178
+ it 'should not be subscribed to its global channel' do
179
+ @socket.global_channel.subscribed.should.equal false
180
+ end
181
+
182
+ it 'should create a subscription to a Public channel' do
183
+ @socket.subscriptions.size.should.equal 0
184
+ @subscription = @socket.subscribe('TestChannel')
185
+ @socket.subscriptions.size.should.equal 1
186
+ @subscription.subscribed.should.equal true
187
+ end
188
+
189
+ it 'should create a subscription to a Presence channel' do
190
+ @socket.subscriptions.size.should.equal 0
191
+ @subscription = @socket.subscribe('presence-TestChannel', 'user_id_1')
192
+ @socket.subscriptions.size.should.equal 1
193
+ @subscription.subscribed.should.equal true
194
+ end
195
+
196
+ it 'should create a subscription to a Private channel' do
197
+ @socket.subscriptions.size.should.equal 0
198
+ @subscription = @socket.subscribe('private-TestChannel', 'user_id_1')
199
+ @socket.subscriptions.size.should.equal 1
200
+ @subscription.subscribed.should.equal true
201
+ end
202
+
203
+ it 'should trigger callbacks for global events' do
204
+ @socket.bind('globalevent') { |data| PusherClient.logger.test("Global event!") }
205
+ @socket.global_channel.callbacks.has_key?('globalevent').should.equal true
206
+
207
+ @socket.simulate_received('globalevent', 'some data', '')
208
+ PusherClient.logger.test_messages.last.should.include?('Global event!')
209
+ end
210
+
211
+ it 'should kill the connection thread when disconnect is called' do
212
+ @socket.disconnect
213
+ Thread.list.size.should.equal 1
214
+ end
215
+
216
+ it 'should not be connected after disconnecting' do
217
+ @socket.disconnect
218
+ @socket.connected.should.equal false
219
+ end
220
+
221
+ describe "...when subscribed to a subscription" do
222
+ before do
223
+ @subscription = @socket.subscribe('TestChannel')
224
+ end
225
+
226
+ it 'should allow binding of callbacks for the subscribed subscription' do
227
+ @socket['TestChannel'].bind('TestEvent') { |data| PusherClient.logger.test(data) }
228
+ @socket['TestChannel'].callbacks.has_key?('TestEvent').should.equal true
229
+ end
230
+
231
+ it "should trigger channel callbacks when a message is received" do
232
+ # Bind 2 events for the channel
233
+ @socket['TestChannel'].bind('coming') { |data| PusherClient.logger.test(data) }
234
+ @socket['TestChannel'].bind('going') { |data| PusherClient.logger.test(data) }
235
+
236
+ # Simulate the first event
237
+ @socket.simulate_received('coming', 'Hello!', 'TestChannel')
238
+ PusherClient.logger.test_messages.last.should.include?('Hello!')
239
+
240
+ # Simulate the second event
241
+ @socket.simulate_received('going', 'Goodbye!', 'TestChannel')
242
+ PusherClient.logger.test_messages.last.should.include?('Goodbye!')
243
+ end
244
+ end
245
+ end
246
+ end