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