pusher 1.3.2 → 1.4.3
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 -6
- data/CHANGELOG.md +37 -1
- data/README.md +82 -31
- data/examples/presence_channels/presence_channels.rb +56 -0
- data/examples/presence_channels/public/presence_channels.html +28 -0
- data/lib/pusher/channel.rb +9 -0
- data/lib/pusher/client.rb +63 -4
- data/lib/pusher/version.rb +1 -1
- data/pusher.gemspec +12 -11
- data/spec/channel_spec.rb +19 -0
- data/spec/client_spec.rb +119 -1
- metadata +44 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4101c5a5932482beeedbb64514332771698b5c462f951d318f5b0412a379da1e
|
4
|
+
data.tar.gz: 10912c158ead0794e8b8ba7cc68d02e8ce4165458abd7cec6dd48d3583feadca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: acdc1eb1c1630f04ab0e5de74c9d62c11bf1f9588421d1e130a641c7ee31a547e727c693c4a3a11bd035d800f0ba8721106f1b322bd36e5e97cde7a0c4dbbe43
|
7
|
+
data.tar.gz: 1692589eb319a5fa54560807af603e52ba368897e91363a81184dcdfa3cc3a5b120ecf2f87dea9c56d8e2ee2d689bf8cb3ccc1f6dc36db2095a0d338363b553c
|
data/.github/stale.yml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Configuration for probot-stale - https://github.com/probot/stale
|
2
|
+
|
3
|
+
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
4
|
+
daysUntilStale: 365
|
5
|
+
|
6
|
+
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
7
|
+
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
8
|
+
daysUntilClose: 7
|
9
|
+
|
10
|
+
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
11
|
+
onlyLabels: []
|
12
|
+
|
13
|
+
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
14
|
+
exemptLabels:
|
15
|
+
- pinned
|
16
|
+
- security
|
17
|
+
|
18
|
+
# Set to true to ignore issues with an assignee (defaults to false)
|
19
|
+
exemptAssignees: true
|
20
|
+
|
21
|
+
# Comment to post when marking as stale. Set to `false` to disable
|
22
|
+
markComment: >
|
23
|
+
This issue has been automatically marked as stale because it has not had
|
24
|
+
recent activity. It will be closed if no further activity occurs. If you'd
|
25
|
+
like this issue to stay open please leave a comment indicating how this issue
|
26
|
+
is affecting you. Thankyou.
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,40 @@
|
|
1
|
+
1.4.3 / 2020-10-28
|
2
|
+
==================
|
3
|
+
|
4
|
+
* Remove newline from end of base64 encoded strings, some decoders don't like
|
5
|
+
them.
|
6
|
+
|
7
|
+
1.4.2 / 2020-10-20
|
8
|
+
==================
|
9
|
+
|
10
|
+
* Return `shared_secret` to support authenticating encrypted channels. Thanks
|
11
|
+
@Benjaminpjacobs
|
12
|
+
|
13
|
+
1.4.1 / 2020-10-05
|
14
|
+
==================
|
15
|
+
|
16
|
+
* Remove rbnacl from dependencies so we don't get errors when it isn't
|
17
|
+
required. Thanks @y-yagi!
|
18
|
+
|
19
|
+
1.4.0 / 2020-09-29
|
20
|
+
==================
|
21
|
+
|
22
|
+
* Support for end-to-end encryption.
|
23
|
+
|
24
|
+
1.3.3 / 2019-07-02
|
25
|
+
==================
|
26
|
+
|
27
|
+
* Rewording to clarify "Pusher Channels" or simply "Channels" product name.
|
28
|
+
|
29
|
+
1.3.2 / 2018-10-17
|
30
|
+
==================
|
31
|
+
|
32
|
+
* Return a specific error for "Request Entity Too Large" (body over 10KB).
|
33
|
+
* Add a `use_tls` option for SSL (defaults to false).
|
34
|
+
* Add a `from_url` client method (in addition to existing `from_env` option).
|
35
|
+
* Improved documentation and fixed typos.
|
36
|
+
* Add Ruby 2.4 to test matrix.
|
37
|
+
|
1
38
|
1.3.1 / 2017-03-15
|
2
39
|
==================
|
3
40
|
|
@@ -100,4 +137,3 @@ First release with a changelog !
|
|
100
137
|
|
101
138
|
* Bump httpclient to v2.4. See #62 (POODLE SSL)
|
102
139
|
* Fix limited channel count at README.md. Thanks @tricknotes
|
103
|
-
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Gem for Pusher Channels
|
2
2
|
|
3
|
-
This Gem provides a Ruby interface to [the Pusher HTTP API for Pusher Channels](https://pusher.com/docs/
|
3
|
+
This Gem provides a Ruby interface to [the Pusher HTTP API for Pusher Channels](https://pusher.com/docs/channels/library_auth_reference/rest-api).
|
4
4
|
|
5
|
-
[](http://travis-ci.org/pusher/pusher-http-ruby)
|
5
|
+
[](http://travis-ci.org/pusher/pusher-http-ruby) [](https://badge.fury.io/rb/pusher)
|
6
6
|
|
7
7
|
## Installation and Configuration
|
8
8
|
|
@@ -18,7 +18,7 @@ or install via gem
|
|
18
18
|
gem install pusher
|
19
19
|
```
|
20
20
|
|
21
|
-
After registering at
|
21
|
+
After registering at [Pusher](https://dashboard.pusher.com/accounts/sign_up), configure your Channels app with the security credentials.
|
22
22
|
|
23
23
|
### Instantiating a Pusher Channels client
|
24
24
|
|
@@ -27,7 +27,7 @@ Creating a new Pusher Channels `client` can be done as follows.
|
|
27
27
|
``` ruby
|
28
28
|
require 'pusher'
|
29
29
|
|
30
|
-
|
30
|
+
pusher = Pusher::Client.new(
|
31
31
|
app_id: 'your-app-id',
|
32
32
|
key: 'your-app-key',
|
33
33
|
secret: 'your-app-secret',
|
@@ -43,7 +43,7 @@ If you want to set a custom `host` value for your client then you can do so when
|
|
43
43
|
``` ruby
|
44
44
|
require 'pusher'
|
45
45
|
|
46
|
-
|
46
|
+
pusher = Pusher::Client.new(
|
47
47
|
app_id: 'your-app-id',
|
48
48
|
key: 'your-app-key',
|
49
49
|
secret: 'your-app-secret',
|
@@ -57,12 +57,12 @@ Finally, if you have the configuration set in an `PUSHER_URL` environment
|
|
57
57
|
variable, you can use:
|
58
58
|
|
59
59
|
``` ruby
|
60
|
-
|
60
|
+
pusher = Pusher::Client.from_env
|
61
61
|
```
|
62
62
|
|
63
63
|
### Global configuration
|
64
64
|
|
65
|
-
|
65
|
+
The library can also be configured globally on the `Pusher` class.
|
66
66
|
|
67
67
|
``` ruby
|
68
68
|
Pusher.app_id = 'your-app-id'
|
@@ -92,9 +92,9 @@ As of version 0.12, SSL certificates are verified when using the synchronous htt
|
|
92
92
|
Pusher.default_client.sync_http_client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
93
93
|
```
|
94
94
|
|
95
|
-
## Interacting with the
|
95
|
+
## Interacting with the Channels HTTP API
|
96
96
|
|
97
|
-
The
|
97
|
+
The `pusher` gem contains a number of helpers for interacting with the API. As a general rule, the library adheres to a set of conventions that we have aimed to make universal.
|
98
98
|
|
99
99
|
### Handling errors
|
100
100
|
|
@@ -102,7 +102,7 @@ Handle errors by rescuing `Pusher::Error` (all errors are descendants of this er
|
|
102
102
|
|
103
103
|
``` ruby
|
104
104
|
begin
|
105
|
-
|
105
|
+
pusher.trigger('a_channel', 'an_event', :some => 'data')
|
106
106
|
rescue Pusher::Error => e
|
107
107
|
# (Pusher::AuthenticationError, Pusher::HTTPError, or Pusher::Error)
|
108
108
|
end
|
@@ -121,14 +121,14 @@ Pusher.logger = Rails.logger
|
|
121
121
|
An event can be published to one or more channels (limited to 10) in one API call:
|
122
122
|
|
123
123
|
``` ruby
|
124
|
-
|
125
|
-
|
124
|
+
pusher.trigger('channel', 'event', foo: 'bar')
|
125
|
+
pusher.trigger(['channel_1', 'channel_2'], 'event_name', foo: 'bar')
|
126
126
|
```
|
127
127
|
|
128
|
-
An optional fourth argument may be used to send additional parameters to the API, for example to [exclude a single connection from receiving the event](
|
128
|
+
An optional fourth argument may be used to send additional parameters to the API, for example to [exclude a single connection from receiving the event](https://pusher.com/docs/channels/server_api/excluding-event-recipients).
|
129
129
|
|
130
130
|
``` ruby
|
131
|
-
|
131
|
+
pusher.trigger('channel', 'event', {foo: 'bar'}, {socket_id: '123.456'})
|
132
132
|
```
|
133
133
|
|
134
134
|
#### Batches
|
@@ -137,7 +137,7 @@ It's also possible to send multiple events with a single API call (max 10
|
|
137
137
|
events per call on multi-tenant clusters):
|
138
138
|
|
139
139
|
``` ruby
|
140
|
-
|
140
|
+
pusher.trigger_batch([
|
141
141
|
{channel: 'channel_1', name: 'event_name', data: { foo: 'bar' }},
|
142
142
|
{channel: 'channel_1', name: 'event_name', data: { hello: 'world' }}
|
143
143
|
])
|
@@ -151,36 +151,36 @@ Most examples and documentation will refer to the following syntax for triggerin
|
|
151
151
|
Pusher['a_channel'].trigger('an_event', :some => 'data')
|
152
152
|
```
|
153
153
|
|
154
|
-
This will continue to work, but has been replaced by `
|
154
|
+
This will continue to work, but has been replaced by `pusher.trigger` which supports one or multiple channels.
|
155
155
|
|
156
156
|
### Getting information about the channels in your Pusher Channels app
|
157
157
|
|
158
|
-
This gem provides methods for accessing information from the [
|
158
|
+
This gem provides methods for accessing information from the [Channels HTTP API](https://pusher.com/docs/channels/library_auth_reference/rest-api). The documentation also shows an example of the responses from each of the API endpoints.
|
159
159
|
|
160
160
|
The following methods are provided by the gem.
|
161
161
|
|
162
|
-
- `
|
162
|
+
- `pusher.channel_info('channel_name', {info:"user_count,subscription_count"})` returns a hash describing the state of the channel([docs](https://pusher.com/docs/channels/library_auth_reference/rest-api#get-channels-fetch-info-for-multiple-channels-)).
|
163
163
|
|
164
|
-
- `
|
164
|
+
- `pusher.channel_users('presence-channel_name')` returns a list of all the users subscribed to the channel (only for Presence Channels) ([docs](https://pusher.com/docs/channels/library_auth_reference/rest-api#get-channels-fetch-info-for-multiple-channels-)).
|
165
165
|
|
166
|
-
- `
|
166
|
+
- `pusher.channels({filter_by_prefix: 'presence-', info: 'user_count'})` returns a hash of occupied channels (optionally filtered by prefix, f.i. `presence-`), and optionally attributes for these channels ([docs](https://pusher.com/docs/channels/library_auth_reference/rest-api#get-channels-fetch-info-for-multiple-channels-)).
|
167
167
|
|
168
168
|
### Asynchronous requests
|
169
169
|
|
170
170
|
There are two main reasons for using the `_async` methods:
|
171
171
|
|
172
|
-
* In a web application where the response from the
|
172
|
+
* In a web application where the response from the Channels HTTP API is not used, but you'd like to avoid a blocking call in the request-response cycle
|
173
173
|
* Your application is running in an event loop and you need to avoid blocking the reactor
|
174
174
|
|
175
175
|
Asynchronous calls are supported either by using an event loop (eventmachine, preferred), or via a thread.
|
176
176
|
|
177
177
|
The following methods are available (in each case the calling interface matches the non-async version):
|
178
178
|
|
179
|
-
* `
|
180
|
-
* `
|
181
|
-
* `
|
179
|
+
* `pusher.get_async`
|
180
|
+
* `pusher.post_async`
|
181
|
+
* `pusher.trigger_async`
|
182
182
|
|
183
|
-
It is of course also possible to make calls to the
|
183
|
+
It is of course also possible to make calls to the Channels HTTP API via a job queue. This approach is recommended if you're sending a large number of events.
|
184
184
|
|
185
185
|
#### With EventMachine
|
186
186
|
|
@@ -190,14 +190,14 @@ It is of course also possible to make calls to the Pusher Channels HTTP API via
|
|
190
190
|
The `_async` methods return an `EM::Deferrable` which you can bind callbacks to:
|
191
191
|
|
192
192
|
``` ruby
|
193
|
-
|
193
|
+
pusher.get_async("/channels").callback { |response|
|
194
194
|
# use reponse[:channels]
|
195
195
|
}.errback { |error|
|
196
196
|
# error is an instance of Pusher::Error
|
197
197
|
}
|
198
198
|
```
|
199
199
|
|
200
|
-
A HTTP error or an error response from
|
200
|
+
A HTTP error or an error response from Channels will cause the errback to be called with an appropriate error object.
|
201
201
|
|
202
202
|
#### Without EventMachine
|
203
203
|
|
@@ -208,12 +208,12 @@ An `HTTPClient::Connection` object is returned immediately which can be [interro
|
|
208
208
|
|
209
209
|
## Authenticating subscription requests
|
210
210
|
|
211
|
-
It's possible to use the gem to authenticate subscription requests to private or presence channels. The `authenticate` method is available on a channel object for this purpose and returns a JSON object that can be returned to the client that made the request. More information on this authentication scheme can be found in the docs on <
|
211
|
+
It's possible to use the gem to authenticate subscription requests to private or presence channels. The `authenticate` method is available on a channel object for this purpose and returns a JSON object that can be returned to the client that made the request. More information on this authentication scheme can be found in the docs on <https://pusher.com/docs/channels/server_api/authenticating-users>
|
212
212
|
|
213
213
|
### Private channels
|
214
214
|
|
215
215
|
``` ruby
|
216
|
-
|
216
|
+
pusher.authenticate('private-my_channel', params[:socket_id])
|
217
217
|
```
|
218
218
|
|
219
219
|
### Presence channels
|
@@ -221,7 +221,7 @@ channels_client.authenticate('private-my_channel', params[:socket_id])
|
|
221
221
|
These work in a very similar way, but require a unique identifier for the user being authenticated, and optionally some attributes that are provided to clients via presence events:
|
222
222
|
|
223
223
|
``` ruby
|
224
|
-
|
224
|
+
pusher.authenticate('presence-my_channel', params[:socket_id],
|
225
225
|
user_id: 'user_id',
|
226
226
|
user_info: {} # optional
|
227
227
|
)
|
@@ -232,7 +232,7 @@ channels_client.authenticate('presence-my_channel', params[:socket_id],
|
|
232
232
|
A WebHook object may be created to validate received WebHooks against your app credentials, and to extract events. It should be created with the `Rack::Request` object (available as `request` in Rails controllers or Sinatra handlers for example).
|
233
233
|
|
234
234
|
``` ruby
|
235
|
-
webhook =
|
235
|
+
webhook = pusher.webhook(request)
|
236
236
|
if webhook.valid?
|
237
237
|
webhook.events.each do |event|
|
238
238
|
case event["name"]
|
@@ -247,3 +247,54 @@ else
|
|
247
247
|
render text: 'invalid', status: 401
|
248
248
|
end
|
249
249
|
```
|
250
|
+
|
251
|
+
### End-to-end encryption
|
252
|
+
|
253
|
+
This library supports [end-to-end encrypted channels](https://pusher.com/docs/channels/using_channels/encrypted-channels). This means that only you and your connected clients will be able to read your messages. Pusher cannot decrypt them. You can enable this feature by following these steps:
|
254
|
+
|
255
|
+
1. Add the `rbnacl` gem to your Gemfile (it's not a gem dependency).
|
256
|
+
|
257
|
+
2. Install [Libsodium](https://github.com/jedisct1/libsodium), which we rely on to do the heavy lifting. [Follow the installation instructions for your platform.](https://github.com/RubyCrypto/rbnacl/wiki/Installing-libsodium)
|
258
|
+
|
259
|
+
3. Encrypted channel subscriptions must be authenticated in the exact same way as private channels. You should therefore [create an authentication endpoint on your server](https://pusher.com/docs/authenticating_users).
|
260
|
+
|
261
|
+
4. Next, generate your 32 byte master encryption key, encode it as base64 and pass it to the Pusher constructor.
|
262
|
+
|
263
|
+
This is secret and you should never share this with anyone.
|
264
|
+
Not even Pusher.
|
265
|
+
|
266
|
+
```bash
|
267
|
+
openssl rand -base64 32
|
268
|
+
```
|
269
|
+
|
270
|
+
```rb
|
271
|
+
pusher = new Pusher::Client.new({
|
272
|
+
app_id: 'your-app-id',
|
273
|
+
key: 'your-app-key',
|
274
|
+
secret: 'your-app-secret',
|
275
|
+
cluster: 'your-app-cluster',
|
276
|
+
use_tls: true
|
277
|
+
encryption_master_key_base64: '<KEY GENERATED BY PREVIOUS COMMAND>',
|
278
|
+
});
|
279
|
+
```
|
280
|
+
|
281
|
+
5. Channels where you wish to use end-to-end encryption should be prefixed with `private-encrypted-`.
|
282
|
+
|
283
|
+
6. Subscribe to these channels in your client, and you're done! You can verify it is working by checking out the debug console on the [https://dashboard.pusher.com/](dashboard) and seeing the scrambled ciphertext.
|
284
|
+
|
285
|
+
**Important note: This will __not__ encrypt messages on channels that are not prefixed by `private-encrypted-`.**
|
286
|
+
|
287
|
+
**Limitation**: you cannot trigger a single event on multiple channels in a call to `trigger`, e.g.
|
288
|
+
|
289
|
+
```rb
|
290
|
+
pusher.trigger(
|
291
|
+
['channel-1', 'private-encrypted-channel-2'],
|
292
|
+
'test_event',
|
293
|
+
{ message: 'hello world' },
|
294
|
+
)
|
295
|
+
```
|
296
|
+
|
297
|
+
Rationale: the methods in this library map directly to individual Channels HTTP API requests. If we allowed triggering a single event on multiple channels (some encrypted, some unencrypted), then it would require two API requests: one where the event is encrypted to the encrypted channels, and one where the event is unencrypted for unencrypted channels.
|
298
|
+
|
299
|
+
## Supported Ruby versions
|
300
|
+
2.4+
|
@@ -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/channel.rb
CHANGED
@@ -174,6 +174,15 @@ module Pusher
|
|
174
174
|
r
|
175
175
|
end
|
176
176
|
|
177
|
+
def shared_secret(encryption_master_key)
|
178
|
+
return unless encryption_master_key
|
179
|
+
|
180
|
+
secret_string = @name + encryption_master_key
|
181
|
+
digest = OpenSSL::Digest::SHA256.new
|
182
|
+
digest << secret_string
|
183
|
+
digest.digest
|
184
|
+
end
|
185
|
+
|
177
186
|
private
|
178
187
|
|
179
188
|
def validate_socket_id(socket_id)
|
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
|
@@ -55,6 +57,11 @@ module Pusher
|
|
55
57
|
:scheme, :host, :port, :app_id, :key, :secret, :notification_host, :notification_scheme
|
56
58
|
)
|
57
59
|
|
60
|
+
if options.has_key?(:encryption_master_key_base64)
|
61
|
+
@encryption_master_key =
|
62
|
+
Base64.strict_decode64(options[:encryption_master_key_base64])
|
63
|
+
end
|
64
|
+
|
58
65
|
@http_proxy = nil
|
59
66
|
self.http_proxy = options[:http_proxy] if options[:http_proxy]
|
60
67
|
|
@@ -138,6 +145,12 @@ module Pusher
|
|
138
145
|
@connect_timeout, @send_timeout, @receive_timeout = value, value, value
|
139
146
|
end
|
140
147
|
|
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.strict_decode64(s) : nil
|
152
|
+
end
|
153
|
+
|
141
154
|
## INTERACT WITH THE API ##
|
142
155
|
|
143
156
|
def resource(path)
|
@@ -362,7 +375,13 @@ module Pusher
|
|
362
375
|
#
|
363
376
|
def authenticate(channel_name, socket_id, custom_data = nil)
|
364
377
|
channel_instance = channel(channel_name)
|
365
|
-
channel_instance.authenticate(socket_id, custom_data)
|
378
|
+
r = channel_instance.authenticate(socket_id, custom_data)
|
379
|
+
if channel_name.match(/^private-encrypted-/)
|
380
|
+
r[:shared_secret] = Base64.strict_encode64(
|
381
|
+
channel_instance.shared_secret(encryption_master_key)
|
382
|
+
)
|
383
|
+
end
|
384
|
+
r
|
366
385
|
end
|
367
386
|
|
368
387
|
# @private Construct a net/http http client
|
@@ -413,10 +432,17 @@ module Pusher
|
|
413
432
|
channels = Array(channels).map(&:to_s)
|
414
433
|
raise Pusher::Error, "Too many channels (#{channels.length}), max 10" if channels.length > 10
|
415
434
|
|
435
|
+
encoded_data = if channels.any?{ |c| c.match(/^private-encrypted-/) } then
|
436
|
+
raise Pusher::Error, "Cannot trigger to multiple channels if any are encrypted" if channels.length > 1
|
437
|
+
encrypt(channels[0], encode_data(data))
|
438
|
+
else
|
439
|
+
encode_data(data)
|
440
|
+
end
|
441
|
+
|
416
442
|
params.merge({
|
417
443
|
name: event_name,
|
418
444
|
channels: channels,
|
419
|
-
data:
|
445
|
+
data: encoded_data,
|
420
446
|
})
|
421
447
|
end
|
422
448
|
|
@@ -424,7 +450,11 @@ module Pusher
|
|
424
450
|
{
|
425
451
|
batch: events.map do |event|
|
426
452
|
event.dup.tap do |e|
|
427
|
-
e[:data] =
|
453
|
+
e[:data] = if e[:channel].match(/^private-encrypted-/) then
|
454
|
+
encrypt(e[:channel], encode_data(e[:data]))
|
455
|
+
else
|
456
|
+
encode_data(e[:data])
|
457
|
+
end
|
428
458
|
end
|
429
459
|
end
|
430
460
|
}
|
@@ -436,8 +466,37 @@ module Pusher
|
|
436
466
|
MultiJson.encode(data)
|
437
467
|
end
|
438
468
|
|
469
|
+
# Encrypts a message with a key derived from the master key and channel
|
470
|
+
# name
|
471
|
+
def encrypt(channel_name, encoded_data)
|
472
|
+
raise ConfigurationError, :encryption_master_key unless @encryption_master_key
|
473
|
+
|
474
|
+
# Only now load rbnacl, so that people that aren't using it don't need to
|
475
|
+
# install libsodium
|
476
|
+
require_rbnacl
|
477
|
+
|
478
|
+
secret_box = RbNaCl::SecretBox.new(
|
479
|
+
channel(channel_name).shared_secret(@encryption_master_key)
|
480
|
+
)
|
481
|
+
|
482
|
+
nonce = RbNaCl::Random.random_bytes(secret_box.nonce_bytes)
|
483
|
+
ciphertext = secret_box.encrypt(nonce, encoded_data)
|
484
|
+
|
485
|
+
MultiJson.encode({
|
486
|
+
"nonce" => Base64::strict_encode64(nonce),
|
487
|
+
"ciphertext" => Base64::strict_encode64(ciphertext),
|
488
|
+
})
|
489
|
+
end
|
490
|
+
|
439
491
|
def configured?
|
440
492
|
host && scheme && key && secret && app_id
|
441
493
|
end
|
494
|
+
|
495
|
+
def require_rbnacl
|
496
|
+
require 'rbnacl'
|
497
|
+
rescue LoadError => e
|
498
|
+
$stderr.puts "You don't have rbnacl installed in your application. Please add it to your Gemfile and run bundle install"
|
499
|
+
raise e
|
500
|
+
end
|
442
501
|
end
|
443
502
|
end
|
data/lib/pusher/version.rb
CHANGED
data/pusher.gemspec
CHANGED
@@ -9,22 +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 "addressable", "
|
25
|
-
s.add_development_dependency "rake", "~>
|
26
|
-
s.add_development_dependency "rack", "~>
|
27
|
-
s.add_development_dependency "json", "~>
|
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"
|
28
29
|
|
29
30
|
s.files = `git ls-files`.split("\n")
|
30
31
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
data/spec/channel_spec.rb
CHANGED
@@ -167,4 +167,23 @@ describe Pusher::Channel do
|
|
167
167
|
}.to raise_error Pusher::Error
|
168
168
|
end
|
169
169
|
end
|
170
|
+
|
171
|
+
describe `#shared_secret` do
|
172
|
+
before(:each) do
|
173
|
+
@channel.instance_variable_set(:@name, 'private-encrypted-1')
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'should return a shared_secret based on the channel name and encryption master key' do
|
177
|
+
key = '3W1pfB/Etr+ZIlfMWwZP3gz8jEeCt4s2pe6Vpr+2c3M='
|
178
|
+
shared_secret = @channel.shared_secret(key)
|
179
|
+
expect(Base64.strict_encode64(shared_secret)).to eq(
|
180
|
+
"6zeEp/chneRPS1cbK/hGeG860UhHomxSN6hTgzwT20I="
|
181
|
+
)
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'should return nil if missing encryption master key' do
|
185
|
+
shared_secret = @channel.shared_secret(nil)
|
186
|
+
expect(shared_secret).to be_nil
|
187
|
+
end
|
188
|
+
end
|
170
189
|
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
|
@@ -171,11 +176,22 @@ describe Pusher do
|
|
171
176
|
end
|
172
177
|
end
|
173
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.strict_encode64(encryption_master_key)
|
183
|
+
|
184
|
+
expect(@client.encryption_master_key).to eq(encryption_master_key)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
174
188
|
describe 'when configured' do
|
175
189
|
before :each do
|
176
190
|
@client.app_id = '20'
|
177
191
|
@client.key = '12345678900000001'
|
178
192
|
@client.secret = '12345678900000001'
|
193
|
+
@client.encryption_master_key_base64 =
|
194
|
+
Base64.strict_encode64(encryption_master_key)
|
179
195
|
end
|
180
196
|
|
181
197
|
describe '#[]' do
|
@@ -260,6 +276,19 @@ describe Pusher do
|
|
260
276
|
})
|
261
277
|
end
|
262
278
|
|
279
|
+
it 'should include a shared_secret if the private-encrypted channel' do
|
280
|
+
allow(MultiJson).to receive(:encode).with(@custom_data).and_return 'a json string'
|
281
|
+
@client.instance_variable_set(:@encryption_master_key, '3W1pfB/Etr+ZIlfMWwZP3gz8jEeCt4s2pe6Vpr+2c3M=')
|
282
|
+
|
283
|
+
response = @client.authenticate('private-encrypted-test_channel', '1.1', @custom_data)
|
284
|
+
|
285
|
+
expect(response).to eq({
|
286
|
+
:auth => "12345678900000001:#{hmac(@client.secret, "1.1:private-encrypted-test_channel:a json string")}",
|
287
|
+
:shared_secret => "o0L3QnIovCeRC8KTD8KBRlmi31dGzHVS2M93uryqDdw=",
|
288
|
+
:channel_data => 'a json string'
|
289
|
+
})
|
290
|
+
end
|
291
|
+
|
263
292
|
end
|
264
293
|
|
265
294
|
describe '#trigger' do
|
@@ -321,6 +350,46 @@ describe Pusher do
|
|
321
350
|
}
|
322
351
|
end
|
323
352
|
end
|
353
|
+
|
354
|
+
it "should fail to publish to encrypted channels when missing key" do
|
355
|
+
@client.encryption_master_key_base64 = nil
|
356
|
+
expect {
|
357
|
+
@client.trigger('private-encrypted-channel', 'event', {'some' => 'data'})
|
358
|
+
}.to raise_error(Pusher::ConfigurationError)
|
359
|
+
expect(WebMock).not_to have_requested(:post, @api_path)
|
360
|
+
end
|
361
|
+
|
362
|
+
it "should fail to publish to multiple channels if one is encrypted" do
|
363
|
+
expect {
|
364
|
+
@client.trigger(
|
365
|
+
['private-encrypted-channel', 'some-other-channel'],
|
366
|
+
'event',
|
367
|
+
{'some' => 'data'},
|
368
|
+
)
|
369
|
+
}.to raise_error(Pusher::Error)
|
370
|
+
expect(WebMock).not_to have_requested(:post, @api_path)
|
371
|
+
end
|
372
|
+
|
373
|
+
it "should encrypt publishes to encrypted channels" do
|
374
|
+
@client.trigger(
|
375
|
+
'private-encrypted-channel',
|
376
|
+
'event',
|
377
|
+
{'some' => 'data'},
|
378
|
+
)
|
379
|
+
|
380
|
+
expect(WebMock).to have_requested(:post, @api_path).with { |req|
|
381
|
+
data = MultiJson.decode(MultiJson.decode(req.body)["data"])
|
382
|
+
|
383
|
+
key = RbNaCl::Hash.sha256(
|
384
|
+
'private-encrypted-channel' + encryption_master_key
|
385
|
+
)
|
386
|
+
|
387
|
+
expect(MultiJson.decode(RbNaCl::SecretBox.new(key).decrypt(
|
388
|
+
Base64.strict_decode64(data["nonce"]),
|
389
|
+
Base64.strict_decode64(data["ciphertext"]),
|
390
|
+
))).to eq({ 'some' => 'data' })
|
391
|
+
}
|
392
|
+
end
|
324
393
|
end
|
325
394
|
|
326
395
|
describe '#trigger_batch' do
|
@@ -352,6 +421,55 @@ describe Pusher do
|
|
352
421
|
)
|
353
422
|
}
|
354
423
|
end
|
424
|
+
|
425
|
+
it "should fail to publish to encrypted channels when missing key" do
|
426
|
+
@client.encryption_master_key_base64 = nil
|
427
|
+
expect {
|
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
|
+
}.to raise_error(Pusher::ConfigurationError)
|
437
|
+
expect(WebMock).not_to have_requested(:post, @api_path)
|
438
|
+
end
|
439
|
+
|
440
|
+
it "should encrypt publishes to encrypted channels" do
|
441
|
+
@client.trigger_batch(
|
442
|
+
{
|
443
|
+
channel: 'private-encrypted-channel',
|
444
|
+
name: 'event',
|
445
|
+
data: {'some' => 'data'},
|
446
|
+
},
|
447
|
+
{channel: 'mychannel', name: 'event', data: 'already encoded'},
|
448
|
+
)
|
449
|
+
|
450
|
+
expect(WebMock).to have_requested(:post, @api_path).with { |req|
|
451
|
+
batch = MultiJson.decode(req.body)["batch"]
|
452
|
+
expect(batch.length).to eq(2)
|
453
|
+
|
454
|
+
expect(batch[0]["channel"]).to eq("private-encrypted-channel")
|
455
|
+
expect(batch[0]["name"]).to eq("event")
|
456
|
+
|
457
|
+
data = MultiJson.decode(batch[0]["data"])
|
458
|
+
|
459
|
+
key = RbNaCl::Hash.sha256(
|
460
|
+
'private-encrypted-channel' + encryption_master_key
|
461
|
+
)
|
462
|
+
|
463
|
+
expect(MultiJson.decode(RbNaCl::SecretBox.new(key).decrypt(
|
464
|
+
Base64.strict_decode64(data["nonce"]),
|
465
|
+
Base64.strict_decode64(data["ciphertext"]),
|
466
|
+
))).to eq({ 'some' => 'data' })
|
467
|
+
|
468
|
+
expect(batch[1]["channel"]).to eq("mychannel")
|
469
|
+
expect(batch[1]["name"]).to eq("event")
|
470
|
+
expect(batch[1]["data"]).to eq("already encoded")
|
471
|
+
}
|
472
|
+
end
|
355
473
|
end
|
356
474
|
|
357
475
|
describe '#trigger_async' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pusher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3
|
4
|
+
version: 1.4.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pusher
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-10-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: multi_json
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.15'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
26
|
+
version: '1.15'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: pusher-signature
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,113 +44,127 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '2.
|
47
|
+
version: '2.8'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '2.
|
54
|
+
version: '2.8'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '3.
|
61
|
+
version: '3.9'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '3.
|
68
|
+
version: '3.9'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: webmock
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '3.9'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '3.9'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: em-http-request
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 1.1
|
89
|
+
version: '1.1'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: 1.1
|
96
|
+
version: '1.1'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: addressable
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- -
|
101
|
+
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: 2.
|
103
|
+
version: '2.7'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- -
|
108
|
+
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: 2.
|
110
|
+
version: '2.7'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: rake
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version:
|
117
|
+
version: '13.0'
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version:
|
124
|
+
version: '13.0'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: rack
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
129
|
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version:
|
131
|
+
version: '2.2'
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
136
|
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version:
|
138
|
+
version: '2.2'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: json
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
143
|
- - "~>"
|
144
144
|
- !ruby/object:Gem::Version
|
145
|
-
version:
|
145
|
+
version: '2.3'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '2.3'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rbnacl
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '7.1'
|
146
160
|
type: :development
|
147
161
|
prerelease: false
|
148
162
|
version_requirements: !ruby/object:Gem::Requirement
|
149
163
|
requirements:
|
150
164
|
- - "~>"
|
151
165
|
- !ruby/object:Gem::Version
|
152
|
-
version: 1
|
153
|
-
description: Wrapper for
|
166
|
+
version: '7.1'
|
167
|
+
description: 'Wrapper for Pusher Channels REST api: : https://pusher.com/channels'
|
154
168
|
email:
|
155
169
|
- support@pusher.com
|
156
170
|
executables: []
|
@@ -159,6 +173,7 @@ extra_rdoc_files: []
|
|
159
173
|
files:
|
160
174
|
- ".document"
|
161
175
|
- ".gemtest"
|
176
|
+
- ".github/stale.yml"
|
162
177
|
- ".gitignore"
|
163
178
|
- ".travis.yml"
|
164
179
|
- CHANGELOG.md
|
@@ -167,6 +182,8 @@ files:
|
|
167
182
|
- README.md
|
168
183
|
- Rakefile
|
169
184
|
- examples/async_message.rb
|
185
|
+
- examples/presence_channels/presence_channels.rb
|
186
|
+
- examples/presence_channels/public/presence_channels.html
|
170
187
|
- lib/pusher.rb
|
171
188
|
- lib/pusher/channel.rb
|
172
189
|
- lib/pusher/client.rb
|
@@ -199,11 +216,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
199
216
|
- !ruby/object:Gem::Version
|
200
217
|
version: '0'
|
201
218
|
requirements: []
|
202
|
-
|
203
|
-
rubygems_version: 2.6.11
|
219
|
+
rubygems_version: 3.1.2
|
204
220
|
signing_key:
|
205
221
|
specification_version: 4
|
206
|
-
summary: Pusher API client
|
222
|
+
summary: Pusher Channels API client
|
207
223
|
test_files:
|
208
224
|
- spec/channel_spec.rb
|
209
225
|
- spec/client_spec.rb
|