pusher 1.3.0 → 1.4.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 +5 -5
- data/.github/stale.yml +26 -0
- data/.travis.yml +6 -5
- data/CHANGELOG.md +31 -1
- data/README.md +104 -117
- data/examples/presence_channels/presence_channels.rb +56 -0
- data/examples/presence_channels/public/presence_channels.html +28 -0
- data/lib/pusher.rb +3 -2
- data/lib/pusher/channel.rb +9 -2
- data/lib/pusher/client.rb +70 -4
- data/lib/pusher/native_notification/client.rb +1 -3
- data/lib/pusher/request.rb +2 -0
- data/lib/pusher/version.rb +1 -1
- data/pusher.gemspec +12 -10
- data/spec/channel_spec.rb +6 -4
- data/spec/client_spec.rb +163 -5
- metadata +59 -25
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'sinatra/cookies'
|
3
|
+
require 'sinatra/json'
|
4
|
+
require 'pusher'
|
5
|
+
|
6
|
+
# You can get these variables from http://dashboard.pusher.com
|
7
|
+
pusher = Pusher::Client.new(
|
8
|
+
app_id: 'your-app-id',
|
9
|
+
key: 'your-app-key',
|
10
|
+
secret: 'your-app-secret',
|
11
|
+
cluster: 'your-app-cluster'
|
12
|
+
)
|
13
|
+
|
14
|
+
set :public_folder, 'public'
|
15
|
+
|
16
|
+
get "/" do
|
17
|
+
redirect '/presence_channels.html'
|
18
|
+
end
|
19
|
+
|
20
|
+
# Emulate rails behaviour where this information would be stored in session
|
21
|
+
get '/signin' do
|
22
|
+
cookies[:user_id] = 'example_cookie'
|
23
|
+
'Ok'
|
24
|
+
end
|
25
|
+
|
26
|
+
# Auth endpoint: https://pusher.com/docs/channels/server_api/authenticating-users
|
27
|
+
post '/pusher/auth' do
|
28
|
+
channel_data = {
|
29
|
+
user_id: 'example_user',
|
30
|
+
user_info: {
|
31
|
+
name: 'example_name',
|
32
|
+
email: 'example_email'
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
if cookies[:user_id] == 'example_cookie'
|
37
|
+
response = pusher.authenticate(params[:channel_name], params[:socket_id], channel_data)
|
38
|
+
json response
|
39
|
+
else
|
40
|
+
status 403
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
get '/pusher_trigger' do
|
45
|
+
channels = ['presence-channel-test'];
|
46
|
+
|
47
|
+
begin
|
48
|
+
pusher.trigger(channels, 'test-event', {
|
49
|
+
message: 'hello world'
|
50
|
+
})
|
51
|
+
rescue Pusher::Error => e
|
52
|
+
# (Pusher::AuthenticationError, Pusher::HTTPError, or Pusher::Error)
|
53
|
+
end
|
54
|
+
|
55
|
+
'Triggered!'
|
56
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<head>
|
3
|
+
<title>Pusher Test</title>
|
4
|
+
<script src="https://js.pusher.com/5.0/pusher.min.js"></script>
|
5
|
+
<script>
|
6
|
+
|
7
|
+
// Enable pusher logging - don't include this in production
|
8
|
+
Pusher.logToConsole = true;
|
9
|
+
|
10
|
+
var pusher = new Pusher('your-app-key', {
|
11
|
+
cluster: 'your-app-cluster',
|
12
|
+
forceTLS: true,
|
13
|
+
authEndpoint: '/pusher/auth'
|
14
|
+
});
|
15
|
+
|
16
|
+
var channel = pusher.subscribe('presence-channel-test');
|
17
|
+
channel.bind('test-event', function(data) {
|
18
|
+
alert(JSON.stringify(data));
|
19
|
+
});
|
20
|
+
</script>
|
21
|
+
</head>
|
22
|
+
<body>
|
23
|
+
<h1>Pusher Test</h1>
|
24
|
+
<p>
|
25
|
+
Try publishing an event to channel <code>presence-channel-test</code>
|
26
|
+
with event name <code>test-event</code>.
|
27
|
+
</p>
|
28
|
+
</body>
|
data/lib/pusher.rb
CHANGED
@@ -32,12 +32,13 @@ module Pusher
|
|
32
32
|
def_delegators :default_client, :scheme=, :host=, :port=, :app_id=, :key=, :secret=, :http_proxy=
|
33
33
|
def_delegators :default_client, :notification_host=, :notification_scheme=
|
34
34
|
|
35
|
-
def_delegators :default_client, :authentication_token, :url
|
35
|
+
def_delegators :default_client, :authentication_token, :url, :cluster
|
36
36
|
def_delegators :default_client, :encrypted=, :url=, :cluster=
|
37
37
|
def_delegators :default_client, :timeout=, :connect_timeout=, :send_timeout=, :receive_timeout=, :keep_alive_timeout=
|
38
38
|
|
39
39
|
def_delegators :default_client, :get, :get_async, :post, :post_async
|
40
|
-
def_delegators :default_client, :channels, :channel_info, :channel_users
|
40
|
+
def_delegators :default_client, :channels, :channel_info, :channel_users
|
41
|
+
def_delegators :default_client, :trigger, :trigger_batch, :trigger_async, :trigger_batch_async
|
41
42
|
def_delegators :default_client, :authenticate, :webhook, :channel, :[]
|
42
43
|
def_delegators :default_client, :notify
|
43
44
|
|
data/lib/pusher/channel.rb
CHANGED
@@ -86,6 +86,9 @@ module Pusher
|
|
86
86
|
|
87
87
|
# Request info for a channel
|
88
88
|
#
|
89
|
+
# @example Response
|
90
|
+
# [{:occupied=>true, :subscription_count => 12}]
|
91
|
+
#
|
89
92
|
# @param info [Array] Array of attributes required (as lowercase strings)
|
90
93
|
# @return [Hash] Hash of requested attributes for this channel
|
91
94
|
# @raise [Pusher::Error] on invalid Pusher response - see the error message for more details
|
@@ -99,7 +102,7 @@ module Pusher
|
|
99
102
|
# Only works on presence channels (see: http://pusher.com/docs/client_api_guide/client_presence_channels and https://pusher.com/docs/rest_api)
|
100
103
|
#
|
101
104
|
# @example Response
|
102
|
-
# [{
|
105
|
+
# [{:id=>"4"}]
|
103
106
|
#
|
104
107
|
# @param params [Hash] Hash of parameters for the API - see REST API docs
|
105
108
|
# @return [Hash] Array of user hashes for this channel
|
@@ -120,6 +123,8 @@ module Pusher
|
|
120
123
|
# @param custom_string [String] Allows signing additional data
|
121
124
|
# @return [String]
|
122
125
|
#
|
126
|
+
# @raise [Pusher::Error] if socket_id or custom_string invalid
|
127
|
+
#
|
123
128
|
def authentication_string(socket_id, custom_string = nil)
|
124
129
|
validate_socket_id(socket_id)
|
125
130
|
|
@@ -144,7 +149,7 @@ module Pusher
|
|
144
149
|
# render :json => Pusher['private-my_channel'].authenticate(params[:socket_id])
|
145
150
|
#
|
146
151
|
# @example Presence channels
|
147
|
-
# render :json => Pusher['
|
152
|
+
# render :json => Pusher['presence-my_channel'].authenticate(params[:socket_id], {
|
148
153
|
# :user_id => current_user.id, # => required
|
149
154
|
# :user_info => { # => optional - for example
|
150
155
|
# :name => current_user.name,
|
@@ -157,6 +162,8 @@ module Pusher
|
|
157
162
|
#
|
158
163
|
# @return [Hash]
|
159
164
|
#
|
165
|
+
# @raise [Pusher::Error] if socket_id or custom_data is invalid
|
166
|
+
#
|
160
167
|
# @private Custom data is sent to server as JSON-encoded string
|
161
168
|
#
|
162
169
|
def authenticate(socket_id, custom_data = nil)
|
data/lib/pusher/client.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
1
3
|
require 'pusher-signature'
|
2
4
|
|
3
5
|
module Pusher
|
4
6
|
class Client
|
5
|
-
attr_accessor :scheme, :host, :port, :app_id, :key, :secret, :notification_host, :notification_scheme
|
7
|
+
attr_accessor :scheme, :host, :port, :app_id, :key, :secret, :notification_host, :notification_scheme, :encryption_master_key
|
6
8
|
attr_reader :http_proxy, :proxy
|
7
9
|
attr_writer :connect_timeout, :send_timeout, :receive_timeout,
|
8
10
|
:keep_alive_timeout
|
@@ -12,6 +14,11 @@ module Pusher
|
|
12
14
|
# Loads the configuration from an url in the environment
|
13
15
|
def self.from_env(key = 'PUSHER_URL')
|
14
16
|
url = ENV[key] || raise(ConfigurationError, key)
|
17
|
+
from_url(url)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Loads the configuration from a url
|
21
|
+
def self.from_url(url)
|
15
22
|
client = new
|
16
23
|
client.url = url
|
17
24
|
client
|
@@ -22,6 +29,12 @@ module Pusher
|
|
22
29
|
:scheme => 'http',
|
23
30
|
:port => 80,
|
24
31
|
}
|
32
|
+
|
33
|
+
if options[:use_tls] || options[:encrypted]
|
34
|
+
default_options[:scheme] = "https"
|
35
|
+
default_options[:port] = 443
|
36
|
+
end
|
37
|
+
|
25
38
|
merged_options = default_options.merge(options)
|
26
39
|
|
27
40
|
if options.has_key?(:host)
|
@@ -44,6 +57,11 @@ module Pusher
|
|
44
57
|
:scheme, :host, :port, :app_id, :key, :secret, :notification_host, :notification_scheme
|
45
58
|
)
|
46
59
|
|
60
|
+
if options.has_key?(:encryption_master_key_base64)
|
61
|
+
@encryption_master_key =
|
62
|
+
Base64.decode64(options[:encryption_master_key_base64])
|
63
|
+
end
|
64
|
+
|
47
65
|
@http_proxy = nil
|
48
66
|
self.http_proxy = options[:http_proxy] if options[:http_proxy]
|
49
67
|
|
@@ -127,7 +145,13 @@ module Pusher
|
|
127
145
|
@connect_timeout, @send_timeout, @receive_timeout = value, value, value
|
128
146
|
end
|
129
147
|
|
130
|
-
|
148
|
+
# Set an encryption_master_key to use with private-encrypted channels from
|
149
|
+
# a base64 encoded string.
|
150
|
+
def encryption_master_key_base64=(s)
|
151
|
+
@encryption_master_key = s ? Base64.decode64(s) : nil
|
152
|
+
end
|
153
|
+
|
154
|
+
## INTERACT WITH THE API ##
|
131
155
|
|
132
156
|
def resource(path)
|
133
157
|
Resource.new(self, path)
|
@@ -345,6 +369,8 @@ module Pusher
|
|
345
369
|
#
|
346
370
|
# @return [Hash]
|
347
371
|
#
|
372
|
+
# @raise [Pusher::Error] if channel_name or socket_id are invalid
|
373
|
+
#
|
348
374
|
# @private Custom data is sent to server as JSON-encoded string
|
349
375
|
#
|
350
376
|
def authenticate(channel_name, socket_id, custom_data = nil)
|
@@ -400,10 +426,17 @@ module Pusher
|
|
400
426
|
channels = Array(channels).map(&:to_s)
|
401
427
|
raise Pusher::Error, "Too many channels (#{channels.length}), max 10" if channels.length > 10
|
402
428
|
|
429
|
+
encoded_data = if channels.any?{ |c| c.match(/^private-encrypted-/) } then
|
430
|
+
raise Pusher::Error, "Cannot trigger to multiple channels if any are encrypted" if channels.length > 1
|
431
|
+
encrypt(channels[0], encode_data(data))
|
432
|
+
else
|
433
|
+
encode_data(data)
|
434
|
+
end
|
435
|
+
|
403
436
|
params.merge({
|
404
437
|
name: event_name,
|
405
438
|
channels: channels,
|
406
|
-
data:
|
439
|
+
data: encoded_data,
|
407
440
|
})
|
408
441
|
end
|
409
442
|
|
@@ -411,7 +444,11 @@ module Pusher
|
|
411
444
|
{
|
412
445
|
batch: events.map do |event|
|
413
446
|
event.dup.tap do |e|
|
414
|
-
e[:data] =
|
447
|
+
e[:data] = if e[:channel].match(/^private-encrypted-/) then
|
448
|
+
encrypt(e[:channel], encode_data(e[:data]))
|
449
|
+
else
|
450
|
+
encode_data(e[:data])
|
451
|
+
end
|
415
452
|
end
|
416
453
|
end
|
417
454
|
}
|
@@ -423,8 +460,37 @@ module Pusher
|
|
423
460
|
MultiJson.encode(data)
|
424
461
|
end
|
425
462
|
|
463
|
+
# Encrypts a message with a key derived from the master key and channel
|
464
|
+
# name
|
465
|
+
def encrypt(channel, encoded_data)
|
466
|
+
raise ConfigurationError, :encryption_master_key unless @encryption_master_key
|
467
|
+
|
468
|
+
# Only now load rbnacl, so that people that aren't using it don't need to
|
469
|
+
# install libsodium
|
470
|
+
require_rbnacl
|
471
|
+
|
472
|
+
secret_box = RbNaCl::SecretBox.new(
|
473
|
+
RbNaCl::Hash.sha256(channel + @encryption_master_key)
|
474
|
+
)
|
475
|
+
|
476
|
+
nonce = RbNaCl::Random.random_bytes(secret_box.nonce_bytes)
|
477
|
+
ciphertext = secret_box.encrypt(nonce, encoded_data)
|
478
|
+
|
479
|
+
MultiJson.encode({
|
480
|
+
"nonce" => Base64::encode64(nonce),
|
481
|
+
"ciphertext" => Base64::encode64(ciphertext),
|
482
|
+
})
|
483
|
+
end
|
484
|
+
|
426
485
|
def configured?
|
427
486
|
host && scheme && key && secret && app_id
|
428
487
|
end
|
488
|
+
|
489
|
+
def require_rbnacl
|
490
|
+
require 'rbnacl'
|
491
|
+
rescue LoadError => e
|
492
|
+
$stderr.puts "You don't have rbnacl installed in your application. Please add it to your Gemfile and run bundle install"
|
493
|
+
raise e
|
494
|
+
end
|
429
495
|
end
|
430
496
|
end
|
@@ -26,8 +26,6 @@ module Pusher
|
|
26
26
|
|
27
27
|
private
|
28
28
|
|
29
|
-
# TODO: Actual links
|
30
|
-
#
|
31
29
|
# {
|
32
30
|
# interests: [Array of interests],
|
33
31
|
# apns: {
|
@@ -38,7 +36,7 @@ module Pusher
|
|
38
36
|
# }
|
39
37
|
# }
|
40
38
|
#
|
41
|
-
# @raise [Pusher::Error] if the
|
39
|
+
# @raise [Pusher::Error] if the interests array is empty
|
42
40
|
# @return [String]
|
43
41
|
def payload(interests, data)
|
44
42
|
interests = Array(interests).map(&:to_s)
|
data/lib/pusher/request.rb
CHANGED
@@ -94,6 +94,8 @@ module Pusher
|
|
94
94
|
raise Error, "404 Not found (#{@uri.path})"
|
95
95
|
when 407
|
96
96
|
raise Error, "Proxy Authentication Required"
|
97
|
+
when 413
|
98
|
+
raise Error, "Payload Too Large > 10KB"
|
97
99
|
else
|
98
100
|
raise Error, "Unknown error (status code #{status_code}): #{body}"
|
99
101
|
end
|
data/lib/pusher/version.rb
CHANGED
data/pusher.gemspec
CHANGED
@@ -9,21 +9,23 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.authors = ["Pusher"]
|
10
10
|
s.email = ["support@pusher.com"]
|
11
11
|
s.homepage = "http://github.com/pusher/pusher-http-ruby"
|
12
|
-
s.summary = %q{Pusher API client}
|
13
|
-
s.description = %q{Wrapper for
|
12
|
+
s.summary = %q{Pusher Channels API client}
|
13
|
+
s.description = %q{Wrapper for Pusher Channels REST api: : https://pusher.com/channels}
|
14
14
|
s.license = "MIT"
|
15
15
|
|
16
|
-
s.add_dependency "multi_json", "~> 1.
|
16
|
+
s.add_dependency "multi_json", "~> 1.15"
|
17
17
|
s.add_dependency 'pusher-signature', "~> 0.1.8"
|
18
|
-
s.add_dependency "httpclient", "~> 2.
|
18
|
+
s.add_dependency "httpclient", "~> 2.8"
|
19
19
|
s.add_dependency "jruby-openssl" if defined?(JRUBY_VERSION)
|
20
20
|
|
21
|
-
s.add_development_dependency "rspec", "~> 3.
|
22
|
-
s.add_development_dependency "webmock"
|
23
|
-
s.add_development_dependency "em-http-request", "~> 1.1
|
24
|
-
s.add_development_dependency "
|
25
|
-
s.add_development_dependency "
|
26
|
-
s.add_development_dependency "
|
21
|
+
s.add_development_dependency "rspec", "~> 3.9"
|
22
|
+
s.add_development_dependency "webmock", "~> 3.9"
|
23
|
+
s.add_development_dependency "em-http-request", "~> 1.1"
|
24
|
+
s.add_development_dependency "addressable", "~> 2.7"
|
25
|
+
s.add_development_dependency "rake", "~> 13.0"
|
26
|
+
s.add_development_dependency "rack", "~> 2.2"
|
27
|
+
s.add_development_dependency "json", "~> 2.3"
|
28
|
+
s.add_development_dependency "rbnacl", "~> 7.1"
|
27
29
|
|
28
30
|
s.files = `git ls-files`.split("\n")
|
29
31
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
data/spec/channel_spec.rb
CHANGED
@@ -71,15 +71,17 @@ describe Pusher::Channel do
|
|
71
71
|
|
72
72
|
describe '#info' do
|
73
73
|
it "should call the Client#channel_info" do
|
74
|
-
expect(@client).to receive(:get)
|
74
|
+
expect(@client).to receive(:get)
|
75
|
+
.with("/channels/mychannel", anything)
|
76
|
+
.and_return({:occupied => true, :subscription_count => 12})
|
75
77
|
@channel = @client['mychannel']
|
76
78
|
@channel.info
|
77
79
|
end
|
78
80
|
|
79
81
|
it "should assemble the requested attributes into the info option" do
|
80
|
-
expect(@client).to receive(:get)
|
81
|
-
|
82
|
-
|
82
|
+
expect(@client).to receive(:get)
|
83
|
+
.with(anything, {:info => "user_count,connection_count"})
|
84
|
+
.and_return({:occupied => true, :subscription_count => 12, :user_count => 12})
|
83
85
|
@channel = @client['presence-foo']
|
84
86
|
@channel.info(%w{user_count connection_count})
|
85
87
|
end
|
data/spec/client_spec.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
-
require '
|
1
|
+
require 'base64'
|
2
2
|
|
3
|
+
require 'rbnacl'
|
3
4
|
require 'em-http'
|
4
5
|
|
6
|
+
require 'spec_helper'
|
7
|
+
|
8
|
+
encryption_master_key = RbNaCl::Random.random_bytes(32)
|
9
|
+
|
5
10
|
describe Pusher do
|
6
11
|
# The behaviour should be the same when using the Client object, or the
|
7
12
|
# 'global' client delegated through the Pusher class
|
@@ -87,19 +92,53 @@ describe Pusher do
|
|
87
92
|
expect(@client.host).to eq('api.staging.pusherapp.com')
|
88
93
|
end
|
89
94
|
|
90
|
-
it 'should
|
95
|
+
it 'should override the url configuration if it comes after' do
|
91
96
|
@client.url = "http://somekey:somesecret@api.staging.pusherapp.com:8080/apps/87"
|
92
97
|
@client.cluster = 'eu'
|
93
98
|
expect(@client.host).to eq('api-eu.pusher.com')
|
94
99
|
end
|
95
100
|
|
96
|
-
it 'should
|
101
|
+
it 'should override the host configuration if it comes after' do
|
97
102
|
@client.host = 'api.staging.pusher.com'
|
98
103
|
@client.cluster = 'eu'
|
99
104
|
expect(@client.host).to eq('api-eu.pusher.com')
|
100
105
|
end
|
101
106
|
end
|
102
107
|
|
108
|
+
describe 'configuring TLS' do
|
109
|
+
it 'should set port and scheme if "use_tls" enabled' do
|
110
|
+
client = Pusher::Client.new({
|
111
|
+
:use_tls => true,
|
112
|
+
})
|
113
|
+
expect(client.scheme).to eq('https')
|
114
|
+
expect(client.port).to eq(443)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should set port and scheme if "encrypted" enabled' do
|
118
|
+
client = Pusher::Client.new({
|
119
|
+
:encrypted => true,
|
120
|
+
})
|
121
|
+
expect(client.scheme).to eq('https')
|
122
|
+
expect(client.port).to eq(443)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should use non-TLS port and scheme if "encrypted" or "use_tls" are not set' do
|
126
|
+
client = Pusher::Client.new
|
127
|
+
expect(client.scheme).to eq('http')
|
128
|
+
expect(client.port).to eq(80)
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'should override port if "use_tls" option set but a different port is specified' do
|
132
|
+
client = Pusher::Client.new({
|
133
|
+
:use_tls => true,
|
134
|
+
:port => 8443
|
135
|
+
})
|
136
|
+
expect(client.scheme).to eq('https')
|
137
|
+
expect(client.port).to eq(8443)
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
103
142
|
describe 'configuring a http proxy' do
|
104
143
|
it "should be possible to configure everything by setting the http_proxy" do
|
105
144
|
@client.http_proxy = 'http://someuser:somepassword@proxy.host.com:8080'
|
@@ -109,6 +148,10 @@ describe Pusher do
|
|
109
148
|
end
|
110
149
|
|
111
150
|
describe 'configuring from env' do
|
151
|
+
after do
|
152
|
+
ENV['PUSHER_URL'] = nil
|
153
|
+
end
|
154
|
+
|
112
155
|
it "works" do
|
113
156
|
url = "http://somekey:somesecret@api.staging.pusherapp.com:8080/apps/87"
|
114
157
|
ENV['PUSHER_URL'] = url
|
@@ -118,7 +161,27 @@ describe Pusher do
|
|
118
161
|
expect(client.secret).to eq("somesecret")
|
119
162
|
expect(client.app_id).to eq("87")
|
120
163
|
expect(client.url.to_s).to eq("http://api.staging.pusherapp.com:8080/apps/87")
|
121
|
-
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe 'configuring from url' do
|
168
|
+
it "works" do
|
169
|
+
url = "http://somekey:somesecret@api.staging.pusherapp.com:8080/apps/87"
|
170
|
+
|
171
|
+
client = Pusher::Client.from_url(url)
|
172
|
+
expect(client.key).to eq("somekey")
|
173
|
+
expect(client.secret).to eq("somesecret")
|
174
|
+
expect(client.app_id).to eq("87")
|
175
|
+
expect(client.url.to_s).to eq("http://api.staging.pusherapp.com:8080/apps/87")
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe 'can set encryption_master_key_base64' do
|
180
|
+
it "sets encryption_master_key" do
|
181
|
+
@client.encryption_master_key_base64 =
|
182
|
+
Base64.encode64(encryption_master_key)
|
183
|
+
|
184
|
+
expect(@client.encryption_master_key).to eq(encryption_master_key)
|
122
185
|
end
|
123
186
|
end
|
124
187
|
|
@@ -127,6 +190,8 @@ describe Pusher do
|
|
127
190
|
@client.app_id = '20'
|
128
191
|
@client.key = '12345678900000001'
|
129
192
|
@client.secret = '12345678900000001'
|
193
|
+
@client.encryption_master_key_base64 =
|
194
|
+
Base64.encode64(encryption_master_key)
|
130
195
|
end
|
131
196
|
|
132
197
|
describe '#[]' do
|
@@ -272,6 +337,46 @@ describe Pusher do
|
|
272
337
|
}
|
273
338
|
end
|
274
339
|
end
|
340
|
+
|
341
|
+
it "should fail to publish to encrypted channels when missing key" do
|
342
|
+
@client.encryption_master_key_base64 = nil
|
343
|
+
expect {
|
344
|
+
@client.trigger('private-encrypted-channel', 'event', {'some' => 'data'})
|
345
|
+
}.to raise_error(Pusher::ConfigurationError)
|
346
|
+
expect(WebMock).not_to have_requested(:post, @api_path)
|
347
|
+
end
|
348
|
+
|
349
|
+
it "should fail to publish to multiple channels if one is encrypted" do
|
350
|
+
expect {
|
351
|
+
@client.trigger(
|
352
|
+
['private-encrypted-channel', 'some-other-channel'],
|
353
|
+
'event',
|
354
|
+
{'some' => 'data'},
|
355
|
+
)
|
356
|
+
}.to raise_error(Pusher::Error)
|
357
|
+
expect(WebMock).not_to have_requested(:post, @api_path)
|
358
|
+
end
|
359
|
+
|
360
|
+
it "should encrypt publishes to encrypted channels" do
|
361
|
+
@client.trigger(
|
362
|
+
'private-encrypted-channel',
|
363
|
+
'event',
|
364
|
+
{'some' => 'data'},
|
365
|
+
)
|
366
|
+
|
367
|
+
expect(WebMock).to have_requested(:post, @api_path).with { |req|
|
368
|
+
data = MultiJson.decode(MultiJson.decode(req.body)["data"])
|
369
|
+
|
370
|
+
key = RbNaCl::Hash.sha256(
|
371
|
+
'private-encrypted-channel' + encryption_master_key
|
372
|
+
)
|
373
|
+
|
374
|
+
expect(MultiJson.decode(RbNaCl::SecretBox.new(key).decrypt(
|
375
|
+
Base64.decode64(data["nonce"]),
|
376
|
+
Base64.decode64(data["ciphertext"]),
|
377
|
+
))).to eq({ 'some' => 'data' })
|
378
|
+
}
|
379
|
+
end
|
275
380
|
end
|
276
381
|
|
277
382
|
describe '#trigger_batch' do
|
@@ -303,6 +408,55 @@ describe Pusher do
|
|
303
408
|
)
|
304
409
|
}
|
305
410
|
end
|
411
|
+
|
412
|
+
it "should fail to publish to encrypted channels when missing key" do
|
413
|
+
@client.encryption_master_key_base64 = nil
|
414
|
+
expect {
|
415
|
+
@client.trigger_batch(
|
416
|
+
{
|
417
|
+
channel: 'private-encrypted-channel',
|
418
|
+
name: 'event',
|
419
|
+
data: {'some' => 'data'},
|
420
|
+
},
|
421
|
+
{channel: 'mychannel', name: 'event', data: 'already encoded'},
|
422
|
+
)
|
423
|
+
}.to raise_error(Pusher::ConfigurationError)
|
424
|
+
expect(WebMock).not_to have_requested(:post, @api_path)
|
425
|
+
end
|
426
|
+
|
427
|
+
it "should encrypt publishes to encrypted channels" do
|
428
|
+
@client.trigger_batch(
|
429
|
+
{
|
430
|
+
channel: 'private-encrypted-channel',
|
431
|
+
name: 'event',
|
432
|
+
data: {'some' => 'data'},
|
433
|
+
},
|
434
|
+
{channel: 'mychannel', name: 'event', data: 'already encoded'},
|
435
|
+
)
|
436
|
+
|
437
|
+
expect(WebMock).to have_requested(:post, @api_path).with { |req|
|
438
|
+
batch = MultiJson.decode(req.body)["batch"]
|
439
|
+
expect(batch.length).to eq(2)
|
440
|
+
|
441
|
+
expect(batch[0]["channel"]).to eq("private-encrypted-channel")
|
442
|
+
expect(batch[0]["name"]).to eq("event")
|
443
|
+
|
444
|
+
data = MultiJson.decode(batch[0]["data"])
|
445
|
+
|
446
|
+
key = RbNaCl::Hash.sha256(
|
447
|
+
'private-encrypted-channel' + encryption_master_key
|
448
|
+
)
|
449
|
+
|
450
|
+
expect(MultiJson.decode(RbNaCl::SecretBox.new(key).decrypt(
|
451
|
+
Base64.decode64(data["nonce"]),
|
452
|
+
Base64.decode64(data["ciphertext"]),
|
453
|
+
))).to eq({ 'some' => 'data' })
|
454
|
+
|
455
|
+
expect(batch[1]["channel"]).to eq("mychannel")
|
456
|
+
expect(batch[1]["name"]).to eq("event")
|
457
|
+
expect(batch[1]["data"]).to eq("already encoded")
|
458
|
+
}
|
459
|
+
end
|
306
460
|
end
|
307
461
|
|
308
462
|
describe '#trigger_async' do
|
@@ -415,6 +569,11 @@ describe Pusher do
|
|
415
569
|
expect { call_api }.to raise_error(Pusher::Error, 'Proxy Authentication Required')
|
416
570
|
end
|
417
571
|
|
572
|
+
it "should raise Pusher::Error if pusher returns 413" do
|
573
|
+
stub_request(verb, @url_regexp).to_return({:status => 413})
|
574
|
+
expect { call_api }.to raise_error(Pusher::Error, 'Payload Too Large > 10KB')
|
575
|
+
end
|
576
|
+
|
418
577
|
it "should raise Pusher::Error if pusher returns 500" do
|
419
578
|
stub_request(verb, @url_regexp).to_return({:status => 500, :body => "some error"})
|
420
579
|
expect { call_api }.to raise_error(Pusher::Error, 'Unknown error (status code 500): some error')
|
@@ -612,4 +771,3 @@ describe Pusher do
|
|
612
771
|
end
|
613
772
|
end
|
614
773
|
end
|
615
|
-
|