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.
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