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
@@ -9,6 +9,8 @@ module Ably
9
9
  include Ably::Modules::Conversions
10
10
  include Ably::Modules::SafeYield
11
11
  extend Ably::Modules::Enum
12
+ using Ably::Util::AblyExtensions
13
+
12
14
 
13
15
  # The current {Ably::Realtime::Connection::STATE} of the connection.
14
16
  # Describes the realtime [Connection]{@link Connection} object states.
@@ -23,26 +25,26 @@ module Ably
23
25
  # or no host is available. The disconnected state is entered if an established connection is dropped,
24
26
  # or if a connection attempt was unsuccessful. In the disconnected state the library will periodically
25
27
  # attempt to open a new connection (approximately every 15 seconds), anticipating that the connection
26
- # will be re-established soon and thus connection and channel continuity will be possible.
28
+ # will be re-established soon and thus connection and channel continuity will be possible.
27
29
  # In this state, developers can continue to publish messages as they are automatically placed
28
- # in a local queue, to be sent as soon as a connection is reestablished. Messages published by
30
+ # in a local queue, to be sent as soon as a connection is reestablished. Messages published by
29
31
  # other clients while this client is disconnected will be delivered to it upon reconnection,
30
- # so long as the connection was resumed within 2 minutes. After 2 minutes have elapsed, recovery
32
+ # so long as the connection was resumed within 2 minutes. After 2 minutes have elapsed, recovery
31
33
  # is no longer possible and the connection will move to the SUSPENDED state.
32
- # SUSPENDED A long term failure condition. No current connection exists because there is no network connectivity
33
- # or no host is available. The suspended state is entered after a failed connection attempt if
34
- # there has then been no connection for a period of two minutes. In the suspended state, the library
35
- # will periodically attempt to open a new connection every 30 seconds. Developers are unable to
34
+ # SUSPENDED A long term failure condition. No current connection exists because there is no network connectivity
35
+ # or no host is available. The suspended state is entered after a failed connection attempt if
36
+ # there has then been no connection for a period of two minutes. In the suspended state, the library
37
+ # will periodically attempt to open a new connection every 30 seconds. Developers are unable to
36
38
  # publish messages in this state. A new connection attempt can also be triggered by an explicit
37
- # call to {Ably::Realtime::Connection#connect}. Once the connection has been re-established,
38
- # channels will be automatically re-attached. The client has been disconnected for too long for them
39
- # to resume from where they left off, so if it wants to catch up on messages published by other clients
39
+ # call to {Ably::Realtime::Connection#connect}. Once the connection has been re-established,
40
+ # channels will be automatically re-attached. The client has been disconnected for too long for them
41
+ # to resume from where they left off, so if it wants to catch up on messages published by other clients
40
42
  # while it was disconnected, it needs to use the History API.
41
- # CLOSING An explicit request by the developer to close the connection has been sent to the Ably service.
42
- # If a reply is not received from Ably within a short period of time, the connection is forcibly
43
+ # CLOSING An explicit request by the developer to close the connection has been sent to the Ably service.
44
+ # If a reply is not received from Ably within a short period of time, the connection is forcibly
43
45
  # terminated and the connection state becomes CLOSED.
44
- # CLOSED The connection has been explicitly closed by the client. In the closed state, no reconnection attempts
45
- # are made automatically by the library, and clients may not publish messages. No connection state is
46
+ # CLOSED The connection has been explicitly closed by the client. In the closed state, no reconnection attempts
47
+ # are made automatically by the library, and clients may not publish messages. No connection state is
46
48
  # preserved by the service or by the library. A new connection attempt can be triggered by an explicit
47
49
  # call to {Ably::Realtime::Connection#connect}, which results in a new connection.
48
50
  # FAILED This state is entered if the client library encounters a failure condition that it cannot recover from.
@@ -77,9 +79,6 @@ module Ably
77
79
  include Ably::Modules::UsesStateMachine
78
80
  ensure_state_machine_emits 'Ably::Models::ConnectionStateChange'
79
81
 
80
- # Expected format for a connection recover key
81
- RECOVER_REGEX = /^(?<recover>[^:]+):(?<connection_serial>[^:]+):(?<msg_serial>\-?\d+)$/
82
-
83
82
  # Defaults for automatic connection recovery and timeouts
84
83
  DEFAULTS = {
85
84
  channel_retry_timeout: 15, # when a channel becomes SUSPENDED, after this delay in seconds, the channel will automatically attempt to reattach if the connection is CONNECTED
@@ -113,16 +112,6 @@ module Ably
113
112
  #
114
113
  attr_reader :key
115
114
 
116
- # The serial number of the last message to be received on this connection, used automatically by the library when
117
- # recovering or resuming a connection. When recovering a connection explicitly, the recoveryKey is used in
118
- # the recover client options as it contains both the key and the last message serial.
119
- #
120
- # @spec RTN10
121
- #
122
- # @return [Integer]
123
- #
124
- attr_reader :serial
125
-
126
115
  # An {Ably::Models::ErrorInfo} object describing the last error received if a connection failure occurs.
127
116
  #
128
117
  # @spec RTN14a
@@ -177,17 +166,6 @@ module Ably
177
166
  end if options.kind_of?(Hash)
178
167
  @defaults.freeze
179
168
 
180
- # If a recover client options is provided, then we need to ensure that the msgSerial matches the
181
- # recover serial immediately at client library instantiation. This is done immediately so that any queued
182
- # publishes use the correct serial number for these queued messages as well.
183
- # There is no harm if the msgSerial is higher than expected if the recover fails.
184
- recovery_msg_serial = connection_recover_parts && connection_recover_parts[:msg_serial].to_i
185
- if recovery_msg_serial
186
- @client_msg_serial = recovery_msg_serial
187
- else
188
- reset_client_msg_serial
189
- end
190
-
191
169
  Client::IncomingMessageDispatcher.new client, self
192
170
  Client::OutgoingMessageDispatcher.new client, self
193
171
 
@@ -196,6 +174,8 @@ module Ably
196
174
  @manager = ConnectionManager.new(self)
197
175
 
198
176
  @current_host = client.endpoint.host
177
+
178
+ reset_client_msg_serial
199
179
  end
200
180
 
201
181
  # Causes the connection to close, entering the {Ably::Realtime::Connection::STATE} CLOSING state.
@@ -347,33 +327,40 @@ module Ably
347
327
  end
348
328
  end
349
329
 
350
- # The recovery key string can be used by another client to recover this connection's state in the recover client options property. See connection state recover options for more information.
330
+ # The recovery key string can be used by another client to recover this connection's state in the
331
+ # recover client options property. See connection state recover options for more information.
351
332
  #
352
333
  # @spec RTN16b, RTN16c
353
334
  #
354
- # @return [String]
335
+ # @deprecated Use {#create_recovery_key} instead
355
336
  #
356
337
  def recovery_key
357
- "#{key}:#{serial}:#{client_msg_serial}" if connection_resumable?
338
+ logger.warn "[DEPRECATION] recovery_key is deprecated, use create_recovery_key method instead"
339
+ create_recovery_key
340
+ end
341
+
342
+ # The recovery key string can be used by another client to recover this connection's state in the recover client
343
+ # options property. See connection state recover options for more information.
344
+ #
345
+ # @spec RTN16g, RTN16c
346
+ #
347
+ # @return [String] a json string which incorporates the @connectionKey@, the current @msgSerial@ and collection
348
+ # of pairs of channel @name@ and current @channelSerial@ for every currently attached channel
349
+ def create_recovery_key
350
+ if key.nil_or_empty? || state == :closing || state == :closed || state == :failed || state == :suspended
351
+ return nil #RTN16g2
352
+ end
353
+ RecoveryKeyContext.new(key, client_msg_serial, client.channels.get_channel_serials).to_json
358
354
  end
359
355
 
360
356
  # Following a new connection being made, the connection ID, connection key
361
- # and connection serial need to match the details provided by the server.
357
+ # need to match the details provided by the server.
362
358
  #
363
359
  # @return [void]
364
360
  # @api private
365
- def configure_new(connection_id, connection_key, connection_serial)
361
+ def configure_new(connection_id, connection_key)
366
362
  @id = connection_id
367
363
  @key = connection_key
368
-
369
- update_connection_serial connection_serial
370
- end
371
-
372
- # Store last received connection serial so that the connection can be resumed from the last known point-in-time
373
- # @return [void]
374
- # @api private
375
- def update_connection_serial(connection_serial)
376
- @serial = connection_serial
377
364
  end
378
365
 
379
366
  # Disable automatic resume of a connection
@@ -381,7 +368,7 @@ module Ably
381
368
  # @api private
382
369
  def reset_resume_info
383
370
  @key = nil
384
- @serial = nil
371
+ @id = nil
385
372
  end
386
373
 
387
374
  # @!attribute [r] __outgoing_protocol_msgbus__
@@ -444,17 +431,28 @@ module Ably
444
431
  # @api private
445
432
  def send_protocol_message(protocol_message)
446
433
  add_message_serial_if_ack_required_to(protocol_message) do
447
- Ably::Models::ProtocolMessage.new(protocol_message, logger: logger).tap do |message|
448
- add_message_to_outgoing_queue message
449
- notify_message_dispatcher_of_new_message message
450
- logger.debug { "Connection: Prot msg queued =>: #{message.action} #{message}" }
451
- end
434
+ message = Ably::Models::ProtocolMessage.new(protocol_message, logger: logger)
435
+ add_message_to_outgoing_queue(message)
436
+ notify_message_dispatcher_of_new_message message
452
437
  end
453
438
  end
454
439
 
440
+ def send_protocol_message_immediately(protocol_message)
441
+ message = Ably::Models::ProtocolMessage.new(protocol_message, logger: logger)
442
+ add_message_to_outgoing_queue(message, true)
443
+ notify_message_dispatcher_of_new_message message
444
+ end
445
+
455
446
  # @api private
456
- def add_message_to_outgoing_queue(protocol_message)
457
- __outgoing_message_queue__ << protocol_message
447
+ def add_message_to_outgoing_queue(protocol_message, send_immediately = false)
448
+ if send_immediately
449
+ # Adding msg at the top of the queue to get processed immediately while connection is CONNECTED
450
+ __outgoing_message_queue__.prepend(protocol_message)
451
+ logger.debug { "Connection: protocol msg pushed at the top =>: #{protocol_message.action} #{protocol_message}" }
452
+ else
453
+ __outgoing_message_queue__ << protocol_message
454
+ logger.debug { "Connection: protocol msg queued =>: #{protocol_message.action} #{protocol_message}" }
455
+ end
458
456
  end
459
457
 
460
458
  # @api private
@@ -472,7 +470,7 @@ module Ably
472
470
  url_params = auth_params.merge(
473
471
  'format' => client.protocol,
474
472
  'echo' => client.echo_messages,
475
- 'v' => Ably::PROTOCOL_VERSION,
473
+ 'v' => Ably::PROTOCOL_VERSION, # RSC7a
476
474
  'agent' => client.rest_client.agent
477
475
  )
478
476
 
@@ -482,18 +480,19 @@ module Ably
482
480
  else
483
481
  'false'
484
482
  end
485
-
486
- url_params['clientId'] = client.auth.client_id if client.auth.has_client_id?
483
+ # RSA7e1
484
+ url_params['clientId'] = client.auth.client_id_for_request_sync if client.auth.client_id_for_request_sync
487
485
  url_params.merge!(client.transport_params)
488
486
 
489
- if connection_resumable?
490
- url_params.merge! resume: key, connection_serial: serial
491
- logger.debug { "Resuming connection key #{key} with serial #{serial}" }
492
- elsif connection_recoverable?
493
- url_params.merge! recover: connection_recover_parts[:recover], connectionSerial: connection_recover_parts[:connection_serial]
494
- logger.debug { "Recovering connection with key #{client.recover}" }
495
- unsafe_once(:connected, :closed, :failed) do
496
- client.disable_automatic_connection_recovery
487
+ if !key.nil_or_empty? and connection_state_available?
488
+ url_params.merge! resume: key
489
+ logger.debug { "Resuming connection with key #{key}" }
490
+ elsif !client.recover.nil_or_empty?
491
+ recovery_context = RecoveryKeyContext.from_json(client.recover, logger)
492
+ unless recovery_context.nil?
493
+ key = recovery_context.connection_key
494
+ logger.debug { "Recovering connection with key #{key}" }
495
+ url_params.merge! recover: key
497
496
  end
498
497
  end
499
498
 
@@ -544,24 +543,6 @@ module Ably
544
543
  @details = connection_details
545
544
  end
546
545
 
547
- # Executes registered callbacks for a successful connection resume event
548
- # @api private
549
- def trigger_resumed
550
- resume_callbacks.each(&:call)
551
- end
552
-
553
- # Provides a simple hook to inject a callback when a connection is successfully resumed
554
- # @api private
555
- def on_resume(&callback)
556
- resume_callbacks << callback
557
- end
558
-
559
- # Remove a registered connection resume callback
560
- # @api private
561
- def off_resume(&callback)
562
- resume_callbacks.delete(callback)
563
- end
564
-
565
546
  # Returns false if messages cannot be published as a result of message queueing being disabled
566
547
  # @api private
567
548
  def can_publish_messages?
@@ -600,6 +581,12 @@ module Ably
600
581
  @client_msg_serial = -1
601
582
  end
602
583
 
584
+ # Sets the client message serial from recover clientOption.
585
+ # @api private
586
+ def set_msg_serial_from_recover=(value)
587
+ @client_msg_serial = value
588
+ end
589
+
603
590
  # When a hearbeat or any other message from Ably is received
604
591
  # we know it's alive, see #RTN23
605
592
  # @api private
@@ -620,20 +607,12 @@ module Ably
620
607
  private
621
608
 
622
609
  # The client message serial (msgSerial) is incremented for every message that is published that requires an ACK.
623
- # Note that this is different to the connection serial that contains the last known serial number
624
- # received from the server.
625
- #
626
610
  # A message serial number does not guarantee a message has been received, only sent.
627
- # A connection serial guarantees the server has received the message and is thus used for connection recovery and resumes.
628
611
  # @return [Integer] starting at -1 indicating no messages sent, 0 when the first message is sent
629
612
  def client_msg_serial
630
613
  @client_msg_serial
631
614
  end
632
615
 
633
- def resume_callbacks
634
- @resume_callbacks ||= []
635
- end
636
-
637
616
  def create_pub_sub_message_bus
638
617
  Ably::Util::PubSub.new(
639
618
  coerce_into: lambda do |event|
@@ -665,10 +644,6 @@ module Ably
665
644
  EventMachine.next_tick { yield }
666
645
  end
667
646
 
668
- def connection_resumable?
669
- !key.nil? && !serial.nil? && connection_state_available?
670
- end
671
-
672
647
  def connection_state_available?
673
648
  return true if connected?
674
649
 
@@ -682,14 +657,6 @@ module Ably
682
657
  end
683
658
  end
684
659
 
685
- def connection_recoverable?
686
- connection_recover_parts
687
- end
688
-
689
- def connection_recover_parts
690
- client.recover.to_s.match(RECOVER_REGEX)
691
- end
692
-
693
660
  def production?
694
661
  client.environment.nil? || client.environment == :production
695
662
  end
@@ -740,3 +707,4 @@ end
740
707
  require 'ably/realtime/connection/connection_manager'
741
708
  require 'ably/realtime/connection/connection_state_machine'
742
709
  require 'ably/realtime/connection/websocket_transport'
710
+ require 'ably/realtime/recovery_key_context'
@@ -23,7 +23,7 @@ module Ably::Realtime
23
23
  :sync_starting, # Indicates the client is waiting for SYNC ProtocolMessages from Ably
24
24
  :sync_none, # Indicates the ATTACHED ProtocolMessage had no presence flag and thus no members on the channel
25
25
  :finalizing_sync,
26
- :in_sync,
26
+ :sync_complete, # Indicates completion of server initiated sync
27
27
  :failed
28
28
  )
29
29
  include Ably::Modules::StateEmitter
@@ -49,16 +49,6 @@ module Ably::Realtime
49
49
  setup_event_handlers
50
50
  end
51
51
 
52
- # When attaching to a channel that has members present, the server
53
- # initiates a sync automatically so that the client has a complete list of members.
54
- #
55
- # Until this sync is complete, this method returns false
56
- #
57
- # @return [Boolean]
58
- def sync_complete?
59
- in_sync?
60
- end
61
-
62
52
  # Update the SYNC serial from the ProtocolMessage so that SYNC can be resumed.
63
53
  # If the serial is nil, or the part after the first : is empty, then the SYNC is complete
64
54
  #
@@ -110,27 +100,27 @@ module Ably::Realtime
110
100
  # Must be defined before subsequent procs reference this callback
111
101
  reset_callbacks = nil
112
102
 
113
- in_sync_callback = lambda do
103
+ sync_complete_callback = lambda do
114
104
  reset_callbacks.call if reset_callbacks
115
105
  result_block.call
116
106
  end
117
107
 
118
- failed_callback = lambda do |error|
108
+ sync_failed_callback = lambda do |error|
119
109
  reset_callbacks.call if reset_callbacks
120
110
  deferrable.fail error
121
111
  end
122
112
 
123
113
  reset_callbacks = lambda do
124
- off(&in_sync_callback)
125
- off(&failed_callback)
126
- channel.off(&failed_callback)
114
+ off(&sync_complete_callback)
115
+ off(&sync_failed_callback)
116
+ channel.off(&sync_failed_callback)
127
117
  end
128
118
 
129
- unsafe_once(:in_sync, &in_sync_callback)
130
- unsafe_once(:failed, &failed_callback)
119
+ unsafe_once(:sync_complete, &sync_complete_callback)
120
+ unsafe_once(:failed, &sync_failed_callback)
131
121
 
132
122
  channel.unsafe_once(:detaching, :detached, :failed) do |error_reason|
133
- failed_callback.call error_reason
123
+ sync_failed_callback.call error_reason
134
124
  end
135
125
  end
136
126
 
@@ -156,12 +146,33 @@ module Ably::Realtime
156
146
  # and thus the responsibility of this library to re-enter on the channel automatically if the
157
147
  # channel loses continuity
158
148
  #
159
- # @return [Array<PresenceMessage>]
149
+ # @return [Hash<String, PresenceMessage>]
160
150
  # @api private
161
151
  def local_members
162
152
  @local_members
163
153
  end
164
154
 
155
+ def enter_local_members
156
+ local_members.values.each do |member|
157
+ logger.debug { "#{self.class.name}: Manually re-entering local presence member, client ID: #{member.client_id} with data: #{member.data}" }
158
+ presence.enter_client_with_id(member.id, member.client_id, member.data).tap do |deferrable|
159
+ deferrable.errback do |error|
160
+ re_enter_error = Ably::Models::ErrorInfo.new(
161
+ message: "unable to automatically re-enter presence channel for client_id '#{member.client_id}'. Source error code #{error.code} and message '#{error.message}'",
162
+ code: Ably::Exceptions::Codes::UNABLE_TO_AUTOMATICALLY_REENTER_PRESENCE_CHANNEL
163
+ )
164
+ channel.emit :update, Ably::Models::ChannelStateChange.new(
165
+ current: channel.state,
166
+ previous: channel.state,
167
+ event: Ably::Realtime::Channel::EVENT(:update),
168
+ reason: re_enter_error,
169
+ resumed: true
170
+ )
171
+ end
172
+ end
173
+ end
174
+ end
175
+
165
176
  private
166
177
  attr_reader :sync_session_id
167
178
 
@@ -213,23 +224,14 @@ module Ably::Realtime
213
224
  update_members_and_emit_events presence_message
214
225
  end
215
226
 
227
+ # RTP5a
216
228
  channel.unsafe_on(:failed, :detached) do
217
229
  reset_members
218
230
  reset_local_members
219
231
  end
220
232
 
221
- resume_sync_proc = method(:resume_sync).to_proc
222
-
223
233
  unsafe_on(:sync_starting) do
224
234
  @sync_session_id += 1
225
-
226
- channel.unsafe_once(:attached) do
227
- connection.on_resume(&resume_sync_proc)
228
- end
229
-
230
- unsafe_once(:in_sync, :failed) do
231
- connection.off_resume(&resume_sync_proc)
232
- end
233
235
  end
234
236
 
235
237
  unsafe_on(:sync_none) do
@@ -240,61 +242,9 @@ module Ably::Realtime
240
242
 
241
243
  unsafe_on(:finalizing_sync) do
242
244
  clean_up_absent_members
243
- clean_up_members_not_present_in_sync
244
- change_state :in_sync
245
+ clean_up_members_not_present_after_sync
246
+ change_state :sync_complete
245
247
  end
246
-
247
- unsafe_on(:in_sync) do
248
- update_local_member_state
249
- end
250
- end
251
-
252
- # Listen for events that change the PresenceMap state and thus
253
- # need to be replicated to the local member set
254
- def update_local_member_state
255
- new_local_members = members.select do |member_key, member|
256
- member.fetch(:message).connection_id == connection.id
257
- end.each_with_object({}) do |(member_key, member), hash_object|
258
- hash_object[member_key] = member.fetch(:message)
259
- end
260
-
261
- @local_members.reject do |member_key, message|
262
- new_local_members.keys.include?(member_key)
263
- end.each do |member_key, message|
264
- re_enter_local_member_missing_from_presence_map message
265
- end
266
-
267
- @local_members = new_local_members
268
- end
269
-
270
- def re_enter_local_member_missing_from_presence_map(presence_message)
271
- local_client_id = presence_message.client_id || client.auth.client_id
272
- logger.debug { "#{self.class.name}: Manually re-entering local presence member, client ID: #{local_client_id} with data: #{presence_message.data}" }
273
- presence.enter_client(local_client_id, presence_message.data).tap do |deferrable|
274
- deferrable.errback do |error|
275
- presence_message_client_id = presence_message.client_id || client.auth.client_id
276
- re_enter_error = Ably::Models::ErrorInfo.new(
277
- message: "unable to automatically re-enter presence channel for client_id '#{presence_message_client_id}'. Source error code #{error.code} and message '#{error.message}'",
278
- code: Ably::Exceptions::Codes::UNABLE_TO_AUTOMATICALLY_REENTER_PRESENCE_CHANNEL
279
- )
280
- channel.emit :update, Ably::Models::ChannelStateChange.new(
281
- current: channel.state,
282
- previous: channel.state,
283
- event: Ably::Realtime::Channel::EVENT(:update),
284
- reason: re_enter_error,
285
- resumed: true
286
- )
287
- end
288
- end
289
- end
290
-
291
- # Trigger a manual SYNC operation to resume member synchronisation from last known cursor position
292
- def resume_sync
293
- connection.send_protocol_message(
294
- action: Ably::Models::ProtocolMessage::ACTION.Sync.to_i,
295
- channel: channel.name,
296
- channel_serial: sync_serial
297
- ) if channel.attached?
298
248
  end
299
249
 
300
250
  def update_members_and_emit_events(presence_message)
@@ -375,7 +325,7 @@ module Ably::Realtime
375
325
  def remove_presence_member(presence_message)
376
326
  logger.debug { "#{self.class.name}: Member '#{presence_message.member_key}' removed.\n#{presence_message.to_json}" }
377
327
 
378
- if in_sync?
328
+ if sync_complete?
379
329
  member_set_delete presence_message
380
330
  else
381
331
  member_set_upsert presence_message, false
@@ -394,17 +344,16 @@ module Ably::Realtime
394
344
  def member_set_upsert(presence_message, present)
395
345
  members[presence_message.member_key] = { present: present, message: presence_message, sync_session_id: sync_session_id }
396
346
  if presence_message.connection_id == connection.id
397
- local_members[presence_message.member_key] = presence_message
398
- logger.debug { "#{self.class.name}: Local member '#{presence_message.member_key}' added" }
347
+ local_members[presence_message.client_id] = presence_message
348
+ logger.debug { "#{self.class.name}: Local member '#{presence_message.client_id}' added" }
399
349
  end
400
350
  end
401
351
 
402
352
  def member_set_delete(presence_message)
403
353
  members.delete presence_message.member_key
404
- if in_sync?
405
- # If not in SYNC, then local members missing may need to be re-entered
406
- # Let #update_local_member_state handle missing members
407
- local_members.delete presence_message.member_key
354
+ if sync_complete? and presence_message.connection_id == connection.id
355
+ local_members.delete presence_message.client_id
356
+ logger.debug { "#{self.class.name}: Local member '#{presence_message.client_id}' deleted" }
408
357
  end
409
358
  end
410
359
 
@@ -431,7 +380,7 @@ module Ably::Realtime
431
380
  end
432
381
  end
433
382
 
434
- def clean_up_members_not_present_in_sync
383
+ def clean_up_members_not_present_after_sync
435
384
  members.select do |member_key, member|
436
385
  member.fetch(:sync_session_id) != sync_session_id
437
386
  end.each do |member_key, member|
@@ -19,13 +19,19 @@ module Ably::Realtime
19
19
  setup_channel_event_handlers
20
20
  end
21
21
 
22
- # Expect SYNC ProtocolMessages from the server with a list of current members on this channel
23
- #
24
- # @return [void]
25
- #
26
22
  # @api private
27
- def sync_expected
28
- presence.members.change_state :sync_starting
23
+ def on_attach(has_presence_flag)
24
+ # RTP1
25
+ if has_presence_flag
26
+ # Expect SYNC ProtocolMessages from the server with a list of current members on this channel
27
+ presence.members.change_state :sync_starting
28
+ else
29
+ # There server has indicated that there are no SYNC ProtocolMessages to come because
30
+ # there are no members on this channel
31
+ logger.debug { "#{self.class.name}: Emitting leave events for all members as a SYNC is not expected and thus there are no members on the channel" }
32
+ presence.members.change_state :sync_none
33
+ end
34
+ presence.members.enter_local_members # RTP17f
29
35
  end
30
36
 
31
37
  # Process presence messages from SYNC messages. Sync can be server-initiated or triggered following ATTACH
@@ -47,17 +53,6 @@ module Ably::Realtime
47
53
  presence.members.change_state :finalizing_sync if presence.members.sync_serial_cursor_at_end?
48
54
  end
49
55
 
50
- # There server has indicated that there are no SYNC ProtocolMessages to come because
51
- # there are no members on this channel
52
- #
53
- # @return [void]
54
- #
55
- # @api private
56
- def sync_not_expected
57
- logger.debug { "#{self.class.name}: Emitting leave events for all members as a SYNC is not expected and thus there are no members on the channel" }
58
- presence.members.change_state :sync_none
59
- end
60
-
61
56
  private
62
57
  def_delegators :presence, :members, :channel
63
58
 
@@ -110,6 +110,14 @@ module Ably::Realtime
110
110
  send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Enter, client_id, data, &success_block)
111
111
  end
112
112
 
113
+ # @api private
114
+ def enter_client_with_id(id, client_id, data = nil, &success_block)
115
+ ensure_supported_client_id client_id
116
+ ensure_supported_payload data
117
+
118
+ send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Enter, client_id, data, id, &success_block)
119
+ end
120
+
113
121
  # Leave this client from this channel. This client will be removed from the presence
114
122
  # set and presence subscribers will see a leave message for this client.
115
123
  #
@@ -338,8 +346,8 @@ module Ably::Realtime
338
346
 
339
347
  private
340
348
  # @return [Ably::Models::PresenceMessage] presence message is returned allowing callbacks to be added
341
- def send_presence_protocol_message(presence_action, client_id, data)
342
- presence_message = create_presence_message(presence_action, client_id, data)
349
+ def send_presence_protocol_message(presence_action, client_id, data, id = nil)
350
+ presence_message = create_presence_message(presence_action, client_id, data, id)
343
351
  unless presence_message.client_id
344
352
  raise Ably::Exceptions::Standard.new('Unable to enter create presence message without a client_id', 400, Ably::Exceptions::Codes::UNABLE_TO_ENTER_PRESENCE_CHANNEL_NO_CLIENTID)
345
353
  end
@@ -355,12 +363,13 @@ module Ably::Realtime
355
363
  presence_message
356
364
  end
357
365
 
358
- def create_presence_message(action, client_id, data)
366
+ def create_presence_message(action, client_id, data, id = nil)
359
367
  model = {
360
368
  action: Ably::Models::PresenceMessage.ACTION(action).to_i,
361
369
  clientId: client_id,
362
- data: data
370
+ data: data,
363
371
  }
372
+ model[:id] = id unless id.nil?
364
373
 
365
374
  Ably::Models::PresenceMessage.new(model, logger: logger).tap do |presence_message|
366
375
  presence_message.encode(client.encoders, channel.options) do |encode_error, error_message|
@@ -433,13 +442,13 @@ module Ably::Realtime
433
442
  deferrable
434
443
  end
435
444
 
436
- def send_presence_action_for_client(action, client_id, data, &success_block)
445
+ def send_presence_action_for_client(action, client_id, data, id = nil, &success_block)
437
446
  requirements_failed_deferrable = ensure_presence_publishable_on_connection_deferrable
438
447
  return requirements_failed_deferrable if requirements_failed_deferrable
439
448
 
440
449
  deferrable = create_deferrable
441
450
  ensure_channel_attached(deferrable) do
442
- send_presence_protocol_message(action, client_id, data).tap do |protocol_message|
451
+ send_presence_protocol_message(action, client_id, data, id).tap do |protocol_message|
443
452
  protocol_message.callback { |message| deferrable_succeed deferrable, &success_block }
444
453
  protocol_message.errback { |error| deferrable_fail deferrable, error }
445
454
  end