cent 0.0.1 → 2.0.0
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/.github/workflows/main.yml +18 -0
- data/.github/workflows/release.yml +33 -0
- data/.gitignore +20 -0
- data/.rspec +3 -0
- data/.rubocop.yml +11 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +21 -0
- data/README.md +282 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/cent.gemspec +36 -0
- data/lib/cent.rb +10 -0
- data/lib/cent/client.rb +273 -0
- data/lib/cent/error.rb +9 -0
- data/lib/cent/http.rb +47 -0
- data/lib/cent/notary.rb +96 -0
- data/lib/cent/version.rb +5 -0
- metadata +58 -77
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ccda06fa2a0da286f06e74761175bb6e822930267d8bd6edf0058359c8a8efe4
|
4
|
+
data.tar.gz: 7a4f9398de445b0853edbdbb464339692af7a35e3777d1b71240159073f39134
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b53b22a81d6d8ef2f9cb02af14ed35675713e77a040be770e07f6f5f1c166eed649fa0b5937e0424a877067b7ffdc31a4e0e22010eb00e85536b86f02cf3830
|
7
|
+
data.tar.gz: 21d2c90b9e905d40611f6ba0010042a8ffbef7bf13ed13abce683b003a5bfcddc3bf52db0b34d5d72c5e050855041f563b487e85ec4071c332be1362659033b4
|
@@ -0,0 +1,18 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
steps:
|
9
|
+
- uses: actions/checkout@v2
|
10
|
+
- name: Set up Ruby
|
11
|
+
uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
ruby-version: 2.7.1
|
14
|
+
- name: Run the default task
|
15
|
+
run: |
|
16
|
+
gem install bundler -v 2.2.10
|
17
|
+
bundle install
|
18
|
+
bundle exec rake
|
@@ -0,0 +1,33 @@
|
|
1
|
+
name: Release Ruby Gem
|
2
|
+
|
3
|
+
on:
|
4
|
+
# Manually publish
|
5
|
+
workflow_dispatch:
|
6
|
+
jobs:
|
7
|
+
build-and-release:
|
8
|
+
name: Release
|
9
|
+
runs-on: ubuntu-latest
|
10
|
+
permissions:
|
11
|
+
packages: write
|
12
|
+
contents: read
|
13
|
+
|
14
|
+
steps:
|
15
|
+
- name: Checkout
|
16
|
+
uses: actions/checkout@v2
|
17
|
+
|
18
|
+
- name: Set up Ruby 2.6
|
19
|
+
uses: ruby/setup-ruby@477b21f02be01bcb8030d50f37cfec92bfa615b6
|
20
|
+
with:
|
21
|
+
ruby-version: 2.6
|
22
|
+
- run: bundle install
|
23
|
+
|
24
|
+
- name: Publish to RubyGems
|
25
|
+
run: |
|
26
|
+
mkdir -p $HOME/.gem
|
27
|
+
touch $HOME/.gem/credentials
|
28
|
+
chmod 0600 $HOME/.gem/credentials
|
29
|
+
printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
30
|
+
gem build *.gemspec
|
31
|
+
gem push *.gem
|
32
|
+
env:
|
33
|
+
GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_API_KEY}}"
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in cent.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem 'rake', '~> 13.0'
|
9
|
+
|
10
|
+
gem 'rspec', '~> 3.0'
|
11
|
+
gem 'webmock', '~> 3.7.5'
|
12
|
+
|
13
|
+
gem 'rubocop', '~> 1.7'
|
14
|
+
gem 'rubocop-rake'
|
15
|
+
gem 'rubocop-rspec'
|
16
|
+
|
17
|
+
gem 'pry'
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Centrifugal
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,282 @@
|
|
1
|
+
# Cent
|
2
|
+
[](https://codeclimate.com/github/centrifugal/centrifuge-ruby)
|
3
|
+

|
4
|
+
|
5
|
+
[Centrifugo HTTP API v2](https://centrifugal.github.io/centrifugo/server/http_api/) client in Ruby.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'cent'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install cent
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
Functionality is split between two classes:
|
26
|
+
- `Cent::Client` to call API methods
|
27
|
+
- `Cent::Notary` to generate tokens
|
28
|
+
|
29
|
+
### Token Generation
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
notary = Cent::Notary.new(secret: 'secret')
|
33
|
+
```
|
34
|
+
|
35
|
+
By default it uses HS256 to generate tokens, but you can set it to one of the HMAC, RSA or ECDSA family.
|
36
|
+
|
37
|
+
#### RSA
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
secret = OpenSSL::PKey::RSA.new(File.read('./rsa_secret.pem'))
|
41
|
+
notary = Cent::Notary.new(secret: secret, algorithm: 'RS256')
|
42
|
+
```
|
43
|
+
|
44
|
+
#### ECDSA
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
secret = OpenSSL::PKey::EC.new(File.read('./ecdsa_secret.pem'))
|
48
|
+
notary = Cent::Notary.new(secret: secret, algorithm: 'ES256')
|
49
|
+
```
|
50
|
+
|
51
|
+
#### Connection token
|
52
|
+
|
53
|
+
When connecting to Centrifugo client [must provide connection JWT token](https://centrifugal.github.io/centrifugo/server/authentication/) with several predefined credential claims.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
notary.issue_connection_token(sub: '42')
|
57
|
+
|
58
|
+
#=> "eyJhbGciOiJIUzI1NiJ9..."
|
59
|
+
```
|
60
|
+
|
61
|
+
`info` and `exp` are supported as well:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
notary.issue_connection_token(sub: '42', info: { scope: 'admin' }, exp: 1629050099)
|
65
|
+
|
66
|
+
#=> "eyJhbGciOiJIUzI1NiJ9..."
|
67
|
+
```
|
68
|
+
|
69
|
+
### Private channel token
|
70
|
+
|
71
|
+
All channels starting with $ considered private and require a **channel token** to subscribe.
|
72
|
+
Private channel subscription token is also JWT([see the claims](https://centrifugal.github.io/centrifugo/server/private_channels/))
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
notary.issue_channel_token(client: 'client', channel: 'channel', exp: 1629050099, info: { scope: 'admin' })
|
76
|
+
|
77
|
+
#=> "eyJhbGciOiJIUzI1NiJ9..."
|
78
|
+
```
|
79
|
+
|
80
|
+
### API Client
|
81
|
+
|
82
|
+
A client requires your Centrifugo API key to execute all requests.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
client = Cent::Client.new(api_key: 'key')
|
86
|
+
```
|
87
|
+
|
88
|
+
you can customize your connection as you wish, just remember it's a [Faraday::Connection](https://lostisland.github.io/faraday/usage/#customizing-faradayconnection) instance:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
client = Cent::Client.new(api_key: 'key', endpoint: 'https://centrifu.go/api') do |connection|
|
92
|
+
connection.headers['User-Agent'] = 'Centrifugo Ruby Client'
|
93
|
+
connection.options.open_timeout = 3
|
94
|
+
connection.options.timeout = 7
|
95
|
+
connection.adapter :typhoeus
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
#### Publish
|
100
|
+
|
101
|
+
Send data to the channel.
|
102
|
+
|
103
|
+
[https://centrifugal.github.io/centrifugo/server/http_api/#publish](https://centrifugal.github.io/centrifugo/server/http_api/#publish)
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
client.publish(channel: 'chat', data: 'hello') # => {}
|
107
|
+
```
|
108
|
+
|
109
|
+
#### Broadcast
|
110
|
+
|
111
|
+
Sends data to multiple channels.
|
112
|
+
|
113
|
+
[https://centrifugal.github.io/centrifugo/server/http_api/#broadcast](https://centrifugal.github.io/centrifugo/server/http_api/#broadcast)
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
client.broadcast(channels: ["clients", "staff"], data: 'hello') # => {}
|
117
|
+
```
|
118
|
+
|
119
|
+
#### Unsubscribe
|
120
|
+
|
121
|
+
Unsubscribe user from channel. Receives to arguments: channel and user (user ID you want to unsubscribe)
|
122
|
+
|
123
|
+
[https://centrifugal.github.io/centrifugo/server/http_api/#unsubscribe](https://centrifugal.github.io/centrifugo/server/http_api/#unsubscribe)
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
client.unsubscribe(channel: 'chat', user: '1') # => {}
|
127
|
+
```
|
128
|
+
|
129
|
+
#### Disconnect
|
130
|
+
|
131
|
+
Allows to disconnect user by it's ID. Receives user ID as an argument.
|
132
|
+
|
133
|
+
[https://centrifugal.github.io/centrifugo/server/http_api/#disconnect](https://centrifugal.github.io/centrifugo/server/http_api/#disconnect)
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
# Disconnect user with `id = 1`
|
137
|
+
#
|
138
|
+
client.disconnect(user: '1') # => {}
|
139
|
+
```
|
140
|
+
|
141
|
+
#### Presence
|
142
|
+
|
143
|
+
Get channel presence information(all clients currently subscribed on this channel).
|
144
|
+
|
145
|
+
[https://centrifugal.github.io/centrifugo/server/http_api/#presence](https://centrifugal.github.io/centrifugo/server/http_api/#presence)
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
client.presence(channel: 'chat')
|
149
|
+
|
150
|
+
# {
|
151
|
+
# 'result' => {
|
152
|
+
# 'presence' => {
|
153
|
+
# 'c54313b2-0442-499a-a70c-051f8588020f' => {
|
154
|
+
# 'client' => 'c54313b2-0442-499a-a70c-051f8588020f',
|
155
|
+
# 'user' => '42'
|
156
|
+
# },
|
157
|
+
# 'adad13b1-0442-499a-a70c-051f858802da' => {
|
158
|
+
# 'client' => 'adad13b1-0442-499a-a70c-051f858802da',
|
159
|
+
# 'user' => '42'
|
160
|
+
# }
|
161
|
+
# }
|
162
|
+
# }
|
163
|
+
# }
|
164
|
+
```
|
165
|
+
|
166
|
+
#### Presence stats
|
167
|
+
|
168
|
+
Get short channel presence information.
|
169
|
+
|
170
|
+
[https://centrifugal.github.io/centrifugo/server/http_api/#presence_stats](https://centrifugal.github.io/centrifugo/server/http_api/#precence_stats)
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
client.presence_stats(channel: 'chat')
|
174
|
+
|
175
|
+
# {
|
176
|
+
# "result" => {
|
177
|
+
# "num_clients" => 0,
|
178
|
+
# "num_users" => 0
|
179
|
+
# }
|
180
|
+
# }
|
181
|
+
```
|
182
|
+
|
183
|
+
#### History
|
184
|
+
|
185
|
+
Get channel history information (list of last messages published into channel).
|
186
|
+
|
187
|
+
[https://centrifugal.github.io/centrifugo/server/http_api/#history](https://centrifugal.github.io/centrifugo/server/http_api/#hisotry)
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
client.history(channel: 'chat')
|
191
|
+
|
192
|
+
# {
|
193
|
+
# 'result' => {
|
194
|
+
# 'publications' => [
|
195
|
+
# {
|
196
|
+
# 'data' => {
|
197
|
+
# 'text' => 'hello'
|
198
|
+
# }
|
199
|
+
# },
|
200
|
+
# {
|
201
|
+
# 'data' => {
|
202
|
+
# 'text' => 'hi!'
|
203
|
+
# }
|
204
|
+
# }
|
205
|
+
# ]
|
206
|
+
# }
|
207
|
+
# }
|
208
|
+
```
|
209
|
+
|
210
|
+
#### Channels
|
211
|
+
|
212
|
+
Get list of active(with one or more subscribers) channels.
|
213
|
+
|
214
|
+
[https://centrifugal.github.io/centrifugo/server/http_api/#channels](https://centrifugal.github.io/centrifugo/server/http_api/#channels)
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
client.channels
|
218
|
+
|
219
|
+
# {
|
220
|
+
# 'result' => {
|
221
|
+
# 'channels' => [
|
222
|
+
# 'chat'
|
223
|
+
# ]
|
224
|
+
# }
|
225
|
+
# }
|
226
|
+
```
|
227
|
+
|
228
|
+
#### Info
|
229
|
+
|
230
|
+
Get running Centrifugo nodes information.
|
231
|
+
|
232
|
+
[https://centrifugal.github.io/centrifugo/server/http_api/#info](https://centrifugal.github.io/centrifugo/server/http_api/#info)
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
client.info
|
236
|
+
|
237
|
+
# {
|
238
|
+
# 'result' => {
|
239
|
+
# 'nodes' => [
|
240
|
+
# {
|
241
|
+
# 'name' => 'Alexanders-MacBook-Pro.local_8000',
|
242
|
+
# 'num_channels' => 0,
|
243
|
+
# 'num_clients' => 0,
|
244
|
+
# 'num_users' => 0,
|
245
|
+
# 'uid' => 'f844a2ed-5edf-4815-b83c-271974003db9',
|
246
|
+
# 'uptime' => 0,
|
247
|
+
# 'version' => ''
|
248
|
+
# }
|
249
|
+
# ]
|
250
|
+
# }
|
251
|
+
# }
|
252
|
+
```
|
253
|
+
|
254
|
+
### Errors
|
255
|
+
|
256
|
+
Network errors are not wrapped and will raise `Faraday::ClientError`.
|
257
|
+
|
258
|
+
In cases when Centrifugo returns 200 with `error` key in the body we wrap it and return custom error:
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
# Raised when response from Centrifugo contains any error as result of API command execution.
|
262
|
+
#
|
263
|
+
begin
|
264
|
+
client.publish(channel: 'channel', data: { foo: :bar })
|
265
|
+
rescue Cent::ResponseError => ex
|
266
|
+
ex.message # => "Invalid format"
|
267
|
+
end
|
268
|
+
```
|
269
|
+
|
270
|
+
## Development
|
271
|
+
|
272
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
273
|
+
|
274
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
275
|
+
|
276
|
+
## Contributing
|
277
|
+
|
278
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/centrifugal/rubycent. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
279
|
+
|
280
|
+
## License
|
281
|
+
|
282
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'cent'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'pry'
|
15
|
+
Pry.start(__FILE__)
|
data/bin/setup
ADDED
data/cent.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/cent/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'cent'
|
7
|
+
spec.version = Cent::VERSION
|
8
|
+
spec.authors = ['Sergey Prikhodko']
|
9
|
+
spec.email = ['prikha@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'Centrifugo API V2 Ruby Client'
|
12
|
+
spec.description = <<~DESC
|
13
|
+
Provides helper classes Cent::Client and Cent::Notary.
|
14
|
+
|
15
|
+
`Cent::Client` is made to communicate to the server API
|
16
|
+
`Client::Notary` is a simple JWT wrapper to generate authorization tokens for the frontend
|
17
|
+
DESC
|
18
|
+
spec.homepage = 'https://github.com/centrifugal/rubycent'
|
19
|
+
spec.license = 'MIT'
|
20
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
|
21
|
+
|
22
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
23
|
+
spec.metadata['source_code_uri'] = 'https://github.com/centrifugal/rubycent'
|
24
|
+
|
25
|
+
# Specify which files should be added to the gem when it is released.
|
26
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
27
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
28
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:spec|Gemfile)/}) }
|
29
|
+
end
|
30
|
+
|
31
|
+
spec.require_paths = ['lib']
|
32
|
+
|
33
|
+
spec.add_dependency 'faraday', '<2.0.0', '~> 1.7.0'
|
34
|
+
spec.add_dependency 'faraday_middleware', '<2.0.0', '~> 1.0'
|
35
|
+
spec.add_dependency 'jwt', '~> 2.2.1'
|
36
|
+
end
|
data/lib/cent.rb
ADDED
data/lib/cent/client.rb
ADDED
@@ -0,0 +1,273 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
require 'faraday_middleware'
|
5
|
+
require 'cent/http'
|
6
|
+
|
7
|
+
module Cent
|
8
|
+
# Cent::Client
|
9
|
+
#
|
10
|
+
# Main object that handles configuration and requests to centrifugo API
|
11
|
+
#
|
12
|
+
class Client
|
13
|
+
# @param endpoint [String]
|
14
|
+
# (default: 'http://localhost:8000/api') Centrifugo HTTP API URL
|
15
|
+
#
|
16
|
+
# @param api_key [String]
|
17
|
+
# Centrifugo API key(used to perform requests)
|
18
|
+
#
|
19
|
+
# @yield [Faraday::Connection] yields connection object so that it can be configured
|
20
|
+
#
|
21
|
+
# @example Construct new client instance
|
22
|
+
# Cent::Client.new(
|
23
|
+
# endpoint: 'http://localhost:8000/api',
|
24
|
+
# api_key: 'api key'
|
25
|
+
# )
|
26
|
+
#
|
27
|
+
def initialize(api_key:, endpoint: 'http://localhost:8000/api')
|
28
|
+
headers = {
|
29
|
+
'Content-Type' => 'application/json',
|
30
|
+
'Authorization' => "apikey #{api_key}"
|
31
|
+
}
|
32
|
+
|
33
|
+
@connection = Faraday.new(endpoint, headers: headers) do |conn|
|
34
|
+
conn.request :json # encode req bodies as JSON
|
35
|
+
|
36
|
+
conn.response :json # decode response bodies as JSON
|
37
|
+
conn.response :raise_error
|
38
|
+
|
39
|
+
yield conn if block_given?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Publish data into channel
|
44
|
+
#
|
45
|
+
# @param channel [String]
|
46
|
+
# Name of the channel to publish
|
47
|
+
#
|
48
|
+
# @param data [Hash]
|
49
|
+
# Data for publication in the channel
|
50
|
+
#
|
51
|
+
# @example Publish `content: 'hello'` into `chat` channel
|
52
|
+
# client.publish(channel: 'chat', data: 'hello') #=> {}
|
53
|
+
#
|
54
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#publish)
|
55
|
+
#
|
56
|
+
# @raise [Cent::Error, Cent::ResponseError]
|
57
|
+
#
|
58
|
+
# @return [Hash] Return empty hash in case of successful publish
|
59
|
+
#
|
60
|
+
def publish(channel:, data:)
|
61
|
+
execute('publish', channel: channel, data: data)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Publish data into multiple channels
|
65
|
+
# (Similar to `#publish` but allows to send the same data into many channels)
|
66
|
+
#
|
67
|
+
# @param channels [Array<String>] Collection of channels names to publish
|
68
|
+
# @param data [Hash] Data for publication in the channels
|
69
|
+
#
|
70
|
+
# @example Broadcast `content: 'hello'` into `channel_1`, 'channel_2' channels
|
71
|
+
# client.broadcast(channels: ['channel_1', 'channel_2'], data: 'hello') #=> {}
|
72
|
+
#
|
73
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#broadcast)
|
74
|
+
#
|
75
|
+
# @raise [Cent::Error, Cent::ResponseError]
|
76
|
+
#
|
77
|
+
# @return [Hash] Return empty hash in case of successful broadcast
|
78
|
+
#
|
79
|
+
def broadcast(channels:, data:)
|
80
|
+
execute('broadcast', channels: channels, data: data)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Unsubscribe user from channel
|
84
|
+
#
|
85
|
+
# @param channel [String]
|
86
|
+
# Channel name to unsubscribe from
|
87
|
+
#
|
88
|
+
# @param user [String, Integer]
|
89
|
+
# User ID you want to unsubscribe
|
90
|
+
#
|
91
|
+
# @example Unsubscribe user with `id = 1` from `chat` channel
|
92
|
+
# client.unsubscribe(channel: 'chat', user: '1') #=> {}
|
93
|
+
#
|
94
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#unsubscribe)
|
95
|
+
#
|
96
|
+
# @raise [Cent::Error, Cent::ResponseError]
|
97
|
+
#
|
98
|
+
# @return [Hash] Return empty hash in case of successful unsubscribe
|
99
|
+
#
|
100
|
+
def unsubscribe(channel:, user:)
|
101
|
+
execute('unsubscribe', channel: channel, user: user)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Disconnect user by it's ID
|
105
|
+
#
|
106
|
+
# @param user [String, Integer]
|
107
|
+
# User ID you want to disconnect
|
108
|
+
#
|
109
|
+
# @example Disconnect user with `id = 1`
|
110
|
+
# client.disconnect(user: '1') #=> {}
|
111
|
+
#
|
112
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#disconnect)
|
113
|
+
#
|
114
|
+
# @raise [Cent::Error, Cent::ResponseError]
|
115
|
+
#
|
116
|
+
# @return [Hash] Return empty hash in case of successful disconnect
|
117
|
+
#
|
118
|
+
def disconnect(user:)
|
119
|
+
execute('disconnect', user: user)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Get channel presence information
|
123
|
+
# (all clients currently subscribed on this channel)
|
124
|
+
#
|
125
|
+
# @param channel [String] Name of the channel
|
126
|
+
#
|
127
|
+
# @example Get presence information for channel `chat`
|
128
|
+
# client.presence(channel: 'chat') #=> {
|
129
|
+
# "result" => {
|
130
|
+
# "presence" => {
|
131
|
+
# "c54313b2-0442-499a-a70c-051f8588020f" => {
|
132
|
+
# "client" => "c54313b2-0442-499a-a70c-051f8588020f",
|
133
|
+
# "user" => "42"
|
134
|
+
# },
|
135
|
+
# "adad13b1-0442-499a-a70c-051f858802da" => {
|
136
|
+
# "client" => "adad13b1-0442-499a-a70c-051f858802da",
|
137
|
+
# "user" => "42"
|
138
|
+
# }
|
139
|
+
# }
|
140
|
+
# }
|
141
|
+
# }
|
142
|
+
#
|
143
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#presence)
|
144
|
+
#
|
145
|
+
# @raise [Cent::Error, Cent::ResponseError]
|
146
|
+
#
|
147
|
+
# @return [Hash]
|
148
|
+
# Return hash with information about all clients currently subscribed on this channel
|
149
|
+
#
|
150
|
+
def presence(channel:)
|
151
|
+
execute('presence', channel: channel)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Get short channel presence information
|
155
|
+
#
|
156
|
+
# @param channel [String] Name of the channel
|
157
|
+
#
|
158
|
+
# @example Get short presence information for channel `chat`
|
159
|
+
# client.presence_stats(channel: 'chat') #=> {
|
160
|
+
# "result" => {
|
161
|
+
# "num_clients" => 0,
|
162
|
+
# "num_users" => 0
|
163
|
+
# }
|
164
|
+
# }
|
165
|
+
#
|
166
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#presence_stats)
|
167
|
+
#
|
168
|
+
# @raise [Cent::Error, Cent::ResponseError]
|
169
|
+
#
|
170
|
+
# @return [Hash]
|
171
|
+
# Return hash with short presence information about channel
|
172
|
+
#
|
173
|
+
def presence_stats(channel:)
|
174
|
+
execute('presence_stats', channel: channel)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Get channel history information
|
178
|
+
# (list of last messages published into channel)
|
179
|
+
#
|
180
|
+
# @param channel [String] Name of the channel
|
181
|
+
#
|
182
|
+
# @example Get history for channel `chat`
|
183
|
+
# client.history(channel: 'chat') #=> {
|
184
|
+
# "result" => {
|
185
|
+
# "publications" => [
|
186
|
+
# {
|
187
|
+
# "data" => {
|
188
|
+
# "text" => "hello"
|
189
|
+
# },
|
190
|
+
# "uid" => "BWcn14OTBrqUhTXyjNg0fg"
|
191
|
+
# },
|
192
|
+
# {
|
193
|
+
# "data" => {
|
194
|
+
# "text" => "hi!"
|
195
|
+
# },
|
196
|
+
# "uid" => "Ascn14OTBrq14OXyjNg0hg"
|
197
|
+
# }
|
198
|
+
# ]
|
199
|
+
# }
|
200
|
+
# }
|
201
|
+
#
|
202
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#history)
|
203
|
+
#
|
204
|
+
# @raise [Cent::Error, Cent::ResponseError]
|
205
|
+
#
|
206
|
+
# @return [Hash]
|
207
|
+
# Return hash with a list of last messages published into channel
|
208
|
+
#
|
209
|
+
def history(channel:)
|
210
|
+
execute('history', channel: channel)
|
211
|
+
end
|
212
|
+
|
213
|
+
# Get list of active(with one or more subscribers) channels.
|
214
|
+
#
|
215
|
+
# @example Get active channels list
|
216
|
+
# client.channels #=> {
|
217
|
+
# "result" => {
|
218
|
+
# "channels" => [
|
219
|
+
# "chat"
|
220
|
+
# ]
|
221
|
+
# }
|
222
|
+
# }
|
223
|
+
#
|
224
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#channels)
|
225
|
+
#
|
226
|
+
# @raise [Cent::Error, Cent::ResponseError]
|
227
|
+
#
|
228
|
+
# @return [Hash]
|
229
|
+
# Return hash with a list of active channels
|
230
|
+
#
|
231
|
+
def channels
|
232
|
+
execute('channels', {})
|
233
|
+
end
|
234
|
+
|
235
|
+
# Get information about running Centrifugo nodes
|
236
|
+
#
|
237
|
+
# @example Get running centrifugo nodes list
|
238
|
+
# client.info #=> {
|
239
|
+
# "result" => {
|
240
|
+
# "nodes" => [
|
241
|
+
# {
|
242
|
+
# "name" => "Alexanders-MacBook-Pro.local_8000",
|
243
|
+
# "num_channels" => 0,
|
244
|
+
# "num_clients" => 0,
|
245
|
+
# "num_users" => 0,
|
246
|
+
# "uid" => "f844a2ed-5edf-4815-b83c-271974003db9",
|
247
|
+
# "uptime" => 0,
|
248
|
+
# "version" => ""
|
249
|
+
# }
|
250
|
+
# ]
|
251
|
+
# }
|
252
|
+
# }
|
253
|
+
#
|
254
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#info)
|
255
|
+
#
|
256
|
+
# @raise [Cent::Error, Cent::ResponseError]
|
257
|
+
#
|
258
|
+
# @return [Hash]
|
259
|
+
# Return hash with a list of last messages published into channel
|
260
|
+
#
|
261
|
+
def info
|
262
|
+
execute('info', {})
|
263
|
+
end
|
264
|
+
|
265
|
+
private
|
266
|
+
|
267
|
+
def execute(method, data)
|
268
|
+
body = { method: method, params: data }
|
269
|
+
|
270
|
+
Cent::HTTP.new(connection: @connection).post(body: body)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
data/lib/cent/error.rb
ADDED
data/lib/cent/http.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cent/error'
|
4
|
+
|
5
|
+
module Cent
|
6
|
+
# Cent::ResponseError
|
7
|
+
#
|
8
|
+
# Raised when response from Centrifugo contains any error as result of API command execution.
|
9
|
+
#
|
10
|
+
class ResponseError < Error
|
11
|
+
attr_reader :code
|
12
|
+
|
13
|
+
def initialize(code:, message:)
|
14
|
+
@code = code
|
15
|
+
super message
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Cent::HTTP
|
20
|
+
#
|
21
|
+
# Holds request call and response handling logic
|
22
|
+
#
|
23
|
+
class HTTP
|
24
|
+
attr_reader :connection
|
25
|
+
|
26
|
+
# @param connection [Faraday::Connection] HTTP Connection object
|
27
|
+
#
|
28
|
+
def initialize(connection:)
|
29
|
+
@connection = connection
|
30
|
+
end
|
31
|
+
|
32
|
+
# Perform POST request to centrifugo API
|
33
|
+
# @param body [Hash] Request body(non serialized)
|
34
|
+
#
|
35
|
+
# @raise [Cent::ResponseError]
|
36
|
+
#
|
37
|
+
# @return [Hash] Parsed response body
|
38
|
+
#
|
39
|
+
def post(body: nil)
|
40
|
+
response = connection.post(nil, body)
|
41
|
+
|
42
|
+
raise ResponseError, response.body['error'].transform_keys(&:to_sym) if response.body.key?('error')
|
43
|
+
|
44
|
+
response.body
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/cent/notary.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jwt'
|
4
|
+
require 'cent/error'
|
5
|
+
|
6
|
+
module Cent
|
7
|
+
# Cent::Notary
|
8
|
+
#
|
9
|
+
# Handle token generation
|
10
|
+
#
|
11
|
+
class Notary
|
12
|
+
# @param secret [String | OpenSSL::PKey::RSA | OpenSSL::PKey::EC] Secret key for the algorithm of your choice.
|
13
|
+
# @param algorithm [String] Specify algorithm(one from HMAC, RSA or ECDSA family). Default is HS256.
|
14
|
+
#
|
15
|
+
# @example Construct new client instance
|
16
|
+
# notary = Cent::Notary.new(secret: 'secret')
|
17
|
+
#
|
18
|
+
def initialize(secret:, algorithm: 'HS256')
|
19
|
+
raise Error, 'Secret can not be nil' if secret.nil?
|
20
|
+
|
21
|
+
@secret = secret
|
22
|
+
@algorithm = algorithm
|
23
|
+
end
|
24
|
+
|
25
|
+
# Generate connection JWT for the given user
|
26
|
+
#
|
27
|
+
# @param sub [String]
|
28
|
+
# Standard JWT claim which must contain an ID of current application user.
|
29
|
+
#
|
30
|
+
# @option channel [String]
|
31
|
+
# Channel that client tries to subscribe to (string).
|
32
|
+
#
|
33
|
+
# @param exp [Integer]
|
34
|
+
# (default: nil) UNIX timestamp seconds when token will expire.
|
35
|
+
#
|
36
|
+
# @param info [Hash]
|
37
|
+
# (default: {}) This claim is optional - this is additional information about
|
38
|
+
# client connection that can be provided for Centrifugo.
|
39
|
+
#
|
40
|
+
# @example Get user JWT with expiration and extra info
|
41
|
+
# notary.issue_connection_token(sub: '1', exp: 3600, info: { 'role' => 'admin' })
|
42
|
+
# #=> "eyJhbGciOiJIUzI1NiJ9.eyJzdWIi..."
|
43
|
+
#
|
44
|
+
# @see (https://centrifugal.github.io/centrifugo/server/authentication/)
|
45
|
+
#
|
46
|
+
# @return [String]
|
47
|
+
#
|
48
|
+
def issue_connection_token(sub:, info: nil, exp: nil)
|
49
|
+
payload = {
|
50
|
+
'sub' => sub,
|
51
|
+
'info' => info,
|
52
|
+
'exp' => exp
|
53
|
+
}.compact
|
54
|
+
|
55
|
+
JWT.encode(payload, secret, algorithm)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Generate JWT for private channels
|
59
|
+
#
|
60
|
+
# @param client [String]
|
61
|
+
# Client ID which wants to subscribe on channel
|
62
|
+
#
|
63
|
+
# @option channel [String]
|
64
|
+
# Channel that client tries to subscribe to (string).
|
65
|
+
#
|
66
|
+
# @param exp [Integer]
|
67
|
+
# (default: nil) UNIX timestamp seconds when token will expire.
|
68
|
+
#
|
69
|
+
# @param info [Hash]
|
70
|
+
# (default: {}) This claim is optional - this is additional information about
|
71
|
+
# client connection that can be provided for Centrifugo.
|
72
|
+
#
|
73
|
+
# @example Get private channel JWT with expiration and extra info
|
74
|
+
# notary.issue_channel_token(client: 'client', channel: 'channel', exp: 3600, info: { 'message' => 'wat' })
|
75
|
+
# #=> eyJhbGciOiJIUzI1NiJ9.eyJjbGllbnQiOiJjbG..."
|
76
|
+
#
|
77
|
+
# @see (https://centrifugal.github.io/centrifugo/server/private_channels/)
|
78
|
+
#
|
79
|
+
# @return [String]
|
80
|
+
#
|
81
|
+
def issue_channel_token(client:, channel:, info: nil, exp: nil)
|
82
|
+
payload = {
|
83
|
+
'client' => client,
|
84
|
+
'channel' => channel,
|
85
|
+
'info' => info,
|
86
|
+
'exp' => exp
|
87
|
+
}.compact
|
88
|
+
|
89
|
+
JWT.encode(payload, secret, algorithm)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
attr_reader :secret, :algorithm
|
95
|
+
end
|
96
|
+
end
|
data/lib/cent/version.rb
ADDED
metadata
CHANGED
@@ -1,124 +1,105 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Sergey Prikhodko
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-08-
|
11
|
+
date: 2021-08-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: faraday
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
17
|
+
- - "~>"
|
25
18
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
27
|
-
-
|
28
|
-
name: multi_json
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
19
|
+
version: 1.7.0
|
20
|
+
- - "<"
|
32
21
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
22
|
+
version: 2.0.0
|
34
23
|
type: :runtime
|
35
24
|
prerelease: false
|
36
25
|
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: bundler
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
26
|
requirements:
|
45
27
|
- - "~>"
|
46
28
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
48
|
-
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
29
|
+
version: 1.7.0
|
30
|
+
- - "<"
|
53
31
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
32
|
+
version: 2.0.0
|
55
33
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
34
|
+
name: faraday_middleware
|
57
35
|
requirement: !ruby/object:Gem::Requirement
|
58
36
|
requirements:
|
59
37
|
- - "~>"
|
60
38
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
62
|
-
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '10.0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: rspec
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
39
|
+
version: '1.0'
|
40
|
+
- - "<"
|
74
41
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
76
|
-
type: :
|
42
|
+
version: 2.0.0
|
43
|
+
type: :runtime
|
77
44
|
prerelease: false
|
78
45
|
version_requirements: !ruby/object:Gem::Requirement
|
79
46
|
requirements:
|
80
|
-
- - "
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: webmock
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - ">="
|
47
|
+
- - "~>"
|
88
48
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
90
|
-
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - ">="
|
49
|
+
version: '1.0'
|
50
|
+
- - "<"
|
95
51
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
52
|
+
version: 2.0.0
|
97
53
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
54
|
+
name: jwt
|
99
55
|
requirement: !ruby/object:Gem::Requirement
|
100
56
|
requirements:
|
101
|
-
- - "
|
57
|
+
- - "~>"
|
102
58
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
104
|
-
type: :
|
59
|
+
version: 2.2.1
|
60
|
+
type: :runtime
|
105
61
|
prerelease: false
|
106
62
|
version_requirements: !ruby/object:Gem::Requirement
|
107
63
|
requirements:
|
108
|
-
- - "
|
64
|
+
- - "~>"
|
109
65
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
111
|
-
description:
|
66
|
+
version: 2.2.1
|
67
|
+
description: |
|
68
|
+
Provides helper classes Cent::Client and Cent::Notary.
|
69
|
+
|
70
|
+
`Cent::Client` is made to communicate to the server API
|
71
|
+
`Client::Notary` is a simple JWT wrapper to generate authorization tokens for the frontend
|
112
72
|
email:
|
113
|
-
-
|
73
|
+
- prikha@gmail.com
|
114
74
|
executables: []
|
115
75
|
extensions: []
|
116
76
|
extra_rdoc_files: []
|
117
|
-
files:
|
77
|
+
files:
|
78
|
+
- ".github/workflows/main.yml"
|
79
|
+
- ".github/workflows/release.yml"
|
80
|
+
- ".gitignore"
|
81
|
+
- ".rspec"
|
82
|
+
- ".rubocop.yml"
|
83
|
+
- CHANGELOG.md
|
84
|
+
- Gemfile
|
85
|
+
- LICENSE.txt
|
86
|
+
- README.md
|
87
|
+
- Rakefile
|
88
|
+
- bin/console
|
89
|
+
- bin/setup
|
90
|
+
- cent.gemspec
|
91
|
+
- lib/cent.rb
|
92
|
+
- lib/cent/client.rb
|
93
|
+
- lib/cent/error.rb
|
94
|
+
- lib/cent/http.rb
|
95
|
+
- lib/cent/notary.rb
|
96
|
+
- lib/cent/version.rb
|
118
97
|
homepage: https://github.com/centrifugal/rubycent
|
119
98
|
licenses:
|
120
99
|
- MIT
|
121
|
-
metadata:
|
100
|
+
metadata:
|
101
|
+
homepage_uri: https://github.com/centrifugal/rubycent
|
102
|
+
source_code_uri: https://github.com/centrifugal/rubycent
|
122
103
|
post_install_message:
|
123
104
|
rdoc_options: []
|
124
105
|
require_paths:
|
@@ -127,15 +108,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
127
108
|
requirements:
|
128
109
|
- - ">="
|
129
110
|
- !ruby/object:Gem::Version
|
130
|
-
version:
|
111
|
+
version: 2.5.0
|
131
112
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
113
|
requirements:
|
133
114
|
- - ">="
|
134
115
|
- !ruby/object:Gem::Version
|
135
116
|
version: '0'
|
136
117
|
requirements: []
|
137
|
-
rubygems_version: 3.0.3
|
118
|
+
rubygems_version: 3.0.3.1
|
138
119
|
signing_key:
|
139
120
|
specification_version: 4
|
140
|
-
summary:
|
121
|
+
summary: Centrifugo API V2 Ruby Client
|
141
122
|
test_files: []
|