ably 1.1.0 → 1.1.4
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/.github/workflows/check.yml +27 -0
- data/CHANGELOG.md +68 -2
- data/COPYRIGHT +1 -0
- data/LICENSE +172 -11
- data/MAINTAINERS.md +1 -0
- data/README.md +3 -7
- data/SPEC.md +944 -914
- data/ably.gemspec +7 -7
- data/lib/ably/auth.rb +12 -2
- data/lib/ably/exceptions.rb +2 -2
- data/lib/ably/logger.rb +7 -1
- data/lib/ably/modules/state_machine.rb +1 -1
- data/lib/ably/realtime/channel.rb +7 -11
- data/lib/ably/realtime/channel/channel_manager.rb +2 -2
- data/lib/ably/realtime/channel/channel_properties.rb +24 -0
- data/lib/ably/realtime/client.rb +12 -3
- data/lib/ably/realtime/connection.rb +31 -19
- data/lib/ably/realtime/connection/connection_manager.rb +19 -3
- data/lib/ably/realtime/connection/websocket_transport.rb +67 -1
- data/lib/ably/realtime/presence.rb +0 -14
- data/lib/ably/rest/channel.rb +25 -17
- data/lib/ably/rest/client.rb +22 -11
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/auth_spec.rb +16 -13
- data/spec/acceptance/realtime/channel_history_spec.rb +26 -20
- data/spec/acceptance/realtime/channel_spec.rb +21 -8
- data/spec/acceptance/realtime/client_spec.rb +80 -20
- data/spec/acceptance/realtime/connection_failures_spec.rb +71 -5
- data/spec/acceptance/realtime/connection_spec.rb +153 -26
- data/spec/acceptance/realtime/message_spec.rb +17 -17
- data/spec/acceptance/realtime/presence_history_spec.rb +0 -58
- data/spec/acceptance/realtime/presence_spec.rb +250 -162
- data/spec/acceptance/realtime/push_admin_spec.rb +49 -25
- data/spec/acceptance/rest/auth_spec.rb +6 -75
- data/spec/acceptance/rest/channel_spec.rb +79 -4
- data/spec/acceptance/rest/channels_spec.rb +6 -0
- data/spec/acceptance/rest/client_spec.rb +72 -12
- data/spec/acceptance/rest/message_spec.rb +8 -27
- data/spec/acceptance/rest/push_admin_spec.rb +67 -27
- data/spec/shared/client_initializer_behaviour.rb +0 -8
- data/spec/spec_helper.rb +2 -1
- data/spec/support/debug_failure_helper.rb +9 -5
- data/spec/support/serialization_helper.rb +21 -0
- data/spec/support/test_app.rb +2 -2
- data/spec/unit/modules/enum_spec.rb +1 -1
- data/spec/unit/realtime/client_spec.rb +20 -7
- data/spec/unit/realtime/connection_spec.rb +1 -1
- metadata +40 -29
- data/.travis.yml +0 -16
data/ably.gemspec
CHANGED
@@ -20,33 +20,33 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_runtime_dependency 'eventmachine', '~> 1.2.6'
|
22
22
|
spec.add_runtime_dependency 'em-http-request', '~> 1.1'
|
23
|
-
spec.add_runtime_dependency 'statesman', '~>
|
24
|
-
spec.add_runtime_dependency 'faraday', '
|
23
|
+
spec.add_runtime_dependency 'statesman', '~> 7.4'
|
24
|
+
spec.add_runtime_dependency 'faraday', '>= 0.12', '< 2.0.0'
|
25
25
|
spec.add_runtime_dependency 'excon', '~> 0.55'
|
26
26
|
|
27
|
-
if RUBY_VERSION.match(/^1
|
27
|
+
if RUBY_VERSION.match(/^1\./)
|
28
28
|
spec.add_runtime_dependency 'json', '< 2.0'
|
29
29
|
else
|
30
30
|
spec.add_runtime_dependency 'json'
|
31
31
|
end
|
32
32
|
spec.add_runtime_dependency 'websocket-driver', '~> 0.7'
|
33
|
-
spec.add_runtime_dependency 'msgpack', '>=
|
33
|
+
spec.add_runtime_dependency 'msgpack', '>= 1.3.0'
|
34
34
|
spec.add_runtime_dependency 'addressable', '>= 2.0.0'
|
35
35
|
|
36
|
-
spec.add_development_dependency 'bundler', '~> 1.3'
|
37
36
|
spec.add_development_dependency 'rake', '~> 11.3'
|
38
37
|
spec.add_development_dependency 'redcarpet', '~> 3.3'
|
39
38
|
spec.add_development_dependency 'rspec', '~> 3.3.0' # version lock, see config.around(:example, :event_machine) in event_machine_helper.rb
|
40
39
|
spec.add_development_dependency 'rspec-retry', '~> 0.6'
|
41
40
|
spec.add_development_dependency 'yard', '~> 0.9'
|
42
41
|
spec.add_development_dependency 'rspec-instafail', '~> 1.0'
|
42
|
+
spec.add_development_dependency 'bundler', '>= 1.3.0'
|
43
43
|
|
44
|
-
if RUBY_VERSION.match(/^1
|
44
|
+
if RUBY_VERSION.match(/^1\./)
|
45
45
|
spec.add_development_dependency 'public_suffix', '~> 1.4.6' # Later versions do not support Ruby 1.9
|
46
46
|
spec.add_development_dependency 'webmock', '2.2'
|
47
47
|
spec.add_development_dependency 'parallel_tests', '~> 2.9.0'
|
48
48
|
else
|
49
|
-
spec.add_development_dependency 'webmock', '~>
|
49
|
+
spec.add_development_dependency 'webmock', '~> 3.11'
|
50
50
|
spec.add_development_dependency 'coveralls'
|
51
51
|
spec.add_development_dependency 'parallel_tests', '~> 2.22'
|
52
52
|
if !RUBY_VERSION.match(/^2\.[0123]/)
|
data/lib/ably/auth.rb
CHANGED
@@ -103,7 +103,6 @@ module Ably
|
|
103
103
|
end
|
104
104
|
|
105
105
|
if has_client_id? && !token_creatable_externally? && !token_option
|
106
|
-
raise ArgumentError, 'client_id cannot be provided without a complete API key or means to authenticate. An API key is needed to automatically authenticate with Ably and obtain a token' unless api_key_present?
|
107
106
|
@client_id = ensure_utf_8(:client_id, client_id) if client_id
|
108
107
|
end
|
109
108
|
|
@@ -377,7 +376,7 @@ module Ably
|
|
377
376
|
# True when Token Auth is being used to authenticate with Ably
|
378
377
|
def using_token_auth?
|
379
378
|
return options[:use_token_auth] if options.has_key?(:use_token_auth)
|
380
|
-
!!(token_option || current_token_details ||
|
379
|
+
!!(token_option || current_token_details || token_creatable_externally?)
|
381
380
|
end
|
382
381
|
|
383
382
|
def client_id
|
@@ -408,6 +407,17 @@ module Ably
|
|
408
407
|
end
|
409
408
|
end
|
410
409
|
|
410
|
+
# Extra headers that may be used during authentication
|
411
|
+
#
|
412
|
+
# @return [Hash] headers
|
413
|
+
def extra_auth_headers
|
414
|
+
if client_id && using_basic_auth?
|
415
|
+
{ 'X-Ably-ClientId' => Base64.urlsafe_encode64(client_id) }
|
416
|
+
else
|
417
|
+
{}
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
411
421
|
# Auth params used in URI endpoint for Realtime connections
|
412
422
|
# Will reauthorize implicitly if required and capable
|
413
423
|
#
|
data/lib/ably/exceptions.rb
CHANGED
@@ -5,7 +5,7 @@ module Ably
|
|
5
5
|
TOKEN_EXPIRED_CODE = 40140..40149
|
6
6
|
|
7
7
|
# Base Ably exception class that contains status and code values used by Ably
|
8
|
-
# Refer to https://github.com/ably/ably-common/blob/
|
8
|
+
# Refer to https://github.com/ably/ably-common/blob/main/protocol/errors.json
|
9
9
|
#
|
10
10
|
# @!attribute [r] message
|
11
11
|
# @return [String] Error message from Ably
|
@@ -116,7 +116,7 @@ module Ably
|
|
116
116
|
class InvalidState < BaseAblyException; end
|
117
117
|
|
118
118
|
# A generic Ably exception taht supports a status & code.
|
119
|
-
# See https://github.com/ably/ably-common/blob/
|
119
|
+
# See https://github.com/ably/ably-common/blob/main/protocol/errors.json for a list of Ably errors
|
120
120
|
class Standard < BaseAblyException; end
|
121
121
|
|
122
122
|
# The HTTP request has returned a 500 error
|
data/lib/ably/logger.rb
CHANGED
@@ -20,6 +20,8 @@ module Ably
|
|
20
20
|
ensure_logger_interface_is_valid
|
21
21
|
|
22
22
|
@logger.level = log_level
|
23
|
+
|
24
|
+
@log_mutex = Mutex.new
|
23
25
|
end
|
24
26
|
|
25
27
|
# The logger used by this class, defaults to {http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html Ruby Logger}
|
@@ -38,7 +40,9 @@ module Ably
|
|
38
40
|
%w(fatal error warn info debug).each do |method_name|
|
39
41
|
define_method(method_name) do |*args, &block|
|
40
42
|
begin
|
41
|
-
|
43
|
+
log_mutex.synchronize do
|
44
|
+
logger.public_send(method_name, *args, &block)
|
45
|
+
end
|
42
46
|
rescue StandardError => e
|
43
47
|
logger.error "Logger: Failed to log #{method_name} block - #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
|
44
48
|
end
|
@@ -46,6 +50,8 @@ module Ably
|
|
46
50
|
end
|
47
51
|
|
48
52
|
private
|
53
|
+
attr_reader :log_mutex
|
54
|
+
|
49
55
|
def client
|
50
56
|
@client
|
51
57
|
end
|
@@ -23,7 +23,7 @@ module Ably::Modules
|
|
23
23
|
def transition_state(state, *args)
|
24
24
|
unless result = transition_to(state.to_sym, *args)
|
25
25
|
exception = exception_for_state_change_to(state)
|
26
|
-
logger.fatal { "#{self.class}: #{exception.message}" }
|
26
|
+
logger.fatal { "#{self.class}: #{exception.message}\n#{caller[0..20].join("\n")}" }
|
27
27
|
end
|
28
28
|
result
|
29
29
|
end
|
@@ -79,6 +79,10 @@ module Ably
|
|
79
79
|
# @return [Hash]
|
80
80
|
attr_reader :options
|
81
81
|
|
82
|
+
# Properties of a channel and its state
|
83
|
+
# @return [{Ably::Realtime::Channel::ChannelProperties}]
|
84
|
+
attr_reader :properties
|
85
|
+
|
82
86
|
# When a channel failure occurs this attribute contains the Ably Exception
|
83
87
|
# @return [Ably::Models::ErrorInfo,Ably::Exceptions::BaseAblyException]
|
84
88
|
attr_reader :error_reason
|
@@ -88,11 +92,6 @@ module Ably
|
|
88
92
|
# @api private
|
89
93
|
attr_reader :manager
|
90
94
|
|
91
|
-
# Serial number assigned to this channel when it was attached
|
92
|
-
# @return [Integer]
|
93
|
-
# @api private
|
94
|
-
attr_reader :attached_serial
|
95
|
-
|
96
95
|
# Initialize a new Channel object
|
97
96
|
#
|
98
97
|
# @param client [Ably::Rest::Client]
|
@@ -112,6 +111,7 @@ module Ably
|
|
112
111
|
@state = STATE(state_machine.current_state)
|
113
112
|
@manager = ChannelManager.new(self, client.connection)
|
114
113
|
@push = PushChannel.new(self)
|
114
|
+
@properties = ChannelProperties.new(self)
|
115
115
|
|
116
116
|
setup_event_handlers
|
117
117
|
setup_presence
|
@@ -292,7 +292,7 @@ module Ably
|
|
292
292
|
error = Ably::Exceptions::InvalidRequest.new('option :until_attach is invalid as the channel is not attached' )
|
293
293
|
return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
|
294
294
|
end
|
295
|
-
options[:from_serial] =
|
295
|
+
options[:from_serial] = properties.attach_serial
|
296
296
|
end
|
297
297
|
|
298
298
|
async_wrap(callback) do
|
@@ -319,11 +319,6 @@ module Ably
|
|
319
319
|
@error_reason = nil
|
320
320
|
end
|
321
321
|
|
322
|
-
# @api private
|
323
|
-
def set_attached_serial(serial)
|
324
|
-
@attached_serial = serial
|
325
|
-
end
|
326
|
-
|
327
322
|
# @api private
|
328
323
|
def update_options(channel_options)
|
329
324
|
@options = channel_options.clone.freeze
|
@@ -372,3 +367,4 @@ end
|
|
372
367
|
require 'ably/realtime/channel/channel_manager'
|
373
368
|
require 'ably/realtime/channel/channel_state_machine'
|
374
369
|
require 'ably/realtime/channel/push_channel'
|
370
|
+
require 'ably/realtime/channel/channel_properties'
|
@@ -37,7 +37,7 @@ module Ably::Realtime
|
|
37
37
|
# library, such as returning to attached whne detach has failed
|
38
38
|
if attached_protocol_message
|
39
39
|
update_presence_sync_state_following_attached attached_protocol_message
|
40
|
-
channel.
|
40
|
+
channel.properties.set_attach_serial(attached_protocol_message.channel_serial)
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -76,7 +76,7 @@ module Ably::Realtime
|
|
76
76
|
update_presence_sync_state_following_attached protocol_message
|
77
77
|
end
|
78
78
|
|
79
|
-
channel.
|
79
|
+
channel.properties.set_attach_serial(protocol_message.channel_serial)
|
80
80
|
end
|
81
81
|
|
82
82
|
# Handle DETACED messages, see #RTL13 for server-initated detaches
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Ably::Realtime
|
2
|
+
class Channel
|
3
|
+
# Represents properties of a channel and its state
|
4
|
+
class ChannelProperties
|
5
|
+
# {Ably::Realtime::Channel} this object associated with
|
6
|
+
# @return [Ably::Realtime::Channel]
|
7
|
+
attr_reader :channel
|
8
|
+
|
9
|
+
# Contains the last channelSerial received in an ATTACHED ProtocolMesage for the channel, see RTL15a
|
10
|
+
#
|
11
|
+
# @return [String]
|
12
|
+
attr_reader :attach_serial
|
13
|
+
|
14
|
+
def initialize(channel)
|
15
|
+
@channel = channel
|
16
|
+
end
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
def set_attach_serial(attach_serial)
|
20
|
+
@attach_serial = attach_serial
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/ably/realtime/client.rb
CHANGED
@@ -65,6 +65,10 @@ module Ably
|
|
65
65
|
# @return [String,Nil]
|
66
66
|
attr_reader :recover
|
67
67
|
|
68
|
+
# Additional parameters to be sent in the querystring when initiating a realtime connection
|
69
|
+
# @return [Hash]
|
70
|
+
attr_reader :transport_params
|
71
|
+
|
68
72
|
def_delegators :auth, :client_id, :auth_options
|
69
73
|
def_delegators :@rest_client, :encoders
|
70
74
|
def_delegators :@rest_client, :use_tls?, :protocol, :protocol_binary?
|
@@ -82,6 +86,7 @@ module Ably
|
|
82
86
|
# @option options [Boolean] :echo_messages If false, prevents messages originating from this connection being echoed back on the same connection
|
83
87
|
# @option options [String] :recover When a recover option is specified a connection inherits the state of a previous connection that may have existed under a different instance of the Realtime library, please refer to the API documentation for further information on connection state recovery
|
84
88
|
# @option options [Boolean] :auto_connect By default as soon as the client library is instantiated it will connect to Ably. You can optionally set this to false and explicitly connect.
|
89
|
+
# @option options [Hash] :transport_params Additional parameters to be sent in the querystring when initiating a realtime connection. Keys are Strings, values are Stringifiable(a value must respond to #to_s)
|
85
90
|
#
|
86
91
|
# @option options [Integer] :channel_retry_timeout (15 seconds). When a channel becomes SUSPENDED, after this delay in seconds, the channel will automatically attempt to reattach if the connection is CONNECTED
|
87
92
|
# @option options [Integer] :disconnected_retry_timeout (15 seconds). When the connection enters the DISCONNECTED state, after this delay in seconds, if the state is still DISCONNECTED, the client library will attempt to reconnect automatically
|
@@ -109,10 +114,10 @@ module Ably
|
|
109
114
|
end
|
110
115
|
end
|
111
116
|
|
117
|
+
@transport_params = options.delete(:transport_params).to_h.each_with_object({}) do |(key, value), acc|
|
118
|
+
acc[key.to_s] = value.to_s
|
119
|
+
end
|
112
120
|
@rest_client = Ably::Rest::Client.new(options.merge(realtime_client: self))
|
113
|
-
@auth = Ably::Realtime::Auth.new(self)
|
114
|
-
@channels = Ably::Realtime::Channels.new(self)
|
115
|
-
@connection = Ably::Realtime::Connection.new(self, options)
|
116
121
|
@echo_messages = rest_client.options.fetch(:echo_messages, true) == false ? false : true
|
117
122
|
@queue_messages = rest_client.options.fetch(:queue_messages, true) == false ? false : true
|
118
123
|
@custom_realtime_host = rest_client.options[:realtime_host] || rest_client.options[:ws_host]
|
@@ -120,6 +125,10 @@ module Ably
|
|
120
125
|
@recover = rest_client.options[:recover]
|
121
126
|
|
122
127
|
raise ArgumentError, "Recovery key '#{recover}' is invalid" if recover && !recover.match(Connection::RECOVER_REGEX)
|
128
|
+
|
129
|
+
@auth = Ably::Realtime::Auth.new(self)
|
130
|
+
@channels = Ably::Realtime::Channels.new(self)
|
131
|
+
@connection = Ably::Realtime::Connection.new(self, options)
|
123
132
|
end
|
124
133
|
|
125
134
|
# Return a {Ably::Realtime::Channel Realtime Channel} for the given name
|
@@ -66,7 +66,7 @@ module Ably
|
|
66
66
|
ensure_state_machine_emits 'Ably::Models::ConnectionStateChange'
|
67
67
|
|
68
68
|
# Expected format for a connection recover key
|
69
|
-
RECOVER_REGEX = /^(?<recover>[
|
69
|
+
RECOVER_REGEX = /^(?<recover>[^:]+):(?<connection_serial>[^:]+):(?<msg_serial>\-?\d+)$/
|
70
70
|
|
71
71
|
# Defaults for automatic connection recovery and timeouts
|
72
72
|
DEFAULTS = {
|
@@ -137,7 +137,6 @@ module Ably
|
|
137
137
|
@client = client
|
138
138
|
@__outgoing_message_queue__ = []
|
139
139
|
@__pending_message_ack_queue__ = []
|
140
|
-
reset_client_serial
|
141
140
|
|
142
141
|
@defaults = DEFAULTS.dup
|
143
142
|
options.each do |key, val|
|
@@ -145,12 +144,25 @@ module Ably
|
|
145
144
|
end if options.kind_of?(Hash)
|
146
145
|
@defaults.freeze
|
147
146
|
|
147
|
+
# If a recover client options is provided, then we need to ensure that the msgSerial matches the
|
148
|
+
# recover serial immediately at client library instantiation. This is done immediately so that any queued
|
149
|
+
# publishes use the correct serial number for these queued messages as well.
|
150
|
+
# There is no harm if the msgSerial is higher than expected if the recover fails.
|
151
|
+
recovery_msg_serial = connection_recover_parts && connection_recover_parts[:msg_serial].to_i
|
152
|
+
if recovery_msg_serial
|
153
|
+
@client_msg_serial = recovery_msg_serial
|
154
|
+
else
|
155
|
+
reset_client_msg_serial
|
156
|
+
end
|
157
|
+
|
148
158
|
Client::IncomingMessageDispatcher.new client, self
|
149
159
|
Client::OutgoingMessageDispatcher.new client, self
|
150
160
|
|
151
161
|
@state_machine = ConnectionStateMachine.new(self)
|
152
162
|
@state = STATE(state_machine.current_state)
|
153
163
|
@manager = ConnectionManager.new(self)
|
164
|
+
|
165
|
+
@current_host = client.endpoint.host
|
154
166
|
end
|
155
167
|
|
156
168
|
# Causes the connection to close, entering the closed state, from any state except
|
@@ -303,18 +315,17 @@ module Ably
|
|
303
315
|
# @!attribute [r] recovery_key
|
304
316
|
# @return [String] recovery key that can be used by another client to recover this connection with the :recover option
|
305
317
|
def recovery_key
|
306
|
-
"#{key}:#{serial}" if connection_resumable?
|
318
|
+
"#{key}:#{serial}:#{client_msg_serial}" if connection_resumable?
|
307
319
|
end
|
308
320
|
|
309
321
|
# Following a new connection being made, the connection ID, connection key
|
310
|
-
# and
|
322
|
+
# and connection serial need to match the details provided by the server.
|
311
323
|
#
|
312
324
|
# @return [void]
|
313
325
|
# @api private
|
314
326
|
def configure_new(connection_id, connection_key, connection_serial)
|
315
327
|
@id = connection_id
|
316
328
|
@key = connection_key
|
317
|
-
@client_serial = connection_serial
|
318
329
|
|
319
330
|
update_connection_serial connection_serial
|
320
331
|
end
|
@@ -420,10 +431,10 @@ module Ably
|
|
420
431
|
client.auth.auth_params.tap do |auth_deferrable|
|
421
432
|
auth_deferrable.callback do |auth_params|
|
422
433
|
url_params = auth_params.merge(
|
423
|
-
format
|
424
|
-
echo
|
425
|
-
v
|
426
|
-
lib
|
434
|
+
'format' => client.protocol,
|
435
|
+
'echo' => client.echo_messages,
|
436
|
+
'v' => Ably::PROTOCOL_VERSION,
|
437
|
+
'lib' => client.rest_client.lib_version_id,
|
427
438
|
)
|
428
439
|
|
429
440
|
# Use native websocket heartbeats if possible, but allow Ably protocol heartbeats
|
@@ -434,6 +445,7 @@ module Ably
|
|
434
445
|
end
|
435
446
|
|
436
447
|
url_params['clientId'] = client.auth.client_id if client.auth.has_client_id?
|
448
|
+
url_params.merge!(client.transport_params)
|
437
449
|
|
438
450
|
if connection_resumable?
|
439
451
|
url_params.merge! resume: key, connection_serial: serial
|
@@ -542,11 +554,11 @@ module Ably
|
|
542
554
|
defaults.fetch(:realtime_request_timeout)
|
543
555
|
end
|
544
556
|
|
545
|
-
# Resets the client serial (msgSerial) sent to Ably for each new {Ably::Models::ProtocolMessage}
|
546
|
-
# (see #
|
557
|
+
# Resets the client message serial (msgSerial) sent to Ably for each new {Ably::Models::ProtocolMessage}
|
558
|
+
# (see #client_msg_serial)
|
547
559
|
# @api private
|
548
|
-
def
|
549
|
-
@
|
560
|
+
def reset_client_msg_serial
|
561
|
+
@client_msg_serial = -1
|
550
562
|
end
|
551
563
|
|
552
564
|
# When a hearbeat or any other message from Ably is received
|
@@ -568,15 +580,15 @@ module Ably
|
|
568
580
|
|
569
581
|
private
|
570
582
|
|
571
|
-
# The client serial is incremented for every message that is published that requires an ACK.
|
583
|
+
# The client message serial (msgSerial) is incremented for every message that is published that requires an ACK.
|
572
584
|
# Note that this is different to the connection serial that contains the last known serial number
|
573
585
|
# received from the server.
|
574
586
|
#
|
575
587
|
# A message serial number does not guarantee a message has been received, only sent.
|
576
588
|
# A connection serial guarantees the server has received the message and is thus used for connection recovery and resumes.
|
577
589
|
# @return [Integer] starting at -1 indicating no messages sent, 0 when the first message is sent
|
578
|
-
def
|
579
|
-
@
|
590
|
+
def client_msg_serial
|
591
|
+
@client_msg_serial
|
580
592
|
end
|
581
593
|
|
582
594
|
def resume_callbacks
|
@@ -601,11 +613,11 @@ module Ably
|
|
601
613
|
end
|
602
614
|
|
603
615
|
def add_message_serial_to(protocol_message)
|
604
|
-
@
|
605
|
-
protocol_message[:msgSerial] =
|
616
|
+
@client_msg_serial += 1
|
617
|
+
protocol_message[:msgSerial] = client_msg_serial
|
606
618
|
yield
|
607
619
|
rescue StandardError => e
|
608
|
-
@
|
620
|
+
@client_msg_serial -= 1
|
609
621
|
raise e
|
610
622
|
end
|
611
623
|
|
@@ -49,18 +49,31 @@ module Ably::Realtime
|
|
49
49
|
|
50
50
|
logger.debug { 'ConnectionManager: Opening a websocket transport connection' }
|
51
51
|
|
52
|
+
# The socket attempt can fail at the same time as a timer firing so ensure
|
53
|
+
# only one outcome is processed from this setup attempt
|
54
|
+
setup_attempt_status = {}
|
55
|
+
setup_failed = lambda do
|
56
|
+
return true if setup_attempt_status[:failed]
|
57
|
+
setup_attempt_status[:failed] = true
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
52
61
|
connection.create_websocket_transport.tap do |socket_deferrable|
|
53
62
|
socket_deferrable.callback do |websocket_transport|
|
54
63
|
subscribe_to_transport_events websocket_transport
|
55
64
|
yield websocket_transport if block_given?
|
56
65
|
end
|
57
66
|
socket_deferrable.errback do |error|
|
67
|
+
next if setup_failed.call
|
58
68
|
connection_opening_failed error
|
59
69
|
end
|
60
70
|
end
|
61
71
|
|
72
|
+
# The connection request timeout must be marginally higher than the REST request timeout to ensure
|
73
|
+
# any HTTP auth request failure due to timeout triggers before the connection timer kicks in
|
62
74
|
logger.debug { "ConnectionManager: Setting up automatic connection timeout timer for #{realtime_request_timeout}s" }
|
63
75
|
create_timeout_timer_whilst_in_state(:connecting, realtime_request_timeout) do
|
76
|
+
next if setup_failed.call
|
64
77
|
connection_opening_failed Ably::Exceptions::ConnectionTimeout.new("Connection to Ably timed out after #{realtime_request_timeout}s", nil, Ably::Exceptions::Codes::CONNECTION_TIMED_OUT)
|
65
78
|
end
|
66
79
|
end
|
@@ -80,7 +93,12 @@ module Ably::Realtime
|
|
80
93
|
|
81
94
|
logger.warn { "ConnectionManager: Connection to #{connection.current_host}:#{connection.port} failed; #{error.message}" }
|
82
95
|
next_state = get_next_retry_state_info
|
83
|
-
|
96
|
+
|
97
|
+
if connection.state == next_state.fetch(:state)
|
98
|
+
logger.error { "ConnectionManager: Skipping next retry state after connection opening failed as already in state #{next_state}\n#{caller[0..20].join("\n")}" }
|
99
|
+
else
|
100
|
+
connection.transition_state_machine next_state.fetch(:state), retry_in: next_state.fetch(:pause), reason: Ably::Exceptions::ConnectionError.new("Connection failed: #{error.message}", nil, Ably::Exceptions::Codes::CONNECTION_FAILED, error)
|
101
|
+
end
|
84
102
|
end
|
85
103
|
|
86
104
|
# Called whenever a new connection is made
|
@@ -100,13 +118,11 @@ module Ably::Realtime
|
|
100
118
|
resend_pending_message_ack_queue
|
101
119
|
else
|
102
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_key}" }
|
103
|
-
connection.reset_client_serial
|
104
121
|
nack_messages_on_all_channels protocol_message.error
|
105
122
|
force_reattach_on_channels protocol_message.error
|
106
123
|
end
|
107
124
|
else
|
108
125
|
logger.debug { "ConnectionManager: New connection created with ID #{protocol_message.connection_id} and key #{protocol_message.connection_key}" }
|
109
|
-
connection.reset_client_serial
|
110
126
|
end
|
111
127
|
|
112
128
|
reattach_suspended_channels protocol_message.error
|