pusher-client-merman 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +23 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +88 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/examples/hello_pusher.rb +20 -0
- data/examples/hello_pusher_async.rb +23 -0
- data/lib/pusher-client.rb +24 -0
- data/lib/pusher-client/channel.rb +54 -0
- data/lib/pusher-client/channels.rb +38 -0
- data/lib/pusher-client/socket.rb +199 -0
- data/lib/pusher-client/websocket.rb +84 -0
- data/pusher-client.gemspec +73 -0
- data/test/pusherclient_test.rb +161 -0
- data/test/test.watchr +63 -0
- data/test/teststrap.rb +58 -0
- metadata +133 -0
data/.document
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -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!
|
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.rdoc
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
@@ -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
|
data/test/test.watchr
ADDED
@@ -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
|
data/test/teststrap.rb
ADDED
@@ -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
|