ably 0.8.7 → 0.8.8

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -3
  3. data/ably.gemspec +1 -1
  4. data/lib/ably/auth.rb +12 -3
  5. data/lib/ably/logger.rb +3 -1
  6. data/lib/ably/models/connection_details.rb +2 -1
  7. data/lib/ably/models/idiomatic_ruby_wrapper.rb +1 -1
  8. data/lib/ably/models/message.rb +4 -2
  9. data/lib/ably/models/message_encoders/cipher.rb +2 -2
  10. data/lib/ably/models/paginated_result.rb +27 -1
  11. data/lib/ably/models/presence_message.rb +3 -1
  12. data/lib/ably/models/{stat.rb → stats.rb} +5 -3
  13. data/lib/ably/modules/async_wrapper.rb +1 -1
  14. data/lib/ably/modules/channels_collection.rb +11 -1
  15. data/lib/ably/modules/enum.rb +18 -2
  16. data/lib/ably/modules/event_emitter.rb +3 -3
  17. data/lib/ably/modules/safe_deferrable.rb +1 -1
  18. data/lib/ably/modules/safe_yield.rb +2 -2
  19. data/lib/ably/modules/state_emitter.rb +8 -8
  20. data/lib/ably/modules/statesman_monkey_patch.rb +2 -2
  21. data/lib/ably/modules/uses_state_machine.rb +4 -2
  22. data/lib/ably/realtime.rb +1 -0
  23. data/lib/ably/realtime/auth.rb +6 -2
  24. data/lib/ably/realtime/channel.rb +6 -4
  25. data/lib/ably/realtime/channel/channel_manager.rb +7 -1
  26. data/lib/ably/realtime/client.rb +7 -12
  27. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +9 -2
  28. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +7 -1
  29. data/lib/ably/realtime/connection.rb +19 -8
  30. data/lib/ably/realtime/connection/connection_manager.rb +16 -9
  31. data/lib/ably/realtime/connection/websocket_transport.rb +12 -3
  32. data/lib/ably/realtime/presence.rb +6 -6
  33. data/lib/ably/realtime/presence/members_map.rb +21 -7
  34. data/lib/ably/rest/channel.rb +8 -8
  35. data/lib/ably/rest/client.rb +1 -1
  36. data/lib/ably/rest/middleware/exceptions.rb +3 -1
  37. data/lib/ably/rest/presence.rb +4 -4
  38. data/lib/ably/version.rb +1 -1
  39. data/spec/acceptance/realtime/channel_history_spec.rb +4 -4
  40. data/spec/acceptance/realtime/connection_failures_spec.rb +2 -4
  41. data/spec/acceptance/realtime/connection_spec.rb +46 -8
  42. data/spec/acceptance/realtime/presence_spec.rb +49 -34
  43. data/spec/acceptance/rest/auth_spec.rb +1 -1
  44. data/spec/acceptance/rest/base_spec.rb +1 -1
  45. data/spec/shared/safe_deferrable_behaviour.rb +4 -4
  46. data/spec/unit/models/message_encoders/cipher_spec.rb +1 -1
  47. data/spec/unit/models/token_details_spec.rb +20 -18
  48. data/spec/unit/modules/event_emitter_spec.rb +2 -2
  49. data/spec/unit/modules/state_emitter_spec.rb +6 -6
  50. data/spec/unit/realtime/channel_spec.rb +4 -4
  51. data/spec/unit/realtime/connection_spec.rb +1 -1
  52. data/spec/unit/realtime/presence_spec.rb +5 -5
  53. metadata +5 -5
@@ -90,7 +90,13 @@ module Ably::Modules
90
90
  end
91
91
 
92
92
  private
93
- attr_reader :by_index, :by_symbol
93
+ def by_index
94
+ @by_index
95
+ end
96
+
97
+ def by_symbol
98
+ @by_symbol
99
+ end
94
100
 
95
101
  # Define constants for each of the Enum values
96
102
  # e.g. define_constants(:dog) creates Enum::Dog
@@ -173,7 +179,17 @@ module Ably::Modules
173
179
  end
174
180
 
175
181
  private
176
- attr_reader :name, :index, :symbol
182
+ def name
183
+ @name
184
+ end
185
+
186
+ def index
187
+ @index
188
+ end
189
+
190
+ def symbol
191
+ @symbol
192
+ end
177
193
 
178
194
  define_values values
179
195
  end
@@ -39,7 +39,7 @@ module Ably
39
39
 
40
40
  # Ensure @event_emitter_coerce_proc option is passed down to any classes that inherit the class with callbacks
41
41
  def inherited(subclass)
42
- subclass.instance_variable_set('@event_emitter_coerce_proc', @event_emitter_coerce_proc)
42
+ subclass.instance_variable_set('@event_emitter_coerce_proc', @event_emitter_coerce_proc) if defined?(@event_emitter_coerce_proc)
43
43
  super
44
44
  end
45
45
  end
@@ -90,7 +90,7 @@ module Ably
90
90
  clone.
91
91
  select do |proc_hash|
92
92
  if proc_hash[:unsafe]
93
- proc_hash[:emit_proc].call *args
93
+ proc_hash[:emit_proc].call(*args)
94
94
  else
95
95
  safe_yield proc_hash[:emit_proc], *args
96
96
  end
@@ -133,7 +133,7 @@ module Ably
133
133
  def proc_for_block(block, options = {})
134
134
  {
135
135
  emit_proc: Proc.new do |*args|
136
- block.call *args
136
+ block.call(*args)
137
137
  true if options[:delete_once_run]
138
138
  end,
139
139
  block: block,
@@ -59,7 +59,7 @@ module Ably::Modules
59
59
 
60
60
  private
61
61
  def safe_deferrable_block(*args)
62
- yield *args
62
+ yield(*args)
63
63
  rescue StandardError => e
64
64
  message = "An exception in a Deferrable callback was caught. #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
65
65
  if defined?(:logger) && logger.respond_to?(:error)
@@ -13,7 +13,7 @@ module Ably::Modules
13
13
  private
14
14
 
15
15
  def safe_yield(block, *args)
16
- block.call *args
16
+ block.call(*args)
17
17
  rescue StandardError => e
18
18
  message = "An exception in an external block was caught. #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
19
19
  safe_yield_log_error message
@@ -23,7 +23,7 @@ module Ably::Modules
23
23
  if defined?(:logger) && logger.respond_to?(:error)
24
24
  return logger.error message
25
25
  end
26
- rescue StandardError => e
26
+ rescue StandardError
27
27
  fallback_logger.error message
28
28
  end
29
29
 
@@ -82,21 +82,21 @@ module Ably::Modules
82
82
 
83
83
  success_wrapper = Proc.new do
84
84
  yield
85
- off &success_wrapper
86
- off &failure_wrapper if failure_wrapper
85
+ off(&success_wrapper)
86
+ off(&failure_wrapper) if failure_wrapper
87
87
  end
88
88
 
89
89
  failure_wrapper = proc do |*args|
90
- failure_block.call *args
91
- off &success_wrapper
92
- off &failure_wrapper
90
+ failure_block.call(*args)
91
+ off(&success_wrapper)
92
+ off(&failure_wrapper)
93
93
  end if failure_block
94
94
 
95
95
  Array(target_states).each do |target_state|
96
96
  safe_unsafe_method options[:unsafe], :once, target_state, &success_wrapper
97
97
 
98
98
  safe_unsafe_method options[:unsafe], :once_state_changed do |*args|
99
- failure_wrapper.call *args unless state == target_state
99
+ failure_wrapper.call(*args) unless state == target_state
100
100
  end if failure_block
101
101
  end
102
102
  end
@@ -119,8 +119,8 @@ module Ably::Modules
119
119
  raise ArgumentError, 'Block required' unless block_given?
120
120
 
121
121
  once_block = proc do |*args|
122
- off *self.class::STATE.map, &once_block
123
- yield *args
122
+ off(*self.class::STATE.map, &once_block)
123
+ yield(*args)
124
124
  end
125
125
 
126
126
  safe_unsafe_method options[:unsafe], :once, *self.class::STATE.map, &once_block
@@ -5,7 +5,7 @@ module Ably::Modules
5
5
  # This can be removed once https://github.com/gocardless/statesman/issues/95 is solved
6
6
  def before_transition(options = nil, &block)
7
7
  arrayify_transition(options) do |options_without_from_array|
8
- super *options_without_from_array, &block
8
+ super(*options_without_from_array, &block)
9
9
  end
10
10
  end
11
11
 
@@ -13,7 +13,7 @@ module Ably::Modules
13
13
  # This can be removed once https://github.com/gocardless/statesman/issues/95 is solved
14
14
  def after_transition(options = nil, &block)
15
15
  arrayify_transition(options) do |options_without_from_array|
16
- super *options_without_from_array, &block
16
+ super(*options_without_from_array, &block)
17
17
  end
18
18
  end
19
19
 
@@ -59,7 +59,9 @@ module Ably::Modules
59
59
  def_delegators :state_machine, :can_transition_to?
60
60
 
61
61
  private
62
- attr_reader :state_machine
62
+ def state_machine
63
+ @state_machine
64
+ end
63
65
 
64
66
  def_delegators :state_machine, :exception_for_state_change_to
65
67
 
@@ -85,7 +87,7 @@ module Ably::Modules
85
87
 
86
88
  module ClassMethods
87
89
  def emits_klass
88
- @emits_klass ||= if @emits_klass_name
90
+ @emits_klass ||= if defined?(@emits_klass_name) && @emits_klass_name
89
91
  get_const(@emits_klass_name)
90
92
  end
91
93
  end
@@ -1,5 +1,6 @@
1
1
  require 'eventmachine'
2
2
  require 'websocket/driver'
3
+ require 'em-http-request'
3
4
 
4
5
  require 'ably/modules/event_emitter'
5
6
 
@@ -189,9 +189,13 @@ module Ably
189
189
  private
190
190
  # The synchronous Auth class instanced by the Rest client
191
191
  # @return [Ably::Auth]
192
- attr_reader :auth_sync
192
+ def auth_sync
193
+ @auth_sync
194
+ end
193
195
 
194
- attr_reader :client
196
+ def client
197
+ @client
198
+ end
195
199
  end
196
200
  end
197
201
  end
@@ -294,7 +294,9 @@ module Ably
294
294
  private :change_state
295
295
 
296
296
  private
297
- attr_reader :queue
297
+ def queue
298
+ @queue
299
+ end
298
300
 
299
301
  def setup_event_handlers
300
302
  __incoming_msgbus__.subscribe(:message) do |message|
@@ -323,7 +325,7 @@ module Ably
323
325
  end
324
326
  end
325
327
 
326
- queue.push *messages
328
+ queue.push(*messages)
327
329
 
328
330
  if attached?
329
331
  process_queue
@@ -385,8 +387,8 @@ module Ably
385
387
  end
386
388
 
387
389
  def create_message(message)
388
- Ably::Models::Message(message.dup).tap do |message|
389
- message.encode self
390
+ Ably::Models::Message(message.dup).tap do |msg|
391
+ msg.encode self
390
392
  end
391
393
  end
392
394
 
@@ -94,8 +94,14 @@ module Ably::Realtime
94
94
  end
95
95
 
96
96
  private
97
+ def channel
98
+ @channel
99
+ end
100
+
101
+ def connection
102
+ @connection
103
+ end
97
104
 
98
- attr_reader :channel, :connection
99
105
  def_delegators :channel, :can_transition_to?
100
106
 
101
107
  # If the connection has not previously connected, connect now
@@ -90,13 +90,13 @@ module Ably
90
90
  @auth = Ably::Realtime::Auth.new(self)
91
91
  @channels = Ably::Realtime::Channels.new(self)
92
92
  @connection = Ably::Realtime::Connection.new(self, options)
93
- @echo_messages = @rest_client.options.fetch(:echo_messages, true) == false ? false : true
94
- @queue_messages = @rest_client.options.fetch(:queue_messages, true) == false ? false : true
95
- @custom_realtime_host = @rest_client.options[:realtime_host] || @rest_client.options[:ws_host]
96
- @auto_connect = @rest_client.options.fetch(:auto_connect, true) == false ? false : true
97
- @recover = @rest_client.options[:recover]
93
+ @echo_messages = rest_client.options.fetch(:echo_messages, true) == false ? false : true
94
+ @queue_messages = rest_client.options.fetch(:queue_messages, true) == false ? false : true
95
+ @custom_realtime_host = rest_client.options[:realtime_host] || rest_client.options[:ws_host]
96
+ @auto_connect = rest_client.options.fetch(:auto_connect, true) == false ? false : true
97
+ @recover = rest_client.options[:recover]
98
98
 
99
- raise ArgumentError, "Recovery key is invalid" if @recover && !@recover.match(Connection::RECOVER_REGEX)
99
+ raise ArgumentError, "Recovery key '#{recover}' is invalid" if recover && !recover.match(Connection::RECOVER_REGEX)
100
100
  end
101
101
 
102
102
  # Return a {Ably::Realtime::Channel Realtime Channel} for the given name
@@ -150,11 +150,6 @@ module Ably
150
150
  endpoint_for_host(custom_realtime_host || [environment, DOMAIN].compact.join('-'))
151
151
  end
152
152
 
153
-
154
- def connection
155
- @connection
156
- end
157
-
158
153
  # (see Ably::Rest::Client#register_encoder)
159
154
  def register_encoder(encoder)
160
155
  rest_client.register_encoder encoder
@@ -176,7 +171,7 @@ module Ably
176
171
  # @return [URI::Generic] Fallback endpoint used to connect to the realtime Ably service. Note, after each connection attempt, a new random {Ably::FALLBACK_HOSTS fallback host} is used
177
172
  # @api private
178
173
  def fallback_endpoint
179
- unless @fallback_endpoints
174
+ unless defined?(@fallback_endpoints) && @fallback_endpoints
180
175
  @fallback_endpoints = Ably::FALLBACK_HOSTS.shuffle.map { |fallback_host| endpoint_for_host(fallback_host) }
181
176
  end
182
177
 
@@ -13,7 +13,13 @@ module Ably::Realtime
13
13
  end
14
14
 
15
15
  private
16
- attr_reader :client, :connection
16
+ def client
17
+ @client
18
+ end
19
+
20
+ def connection
21
+ @connection
22
+ end
17
23
 
18
24
  def channels
19
25
  client.channels
@@ -144,6 +150,7 @@ module Ably::Realtime
144
150
  def process_connected_message(protocol_message)
145
151
  if client.auth.token_client_id_allowed?(protocol_message.connection_details.client_id)
146
152
  client.auth.configure_client_id protocol_message.connection_details.client_id
153
+ client.connection.set_connection_details protocol_message.connection_details
147
154
  connection.transition_state_machine :connected, reason: protocol_message.error, protocol_message: protocol_message
148
155
  else
149
156
  reason = Ably::Exceptions::IncompatibleClientId.new("Client ID '#{protocol_message.connection_details.client_id}' specified by the server is incompatible with the library's configured client ID '#{client.client_id}'", 400, 40012)
@@ -195,7 +202,7 @@ module Ably::Realtime
195
202
 
196
203
  def subscribe_to_incoming_protocol_messages
197
204
  connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |*args|
198
- dispatch_protocol_message *args
205
+ dispatch_protocol_message(*args)
199
206
  end
200
207
  end
201
208
  end
@@ -17,7 +17,13 @@ module Ably::Realtime
17
17
  end
18
18
 
19
19
  private
20
- attr_reader :client, :connection
20
+ def client
21
+ @client
22
+ end
23
+
24
+ def connection
25
+ @connection
26
+ end
21
27
 
22
28
  def can_send_messages?
23
29
  connection.connected? || connection.closing?
@@ -58,7 +58,7 @@ module Ably
58
58
  ensure_state_machine_emits 'Ably::Models::ConnectionStateChange'
59
59
 
60
60
  # Expected format for a connection recover key
61
- RECOVER_REGEX = /^(?<recover>[\w-]+):(?<connection_serial>\-?\w+)$/
61
+ RECOVER_REGEX = /^(?<recover>[\w!-]+):(?<connection_serial>\-?\w+)$/
62
62
 
63
63
  # Defaults for automatic connection recovery and timeouts
64
64
  DEFAULTS = {
@@ -84,6 +84,10 @@ module Ably
84
84
  # @return [Ably::Models::ErrorInfo,Ably::Exceptions::BaseAblyException]
85
85
  attr_reader :error_reason
86
86
 
87
+ # Connection details of the currently established connection
88
+ # @return [Ably::Models::ConnectionDetails]
89
+ attr_reader :details
90
+
87
91
  # {Ably::Realtime::Client} associated with this connection
88
92
  # @return [Ably::Realtime::Client]
89
93
  attr_reader :client
@@ -178,12 +182,12 @@ module Ably
178
182
 
179
183
  once(:connected) do
180
184
  deferrable.succeed
181
- off &fail_callback
185
+ off(&fail_callback)
182
186
  end
183
187
 
184
188
  once(:failed, :closed, :closing) do
185
189
  deferrable.fail
186
- off &succeed_callback
190
+ off(&succeed_callback)
187
191
  end
188
192
  end
189
193
  end
@@ -356,10 +360,10 @@ module Ably
356
360
  # @api private
357
361
  def send_protocol_message(protocol_message)
358
362
  add_message_serial_if_ack_required_to(protocol_message) do
359
- Ably::Models::ProtocolMessage.new(protocol_message, logger: logger).tap do |protocol_message|
360
- add_message_to_outgoing_queue protocol_message
361
- notify_message_dispatcher_of_new_message protocol_message
362
- logger.debug("Connection: Prot msg queued =>: #{protocol_message.action} #{protocol_message}")
363
+ Ably::Models::ProtocolMessage.new(protocol_message, logger: logger).tap do |message|
364
+ add_message_to_outgoing_queue message
365
+ notify_message_dispatcher_of_new_message message
366
+ logger.debug("Connection: Prot msg queued =>: #{message.action} #{message}")
363
367
  end
364
368
  end
365
369
  end
@@ -437,6 +441,11 @@ module Ably
437
441
  @error_reason = nil
438
442
  end
439
443
 
444
+ # @api private
445
+ def set_connection_details(connection_details)
446
+ @details = connection_details
447
+ end
448
+
440
449
  # Executes registered callbacks for a successful connection resume event
441
450
  # @api private
442
451
  def resumed
@@ -476,7 +485,9 @@ module Ably
476
485
  # A connection serial guarantees the server has received the message and is thus used for connection
477
486
  # recovery and resumes.
478
487
  # @return [Integer] starting at -1 indicating no messages sent, 0 when the first message is sent
479
- attr_reader :client_serial
488
+ def client_serial
489
+ @client_serial
490
+ end
480
491
 
481
492
  def resume_callbacks
482
493
  @resume_callbacks ||= []
@@ -1,3 +1,5 @@
1
+ require 'ably/rest/middleware/exceptions'
2
+
1
3
  module Ably::Realtime
2
4
  class Connection
3
5
  # ConnectionManager is responsible for all actions relating to underlying connection and transports,
@@ -10,12 +12,13 @@ module Ably::Realtime
10
12
  class ConnectionManager
11
13
  # Error codes from the server that can potentially be resolved
12
14
  RESOLVABLE_ERROR_CODES = {
13
- token_expired: 40140
15
+ token_expired: Ably::Rest::Middleware::Exceptions::TOKEN_EXPIRED_CODE
14
16
  }
15
17
 
16
18
  def initialize(connection)
17
- @connection = connection
18
- @timers = Hash.new { |hash, key| hash[key] = [] }
19
+ @connection = connection
20
+ @timers = Hash.new { |hash, key| hash[key] = [] }
21
+ @renewing_token = false
19
22
 
20
23
  connection.unsafe_on(:closed) do
21
24
  connection.reset_resume_info
@@ -58,7 +61,7 @@ module Ably::Realtime
58
61
  end
59
62
 
60
63
  logger.debug "ConnectionManager: Setting up automatic connection timeout timer for #{realtime_request_timeout}s"
61
- create_timeout_timer_whilst_in_state(:connect, realtime_request_timeout) do
64
+ create_timeout_timer_whilst_in_state(:connecting, realtime_request_timeout) do
62
65
  connection_opening_failed Ably::Exceptions::ConnectionTimeout.new("Connection to Ably timed out after #{realtime_request_timeout}s", nil, 80014)
63
66
  end
64
67
  end
@@ -123,7 +126,7 @@ module Ably::Realtime
123
126
  def close_connection
124
127
  connection.send_protocol_message(action: Ably::Models::ProtocolMessage::ACTION.Close)
125
128
 
126
- create_timeout_timer_whilst_in_state(:close, realtime_request_timeout) do
129
+ create_timeout_timer_whilst_in_state(:closing, realtime_request_timeout) do
127
130
  force_close_connection if connection.closing?
128
131
  end
129
132
  end
@@ -153,7 +156,7 @@ module Ably::Realtime
153
156
  return unless can_retry_connection? # do not always reattempt connection or change state as client may be re-authorising
154
157
 
155
158
  if error.kind_of?(Ably::Models::ErrorInfo)
156
- if error.code == RESOLVABLE_ERROR_CODES.fetch(:token_expired)
159
+ if RESOLVABLE_ERROR_CODES.fetch(:token_expired).include?(error.code)
157
160
  next_state = get_next_retry_state_info
158
161
  logger.debug "ConnectionManager: Transport disconnected because of token expiry, pausing #{next_state.fetch(:pause)}s before reattempting to connect"
159
162
  EventMachine.add_timer(next_state.fetch(:pause)) { renew_token_and_reconnect error }
@@ -181,7 +184,7 @@ module Ably::Realtime
181
184
  logger.debug "ConnectionManager: Transport disconnected whilst connection in #{connection.state} state"
182
185
  end
183
186
 
184
- if error.kind_of?(Ably::Models::ErrorInfo) && error.code != RESOLVABLE_ERROR_CODES.fetch(:token_expired)
187
+ if error.kind_of?(Ably::Models::ErrorInfo) && !RESOLVABLE_ERROR_CODES.fetch(:token_expired).include?(error.code)
185
188
  connection.emit :error, error
186
189
  logger.error "ConnectionManager: Error in Disconnected ProtocolMessage received from the server - #{error}"
187
190
  end
@@ -213,11 +216,15 @@ module Ably::Realtime
213
216
  end
214
217
 
215
218
  private
216
- attr_reader :connection
219
+ def connection
220
+ @connection
221
+ end
217
222
 
218
223
  # Timers used to manage connection state, for internal use by the client library
219
224
  # @return [Hash]
220
- attr_reader :timers
225
+ def timers
226
+ @timers
227
+ end
221
228
 
222
229
  def transport
223
230
  connection.transport