ably-rest 1.2.5 → 1.2.7

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -8
  3. data/lib/submodules/ably-ruby/.github/workflows/check.yml +2 -1
  4. data/lib/submodules/ably-ruby/CHANGELOG.md +25 -0
  5. data/lib/submodules/ably-ruby/README.md +24 -7
  6. data/lib/submodules/ably-ruby/SPEC.md +1738 -869
  7. data/lib/submodules/ably-ruby/ably.gemspec +1 -1
  8. data/lib/submodules/ably-ruby/lib/ably/auth.rb +19 -11
  9. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +4 -26
  10. data/lib/submodules/ably-ruby/lib/ably/modules/safe_deferrable.rb +2 -2
  11. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +1 -1
  12. data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +4 -0
  13. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +51 -48
  14. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_properties.rb +9 -0
  15. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +2 -0
  16. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +4 -3
  17. data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +20 -0
  18. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -13
  19. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +14 -6
  20. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +21 -22
  21. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +77 -109
  22. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +41 -92
  23. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_manager.rb +12 -17
  24. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +15 -6
  25. data/lib/submodules/ably-ruby/lib/ably/realtime/push.rb +0 -27
  26. data/lib/submodules/ably-ruby/lib/ably/realtime/recovery_key_context.rb +36 -0
  27. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +4 -6
  28. data/lib/submodules/ably-ruby/lib/ably/rest/push/admin.rb +1 -1
  29. data/lib/submodules/ably-ruby/lib/ably/rest/push.rb +0 -19
  30. data/lib/submodules/ably-ruby/lib/ably/util/ably_extensions.rb +29 -0
  31. data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +2 -2
  32. data/lib/submodules/ably-ruby/lib/ably/util/safe_deferrable.rb +1 -1
  33. data/lib/submodules/ably-ruby/lib/ably/version.rb +5 -7
  34. data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +0 -7
  35. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +8 -12
  36. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +474 -300
  37. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +1 -1
  38. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +8 -25
  39. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +28 -120
  40. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +24 -53
  41. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +123 -92
  42. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +2 -2
  43. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +9 -2
  44. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +8 -11
  45. data/lib/submodules/ably-ruby/spec/acceptance/rest/push_admin_spec.rb +20 -15
  46. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +1 -1
  47. data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +1 -1
  48. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +0 -86
  49. data/lib/submodules/ably-ruby/spec/unit/models/token_details_spec.rb +4 -2
  50. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +1 -1
  51. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +0 -30
  52. data/lib/submodules/ably-ruby/spec/unit/realtime/recovery_key_context_spec.rb +36 -0
  53. data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +15 -15
  54. metadata +5 -4
  55. data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_spec.rb +0 -27
  56. data/lib/submodules/ably-ruby/spec/acceptance/rest/push_spec.rb +0 -25
@@ -38,7 +38,7 @@ Gem::Specification.new do |spec|
38
38
  spec.add_development_dependency 'rspec-instafail', '~> 1.0'
39
39
  spec.add_development_dependency 'bundler', '>= 1.3.0'
40
40
  spec.add_development_dependency 'webmock', '~> 3.11'
41
- spec.add_development_dependency 'simplecov', '~> 0.21.2'
41
+ spec.add_development_dependency 'simplecov', '~> 0.22.0'
42
42
  spec.add_development_dependency 'simplecov-lcov', '~> 0.8.0'
43
43
  spec.add_development_dependency 'parallel_tests', '~> 3.8'
44
44
  spec.add_development_dependency 'pry', '~> 0.14.1'
@@ -414,13 +414,20 @@ module Ably
414
414
  #
415
415
  # @return [Hash] headers
416
416
  def extra_auth_headers
417
- if client_id && using_basic_auth?
418
- { 'X-Ably-ClientId' => Base64.urlsafe_encode64(client_id) }
417
+ if client_id_for_request
418
+ { 'X-Ably-ClientId' => Base64.urlsafe_encode64(client_id_for_request) }
419
419
  else
420
420
  {}
421
421
  end
422
422
  end
423
423
 
424
+ # ClientId that needs to be included with every rest/realtime request
425
+ # spec - RSA7e
426
+ # @return string
427
+ def client_id_for_request
428
+ options[:client_id]
429
+ end
430
+
424
431
  # Auth params used in URI endpoint for Realtime connections
425
432
  # Will reauthorize implicitly if required and capable
426
433
  #
@@ -482,15 +489,16 @@ module Ably
482
489
  #
483
490
  # @api private
484
491
  def configure_client_id(new_client_id)
485
- # If new client ID from Ably is a wildcard, but preconfigured clientId is set, then keep the existing clientId
486
- if has_client_id? && new_client_id == '*'
487
- @client_id_validated = true
488
- return
489
- end
490
-
491
- # If client_id is defined and not a wildcard, prevent it changing, this is not supported
492
- if client_id && client_id != '*' && new_client_id != client_id
493
- raise Ably::Exceptions::IncompatibleClientId.new("Client ID is immutable once configured for a client. Client ID cannot be changed to '#{new_client_id}'")
492
+ if has_client_id?
493
+ # If new client ID from Ably is a wildcard, but preconfigured clientId is set, then keep the existing clientId
494
+ if new_client_id == "*"
495
+ @client_id_validated = true
496
+ return
497
+ end
498
+ # If client_id is defined and not a wildcard, prevent it changing, this is not supported
499
+ if new_client_id != client_id
500
+ raise Ably::Exceptions::IncompatibleClientId.new("Client ID is immutable once configured for a client. Client ID cannot be changed to '#{new_client_id}'")
501
+ end
494
502
  end
495
503
  @client_id_validated = true
496
504
  @client_id = new_client_id
@@ -20,8 +20,6 @@ module Ably::Models
20
20
  # @return [String] Contains a serial number for a message on the current channel
21
21
  # @!attribute [r] connection_id
22
22
  # @return [String] Contains a string private connection key used to recover this connection
23
- # @!attribute [r] connection_serial
24
- # @return [Bignum] Contains a serial number for a message sent from the server to the client
25
23
  # @!attribute [r] message_serial
26
24
  # @return [Bignum] Contains a serial number for a message sent from the client to the server
27
25
  # @!attribute [r] timestamp
@@ -129,12 +127,6 @@ module Ably::Models
129
127
  raise TypeError, "msg_serial '#{attributes[:msg_serial]}' is invalid, a positive Integer is expected for a ProtocolMessage"
130
128
  end
131
129
 
132
- def connection_serial
133
- Integer(attributes[:connection_serial])
134
- rescue TypeError
135
- raise TypeError, "connection_serial '#{attributes[:connection_serial]}' is invalid, a positive Integer is expected for a ProtocolMessage"
136
- end
137
-
138
130
  def count
139
131
  [1, attributes[:count].to_i].max
140
132
  end
@@ -146,26 +138,12 @@ module Ably::Models
146
138
  false
147
139
  end
148
140
 
149
- # @api private
150
- def has_connection_serial?
151
- connection_serial && true
141
+ def has_channel_serial?
142
+ channel_serial && true
152
143
  rescue TypeError
153
144
  false
154
145
  end
155
146
 
156
- def serial
157
- if has_connection_serial?
158
- connection_serial
159
- else
160
- message_serial
161
- end
162
- end
163
-
164
- # @api private
165
- def has_serial?
166
- has_connection_serial? || has_message_serial?
167
- end
168
-
169
147
  def messages
170
148
  @messages ||=
171
149
  Array(attributes[:messages]).map do |message|
@@ -271,7 +249,6 @@ module Ably::Models
271
249
  # Return a JSON ready object from the underlying #attributes using Ably naming conventions for keys
272
250
  def as_json(*args)
273
251
  raise TypeError, ':action is missing, cannot generate a valid Hash for ProtocolMessage' unless action
274
- raise TypeError, ':msg_serial or :connection_serial is missing, cannot generate a valid Hash for ProtocolMessage' if ack_required? && !has_serial?
275
252
 
276
253
  attributes.dup.tap do |hash_object|
277
254
  hash_object['action'] = action.to_i
@@ -296,11 +273,12 @@ module Ably::Models
296
273
  end
297
274
 
298
275
  # True if the ProtocolMessage appears to be invalid, however this is not a guarantee
276
+ # Used for validating incoming protocol messages, so no need to add unnecessary checks
299
277
  # @return [Boolean]
300
278
  # @api private
301
279
  def invalid?
302
280
  action_enum = action rescue nil
303
- !action_enum || (ack_required? && !has_serial?)
281
+ !action_enum
304
282
  end
305
283
 
306
284
  # @!attribute [r] logger
@@ -39,7 +39,7 @@ module Ably::Modules
39
39
  end
40
40
  end
41
41
 
42
- # Mark the Deferrable as succeeded and trigger all callbacks.
42
+ # Mark the Deferrable as succeeded and trigger all success callbacks.
43
43
  # See http://www.rubydoc.info/gems/eventmachine/1.0.7/EventMachine/Deferrable#succeed-instance_method
44
44
  #
45
45
  # @return [void]
@@ -48,7 +48,7 @@ module Ably::Modules
48
48
  super(*args)
49
49
  end
50
50
 
51
- # Mark the Deferrable as failed and trigger all callbacks.
51
+ # Mark the Deferrable as failed and trigger all error callbacks.
52
52
  # See http://www.rubydoc.info/gems/eventmachine/1.0.7/EventMachine/Deferrable#fail-instance_method
53
53
  #
54
54
  # @return [void]
@@ -3,7 +3,7 @@ module Ably::Modules
3
3
  # the instance variable @state is used exclusively, the {Enum} STATE is defined prior to inclusion of this
4
4
  # module, and the class is an {EventEmitter}. It then emits state changes.
5
5
  #
6
- # It also ensures the EventEmitter is configured to retrict permitted events to the
6
+ # It also ensures the EventEmitter is configured to restrict permitted events to the
7
7
  # the available STATEs or EVENTs if defined i.e. if EVENTs includes an additional type such as
8
8
  # :update, then it will support all EVENTs being emitted. EVENTs must be a superset of STATEs
9
9
  #
@@ -226,6 +226,10 @@ module Ably
226
226
  auth_sync.auth_header
227
227
  end
228
228
 
229
+ def client_id_for_request_sync
230
+ auth_sync.client_id_for_request
231
+ end
232
+
229
233
  # Auth params used in URI endpoint for Realtime connections
230
234
  # Will reauthorize implicitly if required and capable
231
235
  #
@@ -18,7 +18,7 @@ module Ably::Realtime
18
18
  def attach
19
19
  if can_transition_to?(:attached)
20
20
  connect_if_connection_initialized
21
- send_attach_protocol_message
21
+ send_attach_protocol_message if connection.state?(:connected) # RTL4i
22
22
  end
23
23
  end
24
24
 
@@ -34,9 +34,9 @@ module Ably::Realtime
34
34
  # Channel is attached, notify presence if sync is expected
35
35
  def attached(attached_protocol_message)
36
36
  # If no attached ProtocolMessage then this attached request was triggered by the client
37
- # library, such as returning to attached whne detach has failed
37
+ # library, such as returning to attached when detach has failed
38
38
  if attached_protocol_message
39
- update_presence_sync_state_following_attached attached_protocol_message
39
+ channel.presence.manager.on_attach attached_protocol_message.has_presence_flag?
40
40
  channel.properties.set_attach_serial(attached_protocol_message.channel_serial)
41
41
  channel.options.set_modes_from_flags(attached_protocol_message.flags)
42
42
  channel.options.set_params(attached_protocol_message.params)
@@ -49,17 +49,16 @@ module Ably::Realtime
49
49
  end
50
50
 
51
51
  # Request channel to be reattached by sending an attach protocol message
52
- # @param [Hash] options
53
- # @option options [Ably::Models::ErrorInfo] :reason
54
- def request_reattach(options = {})
55
- reason = options[:reason]
56
- send_attach_protocol_message
57
- logger.debug { "Explicit channel reattach request sent to Ably due to #{reason}" }
52
+ # @param [Ably::Models::ErrorInfo] reason
53
+ def request_reattach(reason = nil)
58
54
  channel.set_channel_error_reason(reason) if reason
59
55
  channel.transition_state_machine! :attaching, reason: reason unless channel.attaching?
56
+ send_attach_protocol_message
57
+ logger.debug { "Explicit channel reattach request sent to Ably due to #{reason}" }
60
58
  end
61
59
 
62
60
  def duplicate_attached_received(protocol_message)
61
+ logger.debug { "Server initiated ATTACHED message received for channel '#{channel.name}' with state #{channel.state}" }
63
62
  if protocol_message.error
64
63
  channel.set_channel_error_reason protocol_message.error
65
64
  log_channel_error protocol_message.error
@@ -68,9 +67,7 @@ module Ably::Realtime
68
67
  channel.properties.set_attach_serial(protocol_message.channel_serial)
69
68
  channel.options.set_modes_from_flags(protocol_message.flags)
70
69
 
71
- if protocol_message.has_channel_resumed_flag?
72
- logger.debug { "ChannelManager: Additional resumed ATTACHED message received for #{channel.state} channel '#{channel.name}'" }
73
- else
70
+ unless protocol_message.has_channel_resumed_flag?
74
71
  channel.emit :update, Ably::Models::ChannelStateChange.new(
75
72
  current: channel.state,
76
73
  previous: channel.state,
@@ -78,7 +75,7 @@ module Ably::Realtime
78
75
  reason: protocol_message.error,
79
76
  resumed: false,
80
77
  )
81
- update_presence_sync_state_following_attached protocol_message
78
+ channel.presence.manager.on_attach protocol_message.has_presence_flag?
82
79
  end
83
80
  end
84
81
 
@@ -170,6 +167,12 @@ module Ably::Realtime
170
167
  end
171
168
  end
172
169
 
170
+ # RTL13c
171
+ def notify_state_change
172
+ @pending_state_change_timer.cancel if @pending_state_change_timer
173
+ @pending_state_change_timer = nil
174
+ end
175
+
173
176
  private
174
177
  attr_reader :pending_state_change_timer
175
178
 
@@ -209,56 +212,56 @@ module Ably::Realtime
209
212
  message_options[:flags] = message_options[:flags].to_i | Ably::Models::ProtocolMessage::ATTACH_FLAGS_MAPPING[:resume]
210
213
  end
211
214
 
212
- send_state_change_protocol_message Ably::Models::ProtocolMessage::ACTION.Attach, :suspended, message_options
213
- end
214
-
215
- def send_detach_protocol_message(previous_state)
216
- send_state_change_protocol_message Ably::Models::ProtocolMessage::ACTION.Detach, previous_state # return to previous state if failed
217
- end
215
+ message_options[:channelSerial] = channel.properties.channel_serial # RTL4c1
218
216
 
219
- def send_state_change_protocol_message(new_state, state_if_failed, message_options = {})
220
217
  state_at_time_of_request = channel.state
218
+ attach_action = Ably::Models::ProtocolMessage::ACTION.Attach
219
+ # RTL4f
221
220
  @pending_state_change_timer = EventMachine::Timer.new(realtime_request_timeout) do
222
221
  if channel.state == state_at_time_of_request
223
- error = Ably::Models::ErrorInfo.new(code: Ably::Exceptions::Codes::CHANNEL_OPERATION_FAILED_NO_RESPONSE_FROM_SERVER, message: "Channel #{new_state} operation failed (timed out)")
224
- channel.transition_state_machine state_if_failed, reason: error
222
+ error = Ably::Models::ErrorInfo.new(code: Ably::Exceptions::Codes::CHANNEL_OPERATION_FAILED_NO_RESPONSE_FROM_SERVER, message: "Channel #{attach_action} operation failed (timed out)")
223
+ channel.transition_state_machine :suspended, reason: error # return to suspended state if failed
225
224
  end
226
225
  end
226
+ # Shouldn't queue attach message as per RTL4i, so message is added top of the queue
227
+ # to be sent immediately while processing next message
228
+ connection.send_protocol_message_immediately(
229
+ action: attach_action.to_i,
230
+ channel: channel.name,
231
+ **message_options.to_h
232
+ )
233
+ end
227
234
 
228
- channel.once_state_changed do
229
- @pending_state_change_timer.cancel if @pending_state_change_timer
230
- @pending_state_change_timer = nil
235
+ def send_detach_protocol_message(previous_state)
236
+ state_at_time_of_request = channel.state
237
+ detach_action = Ably::Models::ProtocolMessage::ACTION.Detach
238
+
239
+ @pending_state_change_timer = EventMachine::Timer.new(realtime_request_timeout) do
240
+ if channel.state == state_at_time_of_request
241
+ error = Ably::Models::ErrorInfo.new(code: Ably::Exceptions::Codes::CHANNEL_OPERATION_FAILED_NO_RESPONSE_FROM_SERVER, message: "Channel #{detach_action} operation failed (timed out)")
242
+ channel.transition_state_machine previous_state, reason: error # return to previous state if failed
243
+ end
231
244
  end
232
245
 
233
- resend_if_disconnected_and_connected = lambda do
246
+ on_disconnected_and_connected = lambda do |&block|
234
247
  connection.unsafe_once(:disconnected) do
235
- next unless pending_state_change_timer
236
248
  connection.unsafe_once(:connected) do
237
- next unless pending_state_change_timer
238
- connection.send_protocol_message(
239
- action: new_state.to_i,
240
- channel: channel.name,
241
- **message_options.to_h
242
- )
243
- resend_if_disconnected_and_connected.call
244
- end
249
+ block.call if pending_state_change_timer
250
+ end if pending_state_change_timer
245
251
  end
246
252
  end
247
- resend_if_disconnected_and_connected.call
248
-
249
- connection.send_protocol_message(
250
- action: new_state.to_i,
251
- channel: channel.name,
252
- **message_options.to_h
253
- )
254
- end
255
253
 
256
- def update_presence_sync_state_following_attached(attached_protocol_message)
257
- if attached_protocol_message.has_presence_flag?
258
- channel.presence.manager.sync_expected
259
- else
260
- channel.presence.manager.sync_not_expected
254
+ send_detach_message = lambda do
255
+ on_disconnected_and_connected.call do
256
+ send_detach_message.call
257
+ end
258
+ connection.send_protocol_message(
259
+ action: detach_action.to_i,
260
+ channel: channel.name
261
+ )
261
262
  end
263
+
264
+ send_detach_message.call
262
265
  end
263
266
 
264
267
  def logger
@@ -18,6 +18,15 @@ module Ably::Realtime
18
18
  #
19
19
  attr_reader :attach_serial
20
20
 
21
+ # ChannelSerial contains the channelSerial from latest ProtocolMessage of action type
22
+ # Message/PresenceMessage received on the channel.
23
+ #
24
+ # @spec CP2b, RTL15b
25
+ #
26
+ # @return [String]
27
+ #
28
+ attr_accessor :channel_serial
29
+
21
30
  def initialize(channel)
22
31
  @channel = channel
23
32
  end
@@ -29,6 +29,7 @@ module Ably::Realtime
29
29
  transition :from => :failed, :to => [:attaching, :initialized]
30
30
 
31
31
  after_transition do |channel, transition|
32
+ channel.manager.notify_state_change # RTL13c
32
33
  channel.synchronize_state_with_statemachine
33
34
  end
34
35
 
@@ -55,6 +56,7 @@ module Ably::Realtime
55
56
  end
56
57
 
57
58
  after_transition(to: [:detached, :failed, :suspended]) do |channel, current_transition|
59
+ channel.properties.channel_serial = nil # RTP5a1
58
60
  err = error_from_state_change(current_transition)
59
61
  channel.manager.fail_queued_messages(err) if channel.failed? or channel.suspended? #RTL11
60
62
  channel.manager.log_channel_error err if err
@@ -42,7 +42,7 @@ module Ably
42
42
  #
43
43
  # @spec RTL2b
44
44
  #
45
- # The permited states for this channel
45
+ # The permitted states for this channel
46
46
  STATE = ruby_enum('STATE',
47
47
  :initialized,
48
48
  :attaching,
@@ -332,6 +332,7 @@ module Ably
332
332
  # @return [Ably::Util::SafeDeferrable]
333
333
  #
334
334
  def history(options = {}, &callback)
335
+ # RTL10b
335
336
  if options.delete(:until_attach)
336
337
  unless attached?
337
338
  error = Ably::Exceptions::InvalidRequest.new('option :until_attach is invalid as the channel is not attached' )
@@ -363,8 +364,8 @@ module Ably
363
364
  # @return [Ably::Models::ChannelOptions]
364
365
  def set_options(channel_options)
365
366
  @options = Ably::Models::ChannelOptions(channel_options)
366
-
367
- manager.request_reattach if need_reattach?
367
+ # RTL4i
368
+ manager.request_reattach if (need_reattach? and connection.state?(:connected))
368
369
  end
369
370
  alias options= set_options
370
371
 
@@ -46,6 +46,26 @@ module Ably
46
46
  @channels.delete(channel)
47
47
  end if @channels.has_key?(channel)
48
48
  end
49
+
50
+ # Sets channel serial to each channel from given serials hashmap
51
+ # @param [Hash] serials - map of channel name to respective channel serial
52
+ # @api private
53
+ def set_channel_serials(serials)
54
+ serials.each do |channel_name, channel_serial|
55
+ get(channel_name).properties.channel_serial = channel_serial
56
+ end
57
+ end
58
+
59
+ # @return [Hash] serials - map of channel name to respective channel serial
60
+ # @api private
61
+ def get_channel_serials
62
+ channel_serials = {}
63
+ self.each do |channel|
64
+ channel_serials[channel.name] = channel.properties.channel_serial if channel.state == :attached
65
+ end
66
+ channel_serials
67
+ end
68
+
49
69
  end
50
70
  end
51
71
  end
@@ -43,19 +43,24 @@ module Ably::Realtime
43
43
  raise ArgumentError, "Expected a ProtocolMessage. Received #{protocol_message}"
44
44
  end
45
45
 
46
- unless protocol_message.action.match_any?(:nack, :error)
47
- logger.debug { "#{protocol_message.action} received: #{protocol_message}" }
46
+ # RTL15b
47
+ if protocol_message.has_channel_serial? &&
48
+ (
49
+ protocol_message.action == :message ||
50
+ protocol_message.action == :presence ||
51
+ protocol_message.action == :attached
52
+ )
53
+ get_channel(protocol_message.channel).tap do |channel|
54
+ logger.info "Setting channel serial for channel #{channel.name}, " <<
55
+ "Previous: #{channel.properties.channel_serial}, New: #{protocol_message.channel_serial}"
56
+ channel.properties.channel_serial = protocol_message.channel_serial
57
+ end
48
58
  end
49
59
 
50
- if protocol_message.action.match_any?(:sync, :presence, :message)
51
- if connection.serial && protocol_message.has_connection_serial? && protocol_message.connection_serial <= connection.serial
52
- error_message = "Protocol error, duplicate message received for serial #{protocol_message.connection_serial}"
53
- logger.error error_message
54
- return
55
- end
60
+ unless protocol_message.action.match_any?(:nack, :error)
61
+ logger.debug { "#{protocol_message.action} received: #{protocol_message}" }
56
62
  end
57
63
 
58
- update_connection_recovery_info protocol_message
59
64
  connection.set_connection_confirmed_alive
60
65
 
61
66
  case protocol_message.action
@@ -172,10 +177,6 @@ module Ably::Realtime
172
177
  end
173
178
  end
174
179
 
175
- def update_connection_recovery_info(protocol_message)
176
- connection.update_connection_serial protocol_message.connection_serial if protocol_message.has_connection_serial?
177
- end
178
-
179
180
  def ack_pending_queue_for_message_serial(ack_protocol_message)
180
181
  drop_pending_queue_from_ack(ack_protocol_message) do |protocol_message|
181
182
  ack_messages protocol_message.messages
@@ -1,5 +1,6 @@
1
1
  require 'uri'
2
2
  require 'ably/realtime/channel/publisher'
3
+ require 'ably/realtime/recovery_key_context'
3
4
 
4
5
  module Ably
5
6
  module Realtime
@@ -11,6 +12,7 @@ module Ably
11
12
  include Ably::Modules::Conversions
12
13
 
13
14
  extend Forwardable
15
+ using Ably::Util::AblyExtensions
14
16
 
15
17
  DOMAIN = 'realtime.ably.io'
16
18
 
@@ -120,17 +122,23 @@ module Ably
120
122
  acc[key.to_s] = value.to_s
121
123
  end
122
124
  @rest_client = Ably::Rest::Client.new(options.merge(realtime_client: self))
123
- @echo_messages = rest_client.options.fetch(:echo_messages, true) == false ? false : true
124
- @queue_messages = rest_client.options.fetch(:queue_messages, true) == false ? false : true
125
+ @echo_messages = rest_client.options.fetch_with_default(:echo_messages, true)
126
+ @queue_messages = rest_client.options.fetch_with_default(:queue_messages, true)
125
127
  @custom_realtime_host = rest_client.options[:realtime_host] || rest_client.options[:ws_host]
126
- @auto_connect = rest_client.options.fetch(:auto_connect, true) == false ? false : true
127
- @recover = rest_client.options[:recover]
128
-
129
- raise ArgumentError, "Recovery key '#{recover}' is invalid" if recover && !recover.match(Connection::RECOVER_REGEX)
128
+ @auto_connect = rest_client.options.fetch_with_default(:auto_connect, true)
129
+ @recover = rest_client.options.fetch_with_default(:recover, '')
130
130
 
131
131
  @auth = Ably::Realtime::Auth.new(self)
132
132
  @channels = Ably::Realtime::Channels.new(self)
133
133
  @connection = Ably::Realtime::Connection.new(self, options)
134
+
135
+ unless @recover.nil_or_empty?
136
+ recovery_context = RecoveryKeyContext.from_json(@recover, logger)
137
+ unless recovery_context.nil?
138
+ @channels.set_channel_serials recovery_context.channel_serials # RTN16j
139
+ @connection.set_msg_serial_from_recover = recovery_context.msg_serial # RTN16f
140
+ end
141
+ end
134
142
  end
135
143
 
136
144
  # Return a {Ably::Realtime::Channel Realtime Channel} for the given name
@@ -14,12 +14,14 @@ module Ably::Realtime
14
14
  RESOLVABLE_ERROR_CODES = {
15
15
  token_expired: Ably::Exceptions::TOKEN_EXPIRED_CODE
16
16
  }
17
+ using Ably::Util::AblyExtensions
17
18
 
18
19
  def initialize(connection)
19
20
  @connection = connection
20
21
  @timers = Hash.new { |hash, key| hash[key] = [] }
21
22
 
22
- connection.unsafe_on(:closed) do
23
+ # RTN8c, RTN9c
24
+ connection.unsafe_on(:closing, :closed, :suspended, :failed) do
23
25
  connection.reset_resume_info
24
26
  end
25
27
 
@@ -111,23 +113,28 @@ module Ably::Realtime
111
113
  # Update the connection details and any associated defaults
112
114
  connection.set_connection_details protocol_message.connection_details
113
115
 
116
+ is_connection_resume_or_recover_attempt = !connection.key.nil_or_empty? || !client.recover.nil_or_empty?
117
+
118
+ # RTN15c7, RTN16d
119
+ failed_resume_or_recover = !protocol_message.connection_id == connection.id && !protocol_message.error.nil?
120
+ if is_connection_resume_or_recover_attempt and failed_resume_or_recover # RTN15c7
121
+ connection.reset_client_msg_serial
122
+ end
123
+ client.disable_automatic_connection_recovery # RTN16k, explicitly setting null, so it won't be used for subsequent connection requests
124
+
114
125
  if connection.key
115
126
  if protocol_message.connection_id == connection.id
116
127
  logger.debug { "ConnectionManager: Connection resumed successfully - ID #{connection.id} and key #{connection.key}" }
117
- EventMachine.next_tick { connection.trigger_resumed }
118
128
  resend_pending_message_ack_queue
119
129
  else
120
- logger.debug { "ConnectionManager: Connection was not resumed, old connection ID #{connection.id} has been updated with new connection ID #{protocol_message.connection_id} and key #{protocol_message.connection_details.connection_key}" }
121
130
  nack_messages_on_all_channels protocol_message.error
122
- force_reattach_on_channels protocol_message.error
123
131
  end
124
132
  else
125
133
  logger.debug { "ConnectionManager: New connection created with ID #{protocol_message.connection_id} and key #{protocol_message.connection_details.connection_key}" }
126
134
  end
127
135
 
128
- reattach_suspended_channels protocol_message.error
129
-
130
- connection.configure_new protocol_message.connection_id, protocol_message.connection_details.connection_key, protocol_message.connection_serial
136
+ connection.configure_new protocol_message.connection_id, protocol_message.connection_details.connection_key
137
+ force_reattach_on_channels protocol_message.error # irrespective of connection success/failure, reattach channels
131
138
  end
132
139
 
133
140
  # When connection is CONNECTED and receives an update
@@ -139,7 +146,7 @@ module Ably::Realtime
139
146
  # Update the connection details and any associated defaults
140
147
  connection.set_connection_details protocol_message.connection_details
141
148
 
142
- connection.configure_new protocol_message.connection_id, protocol_message.connection_details.connection_key, protocol_message.connection_serial
149
+ connection.configure_new protocol_message.connection_id, protocol_message.connection_details.connection_key
143
150
 
144
151
  state_change = Ably::Models::ConnectionStateChange.new(
145
152
  current: connection.state,
@@ -281,7 +288,7 @@ module Ably::Realtime
281
288
  # Any message sent before an ACK/NACK was received on the previous transport
282
289
  # need to be resent to the Ably service so that a subsequent ACK/NACK is received.
283
290
  # It is up to Ably to ensure that duplicate messages are not retransmitted on the channel
284
- # base on the serial numbers
291
+ # based on the message serial numbers
285
292
  #
286
293
  # @api private
287
294
  def resend_pending_message_ack_queue
@@ -568,22 +575,14 @@ module Ably::Realtime
568
575
  client.auth.authorization_in_flight?
569
576
  end
570
577
 
571
- def reattach_suspended_channels(error)
572
- channels.select do |channel|
573
- channel.suspended?
574
- end.each do |channel|
575
- channel.transition_state_machine :attaching
576
- end
577
- end
578
-
579
- # When continuity on a connection is lost all messages
580
- # Channels in the ATTACHED or ATTACHING state should explicitly be re-attached
581
- # by sending a new ATTACH to Ably
578
+ # When reconnected if channel is in ATTACHING, ATTACHED or SUSPENDED state
579
+ # it should explicitly be re-attached by sending a new ATTACH to Ably
580
+ # Spec : RTN15c6, RTN15c7
582
581
  def force_reattach_on_channels(error)
583
582
  channels.select do |channel|
584
- channel.attached? || channel.attaching?
583
+ channel.attached? || channel.attaching? || channel.suspended?
585
584
  end.each do |channel|
586
- channel.manager.request_reattach reason: error
585
+ channel.manager.request_reattach error
587
586
  end
588
587
  end
589
588