ably 1.2.5 → 1.2.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/check.yml +1 -1
- data/CHANGELOG.md +17 -0
- data/README.md +24 -7
- data/SPEC.md +1722 -853
- data/ably.gemspec +1 -1
- data/lib/ably/auth.rb +19 -11
- data/lib/ably/models/protocol_message.rb +5 -26
- data/lib/ably/modules/safe_deferrable.rb +2 -2
- data/lib/ably/modules/state_emitter.rb +1 -1
- data/lib/ably/realtime/auth.rb +4 -0
- data/lib/ably/realtime/channel/channel_manager.rb +51 -48
- data/lib/ably/realtime/channel/channel_properties.rb +9 -0
- data/lib/ably/realtime/channel/channel_state_machine.rb +2 -0
- data/lib/ably/realtime/channel.rb +4 -3
- data/lib/ably/realtime/channels.rb +20 -0
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -13
- data/lib/ably/realtime/client.rb +14 -6
- data/lib/ably/realtime/connection/connection_manager.rb +21 -22
- data/lib/ably/realtime/connection.rb +77 -109
- data/lib/ably/realtime/presence/members_map.rb +41 -92
- data/lib/ably/realtime/presence/presence_manager.rb +12 -17
- data/lib/ably/realtime/presence.rb +15 -6
- data/lib/ably/realtime/push.rb +0 -27
- data/lib/ably/realtime/recovery_key_context.rb +36 -0
- data/lib/ably/rest/client.rb +4 -6
- data/lib/ably/rest/push/admin.rb +1 -1
- data/lib/ably/rest/push.rb +0 -19
- data/lib/ably/util/ably_extensions.rb +29 -0
- data/lib/ably/util/crypto.rb +2 -2
- data/lib/ably/util/safe_deferrable.rb +1 -1
- data/lib/ably/version.rb +5 -7
- data/spec/acceptance/realtime/channel_history_spec.rb +8 -12
- data/spec/acceptance/realtime/channel_spec.rb +474 -300
- data/spec/acceptance/realtime/client_spec.rb +1 -1
- data/spec/acceptance/realtime/connection_failures_spec.rb +8 -25
- data/spec/acceptance/realtime/connection_spec.rb +28 -120
- data/spec/acceptance/realtime/message_spec.rb +23 -52
- data/spec/acceptance/realtime/presence_spec.rb +123 -92
- data/spec/acceptance/rest/channel_spec.rb +2 -2
- data/spec/acceptance/rest/client_spec.rb +9 -2
- data/spec/acceptance/rest/message_spec.rb +8 -11
- data/spec/acceptance/rest/push_admin_spec.rb +20 -15
- data/spec/shared/client_initializer_behaviour.rb +1 -1
- data/spec/support/markdown_spec_formatter.rb +1 -1
- data/spec/unit/models/protocol_message_spec.rb +0 -78
- data/spec/unit/models/token_details_spec.rb +4 -2
- data/spec/unit/realtime/channels_spec.rb +1 -1
- data/spec/unit/realtime/connection_spec.rb +0 -30
- data/spec/unit/realtime/recovery_key_context_spec.rb +36 -0
- data/spec/unit/util/crypto_spec.rb +15 -15
- metadata +9 -9
- data/spec/acceptance/realtime/push_spec.rb +0 -27
- data/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
|