pusher 2.0.0 → 2.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +4 -4
- data/lib/pusher/version.rb +1 -1
- metadata +2 -19
- data/.github/stale.yml +0 -26
- data/.github/workflows/gh-release.yml +0 -35
- data/.github/workflows/publish.yml +0 -17
- data/.github/workflows/release.yml +0 -71
- data/.github/workflows/test.yml +0 -31
- data/.gitignore +0 -24
- data/Gemfile +0 -2
- data/Rakefile +0 -11
- data/examples/async_message.rb +0 -28
- data/examples/presence_channels/presence_channels.rb +0 -56
- data/examples/presence_channels/public/presence_channels.html +0 -28
- data/pull_request_template.md +0 -7
- data/pusher.gemspec +0 -36
- data/spec/channel_spec.rb +0 -187
- data/spec/client_spec.rb +0 -741
- data/spec/spec_helper.rb +0 -26
- data/spec/web_hook_spec.rb +0 -117
data/pull_request_template.md
DELETED
data/pusher.gemspec
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
require File.expand_path('../lib/pusher/version', __FILE__)
|
4
|
-
|
5
|
-
Gem::Specification.new do |s|
|
6
|
-
s.name = "pusher"
|
7
|
-
s.version = Pusher::VERSION
|
8
|
-
s.platform = Gem::Platform::RUBY
|
9
|
-
s.authors = ["Pusher"]
|
10
|
-
s.email = ["support@pusher.com"]
|
11
|
-
s.homepage = "http://github.com/pusher/pusher-http-ruby"
|
12
|
-
s.summary = %q{Pusher Channels API client}
|
13
|
-
s.description = %q{Wrapper for Pusher Channels REST api: : https://pusher.com/channels}
|
14
|
-
s.license = "MIT"
|
15
|
-
|
16
|
-
s.required_ruby_version = ">= 2.6"
|
17
|
-
|
18
|
-
s.add_dependency "multi_json", "~> 1.15"
|
19
|
-
s.add_dependency 'pusher-signature', "~> 0.1.8"
|
20
|
-
s.add_dependency "httpclient", "~> 2.8"
|
21
|
-
s.add_dependency "jruby-openssl" if defined?(JRUBY_VERSION)
|
22
|
-
|
23
|
-
s.add_development_dependency "rspec", "~> 3.9"
|
24
|
-
s.add_development_dependency "webmock", "~> 3.9"
|
25
|
-
s.add_development_dependency "em-http-request", "~> 1.1"
|
26
|
-
s.add_development_dependency "addressable", "~> 2.7"
|
27
|
-
s.add_development_dependency "rake", "~> 13.0"
|
28
|
-
s.add_development_dependency "rack", "~> 2.2"
|
29
|
-
s.add_development_dependency "json", "~> 2.3"
|
30
|
-
s.add_development_dependency "rbnacl", "~> 7.1"
|
31
|
-
|
32
|
-
s.files = `git ls-files`.split("\n")
|
33
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
34
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
35
|
-
s.require_paths = ["lib"]
|
36
|
-
end
|
data/spec/channel_spec.rb
DELETED
@@ -1,187 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
require 'spec_helper'
|
3
|
-
|
4
|
-
describe Pusher::Channel do
|
5
|
-
before do
|
6
|
-
@client = Pusher::Client.new({
|
7
|
-
:app_id => '20',
|
8
|
-
:key => '12345678900000001',
|
9
|
-
:secret => '12345678900000001',
|
10
|
-
:host => 'api.pusherapp.com',
|
11
|
-
:port => 80,
|
12
|
-
})
|
13
|
-
@channel = @client['test_channel']
|
14
|
-
end
|
15
|
-
|
16
|
-
let(:pusher_url_regexp) { %r{/apps/20/events} }
|
17
|
-
|
18
|
-
def stub_post(status, body = nil)
|
19
|
-
options = {:status => status}
|
20
|
-
options.merge!({:body => body}) if body
|
21
|
-
|
22
|
-
stub_request(:post, pusher_url_regexp).to_return(options)
|
23
|
-
end
|
24
|
-
|
25
|
-
def stub_post_to_raise(e)
|
26
|
-
stub_request(:post, pusher_url_regexp).to_raise(e)
|
27
|
-
end
|
28
|
-
|
29
|
-
describe '#trigger!' do
|
30
|
-
it "should use @client.trigger internally" do
|
31
|
-
expect(@client).to receive(:trigger)
|
32
|
-
@channel.trigger!('new_event', 'Some data')
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
describe '#trigger' do
|
37
|
-
it "should log failure if error raised in http call" do
|
38
|
-
stub_post_to_raise(HTTPClient::BadResponseError)
|
39
|
-
|
40
|
-
expect(Pusher.logger).to receive(:error).with("Exception from WebMock (HTTPClient::BadResponseError) (Pusher::HTTPError)")
|
41
|
-
expect(Pusher.logger).to receive(:debug) #backtrace
|
42
|
-
@channel.trigger('new_event', 'Some data')
|
43
|
-
end
|
44
|
-
|
45
|
-
it "should log failure if Pusher returns an error response" do
|
46
|
-
stub_post 401, "some signature info"
|
47
|
-
expect(Pusher.logger).to receive(:error).with("some signature info (Pusher::AuthenticationError)")
|
48
|
-
expect(Pusher.logger).to receive(:debug) #backtrace
|
49
|
-
@channel.trigger('new_event', 'Some data')
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
describe "#initialization" do
|
54
|
-
it "should not be too long" do
|
55
|
-
expect { @client['b'*201] }.to raise_error(Pusher::Error)
|
56
|
-
end
|
57
|
-
|
58
|
-
it "should not use bad characters" do
|
59
|
-
expect { @client['*^!±`/""'] }.to raise_error(Pusher::Error)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
describe "#trigger_async" do
|
64
|
-
it "should use @client.trigger_async internally" do
|
65
|
-
expect(@client).to receive(:trigger_async)
|
66
|
-
@channel.trigger_async('new_event', 'Some data')
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
describe '#info' do
|
71
|
-
it "should call the Client#channel_info" do
|
72
|
-
expect(@client).to receive(:get)
|
73
|
-
.with("/channels/mychannel", anything)
|
74
|
-
.and_return({:occupied => true, :subscription_count => 12})
|
75
|
-
@channel = @client['mychannel']
|
76
|
-
@channel.info
|
77
|
-
end
|
78
|
-
|
79
|
-
it "should assemble the requested attributes into the info option" do
|
80
|
-
expect(@client).to receive(:get)
|
81
|
-
.with(anything, {:info => "user_count,connection_count"})
|
82
|
-
.and_return({:occupied => true, :subscription_count => 12, :user_count => 12})
|
83
|
-
@channel = @client['presence-foo']
|
84
|
-
@channel.info(%w{user_count connection_count})
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
describe '#users' do
|
89
|
-
it "should call the Client#channel_users" do
|
90
|
-
expect(@client).to receive(:get).with("/channels/presence-mychannel/users", {}).and_return({:users => {'id' => '4'}})
|
91
|
-
@channel = @client['presence-mychannel']
|
92
|
-
@channel.users
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
describe "#authentication_string" do
|
97
|
-
def authentication_string(*data)
|
98
|
-
lambda { @channel.authentication_string(*data) }
|
99
|
-
end
|
100
|
-
|
101
|
-
it "should return an authentication string given a socket id" do
|
102
|
-
auth = @channel.authentication_string('1.1')
|
103
|
-
|
104
|
-
expect(auth).to eq('12345678900000001:02259dff9a2a3f71ea8ab29ac0c0c0ef7996c8f3fd3702be5533f30da7d7fed4')
|
105
|
-
end
|
106
|
-
|
107
|
-
it "should raise error if authentication is invalid" do
|
108
|
-
[nil, ''].each do |invalid|
|
109
|
-
expect(authentication_string(invalid)).to raise_error Pusher::Error
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
describe 'with extra string argument' do
|
114
|
-
it 'should be a string or nil' do
|
115
|
-
expect(authentication_string('1.1', 123)).to raise_error Pusher::Error
|
116
|
-
expect(authentication_string('1.1', {})).to raise_error Pusher::Error
|
117
|
-
|
118
|
-
expect(authentication_string('1.1', 'boom')).not_to raise_error
|
119
|
-
expect(authentication_string('1.1', nil)).not_to raise_error
|
120
|
-
end
|
121
|
-
|
122
|
-
it "should return an authentication string given a socket id and custom args" do
|
123
|
-
auth = @channel.authentication_string('1.1', 'foobar')
|
124
|
-
|
125
|
-
expect(auth).to eq("12345678900000001:#{hmac(@client.secret, "1.1:test_channel:foobar")}")
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
describe '#authenticate' do
|
131
|
-
before :each do
|
132
|
-
@custom_data = {:uid => 123, :info => {:name => 'Foo'}}
|
133
|
-
end
|
134
|
-
|
135
|
-
it 'should return a hash with signature including custom data and data as json string' do
|
136
|
-
allow(MultiJson).to receive(:encode).with(@custom_data).and_return 'a json string'
|
137
|
-
|
138
|
-
response = @channel.authenticate('1.1', @custom_data)
|
139
|
-
|
140
|
-
expect(response).to eq({
|
141
|
-
:auth => "12345678900000001:#{hmac(@client.secret, "1.1:test_channel:a json string")}",
|
142
|
-
:channel_data => 'a json string'
|
143
|
-
})
|
144
|
-
end
|
145
|
-
|
146
|
-
it 'should fail on invalid socket_ids' do
|
147
|
-
expect {
|
148
|
-
@channel.authenticate('1.1:')
|
149
|
-
}.to raise_error Pusher::Error
|
150
|
-
|
151
|
-
expect {
|
152
|
-
@channel.authenticate('1.1foo', 'channel')
|
153
|
-
}.to raise_error Pusher::Error
|
154
|
-
|
155
|
-
expect {
|
156
|
-
@channel.authenticate(':1.1')
|
157
|
-
}.to raise_error Pusher::Error
|
158
|
-
|
159
|
-
expect {
|
160
|
-
@channel.authenticate('foo1.1', 'channel')
|
161
|
-
}.to raise_error Pusher::Error
|
162
|
-
|
163
|
-
expect {
|
164
|
-
@channel.authenticate('foo', 'channel')
|
165
|
-
}.to raise_error Pusher::Error
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
describe `#shared_secret` do
|
170
|
-
before(:each) do
|
171
|
-
@channel.instance_variable_set(:@name, 'private-encrypted-1')
|
172
|
-
end
|
173
|
-
|
174
|
-
it 'should return a shared_secret based on the channel name and encryption master key' do
|
175
|
-
key = '3W1pfB/Etr+ZIlfMWwZP3gz8jEeCt4s2pe6Vpr+2c3M='
|
176
|
-
shared_secret = @channel.shared_secret(key)
|
177
|
-
expect(Base64.strict_encode64(shared_secret)).to eq(
|
178
|
-
"6zeEp/chneRPS1cbK/hGeG860UhHomxSN6hTgzwT20I="
|
179
|
-
)
|
180
|
-
end
|
181
|
-
|
182
|
-
it 'should return nil if missing encryption master key' do
|
183
|
-
shared_secret = @channel.shared_secret(nil)
|
184
|
-
expect(shared_secret).to be_nil
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
data/spec/client_spec.rb
DELETED
@@ -1,741 +0,0 @@
|
|
1
|
-
require 'base64'
|
2
|
-
|
3
|
-
require 'rbnacl'
|
4
|
-
require 'em-http'
|
5
|
-
|
6
|
-
require 'spec_helper'
|
7
|
-
|
8
|
-
encryption_master_key = RbNaCl::Random.random_bytes(32)
|
9
|
-
|
10
|
-
describe Pusher do
|
11
|
-
# The behaviour should be the same when using the Client object, or the
|
12
|
-
# 'global' client delegated through the Pusher class
|
13
|
-
[lambda { Pusher }, lambda { Pusher::Client.new }].each do |client_gen|
|
14
|
-
before :each do
|
15
|
-
@client = client_gen.call
|
16
|
-
end
|
17
|
-
|
18
|
-
describe 'default configuration' do
|
19
|
-
it 'should be preconfigured for api host' do
|
20
|
-
expect(@client.host).to eq('api-mt1.pusher.com')
|
21
|
-
end
|
22
|
-
|
23
|
-
it 'should be preconfigured for port 443' do
|
24
|
-
expect(@client.port).to eq(443)
|
25
|
-
end
|
26
|
-
|
27
|
-
it 'should use standard logger if no other logger if defined' do
|
28
|
-
Pusher.logger.debug('foo')
|
29
|
-
expect(Pusher.logger).to be_kind_of(Logger)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
describe 'logging configuration' do
|
34
|
-
it "can be configured to use any logger" do
|
35
|
-
logger = double("ALogger")
|
36
|
-
expect(logger).to receive(:debug).with('foo')
|
37
|
-
Pusher.logger = logger
|
38
|
-
Pusher.logger.debug('foo')
|
39
|
-
Pusher.logger = nil
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
describe "configuration using url" do
|
44
|
-
it "should be possible to configure everything by setting the url" do
|
45
|
-
@client.url = "test://somekey:somesecret@api.staging.pusherapp.com:8080/apps/87"
|
46
|
-
|
47
|
-
expect(@client.scheme).to eq('test')
|
48
|
-
expect(@client.host).to eq('api.staging.pusherapp.com')
|
49
|
-
expect(@client.port).to eq(8080)
|
50
|
-
expect(@client.key).to eq('somekey')
|
51
|
-
expect(@client.secret).to eq('somesecret')
|
52
|
-
expect(@client.app_id).to eq('87')
|
53
|
-
end
|
54
|
-
|
55
|
-
it "should override scheme and port when setting encrypted=true after url" do
|
56
|
-
@client.url = "http://somekey:somesecret@api.staging.pusherapp.com:8080/apps/87"
|
57
|
-
@client.encrypted = true
|
58
|
-
|
59
|
-
expect(@client.scheme).to eq('https')
|
60
|
-
expect(@client.port).to eq(443)
|
61
|
-
end
|
62
|
-
|
63
|
-
it "should fail on bad urls" do
|
64
|
-
expect { @client.url = "gopher/somekey:somesecret@://api.staging.pusherapp.co://m:8080\apps\87" }.to raise_error(URI::InvalidURIError)
|
65
|
-
end
|
66
|
-
|
67
|
-
it "should raise exception if app_id is not configured" do
|
68
|
-
@client.app_id = nil
|
69
|
-
expect {
|
70
|
-
@client.url
|
71
|
-
}.to raise_error(Pusher::ConfigurationError)
|
72
|
-
end
|
73
|
-
|
74
|
-
end
|
75
|
-
|
76
|
-
describe 'configuring the cluster' do
|
77
|
-
it 'should set a new default host' do
|
78
|
-
@client.cluster = 'eu'
|
79
|
-
expect(@client.host).to eq('api-eu.pusher.com')
|
80
|
-
end
|
81
|
-
|
82
|
-
it 'should handle nil gracefully' do
|
83
|
-
@client.cluster = nil
|
84
|
-
expect(@client.host).to eq('api-mt1.pusher.com')
|
85
|
-
end
|
86
|
-
|
87
|
-
it 'should handle empty string' do
|
88
|
-
@client.cluster = ""
|
89
|
-
expect(@client.host).to eq('api-mt1.pusher.com')
|
90
|
-
end
|
91
|
-
|
92
|
-
it 'should be overridden by host if it comes after' do
|
93
|
-
@client.cluster = 'eu'
|
94
|
-
@client.host = 'api.staging.pusher.com'
|
95
|
-
expect(@client.host).to eq('api.staging.pusher.com')
|
96
|
-
end
|
97
|
-
|
98
|
-
it 'should be overridden by url if it comes after' do
|
99
|
-
@client.cluster = 'eu'
|
100
|
-
@client.url = "http://somekey:somesecret@api.staging.pusherapp.com:8080/apps/87"
|
101
|
-
|
102
|
-
expect(@client.host).to eq('api.staging.pusherapp.com')
|
103
|
-
end
|
104
|
-
|
105
|
-
it 'should override the url configuration if it comes after' do
|
106
|
-
@client.url = "http://somekey:somesecret@api.staging.pusherapp.com:8080/apps/87"
|
107
|
-
@client.cluster = 'eu'
|
108
|
-
expect(@client.host).to eq('api-eu.pusher.com')
|
109
|
-
end
|
110
|
-
|
111
|
-
it 'should override the host configuration if it comes after' do
|
112
|
-
@client.host = 'api.staging.pusher.com'
|
113
|
-
@client.cluster = 'eu'
|
114
|
-
expect(@client.host).to eq('api-eu.pusher.com')
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
describe 'configuring TLS' do
|
119
|
-
it 'should set port and scheme if "use_tls" disabled' do
|
120
|
-
client = Pusher::Client.new({
|
121
|
-
:use_tls => false,
|
122
|
-
})
|
123
|
-
expect(client.scheme).to eq('http')
|
124
|
-
expect(client.port).to eq(80)
|
125
|
-
end
|
126
|
-
|
127
|
-
it 'should set port and scheme if "encrypted" disabled' do
|
128
|
-
client = Pusher::Client.new({
|
129
|
-
:encrypted => false,
|
130
|
-
})
|
131
|
-
expect(client.scheme).to eq('http')
|
132
|
-
expect(client.port).to eq(80)
|
133
|
-
end
|
134
|
-
|
135
|
-
it 'should use TLS port and scheme if "encrypted" or "use_tls" are not set' do
|
136
|
-
client = Pusher::Client.new
|
137
|
-
expect(client.scheme).to eq('https')
|
138
|
-
expect(client.port).to eq(443)
|
139
|
-
end
|
140
|
-
|
141
|
-
it 'should override port if "use_tls" option set but a different port is specified' do
|
142
|
-
client = Pusher::Client.new({
|
143
|
-
:use_tls => true,
|
144
|
-
:port => 8443
|
145
|
-
})
|
146
|
-
expect(client.scheme).to eq('https')
|
147
|
-
expect(client.port).to eq(8443)
|
148
|
-
end
|
149
|
-
|
150
|
-
it 'should override port if "use_tls" option set but a different port is specified' do
|
151
|
-
client = Pusher::Client.new({
|
152
|
-
:use_tls => false,
|
153
|
-
:port => 8000
|
154
|
-
})
|
155
|
-
expect(client.scheme).to eq('http')
|
156
|
-
expect(client.port).to eq(8000)
|
157
|
-
end
|
158
|
-
|
159
|
-
end
|
160
|
-
|
161
|
-
describe 'configuring a http proxy' do
|
162
|
-
it "should be possible to configure everything by setting the http_proxy" do
|
163
|
-
@client.http_proxy = 'http://someuser:somepassword@proxy.host.com:8080'
|
164
|
-
|
165
|
-
expect(@client.proxy).to eq({:scheme => 'http', :host => 'proxy.host.com', :port => 8080, :user => 'someuser', :password => 'somepassword'})
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
describe 'configuring from env' do
|
170
|
-
after do
|
171
|
-
ENV['PUSHER_URL'] = nil
|
172
|
-
end
|
173
|
-
|
174
|
-
it "works" do
|
175
|
-
url = "http://somekey:somesecret@api.staging.pusherapp.com:8080/apps/87"
|
176
|
-
ENV['PUSHER_URL'] = url
|
177
|
-
|
178
|
-
client = Pusher::Client.from_env
|
179
|
-
expect(client.key).to eq("somekey")
|
180
|
-
expect(client.secret).to eq("somesecret")
|
181
|
-
expect(client.app_id).to eq("87")
|
182
|
-
expect(client.url.to_s).to eq("http://api.staging.pusherapp.com:8080/apps/87")
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
describe 'configuring from url' do
|
187
|
-
it "works" do
|
188
|
-
url = "http://somekey:somesecret@api.staging.pusherapp.com:8080/apps/87"
|
189
|
-
|
190
|
-
client = Pusher::Client.from_url(url)
|
191
|
-
expect(client.key).to eq("somekey")
|
192
|
-
expect(client.secret).to eq("somesecret")
|
193
|
-
expect(client.app_id).to eq("87")
|
194
|
-
expect(client.url.to_s).to eq("http://api.staging.pusherapp.com:8080/apps/87")
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
describe 'can set encryption_master_key_base64' do
|
199
|
-
it "sets encryption_master_key" do
|
200
|
-
@client.encryption_master_key_base64 =
|
201
|
-
Base64.strict_encode64(encryption_master_key)
|
202
|
-
|
203
|
-
expect(@client.encryption_master_key).to eq(encryption_master_key)
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
describe 'when configured' do
|
208
|
-
before :each do
|
209
|
-
@client.app_id = '20'
|
210
|
-
@client.key = '12345678900000001'
|
211
|
-
@client.secret = '12345678900000001'
|
212
|
-
@client.encryption_master_key_base64 =
|
213
|
-
Base64.strict_encode64(encryption_master_key)
|
214
|
-
end
|
215
|
-
|
216
|
-
describe '#[]' do
|
217
|
-
before do
|
218
|
-
@channel = @client['test_channel']
|
219
|
-
end
|
220
|
-
|
221
|
-
it 'should return a channel' do
|
222
|
-
expect(@channel).to be_kind_of(Pusher::Channel)
|
223
|
-
end
|
224
|
-
|
225
|
-
it "should raise exception if app_id is not configured" do
|
226
|
-
@client.app_id = nil
|
227
|
-
expect {
|
228
|
-
@channel.trigger!('foo', 'bar')
|
229
|
-
}.to raise_error(Pusher::ConfigurationError)
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
describe '#channels' do
|
234
|
-
it "should call the correct URL and symbolise response correctly" do
|
235
|
-
api_path = %r{/apps/20/channels}
|
236
|
-
stub_request(:get, api_path).to_return({
|
237
|
-
:status => 200,
|
238
|
-
:body => MultiJson.encode('channels' => {
|
239
|
-
"channel1" => {},
|
240
|
-
"channel2" => {}
|
241
|
-
})
|
242
|
-
})
|
243
|
-
expect(@client.channels).to eq({
|
244
|
-
:channels => {
|
245
|
-
"channel1" => {},
|
246
|
-
"channel2" => {}
|
247
|
-
}
|
248
|
-
})
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
describe '#channel_info' do
|
253
|
-
it "should call correct URL and symbolise response" do
|
254
|
-
api_path = %r{/apps/20/channels/mychannel}
|
255
|
-
stub_request(:get, api_path).to_return({
|
256
|
-
:status => 200,
|
257
|
-
:body => MultiJson.encode({
|
258
|
-
'occupied' => false,
|
259
|
-
})
|
260
|
-
})
|
261
|
-
expect(@client.channel_info('mychannel')).to eq({
|
262
|
-
:occupied => false,
|
263
|
-
})
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
describe '#channel_users' do
|
268
|
-
it "should call correct URL and symbolise response" do
|
269
|
-
api_path = %r{/apps/20/channels/mychannel/users}
|
270
|
-
stub_request(:get, api_path).to_return({
|
271
|
-
:status => 200,
|
272
|
-
:body => MultiJson.encode({
|
273
|
-
'users' => [{ 'id' => 1 }]
|
274
|
-
})
|
275
|
-
})
|
276
|
-
expect(@client.channel_users('mychannel')).to eq({
|
277
|
-
:users => [{ 'id' => 1}]
|
278
|
-
})
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
describe '#authenticate' do
|
283
|
-
before :each do
|
284
|
-
@custom_data = {:uid => 123, :info => {:name => 'Foo'}}
|
285
|
-
end
|
286
|
-
|
287
|
-
it 'should return a hash with signature including custom data and data as json string' do
|
288
|
-
allow(MultiJson).to receive(:encode).with(@custom_data).and_return 'a json string'
|
289
|
-
|
290
|
-
response = @client.authenticate('test_channel', '1.1', @custom_data)
|
291
|
-
|
292
|
-
expect(response).to eq({
|
293
|
-
:auth => "12345678900000001:#{hmac(@client.secret, "1.1:test_channel:a json string")}",
|
294
|
-
:channel_data => 'a json string'
|
295
|
-
})
|
296
|
-
end
|
297
|
-
|
298
|
-
it 'should include a shared_secret if the private-encrypted channel' do
|
299
|
-
allow(MultiJson).to receive(:encode).with(@custom_data).and_return 'a json string'
|
300
|
-
@client.instance_variable_set(:@encryption_master_key, '3W1pfB/Etr+ZIlfMWwZP3gz8jEeCt4s2pe6Vpr+2c3M=')
|
301
|
-
|
302
|
-
response = @client.authenticate('private-encrypted-test_channel', '1.1', @custom_data)
|
303
|
-
|
304
|
-
expect(response).to eq({
|
305
|
-
:auth => "12345678900000001:#{hmac(@client.secret, "1.1:private-encrypted-test_channel:a json string")}",
|
306
|
-
:shared_secret => "o0L3QnIovCeRC8KTD8KBRlmi31dGzHVS2M93uryqDdw=",
|
307
|
-
:channel_data => 'a json string'
|
308
|
-
})
|
309
|
-
end
|
310
|
-
|
311
|
-
end
|
312
|
-
|
313
|
-
describe '#trigger' do
|
314
|
-
before :each do
|
315
|
-
@api_path = %r{/apps/20/events}
|
316
|
-
stub_request(:post, @api_path).to_return({
|
317
|
-
:status => 200,
|
318
|
-
:body => MultiJson.encode({})
|
319
|
-
})
|
320
|
-
end
|
321
|
-
|
322
|
-
it "should call correct URL" do
|
323
|
-
expect(@client.trigger(['mychannel'], 'event', {'some' => 'data'})).
|
324
|
-
to eq({})
|
325
|
-
end
|
326
|
-
|
327
|
-
it "should not allow too many channels" do
|
328
|
-
expect {
|
329
|
-
@client.trigger((0..11).map{|i| 'mychannel#{i}'},
|
330
|
-
'event', {'some' => 'data'}, {
|
331
|
-
:socket_id => "12.34"
|
332
|
-
})}.to raise_error(Pusher::Error)
|
333
|
-
end
|
334
|
-
|
335
|
-
it "should pass any parameters in the body of the request" do
|
336
|
-
@client.trigger(['mychannel', 'c2'], 'event', {'some' => 'data'}, {
|
337
|
-
:socket_id => "12.34"
|
338
|
-
})
|
339
|
-
expect(WebMock).to have_requested(:post, @api_path).with { |req|
|
340
|
-
parsed = MultiJson.decode(req.body)
|
341
|
-
expect(parsed["name"]).to eq('event')
|
342
|
-
expect(parsed["channels"]).to eq(["mychannel", "c2"])
|
343
|
-
expect(parsed["socket_id"]).to eq('12.34')
|
344
|
-
}
|
345
|
-
end
|
346
|
-
|
347
|
-
it "should convert non string data to JSON before posting" do
|
348
|
-
@client.trigger(['mychannel'], 'event', {'some' => 'data'})
|
349
|
-
expect(WebMock).to have_requested(:post, @api_path).with { |req|
|
350
|
-
expect(MultiJson.decode(req.body)["data"]).to eq('{"some":"data"}')
|
351
|
-
}
|
352
|
-
end
|
353
|
-
|
354
|
-
it "should accept a single channel as well as an array" do
|
355
|
-
@client.trigger('mychannel', 'event', {'some' => 'data'})
|
356
|
-
expect(WebMock).to have_requested(:post, @api_path).with { |req|
|
357
|
-
expect(MultiJson.decode(req.body)["channels"]).to eq(['mychannel'])
|
358
|
-
}
|
359
|
-
end
|
360
|
-
|
361
|
-
%w[app_id key secret].each do |key|
|
362
|
-
it "should fail in missing #{key}" do
|
363
|
-
@client.public_send("#{key}=", nil)
|
364
|
-
expect {
|
365
|
-
@client.trigger('mychannel', 'event', {'some' => 'data'})
|
366
|
-
}.to raise_error(Pusher::ConfigurationError)
|
367
|
-
expect(WebMock).not_to have_requested(:post, @api_path).with { |req|
|
368
|
-
expect(MultiJson.decode(req.body)["channels"]).to eq(['mychannel'])
|
369
|
-
}
|
370
|
-
end
|
371
|
-
end
|
372
|
-
|
373
|
-
it "should fail to publish to encrypted channels when missing key" do
|
374
|
-
@client.encryption_master_key_base64 = nil
|
375
|
-
expect {
|
376
|
-
@client.trigger('private-encrypted-channel', 'event', {'some' => 'data'})
|
377
|
-
}.to raise_error(Pusher::ConfigurationError)
|
378
|
-
expect(WebMock).not_to have_requested(:post, @api_path)
|
379
|
-
end
|
380
|
-
|
381
|
-
it "should fail to publish to multiple channels if one is encrypted" do
|
382
|
-
expect {
|
383
|
-
@client.trigger(
|
384
|
-
['private-encrypted-channel', 'some-other-channel'],
|
385
|
-
'event',
|
386
|
-
{'some' => 'data'},
|
387
|
-
)
|
388
|
-
}.to raise_error(Pusher::Error)
|
389
|
-
expect(WebMock).not_to have_requested(:post, @api_path)
|
390
|
-
end
|
391
|
-
|
392
|
-
it "should encrypt publishes to encrypted channels" do
|
393
|
-
@client.trigger(
|
394
|
-
'private-encrypted-channel',
|
395
|
-
'event',
|
396
|
-
{'some' => 'data'},
|
397
|
-
)
|
398
|
-
|
399
|
-
expect(WebMock).to have_requested(:post, @api_path).with { |req|
|
400
|
-
data = MultiJson.decode(MultiJson.decode(req.body)["data"])
|
401
|
-
|
402
|
-
key = RbNaCl::Hash.sha256(
|
403
|
-
'private-encrypted-channel' + encryption_master_key
|
404
|
-
)
|
405
|
-
|
406
|
-
expect(MultiJson.decode(RbNaCl::SecretBox.new(key).decrypt(
|
407
|
-
Base64.strict_decode64(data["nonce"]),
|
408
|
-
Base64.strict_decode64(data["ciphertext"]),
|
409
|
-
))).to eq({ 'some' => 'data' })
|
410
|
-
}
|
411
|
-
end
|
412
|
-
end
|
413
|
-
|
414
|
-
describe '#trigger_batch' do
|
415
|
-
before :each do
|
416
|
-
@api_path = %r{/apps/20/batch_events}
|
417
|
-
stub_request(:post, @api_path).to_return({
|
418
|
-
:status => 200,
|
419
|
-
:body => MultiJson.encode({})
|
420
|
-
})
|
421
|
-
end
|
422
|
-
|
423
|
-
it "should call correct URL" do
|
424
|
-
expect(@client.trigger_batch(channel: 'mychannel', name: 'event', data: {'some' => 'data'})).
|
425
|
-
to eq({})
|
426
|
-
end
|
427
|
-
|
428
|
-
it "should convert non string data to JSON before posting" do
|
429
|
-
@client.trigger_batch(
|
430
|
-
{channel: 'mychannel', name: 'event', data: {'some' => 'data'}},
|
431
|
-
{channel: 'mychannel', name: 'event', data: 'already encoded'},
|
432
|
-
)
|
433
|
-
expect(WebMock).to have_requested(:post, @api_path).with { |req|
|
434
|
-
parsed = MultiJson.decode(req.body)
|
435
|
-
expect(parsed).to eq(
|
436
|
-
"batch" => [
|
437
|
-
{ "channel" => "mychannel", "name" => "event", "data" => "{\"some\":\"data\"}"},
|
438
|
-
{ "channel" => "mychannel", "name" => "event", "data" => "already encoded"}
|
439
|
-
]
|
440
|
-
)
|
441
|
-
}
|
442
|
-
end
|
443
|
-
|
444
|
-
it "should fail to publish to encrypted channels when missing key" do
|
445
|
-
@client.encryption_master_key_base64 = nil
|
446
|
-
expect {
|
447
|
-
@client.trigger_batch(
|
448
|
-
{
|
449
|
-
channel: 'private-encrypted-channel',
|
450
|
-
name: 'event',
|
451
|
-
data: {'some' => 'data'},
|
452
|
-
},
|
453
|
-
{channel: 'mychannel', name: 'event', data: 'already encoded'},
|
454
|
-
)
|
455
|
-
}.to raise_error(Pusher::ConfigurationError)
|
456
|
-
expect(WebMock).not_to have_requested(:post, @api_path)
|
457
|
-
end
|
458
|
-
|
459
|
-
it "should encrypt publishes to encrypted channels" do
|
460
|
-
@client.trigger_batch(
|
461
|
-
{
|
462
|
-
channel: 'private-encrypted-channel',
|
463
|
-
name: 'event',
|
464
|
-
data: {'some' => 'data'},
|
465
|
-
},
|
466
|
-
{channel: 'mychannel', name: 'event', data: 'already encoded'},
|
467
|
-
)
|
468
|
-
|
469
|
-
expect(WebMock).to have_requested(:post, @api_path).with { |req|
|
470
|
-
batch = MultiJson.decode(req.body)["batch"]
|
471
|
-
expect(batch.length).to eq(2)
|
472
|
-
|
473
|
-
expect(batch[0]["channel"]).to eq("private-encrypted-channel")
|
474
|
-
expect(batch[0]["name"]).to eq("event")
|
475
|
-
|
476
|
-
data = MultiJson.decode(batch[0]["data"])
|
477
|
-
|
478
|
-
key = RbNaCl::Hash.sha256(
|
479
|
-
'private-encrypted-channel' + encryption_master_key
|
480
|
-
)
|
481
|
-
|
482
|
-
expect(MultiJson.decode(RbNaCl::SecretBox.new(key).decrypt(
|
483
|
-
Base64.strict_decode64(data["nonce"]),
|
484
|
-
Base64.strict_decode64(data["ciphertext"]),
|
485
|
-
))).to eq({ 'some' => 'data' })
|
486
|
-
|
487
|
-
expect(batch[1]["channel"]).to eq("mychannel")
|
488
|
-
expect(batch[1]["name"]).to eq("event")
|
489
|
-
expect(batch[1]["data"]).to eq("already encoded")
|
490
|
-
}
|
491
|
-
end
|
492
|
-
end
|
493
|
-
|
494
|
-
describe '#trigger_async' do
|
495
|
-
before :each do
|
496
|
-
@api_path = %r{/apps/20/events}
|
497
|
-
stub_request(:post, @api_path).to_return({
|
498
|
-
:status => 200,
|
499
|
-
:body => MultiJson.encode({})
|
500
|
-
})
|
501
|
-
end
|
502
|
-
|
503
|
-
it "should call correct URL" do
|
504
|
-
EM.run {
|
505
|
-
@client.trigger_async('mychannel', 'event', {'some' => 'data'}).callback { |r|
|
506
|
-
expect(r).to eq({})
|
507
|
-
EM.stop
|
508
|
-
}
|
509
|
-
}
|
510
|
-
end
|
511
|
-
|
512
|
-
it "should pass any parameters in the body of the request" do
|
513
|
-
EM.run {
|
514
|
-
@client.trigger_async('mychannel', 'event', {'some' => 'data'}, {
|
515
|
-
:socket_id => "12.34"
|
516
|
-
}).callback {
|
517
|
-
expect(WebMock).to have_requested(:post, @api_path).with { |req|
|
518
|
-
expect(MultiJson.decode(req.body)["socket_id"]).to eq('12.34')
|
519
|
-
}
|
520
|
-
EM.stop
|
521
|
-
}
|
522
|
-
}
|
523
|
-
end
|
524
|
-
|
525
|
-
it "should convert non string data to JSON before posting" do
|
526
|
-
EM.run {
|
527
|
-
@client.trigger_async('mychannel', 'event', {'some' => 'data'}).callback {
|
528
|
-
expect(WebMock).to have_requested(:post, @api_path).with { |req|
|
529
|
-
expect(MultiJson.decode(req.body)["data"]).to eq('{"some":"data"}')
|
530
|
-
}
|
531
|
-
EM.stop
|
532
|
-
}
|
533
|
-
}
|
534
|
-
end
|
535
|
-
end
|
536
|
-
|
537
|
-
[:get, :post].each do |verb|
|
538
|
-
describe "##{verb}" do
|
539
|
-
before :each do
|
540
|
-
@url_regexp = %r{api-mt1.pusher.com}
|
541
|
-
stub_request(verb, @url_regexp).
|
542
|
-
to_return(:status => 200, :body => "{}")
|
543
|
-
end
|
544
|
-
|
545
|
-
let(:call_api) { @client.send(verb, '/path') }
|
546
|
-
|
547
|
-
it "should use https by default" do
|
548
|
-
call_api
|
549
|
-
expect(WebMock).to have_requested(verb, %r{https://api-mt1.pusher.com/apps/20/path})
|
550
|
-
end
|
551
|
-
|
552
|
-
it "should use https if configured" do
|
553
|
-
@client.encrypted = false
|
554
|
-
call_api
|
555
|
-
expect(WebMock).to have_requested(verb, %r{http://api-mt1.pusher.com})
|
556
|
-
end
|
557
|
-
|
558
|
-
it "should format the respose hash with symbols at first level" do
|
559
|
-
stub_request(verb, @url_regexp).to_return({
|
560
|
-
:status => 200,
|
561
|
-
:body => MultiJson.encode({'something' => {'a' => 'hash'}})
|
562
|
-
})
|
563
|
-
expect(call_api).to eq({
|
564
|
-
:something => {'a' => 'hash'}
|
565
|
-
})
|
566
|
-
end
|
567
|
-
|
568
|
-
it "should catch all http exceptions and raise a Pusher::HTTPError wrapping the original error" do
|
569
|
-
stub_request(verb, @url_regexp).to_raise(HTTPClient::TimeoutError)
|
570
|
-
|
571
|
-
error = nil
|
572
|
-
begin
|
573
|
-
call_api
|
574
|
-
rescue => e
|
575
|
-
error = e
|
576
|
-
end
|
577
|
-
|
578
|
-
expect(error.class).to eq(Pusher::HTTPError)
|
579
|
-
expect(error).to be_kind_of(Pusher::Error)
|
580
|
-
expect(error.message).to eq('Exception from WebMock (HTTPClient::TimeoutError)')
|
581
|
-
expect(error.original_error.class).to eq(HTTPClient::TimeoutError)
|
582
|
-
end
|
583
|
-
|
584
|
-
it "should raise Pusher::Error if call returns 400" do
|
585
|
-
stub_request(verb, @url_regexp).to_return({:status => 400})
|
586
|
-
expect { call_api }.to raise_error(Pusher::Error)
|
587
|
-
end
|
588
|
-
|
589
|
-
it "should raise AuthenticationError if pusher returns 401" do
|
590
|
-
stub_request(verb, @url_regexp).to_return({:status => 401})
|
591
|
-
expect { call_api }.to raise_error(Pusher::AuthenticationError)
|
592
|
-
end
|
593
|
-
|
594
|
-
it "should raise Pusher::Error if pusher returns 404" do
|
595
|
-
stub_request(verb, @url_regexp).to_return({:status => 404})
|
596
|
-
expect { call_api }.to raise_error(Pusher::Error, '404 Not found (/apps/20/path)')
|
597
|
-
end
|
598
|
-
|
599
|
-
it "should raise Pusher::Error if pusher returns 407" do
|
600
|
-
stub_request(verb, @url_regexp).to_return({:status => 407})
|
601
|
-
expect { call_api }.to raise_error(Pusher::Error, 'Proxy Authentication Required')
|
602
|
-
end
|
603
|
-
|
604
|
-
it "should raise Pusher::Error if pusher returns 413" do
|
605
|
-
stub_request(verb, @url_regexp).to_return({:status => 413})
|
606
|
-
expect { call_api }.to raise_error(Pusher::Error, 'Payload Too Large > 10KB')
|
607
|
-
end
|
608
|
-
|
609
|
-
it "should raise Pusher::Error if pusher returns 500" do
|
610
|
-
stub_request(verb, @url_regexp).to_return({:status => 500, :body => "some error"})
|
611
|
-
expect { call_api }.to raise_error(Pusher::Error, 'Unknown error (status code 500): some error')
|
612
|
-
end
|
613
|
-
end
|
614
|
-
end
|
615
|
-
|
616
|
-
describe "async calling without eventmachine" do
|
617
|
-
[[:get, :get_async], [:post, :post_async]].each do |verb, method|
|
618
|
-
describe "##{method}" do
|
619
|
-
before :each do
|
620
|
-
@url_regexp = %r{api-mt1.pusher.com}
|
621
|
-
stub_request(verb, @url_regexp).
|
622
|
-
to_return(:status => 200, :body => "{}")
|
623
|
-
end
|
624
|
-
|
625
|
-
let(:call_api) {
|
626
|
-
@client.send(method, '/path').tap { |c|
|
627
|
-
# Allow the async thread (inside httpclient) to run
|
628
|
-
while !c.finished?
|
629
|
-
sleep 0.01
|
630
|
-
end
|
631
|
-
}
|
632
|
-
}
|
633
|
-
|
634
|
-
it "should use https by default" do
|
635
|
-
call_api
|
636
|
-
expect(WebMock).to have_requested(verb, %r{https://api-mt1.pusher.com/apps/20/path})
|
637
|
-
end
|
638
|
-
|
639
|
-
it "should use http if configured" do
|
640
|
-
@client.encrypted = false
|
641
|
-
call_api
|
642
|
-
expect(WebMock).to have_requested(verb, %r{http://api-mt1.pusher.com})
|
643
|
-
end
|
644
|
-
|
645
|
-
# Note that the raw httpclient connection object is returned and
|
646
|
-
# the response isn't handled (by handle_response) in the normal way.
|
647
|
-
it "should return a httpclient connection object" do
|
648
|
-
connection = call_api
|
649
|
-
expect(connection.finished?).to be_truthy
|
650
|
-
response = connection.pop
|
651
|
-
expect(response.status).to eq(200)
|
652
|
-
expect(response.body.read).to eq("{}")
|
653
|
-
end
|
654
|
-
end
|
655
|
-
end
|
656
|
-
end
|
657
|
-
|
658
|
-
describe "async calling with eventmachine" do
|
659
|
-
[[:get, :get_async], [:post, :post_async]].each do |verb, method|
|
660
|
-
describe "##{method}" do
|
661
|
-
before :each do
|
662
|
-
@url_regexp = %r{api-mt1.pusher.com}
|
663
|
-
stub_request(verb, @url_regexp).
|
664
|
-
to_return(:status => 200, :body => "{}")
|
665
|
-
end
|
666
|
-
|
667
|
-
let(:call_api) { @client.send(method, '/path') }
|
668
|
-
|
669
|
-
it "should use https by default" do
|
670
|
-
EM.run {
|
671
|
-
call_api.callback {
|
672
|
-
expect(WebMock).to have_requested(verb, %r{https://api-mt1.pusher.com/apps/20/path})
|
673
|
-
EM.stop
|
674
|
-
}
|
675
|
-
}
|
676
|
-
end
|
677
|
-
|
678
|
-
it "should use http if configured" do
|
679
|
-
EM.run {
|
680
|
-
@client.encrypted = false
|
681
|
-
call_api.callback {
|
682
|
-
expect(WebMock).to have_requested(verb, %r{http://api-mt1.pusher.com})
|
683
|
-
EM.stop
|
684
|
-
}
|
685
|
-
}
|
686
|
-
end
|
687
|
-
|
688
|
-
it "should format the respose hash with symbols at first level" do
|
689
|
-
EM.run {
|
690
|
-
stub_request(verb, @url_regexp).to_return({
|
691
|
-
:status => 200,
|
692
|
-
:body => MultiJson.encode({'something' => {'a' => 'hash'}})
|
693
|
-
})
|
694
|
-
call_api.callback { |response|
|
695
|
-
expect(response).to eq({
|
696
|
-
:something => {'a' => 'hash'}
|
697
|
-
})
|
698
|
-
EM.stop
|
699
|
-
}
|
700
|
-
}
|
701
|
-
end
|
702
|
-
|
703
|
-
it "should errback with Pusher::Error on unsuccessful response" do
|
704
|
-
EM.run {
|
705
|
-
stub_request(verb, @url_regexp).to_return({:status => 400})
|
706
|
-
|
707
|
-
call_api.errback { |e|
|
708
|
-
expect(e.class).to eq(Pusher::Error)
|
709
|
-
EM.stop
|
710
|
-
}.callback {
|
711
|
-
fail
|
712
|
-
}
|
713
|
-
}
|
714
|
-
end
|
715
|
-
end
|
716
|
-
end
|
717
|
-
end
|
718
|
-
end
|
719
|
-
end
|
720
|
-
|
721
|
-
describe 'configuring cluster' do
|
722
|
-
it 'should allow clients to specify the cluster only with the default host' do
|
723
|
-
client = Pusher::Client.new({
|
724
|
-
:scheme => 'http',
|
725
|
-
:cluster => 'eu',
|
726
|
-
:port => 80
|
727
|
-
})
|
728
|
-
expect(client.host).to eq('api-eu.pusher.com')
|
729
|
-
end
|
730
|
-
|
731
|
-
it 'should always have host override any supplied cluster value' do
|
732
|
-
client = Pusher::Client.new({
|
733
|
-
:scheme => 'http',
|
734
|
-
:host => 'api.staging.pusherapp.com',
|
735
|
-
:cluster => 'eu',
|
736
|
-
:port => 80
|
737
|
-
})
|
738
|
-
expect(client.host).to eq('api.staging.pusherapp.com')
|
739
|
-
end
|
740
|
-
end
|
741
|
-
end
|