ably 0.8.15 → 1.0.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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -4
  3. data/CHANGELOG.md +6 -2
  4. data/README.md +5 -1
  5. data/SPEC.md +1473 -852
  6. data/ably.gemspec +11 -8
  7. data/lib/ably/auth.rb +90 -53
  8. data/lib/ably/exceptions.rb +37 -8
  9. data/lib/ably/logger.rb +10 -1
  10. data/lib/ably/models/auth_details.rb +42 -0
  11. data/lib/ably/models/channel_state_change.rb +18 -4
  12. data/lib/ably/models/connection_details.rb +6 -3
  13. data/lib/ably/models/connection_state_change.rb +4 -3
  14. data/lib/ably/models/error_info.rb +1 -1
  15. data/lib/ably/models/message.rb +17 -1
  16. data/lib/ably/models/message_encoders/base.rb +103 -82
  17. data/lib/ably/models/message_encoders/base64.rb +1 -1
  18. data/lib/ably/models/presence_message.rb +16 -1
  19. data/lib/ably/models/protocol_message.rb +20 -3
  20. data/lib/ably/models/token_details.rb +11 -1
  21. data/lib/ably/models/token_request.rb +16 -6
  22. data/lib/ably/modules/async_wrapper.rb +7 -3
  23. data/lib/ably/modules/encodeable.rb +51 -12
  24. data/lib/ably/modules/enum.rb +17 -7
  25. data/lib/ably/modules/event_emitter.rb +29 -14
  26. data/lib/ably/modules/model_common.rb +13 -21
  27. data/lib/ably/modules/state_emitter.rb +7 -4
  28. data/lib/ably/modules/state_machine.rb +2 -4
  29. data/lib/ably/modules/uses_state_machine.rb +7 -3
  30. data/lib/ably/realtime.rb +2 -0
  31. data/lib/ably/realtime/auth.rb +102 -42
  32. data/lib/ably/realtime/channel.rb +68 -26
  33. data/lib/ably/realtime/channel/channel_manager.rb +154 -65
  34. data/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
  35. data/lib/ably/realtime/client.rb +18 -3
  36. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
  37. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
  38. data/lib/ably/realtime/connection.rb +108 -49
  39. data/lib/ably/realtime/connection/connection_manager.rb +167 -61
  40. data/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
  41. data/lib/ably/realtime/connection/websocket_transport.rb +19 -10
  42. data/lib/ably/realtime/presence.rb +70 -45
  43. data/lib/ably/realtime/presence/members_map.rb +201 -36
  44. data/lib/ably/realtime/presence/presence_manager.rb +30 -6
  45. data/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
  46. data/lib/ably/rest.rb +2 -2
  47. data/lib/ably/rest/channel.rb +5 -5
  48. data/lib/ably/rest/client.rb +31 -27
  49. data/lib/ably/rest/middleware/exceptions.rb +1 -3
  50. data/lib/ably/rest/middleware/logger.rb +2 -2
  51. data/lib/ably/rest/presence.rb +2 -2
  52. data/lib/ably/util/pub_sub.rb +1 -1
  53. data/lib/ably/util/safe_deferrable.rb +26 -0
  54. data/lib/ably/version.rb +2 -2
  55. data/spec/acceptance/realtime/auth_spec.rb +470 -111
  56. data/spec/acceptance/realtime/channel_history_spec.rb +5 -3
  57. data/spec/acceptance/realtime/channel_spec.rb +1017 -168
  58. data/spec/acceptance/realtime/client_spec.rb +6 -6
  59. data/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
  60. data/spec/acceptance/realtime/connection_spec.rb +424 -105
  61. data/spec/acceptance/realtime/message_spec.rb +52 -23
  62. data/spec/acceptance/realtime/presence_history_spec.rb +5 -3
  63. data/spec/acceptance/realtime/presence_spec.rb +1110 -96
  64. data/spec/acceptance/rest/auth_spec.rb +222 -59
  65. data/spec/acceptance/rest/base_spec.rb +1 -1
  66. data/spec/acceptance/rest/channel_spec.rb +1 -2
  67. data/spec/acceptance/rest/client_spec.rb +104 -48
  68. data/spec/acceptance/rest/message_spec.rb +42 -15
  69. data/spec/acceptance/rest/presence_spec.rb +4 -11
  70. data/spec/rspec_config.rb +2 -1
  71. data/spec/shared/client_initializer_behaviour.rb +2 -2
  72. data/spec/shared/safe_deferrable_behaviour.rb +6 -2
  73. data/spec/spec_helper.rb +4 -2
  74. data/spec/support/debug_failure_helper.rb +20 -4
  75. data/spec/support/event_machine_helper.rb +32 -1
  76. data/spec/unit/auth_spec.rb +4 -11
  77. data/spec/unit/logger_spec.rb +28 -2
  78. data/spec/unit/models/auth_details_spec.rb +49 -0
  79. data/spec/unit/models/channel_state_change_spec.rb +23 -3
  80. data/spec/unit/models/connection_details_spec.rb +12 -1
  81. data/spec/unit/models/connection_state_change_spec.rb +15 -4
  82. data/spec/unit/models/message_encoders/base64_spec.rb +2 -1
  83. data/spec/unit/models/message_spec.rb +153 -0
  84. data/spec/unit/models/presence_message_spec.rb +192 -0
  85. data/spec/unit/models/protocol_message_spec.rb +64 -6
  86. data/spec/unit/models/token_details_spec.rb +75 -0
  87. data/spec/unit/models/token_request_spec.rb +74 -0
  88. data/spec/unit/modules/async_wrapper_spec.rb +2 -1
  89. data/spec/unit/modules/enum_spec.rb +69 -0
  90. data/spec/unit/modules/event_emitter_spec.rb +149 -22
  91. data/spec/unit/modules/state_emitter_spec.rb +9 -3
  92. data/spec/unit/realtime/client_spec.rb +1 -1
  93. data/spec/unit/realtime/connection_spec.rb +8 -5
  94. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
  95. data/spec/unit/realtime/presence_spec.rb +4 -3
  96. data/spec/unit/rest/client_spec.rb +1 -1
  97. data/spec/unit/util/crypto_spec.rb +3 -3
  98. metadata +22 -19
data/ably.gemspec CHANGED
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.add_runtime_dependency 'em-http-request', '~> 1.1'
23
23
  spec.add_runtime_dependency 'statesman', '~> 1.0.0'
24
24
  spec.add_runtime_dependency 'faraday', '~> 0.9'
25
+
25
26
  if RUBY_VERSION.match(/^1/)
26
27
  spec.add_runtime_dependency 'json', '< 2.0'
27
28
  else
@@ -32,16 +33,18 @@ Gem::Specification.new do |spec|
32
33
  spec.add_runtime_dependency 'addressable', '>= 2.0.0'
33
34
 
34
35
  spec.add_development_dependency 'bundler', '~> 1.3'
35
- spec.add_development_dependency 'rake'
36
- spec.add_development_dependency 'redcarpet'
36
+ spec.add_development_dependency 'rake', '~> 11.3'
37
+ spec.add_development_dependency 'redcarpet', '~> 3.3'
37
38
  spec.add_development_dependency 'rspec', '~> 3.2.0' # version lock, see config.around(:example, :event_machine) in event_machine_helper.rb
38
- spec.add_development_dependency 'rspec-retry'
39
- spec.add_development_dependency 'yard'
40
- spec.add_development_dependency 'webmock', '~> 2.3.0'
41
-
42
- spec.add_development_dependency 'coveralls'
39
+ spec.add_development_dependency 'rspec-retry', '~> 0.4'
40
+ spec.add_development_dependency 'yard', '~> 0.9'
43
41
 
44
- unless RUBY_VERSION.match(/^1/)
42
+ if RUBY_VERSION.match(/^1/)
43
+ spec.add_development_dependency 'public_suffix', '~> 1.4.6' # Later versions do not support Ruby 1.9
44
+ spec.add_development_dependency 'webmock', '2.2'
45
+ else
46
+ spec.add_development_dependency 'webmock', '~> 2.2'
47
+ spec.add_development_dependency 'coveralls'
45
48
  spec.add_development_dependency 'pry'
46
49
  spec.add_development_dependency 'pry-byebug'
47
50
  end
data/lib/ably/auth.rb CHANGED
@@ -30,11 +30,11 @@ module Ably
30
30
 
31
31
  # Default capability Hash object and TTL in seconds for issued tokens
32
32
  TOKEN_DEFAULTS = {
33
- capability: { '*' => ['*'] },
34
- ttl: 60 * 60, # 1 hour in seconds
35
33
  renew_token_buffer: 10 # buffer to allow a token to be reissued before the token is considered expired (Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER)
36
34
  }.freeze
37
35
 
36
+ API_KEY_REGEX = /^[\w-]{2,}\.[\w-]{2,}:[\w-]{2,}$/
37
+
38
38
  attr_reader :options, :token_params, :current_token_details
39
39
  alias_method :auth_options, :options
40
40
 
@@ -65,8 +65,6 @@ module Ably
65
65
  @token_params = token_params.dup
66
66
  @token_option = options[:token] || options[:token_details]
67
67
 
68
- @options.delete :force # Forcing token auth for every request is not a valid default
69
-
70
68
  if options[:key] && (options[:key_secret] || options[:key_name])
71
69
  raise ArgumentError, 'key and key_name or key_secret are mutually exclusive. Provider either a key or key_name & key_secret'
72
70
  end
@@ -79,7 +77,7 @@ module Ably
79
77
  end
80
78
 
81
79
  if options[:client_id] == '*'
82
- raise ArgumentError, 'A client cannot be configured with a wildcard client_id'
80
+ raise ArgumentError, 'A client cannot be configured with a wildcard client_id, only a token can have a wildcard client_id privilege'
83
81
  end
84
82
 
85
83
  if has_client_id? && !token_creatable_externally? && !token_option
@@ -88,12 +86,16 @@ module Ably
88
86
  end
89
87
 
90
88
  # If a token details object or token string is provided in the initializer
91
- # then the client can be authorised immediately using this token
89
+ # then the client can be authorized immediately using this token
92
90
  if token_option
93
91
  token_details = convert_to_token_details(token_option)
94
92
  if token_details
95
- token_details = authorise_with_token(token_details)
96
- logger.debug "Auth: new token passed in to the initializer: #{token_details}"
93
+ begin
94
+ token_details = authorize_with_token(token_details)
95
+ logger.debug { "Auth: new token passed in to the initializer: #{token_details}" }
96
+ rescue StandardError => e
97
+ logger.error { "Auth: Implicit authorization using the provided token failed: #{e}" }
98
+ end
97
99
  end
98
100
  end
99
101
 
@@ -107,7 +109,6 @@ module Ably
107
109
  #
108
110
  # @param [Hash, nil] token_params the token params used for future token requests. When nil, previously configured token params are used
109
111
  # @param [Hash, nil] auth_options the authentication options used for future token requests. When nil, previously configure authentication options are used
110
- # @option auth_options [Boolean] :force obtains a new token even if the current token is valid. If the provided +auth_options+ Hash contains only this +:force+ attribute, the existing configured authentication options are not overwriten
111
112
  # @option (see #request_token)
112
113
  #
113
114
  # @return (see #create_token_request)
@@ -115,23 +116,29 @@ module Ably
115
116
  # @example
116
117
  # # will issue a simple token request using basic auth
117
118
  # client = Ably::Rest::Client.new(key: 'key.id:secret')
118
- # token_details = client.auth.authorise
119
+ # token_details = client.auth.authorize
119
120
  #
120
- # # will use token request from block to authorise if not already authorised
121
- # token_details = client.auth.authorise {}, auth_callback: Proc.new do
121
+ # # will use token request from block to authorize if not already authorized
122
+ # token_details = client.auth.authorize {}, auth_callback: Proc.new do
122
123
  # # create token_request object
123
124
  # token_request
124
125
  # end
125
126
  #
126
- def authorise(token_params = nil, auth_options = nil)
127
- if auth_options == { force: true }
128
- auth_options = options.merge(force: true)
129
- elsif auth_options.nil?
130
- auth_options = options
127
+ def authorize(token_params = nil, auth_options = nil)
128
+ if auth_options.nil?
129
+ auth_options = options # Use default options
130
+
131
+ if options.has_key?(:query_time)
132
+ @options = options.dup
133
+ # Query the server time only happens once
134
+ # the options remain in auth_options though so they are passed to request_token
135
+ @options.delete(:query_time)
136
+ @options.freeze
137
+ end
131
138
  else
132
139
  ensure_valid_auth_attributes auth_options
133
140
 
134
- auth_options = auth_options.clone
141
+ auth_options = auth_options.dup
135
142
 
136
143
  if auth_options[:token_params]
137
144
  token_params = auth_options.delete(:token_params).merge(token_params || {})
@@ -144,36 +151,39 @@ module Ably
144
151
  store_and_delete_basic_auth_key_from_options! auth_options
145
152
  end
146
153
 
147
- @options = auth_options.clone
154
+ @options = auth_options.dup
148
155
 
149
- # Force reauth and query the server time only happens once
150
- # the otpions remain in auth_options though so they are passed to request_token
156
+ # Query the server time only happens once
157
+ # the options remain in auth_options though so they are passed to request_token
151
158
  @options.delete(:query_time)
152
- @options.delete(:force)
153
159
 
154
160
  @options.freeze
155
161
  end
156
162
 
163
+ # Unless provided, defaults are used
157
164
  unless token_params.nil?
158
- @token_params = token_params
165
+ @token_params = token_params.dup
166
+ # Timestamp is only valid for this request
167
+ @token_params.delete(:timestamp)
159
168
  @token_params.freeze
160
169
  end
161
170
 
162
- if current_token_details && !auth_options[:force]
163
- return current_token_details unless current_token_details.expired?
164
- end
171
+ authorize_with_token(request_token(token_params || @token_params, auth_options)).tap do |new_token_details|
172
+ logger.debug { "Auth: new token following authorisation: #{new_token_details}" }
165
173
 
166
- authorise_with_token(request_token(@token_params, auth_options)).tap do |new_token_details|
167
- logger.debug "Auth: new token following authorisation: #{new_token_details}"
168
-
169
- # If authorise was forced allow a block to be called so that the realtime library
170
- # can force upgrade the authorisation
171
- if auth_options[:force] && block_given?
174
+ # If authorize the realtime library required auth, then yield the token in a block
175
+ if block_given?
172
176
  yield new_token_details
173
177
  end
174
178
  end
175
179
  end
176
180
 
181
+ # @deprecated Use {#authorize} instead
182
+ def authorise(*args, &block)
183
+ logger.warn { "Auth#authorise is deprecated and will be removed in 1.0. Please use Auth#authorize instead" }
184
+ authorize(*args, &block)
185
+ end
186
+
177
187
  # Request a {Ably::Models::TokenDetails} which can be used to make authenticated token based requests
178
188
  #
179
189
  # @param [Hash] auth_options (see #create_token_request)
@@ -216,9 +226,21 @@ module Ably
216
226
  auth_options = self.options.merge(auth_options)
217
227
 
218
228
  token_request = if auth_callback = auth_options.delete(:auth_callback)
219
- auth_callback.call(token_params)
229
+ begin
230
+ Timeout::timeout(client.auth_request_timeout) do
231
+ auth_callback.call(token_params)
232
+ end
233
+ rescue StandardError => err
234
+ raise Ably::Exceptions::AuthenticationFailed.new("auth_callback failed: #{err.message}", nil, nil, err, fallback_status: 500, fallback_code: 80019)
235
+ end
220
236
  elsif auth_url = auth_options.delete(:auth_url)
221
- token_request_from_auth_url(auth_url, auth_options, token_params)
237
+ begin
238
+ Timeout::timeout(client.auth_request_timeout) do
239
+ token_request_from_auth_url(auth_url, auth_options, token_params)
240
+ end
241
+ rescue StandardError => err
242
+ raise Ably::Exceptions::AuthenticationFailed.new("auth_url failed: #{err.message}", nil, nil, err, fallback_status: 500, fallback_code: 80019)
243
+ end
222
244
  else
223
245
  create_token_request(token_params, auth_options)
224
246
  end
@@ -263,7 +285,7 @@ module Ably
263
285
  def create_token_request(token_params = {}, auth_options = {})
264
286
  ensure_valid_auth_attributes auth_options
265
287
 
266
- auth_options = auth_options.clone
288
+ auth_options = auth_options.dup
267
289
  token_params = (auth_options[:token_params] || {}).merge(token_params)
268
290
 
269
291
  split_api_key_into_key_and_secret! auth_options if auth_options[:key]
@@ -276,20 +298,26 @@ module Ably
276
298
  timestamp = token_params.delete(:timestamp) || current_time
277
299
  timestamp = Time.at(timestamp) if timestamp.kind_of?(Integer)
278
300
 
279
- ttl = [
280
- (token_params[:ttl] || TOKEN_DEFAULTS.fetch(:ttl)),
281
- Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER + TOKEN_DEFAULTS.fetch(:renew_token_buffer) # never issue a token that will be immediately considered expired due to the buffer
282
- ].max
301
+
283
302
 
284
303
  token_request = {
285
304
  keyName: request_key_name,
286
- clientId: token_params[:client_id] || auth_options[:client_id] || client_id,
287
- ttl: (ttl * 1000).to_i,
288
305
  timestamp: (timestamp.to_f * 1000).round,
289
- capability: token_params[:capability] || TOKEN_DEFAULTS.fetch(:capability),
290
306
  nonce: token_params[:nonce] || SecureRandom.hex.force_encoding('UTF-8')
291
307
  }
292
308
 
309
+ token_client_id = token_params[:client_id] || auth_options[:client_id] || client_id
310
+ token_request[:clientId] = token_client_id if token_client_id
311
+
312
+ if token_params[:ttl]
313
+ token_ttl = [
314
+ token_params[:ttl],
315
+ Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER + TOKEN_DEFAULTS.fetch(:renew_token_buffer) # never issue a token that will be immediately considered expired due to the buffer
316
+ ].max
317
+ token_request[:ttl] = (token_ttl * 1000).to_i
318
+ end
319
+
320
+ token_request[:capability] = token_params[:capability] if token_params[:capability]
293
321
  if token_request[:capability].is_a?(Hash)
294
322
  lexicographic_ordered_capabilities = Hash[
295
323
  token_request[:capability].sort_by { |key, value| key }.map do |key, value|
@@ -347,7 +375,7 @@ module Ably
347
375
  end
348
376
 
349
377
  # Auth header string used in HTTP requests to Ably
350
- # Will reauthorise implicitly if required and capable
378
+ # Will reauthorize implicitly if required and capable
351
379
  #
352
380
  # @return [String] HTTP authentication value used in HTTP_AUTHORIZATION header
353
381
  def auth_header
@@ -359,7 +387,7 @@ module Ably
359
387
  end
360
388
 
361
389
  # Auth params used in URI endpoint for Realtime connections
362
- # Will reauthorise implicitly if required and capable
390
+ # Will reauthorize implicitly if required and capable
363
391
  #
364
392
  # @return [Hash] Auth params for a new Realtime connection
365
393
  def auth_params
@@ -427,7 +455,7 @@ module Ably
427
455
 
428
456
  # If client_id is defined and not a wildcard, prevent it changing, this is not supported
429
457
  if client_id && client_id != '*' && new_client_id != client_id
430
- raise Ably::Exceptions::IncompatibleClientId.new("Client ID is immutable once configured for a client. Client ID cannot be changed to '#{new_client_id}'", 400, 40012)
458
+ raise Ably::Exceptions::IncompatibleClientId.new("Client ID is immutable once configured for a client. Client ID cannot be changed to '#{new_client_id}'")
431
459
  end
432
460
  @client_id_validated = true
433
461
  @client_id = new_client_id
@@ -449,6 +477,14 @@ module Ably
449
477
  @token_option
450
478
  end
451
479
 
480
+ def authorize_when_necessary
481
+ if current_token_details && !current_token_details.expired?
482
+ return current_token_details
483
+ else
484
+ authorize
485
+ end
486
+ end
487
+
452
488
  # Returns the current device clock time unless the
453
489
  # the server time has previously been requested with query_time: true
454
490
  # and the @server_time_offset is configured
@@ -530,16 +566,17 @@ module Ably
530
566
  @key_secret = options.delete(:key_secret)
531
567
  end
532
568
 
533
- # Returns the current token if it exists or authorises and retrieves a token
569
+ # Returns the current token if it exists or authorizes and retrieves a token
534
570
  def token_auth_string
535
571
  if !current_token_details && token_option
572
+ logger.debug { "Auth: Token auth string missing, authorizing implicitly now" }
536
573
  # A TokenRequest was configured in the ClientOptions +:token field+ and no current token exists
537
574
  # Note: If a Token or TokenDetails is provided in the initializer, the token is stored in +current_token_details+
538
- authorise_with_token send_token_request(token_option)
575
+ authorize_with_token send_token_request(token_option)
539
576
  current_token_details.token
540
577
  else
541
- # Authorise will use the current token if one exists and is not expired, otherwise a new token will be issued
542
- authorise.token
578
+ # Authorize will use the current token if one exists and is not expired, otherwise a new token will be issued
579
+ authorize_when_necessary.token
543
580
  end
544
581
  end
545
582
 
@@ -587,7 +624,7 @@ module Ably
587
624
  )
588
625
  end
589
626
 
590
- # Retrieve a token request from a specified URL, expects a JSON response
627
+ # Retrieve a token request from a specified URL, expects a JSON or text response
591
628
  #
592
629
  # @return [Hash]
593
630
  def token_request_from_auth_url(auth_url, auth_options, token_params)
@@ -615,10 +652,10 @@ module Ably
615
652
  end
616
653
 
617
654
  # Use the provided token to authenticate immediately and store the token details in +current_token_details+
618
- def authorise_with_token(new_token_details)
655
+ def authorize_with_token(new_token_details)
619
656
  if new_token_details && !new_token_details.from_token_string?
620
657
  if !token_client_id_allowed?(new_token_details.client_id)
621
- raise Ably::Exceptions::IncompatibleClientId.new("Client ID '#{new_token_details.client_id}' in the token is incompatible with the current client ID '#{client_id}'", 400, 40012)
658
+ raise Ably::Exceptions::IncompatibleClientId.new("Client ID '#{new_token_details.client_id}' in the token is incompatible with the current client ID '#{client_id}'")
622
659
  end
623
660
  configure_client_id new_token_details.client_id
624
661
  end
@@ -645,7 +682,7 @@ module Ably
645
682
 
646
683
  response = client.post("/keys/#{token_request.key_name}/requestToken",
647
684
  token_request.attributes, send_auth_header: false,
648
- disable_automatic_reauthorise: true)
685
+ disable_automatic_reauthorize: true)
649
686
 
650
687
  Ably::Models::TokenDetails.new(response.body)
651
688
  end
@@ -1,5 +1,8 @@
1
1
  module Ably
2
2
  module Exceptions
3
+ TOKEN_EXPIRED_CODE = 40140..40149
4
+ INVALID_CLIENT_ID = 40012
5
+
3
6
  # Base Ably exception class that contains status and code values used by Ably
4
7
  # Refer to https://github.com/ably/ably-common/blob/master/protocol/errors.json
5
8
  #
@@ -11,10 +14,17 @@ module Ably
11
14
  # @return [String] Ably specific error code
12
15
  class BaseAblyException < StandardError
13
16
  attr_reader :status, :code
14
- def initialize(message, status = nil, code = nil)
17
+
18
+ def initialize(message, status = nil, code = nil, base_exception = nil, options = {})
15
19
  super message
20
+
21
+ @base_exception = base_exception
16
22
  @status = status
23
+ @status ||= base_exception.status if base_exception && base_exception.respond_to?(:status)
24
+ @status ||= options[:fallback_status]
17
25
  @code = code
26
+ @code ||= base_exception.code if base_exception && base_exception.respond_to?(:code)
27
+ @code ||= options[:fallback_code]
18
28
  end
19
29
 
20
30
  def to_s
@@ -23,10 +33,19 @@ module Ably
23
33
  additional_info = []
24
34
  additional_info << "code: #{code}" if code
25
35
  additional_info << "http status: #{status}" if status
36
+ additional_info << "base exception: #{@base_exception.class}" if @base_exception
26
37
  message << "(#{additional_info.join(', ')})"
27
38
  end
28
39
  message.join(' ')
29
40
  end
41
+
42
+ def as_json
43
+ {
44
+ message: "#{self.class}: #{message}",
45
+ status: @status,
46
+ code: @code
47
+ }.delete_if { |key, val| val.nil? }
48
+ end
30
49
  end
31
50
 
32
51
  # An invalid request was received by Ably
@@ -52,16 +71,15 @@ module Ably
52
71
 
53
72
  # Connection error from Realtime or REST service
54
73
  class ConnectionError < BaseAblyException
55
- def initialize(message, status = nil, code = nil, base_error = nil)
56
- super message, status, code
57
- @base_error = base_error
74
+ def initialize(message, status = nil, code = nil, base_exception = nil, options = {})
75
+ super message, status, code, base_exception, options
58
76
  end
59
77
 
60
78
  def to_s
61
79
  message = [super]
62
- if @base_error
63
- message << "#{@base_error}"
64
- if @base_error.respond_to?(:message) && @base_error.message.match(/certificate verify failed/i)
80
+ if @base_exception
81
+ message << "#{@base_exception}"
82
+ if @base_exception.respond_to?(:message) && @base_exception.message.match(/certificate verify failed/i)
65
83
  message << "See https://goo.gl/eKvfcR to resolve this issue."
66
84
  end
67
85
  end
@@ -84,9 +102,13 @@ module Ably
84
102
  # Connection failed
85
103
  class ConnectionFailed < ConnectionError; end
86
104
 
105
+ class AuthenticationFailed < ConnectionError; end
106
+
87
107
  # Invalid State Change error on a {https://github.com/gocardless/statesman Statesman State Machine}
88
108
  class InvalidStateChange < BaseAblyException; end
89
109
 
110
+ class InvalidState < BaseAblyException; end
111
+
90
112
  # A generic Ably exception taht supports a status & code.
91
113
  # See https://github.com/ably/ably-common/blob/master/protocol/errors.json for a list of Ably errors
92
114
  class Standard < BaseAblyException; end
@@ -121,6 +143,13 @@ module Ably
121
143
  # When a channel is detached / failed, certain operations are not permitted such as publishing messages
122
144
  class ChannelInactive < BaseAblyException; end
123
145
 
124
- class IncompatibleClientId < BaseAblyException; end
146
+ class IncompatibleClientId < BaseAblyException
147
+ def initialize(messages, status = 400, code = INVALID_CLIENT_ID, *args)
148
+ super(message, status, code, *args)
149
+ end
150
+ end
151
+
152
+ # Token request has missing or invalid attributes
153
+ class InvalidTokenRequest < BaseAblyException; end
125
154
  end
126
155
  end
data/lib/ably/logger.rb CHANGED
@@ -34,7 +34,16 @@ module Ably
34
34
  # @return {Integer}
35
35
  attr_reader :log_level
36
36
 
37
- def_delegators :logger, :fatal, :error, :warn, :info, :debug
37
+ # Catch exceptiosn in blocks passed to the logger, log the error and continue
38
+ %w(fatal error warn info debug).each do |method_name|
39
+ define_method(method_name) do |*args, &block|
40
+ begin
41
+ logger.public_send(method_name, *args, &block)
42
+ rescue StandardError => e
43
+ logger.error "Logger: Failed to log #{method_name} block - #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
44
+ end
45
+ end
46
+ end
38
47
 
39
48
  private
40
49
  def client