rubycent 0.1.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 +7 -0
- data/.gitignore +66 -0
- data/.rspec +3 -0
- data/.travis.yml +21 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +344 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/rubycent.rb +52 -0
- data/lib/rubycent/client.rb +375 -0
- data/lib/rubycent/error.rb +9 -0
- data/lib/rubycent/errors/network_error.rb +11 -0
- data/lib/rubycent/errors/request_error.rb +18 -0
- data/lib/rubycent/errors/response_error.rb +17 -0
- data/lib/rubycent/query.rb +65 -0
- data/lib/rubycent/request.rb +117 -0
- data/lib/rubycent/version.rb +5 -0
- data/rubycent.gemspec +45 -0
- metadata +163 -0
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 'rubycent'
|
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 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/rubycent.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'multi_json'
|
5
|
+
|
6
|
+
require 'rubycent/version'
|
7
|
+
|
8
|
+
require 'rubycent/client'
|
9
|
+
require 'rubycent/request'
|
10
|
+
|
11
|
+
require 'rubycent/error'
|
12
|
+
require 'rubycent/errors/network_error'
|
13
|
+
require 'rubycent/errors/request_error'
|
14
|
+
require 'rubycent/errors/response_error'
|
15
|
+
|
16
|
+
# Rubycent
|
17
|
+
#
|
18
|
+
# Entry point and configuration definition
|
19
|
+
#
|
20
|
+
module Rubycent
|
21
|
+
class << self
|
22
|
+
extend Forwardable
|
23
|
+
|
24
|
+
def_delegators :api_client, :scheme, :host, :port, :secret, :api_key
|
25
|
+
def_delegators :api_client, :scheme=, :host=, :port=, :secret=, :api_key=
|
26
|
+
|
27
|
+
def_delegators :api_client, :timeout=, :open_timeout=
|
28
|
+
|
29
|
+
def_delegators :api_client,
|
30
|
+
:publish, :broadcast,
|
31
|
+
:unsubscribe, :disconnect,
|
32
|
+
:presence, :presence_stats,
|
33
|
+
:history, :channels, :info,
|
34
|
+
:issue_user_token, :issue_channel_token
|
35
|
+
|
36
|
+
attr_writer :logger, :request_adapter
|
37
|
+
|
38
|
+
def logger
|
39
|
+
@logger ||= begin
|
40
|
+
Logger.new($stdout).tap { |log| log.level = Logger::INFO }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def request_adapter
|
45
|
+
@request_adapter ||= Faraday.default_adapter
|
46
|
+
end
|
47
|
+
|
48
|
+
def api_client
|
49
|
+
@api_client ||= Rubycent::Client.new
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,375 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubycent/query'
|
4
|
+
|
5
|
+
require 'jwt'
|
6
|
+
|
7
|
+
module Rubycent
|
8
|
+
# Rubycent::Client
|
9
|
+
#
|
10
|
+
# Main object that handles configuration and requests to centrifugo API
|
11
|
+
#
|
12
|
+
class Client
|
13
|
+
DEFAULT_OPTIONS = {
|
14
|
+
scheme: 'http',
|
15
|
+
host: 'localhost',
|
16
|
+
port: 8000
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
private_constant :DEFAULT_OPTIONS
|
20
|
+
|
21
|
+
attr_accessor :scheme, :host, :port, :secret, :api_key
|
22
|
+
|
23
|
+
attr_accessor :timeout, :open_timeout
|
24
|
+
|
25
|
+
# @param options [Hash]
|
26
|
+
# (default: {}) Parameters to configure centrifugo client
|
27
|
+
#
|
28
|
+
# @option options [String] :scheme
|
29
|
+
# Centrifugo address scheme
|
30
|
+
#
|
31
|
+
# @option options [String] :host
|
32
|
+
# Centrifugo address host
|
33
|
+
#
|
34
|
+
# @option options [String] :port
|
35
|
+
# Centrifugo address port
|
36
|
+
#
|
37
|
+
# @option options [String] :secret
|
38
|
+
# Centrifugo secret(used to issue JWT)
|
39
|
+
#
|
40
|
+
# @option options [String] :api_key
|
41
|
+
# Centrifugo API key(used to perform requests)
|
42
|
+
#
|
43
|
+
# @option options [String] :timeout
|
44
|
+
# Number of seconds to wait for the connection to open.
|
45
|
+
#
|
46
|
+
# @option options [String] :open_timeout
|
47
|
+
# Number of seconds to wait for one block to be read.
|
48
|
+
#
|
49
|
+
# @example Construct new client instance
|
50
|
+
# Rubycent::Client.new(
|
51
|
+
# scheme: 'http',
|
52
|
+
# host: 'localhost',
|
53
|
+
# port: '8000',
|
54
|
+
# secret: 'secret',
|
55
|
+
# api_key: 'api key',
|
56
|
+
# timeout: 10,
|
57
|
+
# open_timeout: 15
|
58
|
+
# )
|
59
|
+
#
|
60
|
+
def initialize(options = {})
|
61
|
+
options = DEFAULT_OPTIONS.merge(options)
|
62
|
+
|
63
|
+
@scheme, @host, @port, @secret, @api_key = options.values_at(
|
64
|
+
:scheme, :host, :port, :secret, :api_key
|
65
|
+
)
|
66
|
+
|
67
|
+
@timeout = 5
|
68
|
+
@open_timeout = 5
|
69
|
+
end
|
70
|
+
|
71
|
+
# Publish data into channel
|
72
|
+
#
|
73
|
+
# @param channel [String]
|
74
|
+
# Name of the channel to publish
|
75
|
+
#
|
76
|
+
# @param data [Hash]
|
77
|
+
# Data for publication in the channel
|
78
|
+
#
|
79
|
+
# @example Publish `content: 'hello'` into `chat` channel
|
80
|
+
# Rubycent::Client.new.publish('chat', content: 'hello') #=> {}
|
81
|
+
#
|
82
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#publish)
|
83
|
+
#
|
84
|
+
# @raise [Rubycent::Error, Rubycent::NetworkError, Rubycent::RequestError, Rubycent::ResponseError]
|
85
|
+
#
|
86
|
+
# @return [Hash] Return empty hash in case of successful publish
|
87
|
+
#
|
88
|
+
def publish(channel, data)
|
89
|
+
construct_query.execute('publish', channel: channel, data: data)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Publish data into multiple channels
|
93
|
+
# (Similar to `#publish` but allows to send the same data into many channels)
|
94
|
+
#
|
95
|
+
# @param channels [Array<String>] Collection of channels names to publish
|
96
|
+
# @param data [Hash] Data for publication in the channels
|
97
|
+
#
|
98
|
+
# @example Broadcast `content: 'hello'` into `channel_1`, 'channel_2' channels
|
99
|
+
# Rubycent::Client.new.broadcast(['channel_1', 'channel_2'], content: 'hello') #=> {}
|
100
|
+
#
|
101
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#broadcast)
|
102
|
+
#
|
103
|
+
# @raise [Rubycent::Error, Rubycent::NetworkError, Rubycent::RequestError, Rubycent::ResponseError]
|
104
|
+
#
|
105
|
+
# @return [Hash] Return empty hash in case of successful broadcast
|
106
|
+
#
|
107
|
+
def broadcast(channels, data)
|
108
|
+
construct_query.execute('broadcast', channels: channels, data: data)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Unsubscribe user from channel
|
112
|
+
#
|
113
|
+
# @param channel [String]
|
114
|
+
# Channel name to unsubscribe from
|
115
|
+
#
|
116
|
+
# @param user_id [String, Integer]
|
117
|
+
# User ID you want to unsubscribe
|
118
|
+
#
|
119
|
+
# @example Unsubscribe user with `id = 1` from `chat` channel
|
120
|
+
# Rubycent::Client.new.unsubscribe('chat', 1) #=> {}
|
121
|
+
#
|
122
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#unsubscribe)
|
123
|
+
#
|
124
|
+
# @raise [Rubycent::Error, Rubycent::NetworkError, Rubycent::RequestError, Rubycent::ResponseError]
|
125
|
+
#
|
126
|
+
# @return [Hash] Return empty hash in case of successful unsubscribe
|
127
|
+
#
|
128
|
+
def unsubscribe(channel, user_id)
|
129
|
+
construct_query.execute('unsubscribe', channel: channel, user: user_id)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Disconnect user by it's ID
|
133
|
+
#
|
134
|
+
# @param user_id [String, Integer]
|
135
|
+
# User ID you want to disconnect
|
136
|
+
#
|
137
|
+
# @example Disconnect user with `id = 1`
|
138
|
+
# Rubycent::Client.new.disconnect(1) #=> {}
|
139
|
+
#
|
140
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#disconnect)
|
141
|
+
#
|
142
|
+
# @raise [Rubycent::Error, Rubycent::NetworkError, Rubycent::RequestError, Rubycent::ResponseError]
|
143
|
+
#
|
144
|
+
# @return [Hash] Return empty hash in case of successful disconnect
|
145
|
+
#
|
146
|
+
def disconnect(user_id)
|
147
|
+
construct_query.execute('disconnect', user: user_id)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Get channel presence information
|
151
|
+
# (all clients currently subscribed on this channel)
|
152
|
+
#
|
153
|
+
# @param channel [String] Name of the channel
|
154
|
+
#
|
155
|
+
# @example Get presence information for channel `chat`
|
156
|
+
# Rubycent::Client.new.presence('chat') #=> {
|
157
|
+
# "result" => {
|
158
|
+
# "presence" => {
|
159
|
+
# "c54313b2-0442-499a-a70c-051f8588020f" => {
|
160
|
+
# "client" => "c54313b2-0442-499a-a70c-051f8588020f",
|
161
|
+
# "user" => "42"
|
162
|
+
# },
|
163
|
+
# "adad13b1-0442-499a-a70c-051f858802da" => {
|
164
|
+
# "client" => "adad13b1-0442-499a-a70c-051f858802da",
|
165
|
+
# "user" => "42"
|
166
|
+
# }
|
167
|
+
# }
|
168
|
+
# }
|
169
|
+
# }
|
170
|
+
#
|
171
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#presence)
|
172
|
+
#
|
173
|
+
# @raise [Rubycent::Error, Rubycent::NetworkError, Rubycent::RequestError, Rubycent::ResponseError]
|
174
|
+
#
|
175
|
+
# @return [Hash]
|
176
|
+
# Return hash with information about all clients currently subscribed on this channel
|
177
|
+
#
|
178
|
+
def presence(channel)
|
179
|
+
construct_query.execute('presence', channel: channel)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Get short channel presence information
|
183
|
+
#
|
184
|
+
# @param channel [String] Name of the channel
|
185
|
+
#
|
186
|
+
# @example Get short presence information for channel `chat`
|
187
|
+
# Rubycent::Client.new.presence_stats('chat') #=> {
|
188
|
+
# "result" => {
|
189
|
+
# "num_clients" => 0,
|
190
|
+
# "num_users" => 0
|
191
|
+
# }
|
192
|
+
# }
|
193
|
+
#
|
194
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#presence_stats)
|
195
|
+
#
|
196
|
+
# @raise [Rubycent::Error, Rubycent::NetworkError, Rubycent::RequestError, Rubycent::ResponseError]
|
197
|
+
#
|
198
|
+
# @return [Hash]
|
199
|
+
# Return hash with short presence information about channel
|
200
|
+
#
|
201
|
+
def presence_stats(channel)
|
202
|
+
construct_query.execute('presence_stats', channel: channel)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Get channel history information
|
206
|
+
# (list of last messages published into channel)
|
207
|
+
#
|
208
|
+
# @param channel [String] Name of the channel
|
209
|
+
#
|
210
|
+
# @example Get history for channel `chat`
|
211
|
+
# Rubycent::Client.new.history('chat') #=> {
|
212
|
+
# "result" => {
|
213
|
+
# "publications" => [
|
214
|
+
# {
|
215
|
+
# "data" => {
|
216
|
+
# "text" => "hello"
|
217
|
+
# },
|
218
|
+
# "uid" => "BWcn14OTBrqUhTXyjNg0fg"
|
219
|
+
# },
|
220
|
+
# {
|
221
|
+
# "data" => {
|
222
|
+
# "text" => "hi!"
|
223
|
+
# },
|
224
|
+
# "uid" => "Ascn14OTBrq14OXyjNg0hg"
|
225
|
+
# }
|
226
|
+
# ]
|
227
|
+
# }
|
228
|
+
# }
|
229
|
+
#
|
230
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#history)
|
231
|
+
#
|
232
|
+
# @raise [Rubycent::Error, Rubycent::NetworkError, Rubycent::RequestError, Rubycent::ResponseError]
|
233
|
+
#
|
234
|
+
# @return [Hash]
|
235
|
+
# Return hash with a list of last messages published into channel
|
236
|
+
#
|
237
|
+
def history(channel)
|
238
|
+
construct_query.execute('history', channel: channel)
|
239
|
+
end
|
240
|
+
|
241
|
+
# Get list of active(with one or more subscribers) channels.
|
242
|
+
#
|
243
|
+
# @example Get active channels list
|
244
|
+
# Rubycent::Client.new.channels #=> {
|
245
|
+
# "result" => {
|
246
|
+
# "channels" => [
|
247
|
+
# "chat"
|
248
|
+
# ]
|
249
|
+
# }
|
250
|
+
# }
|
251
|
+
#
|
252
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#channels)
|
253
|
+
#
|
254
|
+
# @raise [Rubycent::Error, Rubycent::NetworkError, Rubycent::RequestError, Rubycent::ResponseError]
|
255
|
+
#
|
256
|
+
# @return [Hash]
|
257
|
+
# Return hash with a list of active channels
|
258
|
+
#
|
259
|
+
def channels
|
260
|
+
construct_query.execute('channels', {})
|
261
|
+
end
|
262
|
+
|
263
|
+
# Get information about running Centrifugo nodes
|
264
|
+
#
|
265
|
+
# @example Get running centrifugo nodes list
|
266
|
+
# Rubycent::Client.new.info #=> {
|
267
|
+
# "result" => {
|
268
|
+
# "nodes" => [
|
269
|
+
# {
|
270
|
+
# "name" => "Alexanders-MacBook-Pro.local_8000",
|
271
|
+
# "num_channels" => 0,
|
272
|
+
# "num_clients" => 0,
|
273
|
+
# "num_users" => 0,
|
274
|
+
# "uid" => "f844a2ed-5edf-4815-b83c-271974003db9",
|
275
|
+
# "uptime" => 0,
|
276
|
+
# "version" => ""
|
277
|
+
# }
|
278
|
+
# ]
|
279
|
+
# }
|
280
|
+
# }
|
281
|
+
#
|
282
|
+
# @see (https://centrifugal.github.io/centrifugo/server/http_api/#info)
|
283
|
+
#
|
284
|
+
# @raise [Rubycent::Error, Rubycent::NetworkError, Rubycent::RequestError, Rubycent::ResponseError]
|
285
|
+
#
|
286
|
+
# @return [Hash]
|
287
|
+
# Return hash with a list of last messages published into channel
|
288
|
+
#
|
289
|
+
def info
|
290
|
+
construct_query.execute('info', {})
|
291
|
+
end
|
292
|
+
|
293
|
+
# Generate connection JWT for the given user
|
294
|
+
#
|
295
|
+
# @param user_id [String]
|
296
|
+
# Standard JWT claim which must contain an ID of current application user.
|
297
|
+
#
|
298
|
+
# @option subscriber [String] :channel
|
299
|
+
# Channel that client tries to subscribe to (string).
|
300
|
+
#
|
301
|
+
# @param expiration [Integer]
|
302
|
+
# (default: nil) UNIX timestamp seconds when token will expire.
|
303
|
+
#
|
304
|
+
# @param info [Hash]
|
305
|
+
# (default: {}) This claim is optional - this is additional information about
|
306
|
+
# client connection that can be provided for Centrifugo.
|
307
|
+
#
|
308
|
+
# @param algorithm [String] The algorithm used for the cryptographic signing
|
309
|
+
#
|
310
|
+
# @note At moment the only supported JWT algorithm is HS256 - i.e. HMAC SHA-256.
|
311
|
+
# This can be extended later.
|
312
|
+
#
|
313
|
+
# @example Get user JWT with expiration and extra info
|
314
|
+
# Rubycent::Client.new.issue_user_token('1', 3600, { 'role' => 'admin' }) #=> "eyJhbGciOiJIUzI1NiJ9.eyJzdWIi..."
|
315
|
+
#
|
316
|
+
# @see (https://centrifugal.github.io/centrifugo/server/authentication/)
|
317
|
+
#
|
318
|
+
# @raise [Rubycent::Error]
|
319
|
+
#
|
320
|
+
# @return [String]
|
321
|
+
#
|
322
|
+
def issue_user_token(user_id, expiration = nil, info = {}, algorithm = 'HS256')
|
323
|
+
issue_token({ 'sub' => user_id }, expiration, info, algorithm)
|
324
|
+
end
|
325
|
+
|
326
|
+
# Generate JWT for private channels
|
327
|
+
#
|
328
|
+
# @param client [String]
|
329
|
+
# Client ID which wants to subscribe on channel
|
330
|
+
#
|
331
|
+
# @option channel [String]
|
332
|
+
# Channel that client tries to subscribe to (string).
|
333
|
+
#
|
334
|
+
# @param expiration [Integer]
|
335
|
+
# (default: nil) UNIX timestamp seconds when token will expire.
|
336
|
+
#
|
337
|
+
# @param info [Hash]
|
338
|
+
# (default: {}) This claim is optional - this is additional information about
|
339
|
+
# client connection that can be provided for Centrifugo.
|
340
|
+
#
|
341
|
+
# @param algorithm [String] The algorithm used for the cryptographic signing
|
342
|
+
#
|
343
|
+
# @example Get private channel JWT with expiration and extra info
|
344
|
+
# Rubycent::Client.new.issue_channel_token('client', 'channel', 3600, { 'message' => 'wat' }) #=> eyJhbGciOiJIUzI1NiJ9.eyJjbGllbnQiOiJjbG..."
|
345
|
+
#
|
346
|
+
# @note At moment the only supported JWT algorithm is HS256 - i.e. HMAC SHA-256.
|
347
|
+
# This can be extended later.
|
348
|
+
#
|
349
|
+
# @see (https://centrifugal.github.io/centrifugo/server/private_channels/)
|
350
|
+
#
|
351
|
+
# @raise [Rubycent::Error]
|
352
|
+
#
|
353
|
+
# @return [String]
|
354
|
+
#
|
355
|
+
def issue_channel_token(client, channel, expiration = nil, info = {}, algorithm = 'HS256')
|
356
|
+
issue_token({ 'client' => client, 'channel' => channel }, expiration, info, algorithm)
|
357
|
+
end
|
358
|
+
|
359
|
+
private
|
360
|
+
|
361
|
+
def issue_token(subscriber, expiration, info, algorithm)
|
362
|
+
raise Error, 'Secret can not be nil' if secret.nil?
|
363
|
+
|
364
|
+
payload = subscriber.merge('info' => info).tap do |p|
|
365
|
+
p['exp'] = expiration if expiration
|
366
|
+
end
|
367
|
+
|
368
|
+
JWT.encode(payload, secret, algorithm)
|
369
|
+
end
|
370
|
+
|
371
|
+
def construct_query
|
372
|
+
Query.new(self)
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|