rubycent 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
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