rubycent 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|