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