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.
- checksums.yaml +4 -4
- data/README.md +11 -8
- data/lib/submodules/ably-ruby/.github/workflows/check.yml +2 -1
- data/lib/submodules/ably-ruby/CHANGELOG.md +25 -0
- data/lib/submodules/ably-ruby/README.md +24 -7
- data/lib/submodules/ably-ruby/SPEC.md +1738 -869
- data/lib/submodules/ably-ruby/ably.gemspec +1 -1
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +19 -11
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +4 -26
- data/lib/submodules/ably-ruby/lib/ably/modules/safe_deferrable.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +4 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +51 -48
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_properties.rb +9 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +2 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +4 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +20 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -13
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +14 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +21 -22
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +77 -109
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +41 -92
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_manager.rb +12 -17
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +15 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/push.rb +0 -27
- data/lib/submodules/ably-ruby/lib/ably/realtime/recovery_key_context.rb +36 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +4 -6
- data/lib/submodules/ably-ruby/lib/ably/rest/push/admin.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/rest/push.rb +0 -19
- data/lib/submodules/ably-ruby/lib/ably/util/ably_extensions.rb +29 -0
- data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/util/safe_deferrable.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/version.rb +5 -7
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +0 -7
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +8 -12
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +474 -300
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +8 -25
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +28 -120
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +24 -53
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +123 -92
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +9 -2
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +8 -11
- data/lib/submodules/ably-ruby/spec/acceptance/rest/push_admin_spec.rb +20 -15
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +1 -1
- data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +0 -86
- data/lib/submodules/ably-ruby/spec/unit/models/token_details_spec.rb +4 -2
- data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +0 -30
- data/lib/submodules/ably-ruby/spec/unit/realtime/recovery_key_context_spec.rb +36 -0
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +15 -15
- metadata +5 -4
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_spec.rb +0 -27
- 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
|
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
|
-
# @
|
335
|
+
# @deprecated Use {#create_recovery_key} instead
|
355
336
|
#
|
356
337
|
def recovery_key
|
357
|
-
"
|
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
|
-
#
|
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
|
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
|
-
@
|
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)
|
448
|
-
|
449
|
-
|
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
|
-
|
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.
|
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
|
490
|
-
url_params.merge! resume: key
|
491
|
-
logger.debug { "Resuming connection key #{key}
|
492
|
-
elsif
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
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
|
-
:
|
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
|
-
|
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
|
-
|
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(&
|
125
|
-
off(&
|
126
|
-
channel.off(&
|
114
|
+
off(&sync_complete_callback)
|
115
|
+
off(&sync_failed_callback)
|
116
|
+
channel.off(&sync_failed_callback)
|
127
117
|
end
|
128
118
|
|
129
|
-
unsafe_once(:
|
130
|
-
unsafe_once(:failed, &
|
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
|
-
|
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 [
|
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
|
-
|
244
|
-
change_state :
|
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
|
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.
|
398
|
-
logger.debug { "#{self.class.name}: Local member '#{presence_message.
|
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
|
405
|
-
|
406
|
-
#
|
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
|
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
|
28
|
-
|
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
|