pusher-client-nc 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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