ably 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +103 -0
  6. data/Rakefile +4 -0
  7. data/ably.gemspec +32 -0
  8. data/lib/ably.rb +11 -0
  9. data/lib/ably/auth.rb +381 -0
  10. data/lib/ably/exceptions.rb +16 -0
  11. data/lib/ably/realtime.rb +38 -0
  12. data/lib/ably/realtime/callbacks.rb +15 -0
  13. data/lib/ably/realtime/channel.rb +51 -0
  14. data/lib/ably/realtime/client.rb +82 -0
  15. data/lib/ably/realtime/connection.rb +61 -0
  16. data/lib/ably/rest.rb +15 -0
  17. data/lib/ably/rest/channel.rb +58 -0
  18. data/lib/ably/rest/client.rb +194 -0
  19. data/lib/ably/rest/middleware/exceptions.rb +42 -0
  20. data/lib/ably/rest/middleware/external_exceptions.rb +26 -0
  21. data/lib/ably/rest/middleware/parse_json.rb +15 -0
  22. data/lib/ably/rest/paged_resource.rb +107 -0
  23. data/lib/ably/rest/presence.rb +44 -0
  24. data/lib/ably/support.rb +14 -0
  25. data/lib/ably/token.rb +55 -0
  26. data/lib/ably/version.rb +3 -0
  27. data/spec/acceptance/realtime_client_spec.rb +12 -0
  28. data/spec/acceptance/rest/auth_spec.rb +441 -0
  29. data/spec/acceptance/rest/base_spec.rb +113 -0
  30. data/spec/acceptance/rest/channel_spec.rb +68 -0
  31. data/spec/acceptance/rest/presence_spec.rb +22 -0
  32. data/spec/acceptance/rest/stats_spec.rb +57 -0
  33. data/spec/acceptance/rest/time_spec.rb +14 -0
  34. data/spec/spec_helper.rb +31 -0
  35. data/spec/support/api_helper.rb +41 -0
  36. data/spec/support/test_app.rb +77 -0
  37. data/spec/unit/auth.rb +9 -0
  38. data/spec/unit/realtime_spec.rb +9 -0
  39. data/spec/unit/rest_spec.rb +99 -0
  40. data/spec/unit/token_spec.rb +90 -0
  41. metadata +240 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3b4423dc2ad92073b07e69cb72a3acbbe2d0973a
4
+ data.tar.gz: 76c5bb17b624875ec6fea9300c3778b216635266
5
+ SHA512:
6
+ metadata.gz: e107b6875dbfc0bd05ba14c48b37bb0d6fea3d514666353495ff12c822bcc62f09322a3442eed5239ccf1ac1df466e48cb59387970be373260bfb9c40f28dce2
7
+ data.tar.gz: aa9f928e0f48b74cb158d8f0b7b8d21dec755427b02bff93d8f73fd6233be0ec4f8a5926acfd7ee0de3d6e6c3aff364f005122b561d7db4ee18aa6c169934703
@@ -0,0 +1,4 @@
1
+ Gemfile.lock
2
+ .yardoc
3
+ doc/*
4
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ably.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Lewis Marshall
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,103 @@
1
+ # Ably
2
+
3
+ A Ruby client library for [ably.io](https://ably.io), the real-time messaging service.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'ably'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install ably
18
+
19
+ ## Using the Realtime API
20
+
21
+ ### Subscribing to a channel
22
+
23
+ Given:
24
+
25
+ ```
26
+ client = Ably::Realtime.new(api_key: "xxxxx")
27
+
28
+ channel = client.channel("test")
29
+ ```
30
+
31
+ Subscribe to all events:
32
+
33
+ ```
34
+ channel.subscribe do |message|
35
+ message[:name] #=> "greeting"
36
+ message[:data] #=> "Hello World!"
37
+ end
38
+ ```
39
+
40
+ Only certain events:
41
+
42
+ ```
43
+ channel.subscribe("myEvent") do |message|
44
+ message[:name] #=> "myEvent"
45
+ message[:data] #=> "myData"
46
+ end
47
+ ```
48
+
49
+ ### Publishing to a channel
50
+
51
+ ```
52
+ client = Ably::Realtime.new(api_key: "xxxxx")
53
+
54
+ channel = client.channel("test")
55
+
56
+ channel.publish("greeting", "Hello World!")
57
+ ```
58
+
59
+ ## Using the REST API
60
+
61
+ ### Publishing a message to a channel
62
+
63
+ ```
64
+ client = Ably::Rest.new(api_key: "xxxxx")
65
+
66
+ channel = client.channel("test")
67
+
68
+ channel.publish("myEvent", "Hello!") #=> true
69
+ ```
70
+
71
+ ### Fetching a channel's history
72
+
73
+ ```
74
+ client = Ably::Rest.new(api_key: "xxxxx")
75
+
76
+ channel = client.channel("test")
77
+
78
+ channel.history #=> [{:name=>"test", :data=>"payload"}]
79
+ ```
80
+
81
+ ### Fetching your application's stats
82
+
83
+ ```
84
+ client = Ably::Rest.new(api_key: "xxxxx")
85
+
86
+ client.stats #=> [{:channels=>..., :apiRequests=>..., ...}]
87
+ ```
88
+
89
+ ### Fetching the Ably service time
90
+
91
+ ```
92
+ client = Ably::Rest.new(api_key: "xxxxx")
93
+
94
+ client.time #=> 2013-12-12 14:23:34 +0000
95
+ ```
96
+
97
+ ## Contributing
98
+
99
+ 1. Fork it
100
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
101
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
102
+ 4. Push to the branch (`git push origin my-new-feature`)
103
+ 5. Create new Pull Request
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "yard"
4
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ably/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ably"
8
+ spec.version = Ably::VERSION
9
+ spec.authors = ["Lewis Marshall", "Matthew O'Riordan"]
10
+ spec.email = ["lewis@lmars.net", "matt@ably.io"]
11
+ spec.description = %q{A Ruby client library for ably.io, the real-time messaging service}
12
+ spec.summary = %q{A Ruby client library for ably.io, the real-time messaging service}
13
+ spec.homepage = "http://github.com/ably/ably-ruby"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "eventmachine"
22
+ spec.add_runtime_dependency "faraday", "~> 0.9"
23
+ spec.add_runtime_dependency "json"
24
+ spec.add_runtime_dependency "websocket-driver"
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.3"
27
+ spec.add_development_dependency "rake"
28
+ spec.add_development_dependency "redcarpet"
29
+ spec.add_development_dependency "rspec", "~> 3.0"
30
+ spec.add_development_dependency "yard"
31
+ spec.add_development_dependency "webmock"
32
+ end
@@ -0,0 +1,11 @@
1
+ require "ably/support"
2
+
3
+ require "ably/auth"
4
+ require "ably/exceptions"
5
+ require "ably/rest"
6
+ require "ably/realtime"
7
+ require "ably/token"
8
+ require "ably/version"
9
+
10
+ module Ably
11
+ end
@@ -0,0 +1,381 @@
1
+ require "json"
2
+ require "faraday"
3
+ require "securerandom"
4
+
5
+ require "ably/rest/middleware/external_exceptions"
6
+ require "ably/rest/middleware/parse_json"
7
+
8
+ module Ably
9
+ # Auth is responsible for authentication with {https://ably.io Ably} using basic or token authentication
10
+ #
11
+ # Find out more about Ably authentication at: http://docs.ably.io/other/authentication/
12
+ #
13
+ # @!attribute [r] client_id
14
+ # @return [String] The provided client ID, used for identifying this client for presence purposes
15
+ # @!attribute [r] current_token
16
+ # @return [Ably::Token] Current {Ably::Token} issued by this library or one of the provided callbacks used to authenticate requests
17
+ # @!attribute [r] token_id
18
+ # @return [String] Token ID provided to the {Ably::Client} constructor that is used to authenticate all requests
19
+ # @!attribute [r] api_key
20
+ # @return [String] Complete API key containing both the key ID and key secret, if present
21
+ # @!attribute [r] key_id
22
+ # @return [String] Key ID (public part of the API key), if present
23
+ # @!attribute [r] key_secret
24
+ # @return [String] Key secret (private secure part of the API key), if present
25
+ # @!attribute [r] options
26
+ # @return [Hash] {Ably::Auth} options configured for this client
27
+
28
+ class Auth
29
+ include Ably::Support
30
+
31
+ attr_reader :options, :current_token
32
+ alias_method :auth_options, :options
33
+
34
+ # Creates an Auth object
35
+ #
36
+ # @param [Ably::Rest::Client] client {Ably::Rest::Client} this Auth object uses
37
+ # @param [Hash] auth_options see {Ably::Rest::Client#initialize}
38
+ # @yield [auth_options] see {Ably::Rest::Client#initialize}
39
+ def initialize(client, auth_options, &auth_block)
40
+ @client = client
41
+ @options = auth_options
42
+ @auth_callback = auth_block if block_given?
43
+
44
+ unless auth_options.kind_of?(Hash)
45
+ raise ArgumentError, "Expected auth_options to be a Hash"
46
+ end
47
+
48
+ if auth_options[:api_key] && (auth_options[:key_secret] || auth_options[:key_id])
49
+ raise ArgumentError, "api_key and key_id or key_secret are mutually exclusive. Provider either an api_key or key_id & key_secret"
50
+ end
51
+
52
+ if auth_options[:api_key]
53
+ api_key_parts = auth_options[:api_key].to_s.match(/(?<id>[\w_-]+\.[\w_-]+):(?<secret>[\w_-]+)/)
54
+ raise ArgumentError, "api_key is invalid" unless api_key_parts
55
+ auth_options[:key_id] = api_key_parts[:id]
56
+ auth_options[:key_secret] = api_key_parts[:secret]
57
+ end
58
+
59
+ if using_basic_auth? && !api_key_present?
60
+ raise ArgumentError, "api_key is missing. Either an API key, token, or token auth method must be provided"
61
+ end
62
+
63
+ if has_client_id? && !api_key_present?
64
+ raise ArgumentError, "client_id cannot be provided without a complete API key. Key ID & Secret is needed to authenticate with Ably and obtain a token"
65
+ end
66
+
67
+ @options.freeze
68
+ end
69
+
70
+ # Ensures valid auth credentials are present for the library instance. This may rely on an already-known and valid token, and will obtain a new token if necessary.
71
+ #
72
+ # In the event that a new token request is made, the specified options are used.
73
+ #
74
+ # @param [Hash] options the options for the token request
75
+ # @option options [String] :key_id key ID for the designated application (defaults to client key_id)
76
+ # @option options [String] :key_secret key secret for the designated application used to sign token requests (defaults to client key_secret)
77
+ # @option options [String] :client_id client ID identifying this connection to other clients (defaults to client client_id if configured)
78
+ # @option options [String] :auth_url a URL to be used to GET or POST a set of token request params, to obtain a signed token request.
79
+ # @option options [Hash] :auth_headers a set of application-specific headers to be added to any request made to the authUrl
80
+ # @option options [Hash] :auth_params a set of application-specific query params to be added to any request made to the authUrl
81
+ # @option options [Symbol] :auth_method HTTP method to use with auth_url, must be either `:get` or `:post` (defaults to :get)
82
+ # @option options [Integer] :ttl validity time in seconds for the requested {Ably::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
83
+ # @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
84
+ # @option options [Boolean] :query_time when true will query the {https://ably.io Ably} system for the current time instead of using the local time
85
+ # @option options [Integer] :timestamp the time of the of the request in seconds since the epoch
86
+ # @option options [String] :nonce an unquoted, unescaped random string of at least 16 characters
87
+ # @option options [Boolean] :force obtains a new token even if the current token is valid
88
+ #
89
+ # @yield [options] (optional) if an auth block is passed to this method, then this block will be called to create a new token request object
90
+ # @yieldparam [Hash] options options passed to request_token will be in turn sent to the block in this argument
91
+ # @yieldreturn [Hash] valid token request object, see {#create_token_request}
92
+ #
93
+ # @return [Ably::Token]
94
+ #
95
+ # @example
96
+ # # will issue a simple token request using basic auth
97
+ # client = Ably::Rest::Client.new(api_key: 'key.id:secret')
98
+ # token = client.auth.authorise
99
+ #
100
+ # # will use token request from block to authorise if not already authorised
101
+ # token = client.auth.authorise do |options|
102
+ # # create token_request object
103
+ # token_request
104
+ # end
105
+ #
106
+ def authorise(options = {}, &block)
107
+ if !options[:force] && current_token
108
+ return current_token unless current_token.expired?
109
+ end
110
+
111
+ @current_token = request_token(options, &block)
112
+ end
113
+
114
+ # Request a {Ably::Token} which can be used to make authenticated token based requests
115
+ #
116
+ # @param [Hash] options the options for the token request
117
+ # @option options [String] :key_id key ID for the designated application (defaults to client key_id)
118
+ # @option options [String] :key_secret key secret for the designated application used to sign token requests (defaults to client key_secret)
119
+ # @option options [String] :client_id client ID identifying this connection to other clients (defaults to client client_id if configured)
120
+ # @option options [String] :auth_url a URL to be used to GET or POST a set of token request params, to obtain a signed token request.
121
+ # @option options [Hash] :auth_headers a set of application-specific headers to be added to any request made to the authUrl
122
+ # @option options [Hash] :auth_params a set of application-specific query params to be added to any request made to the authUrl
123
+ # @option options [Symbol] :auth_method HTTP method to use with auth_url, must be either `:get` or `:post` (defaults to :get)
124
+ # @option options [Integer] :ttl validity time in seconds for the requested {Ably::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
125
+ # @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
126
+ # @option options [Boolean] :query_time when true will query the {https://ably.io Ably} system for the current time instead of using the local time
127
+ # @option options [Integer] :timestamp the time of the of the request in seconds since the epoch
128
+ # @option options [String] :nonce an unquoted, unescaped random string of at least 16 characters
129
+ #
130
+ # @yield [options] (optional) if an auth block is passed to this method, then this block will be called to create a new token request object
131
+ # @yieldparam [Hash] options options passed to request_token will be in turn sent to the block in this argument
132
+ # @yieldreturn [Hash] valid token request object, see {#create_token_request}
133
+ #
134
+ # @return [Ably::Token]
135
+ #
136
+ # @example
137
+ # # simple token request using basic auth
138
+ # client = Ably::Rest::Client.new(api_key: 'key.id:secret')
139
+ # token = client.auth.request_token
140
+ #
141
+ # # token request using auth block
142
+ # token = client.auth.request_token do |options|
143
+ # # create token_request object
144
+ # token_request
145
+ # end
146
+ #
147
+ def request_token(options = {}, &block)
148
+ token_options = self.auth_options.merge(options)
149
+
150
+ auth_url = token_options.delete(:auth_url)
151
+ token_request = if block_given?
152
+ yield(token_options)
153
+ elsif auth_callback
154
+ auth_callback.call(token_options)
155
+ elsif auth_url
156
+ token_request_from_auth_url(auth_url, token_options)
157
+ else
158
+ create_token_request(token_options)
159
+ end
160
+
161
+ response = client.post("/keys/#{token_request.fetch(:id)}/requestToken", token_request, send_auth_header: false)
162
+
163
+ Ably::Token.new(response.body[:access_token])
164
+ end
165
+
166
+ # Creates and signs a token request that can then subsequently be used by any client to request a token
167
+ #
168
+ # @param [Hash] options the options for the token request
169
+ # @option options [String] :key_id key ID for the designated application
170
+ # @option options [String] :key_secret key secret for the designated application used to sign token requests (defaults to client key_secret)
171
+ # @option options [String] :client_id client ID identifying this connection to other clients
172
+ # @option options [Integer] :ttl validity time in seconds for the requested {Ably::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
173
+ # @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
174
+ # @option options [Boolean] :query_time when true will query the {https://ably.io Ably} system for the current time instead of using the local time
175
+ # @option options [Integer] :timestamp the time of the of the request in seconds since the epoch
176
+ # @option options [String] :nonce an unquoted, unescaped random string of at least 16 characters
177
+ # @return [Hash]
178
+ #
179
+ # @example
180
+ # client.auth.create_request_token(id: 'asd.asd', ttl: 3600)
181
+ # # => {
182
+ # # :id=>"asds.adsa",
183
+ # # :client_id=>nil,
184
+ # # :ttl=>3600,
185
+ # # :timestamp=>1410718527,
186
+ # # :capability=>"{\"*\":[\"*\"]}",
187
+ # # :nonce=>"95e543b88299f6bae83df9b12fbd1ecd",
188
+ # # :mac=>"881oZHeFo6oMim7N64y2vFHtSlpQ2gn/uE56a8gUxHw="
189
+ # # }
190
+ def create_token_request(options = {})
191
+ token_attributes = %w(id client_id ttl timestamp capability nonce)
192
+
193
+ token_options = options.dup
194
+ request_key_id = token_options.delete(:key_id) || key_id
195
+ request_key_secret = token_options.delete(:key_secret) || key_secret
196
+
197
+ raise TokenRequestError, "Key ID and Key Secret are required to generate a new token request" unless request_key_id && request_key_secret
198
+
199
+ timestamp = if token_options[:query_time]
200
+ client.time
201
+ else
202
+ Time.now
203
+ end.to_i
204
+
205
+ token_request = {
206
+ id: request_key_id,
207
+ client_id: client_id,
208
+ ttl: Token::DEFAULTS[:ttl],
209
+ timestamp: timestamp,
210
+ capability: Token::DEFAULTS[:capability],
211
+ nonce: SecureRandom.hex
212
+ }.merge(token_options.select { |key, val| token_attributes.include?(key.to_s) })
213
+
214
+ if token_request[:capability].is_a?(Hash)
215
+ token_request[:capability] = token_request[:capability].to_json
216
+ end
217
+
218
+ token_request[:mac] = sign_params(token_request, request_key_secret)
219
+
220
+ token_request
221
+ end
222
+
223
+ def api_key
224
+ "#{key_id}:#{key_secret}" if api_key_present?
225
+ end
226
+
227
+ def key_id
228
+ options[:key_id]
229
+ end
230
+
231
+ def key_secret
232
+ options[:key_secret]
233
+ end
234
+
235
+ # True when Basic Auth is being used to authenticate with Ably
236
+ def using_basic_auth?
237
+ !using_token_auth?
238
+ end
239
+
240
+ # True when Token Auth is being used to authenticate with Ably
241
+ def using_token_auth?
242
+ token_id || current_token || has_client_id? || token_creatable_externally?
243
+ end
244
+
245
+ def client_id
246
+ options[:client_id]
247
+ end
248
+
249
+ def token_id
250
+ options[:token_id]
251
+ end
252
+
253
+ # Auth header string used in HTTP requests to Ably
254
+ #
255
+ # @return [String] HTTP authentication value used in HTTP_AUTHORIZATION header
256
+ def auth_header
257
+ if using_token_auth?
258
+ token_auth_header
259
+ else
260
+ basic_auth_header
261
+ end
262
+ end
263
+
264
+ private
265
+ attr_reader :auth_callback
266
+
267
+ def basic_auth_header
268
+ raise InsecureRequestError, "Cannot use Basic Auth over non-TLS connections" unless client.use_tls?
269
+ "Basic #{encode64("#{api_key}")}"
270
+ end
271
+
272
+ def token_auth_header
273
+ current_token_id = if token_id
274
+ token_id
275
+ else
276
+ authorise.id
277
+ end
278
+
279
+ "Bearer #{encode64(current_token_id)}"
280
+ end
281
+
282
+ # Sign the request params using the secret
283
+ #
284
+ # @return [Hash]
285
+ def sign_params(params, secret)
286
+ text = params.values_at(
287
+ :id,
288
+ :ttl,
289
+ :capability,
290
+ :client_id,
291
+ :timestamp,
292
+ :nonce
293
+ ).map { |t| "#{t}\n" }.join("")
294
+
295
+ encode64(
296
+ Digest::HMAC.digest(text, secret, Digest::SHA256)
297
+ )
298
+ end
299
+
300
+ # Retrieve a token request from a specified URL, expects a JSON response
301
+ #
302
+ # @return [Hash]
303
+ def token_request_from_auth_url(auth_url, options = {})
304
+ uri = URI.parse(auth_url)
305
+ connection = Faraday.new("#{uri.scheme}://#{uri.host}", connection_options)
306
+ method = options[:auth_method] || :get
307
+
308
+ connection.send(method) do |request|
309
+ request.url uri.path
310
+ request.params = options[:auth_params] || {}
311
+ request.headers = options[:auth_headers] || {}
312
+ end.body
313
+ end
314
+
315
+ # Return a Hash of connection options to initiate the Faraday::Connection with
316
+ #
317
+ # @return [Hash]
318
+ def connection_options
319
+ @connection_options ||= {
320
+ builder: middleware,
321
+ headers: {
322
+ accept: "application/json",
323
+ user_agent: user_agent
324
+ },
325
+ request: {
326
+ open_timeout: 5,
327
+ timeout: 10
328
+ }
329
+ }
330
+ end
331
+
332
+ # Return a Faraday middleware stack to initiate the Faraday::Connection with
333
+ #
334
+ # @see http://mislav.uniqpath.com/2011/07/faraday-advanced-http/
335
+ def middleware
336
+ @middleware ||= Faraday::RackBuilder.new do |builder|
337
+ # Convert request params to "www-form-urlencoded"
338
+ builder.use Faraday::Request::UrlEncoded
339
+
340
+ # Parse JSON response bodies
341
+ builder.use Ably::Rest::Middleware::ParseJson
342
+
343
+ # Log HTTP requests if debug_http option set
344
+ builder.response :logger if @debug_http
345
+
346
+ # Raise exceptions if response code is invalid
347
+ builder.use Ably::Rest::Middleware::ExternalExceptions
348
+
349
+ # Set Faraday's HTTP adapter
350
+ builder.adapter Faraday.default_adapter
351
+ end
352
+ end
353
+
354
+ def token_callback_present?
355
+ !!auth_callback
356
+ end
357
+
358
+ def token_url_present?
359
+ !!options[:auth_url]
360
+ end
361
+
362
+ def token_creatable_externally?
363
+ token_callback_present? || token_url_present?
364
+ end
365
+
366
+ def token_renewable?
367
+ use_basic_auth? || token_creatable_externally?
368
+ end
369
+
370
+ def has_client_id?
371
+ !!client_id
372
+ end
373
+
374
+ def api_key_present?
375
+ key_id && key_secret
376
+ end
377
+
378
+ private
379
+ attr_reader :client
380
+ end
381
+ end