ably-rest 0.8.5 → 0.8.6
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 +1 -1
- data/SPEC.md +1380 -631
- data/ably-rest.gemspec +11 -5
- data/lib/submodules/ably-ruby/.travis.yml +1 -1
- data/lib/submodules/ably-ruby/CHANGELOG.md +42 -48
- data/lib/submodules/ably-ruby/ably.gemspec +7 -1
- data/lib/submodules/ably-ruby/lib/ably.rb +2 -0
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +155 -47
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +2 -0
- data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +2 -3
- data/lib/submodules/ably-ruby/lib/ably/models/connection_details.rb +54 -0
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +14 -4
- data/lib/submodules/ably-ruby/lib/ably/models/token_details.rb +13 -7
- data/lib/submodules/ably-ruby/lib/ably/models/token_request.rb +1 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +3 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/message_emitter.rb +1 -3
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +6 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +15 -4
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +2 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +10 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +11 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +62 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +58 -54
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +18 -5
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +9 -1
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +32 -14
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +251 -11
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +12 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +316 -24
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +93 -1
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +177 -86
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +284 -60
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +45 -6
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +4 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +181 -49
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +13 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +222 -4
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +132 -1
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +129 -28
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +7 -7
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +10 -0
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +41 -17
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +1 -0
- data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +16 -0
- data/lib/submodules/ably-ruby/spec/unit/models/connection_details_spec.rb +60 -0
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +45 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +3 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +6 -5
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +5 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +5 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +5 -1
- metadata +57 -13
@@ -3,9 +3,9 @@ module Ably::Models
|
|
3
3
|
# when a state change occurs
|
4
4
|
#
|
5
5
|
# @!attribute [r] current
|
6
|
-
# @return [Connection::STATE] Current
|
6
|
+
# @return [Connection::STATE] Current channel state
|
7
7
|
# @!attribute [r] previous
|
8
|
-
# @return [Connection::STATE] Previous
|
8
|
+
# @return [Connection::STATE] Previous channel state
|
9
9
|
# @!attribute [r] reason
|
10
10
|
# @return [Ably::Models::ErrorInfo] Object describing the reason for a state change when not initiated by the consumer of the client library
|
11
11
|
#
|
@@ -20,7 +20,6 @@ module Ably::Models
|
|
20
20
|
@hash_object = {
|
21
21
|
current: hash_object.fetch(:current),
|
22
22
|
previous: hash_object.fetch(:previous),
|
23
|
-
retry_in: hash_object[:retry_in],
|
24
23
|
reason: hash_object[:reason],
|
25
24
|
protocol_message: hash_object[:protocol_message]
|
26
25
|
}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Ably::Models
|
2
|
+
# Convert connection details attributes to a {ConnectionDetails} object
|
3
|
+
#
|
4
|
+
# @param attributes (see #initialize)
|
5
|
+
#
|
6
|
+
# @return [ConnectionDetails]
|
7
|
+
def self.ConnectionDetails(attributes)
|
8
|
+
case attributes
|
9
|
+
when ConnectionDetails
|
10
|
+
return attributes
|
11
|
+
else
|
12
|
+
ConnectionDetails.new(attributes || {})
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# ConnectionDetails are optionally passed to the client library in the +CONNECTED+ {Ably::Models::ProtocolMessage#connectionDetails} attribute
|
17
|
+
# to inform the client about any constraints it should adhere to and provide additional metadata about the connection.
|
18
|
+
# For example, if a request is made to publish a message that exceeds the +maxMessageSize+, the client library can reject
|
19
|
+
# the message immediately, without communicating with the Ably service
|
20
|
+
#
|
21
|
+
class ConnectionDetails
|
22
|
+
include Ably::Modules::ModelCommon
|
23
|
+
|
24
|
+
# @param attributes [Hash]
|
25
|
+
# @option attributes [String] :client_id contains the client ID assigned to the connection
|
26
|
+
# @option attributes [String] :connection_key the connection secret key string that is used to resume a connection and its state
|
27
|
+
# @option attributes [Integer] :max_message_size maximum individual message size in bytes
|
28
|
+
# @option attributes [Integer] :max_frame_size maximum size for a single frame of data sent to Ably. This restriction applies to a {Ably::Models::ProtocolMessage} sent over a realtime connection, or the total body size for a REST request
|
29
|
+
# @option attributes [Integer] :max_inbound_rate maximum allowable number of requests per second from a client
|
30
|
+
# @option attributes [Integer] :connection_state_ttl duration in seconds that Ably will persist the connection state when a Realtime client is abruptly disconnected
|
31
|
+
#
|
32
|
+
def initialize(attributes = {})
|
33
|
+
@hash_object = IdiomaticRubyWrapper(attributes.clone)
|
34
|
+
hash[:connection_state_ttl] = (hash[:connection_state_ttl].to_f / 1000).round if hash[:connection_state_ttl]
|
35
|
+
hash.freeze
|
36
|
+
end
|
37
|
+
|
38
|
+
%w(client_id connection_key max_message_size max_frame_size max_inbound_rate connection_state_ttl).each do |attribute|
|
39
|
+
define_method attribute do
|
40
|
+
hash[attribute.to_sym]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_client_id?
|
45
|
+
hash.has_key?(:client_id)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @!attribute [r] hash
|
49
|
+
# @return [Hash] Access the token details Hash object ruby'fied to use symbolized keys
|
50
|
+
def hash
|
51
|
+
@hash_object
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -10,12 +10,12 @@ module Ably::Models
|
|
10
10
|
# @return [ACTION] Protocol Message action {Ably::Modules::Enum} from list of {ACTION}. Returns nil if action is unsupported by protocol
|
11
11
|
# @!attribute [r] count
|
12
12
|
# @return [Integer] The count field is used for ACK and NACK actions. See {http://docs.ably.io/client-lib-development-guide/protocol/#message-acknowledgement message acknowledgement protocol}
|
13
|
-
# @!attribute [r]
|
13
|
+
# @!attribute [r] error
|
14
14
|
# @return [ErrorInfo] Contains error information
|
15
15
|
# @!attribute [r] channel
|
16
16
|
# @return [String] Channel name for messages
|
17
17
|
# @!attribute [r] channel_serial
|
18
|
-
# @return [String] Contains a serial number for a message on the current
|
18
|
+
# @return [String] Contains a serial number for a message on the current channelƒ
|
19
19
|
# @!attribute [r] connection_id
|
20
20
|
# @return [String] Contains a string public identifier for the connection
|
21
21
|
# @!attribute [r] connection_key
|
@@ -88,12 +88,18 @@ module Ably::Models
|
|
88
88
|
@hash_object.freeze
|
89
89
|
end
|
90
90
|
|
91
|
-
%w(id channel channel_serial connection_id
|
91
|
+
%w(id channel channel_serial connection_id).each do |attribute|
|
92
92
|
define_method attribute do
|
93
93
|
hash[attribute.to_sym]
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
+
def connection_key
|
98
|
+
# connection_key in connection details takes precedence over connection_key on the ProtocolMessage
|
99
|
+
# connection_key in the ProtocolMessage will be deprecated in future protocol versions > 0.8
|
100
|
+
connection_details.connection_key || hash[:connection_key]
|
101
|
+
end
|
102
|
+
|
97
103
|
def id!
|
98
104
|
raise RuntimeError, 'ProtocolMessage #id is nil' unless id
|
99
105
|
id
|
@@ -106,7 +112,7 @@ module Ably::Models
|
|
106
112
|
end
|
107
113
|
|
108
114
|
def error
|
109
|
-
@
|
115
|
+
@error ||= ErrorInfo.new(hash[:error]) if hash[:error]
|
110
116
|
end
|
111
117
|
|
112
118
|
def timestamp
|
@@ -181,6 +187,10 @@ module Ably::Models
|
|
181
187
|
flags & 1 == 1
|
182
188
|
end
|
183
189
|
|
190
|
+
def connection_details
|
191
|
+
@connection_details ||= Ably::Models::ConnectionDetails(hash[:connection_details])
|
192
|
+
end
|
193
|
+
|
184
194
|
# Indicates this protocol message will generate an ACK response when sent
|
185
195
|
# Examples of protocol messages required ACK include :message and :presence
|
186
196
|
def ack_required?
|
@@ -1,8 +1,7 @@
|
|
1
1
|
module Ably::Models
|
2
2
|
# Convert token details argument to a {TokenDetails} object
|
3
3
|
#
|
4
|
-
# @param attributes
|
5
|
-
# @option attributes (see TokenDetails#initialize)
|
4
|
+
# @param attributes (see #initialize)
|
6
5
|
#
|
7
6
|
# @return [TokenDetails]
|
8
7
|
def self.TokenDetails(attributes)
|
@@ -27,10 +26,6 @@ module Ably::Models
|
|
27
26
|
# For example, if buffer is 10s, the token can no longer be used for new requests 9s before it expires
|
28
27
|
TOKEN_EXPIRY_BUFFER = 15
|
29
28
|
|
30
|
-
def initialize(attributes)
|
31
|
-
@hash_object = IdiomaticRubyWrapper(attributes.clone.freeze)
|
32
|
-
end
|
33
|
-
|
34
29
|
# @param attributes
|
35
30
|
# @option attributes [String] :token token used to authenticate requests
|
36
31
|
# @option attributes [String] :key_name API key name used to create this token
|
@@ -76,7 +71,7 @@ module Ably::Models
|
|
76
71
|
# @!attribute [r] capability
|
77
72
|
# @return [Hash] Capabilities assigned to this token
|
78
73
|
def capability
|
79
|
-
JSON.parse(hash.fetch(:capability)) if hash.
|
74
|
+
JSON.parse(hash.fetch(:capability)) if hash.has_key?(:capability)
|
80
75
|
end
|
81
76
|
|
82
77
|
# @!attribute [r] client_id
|
@@ -94,10 +89,21 @@ module Ably::Models
|
|
94
89
|
expires < Time.now + TOKEN_EXPIRY_BUFFER
|
95
90
|
end
|
96
91
|
|
92
|
+
# True if the TokenDetails was created from an opaque string i.e. no metadata exists for this token
|
93
|
+
# @return [Boolean]
|
94
|
+
# @api private
|
95
|
+
def from_token_string?
|
96
|
+
hash.keys == [:token]
|
97
|
+
end
|
98
|
+
|
97
99
|
# @!attribute [r] hash
|
98
100
|
# @return [Hash] Access the token details Hash object ruby'fied to use symbolized keys
|
99
101
|
def hash
|
100
102
|
@hash_object
|
101
103
|
end
|
104
|
+
|
105
|
+
def to_s
|
106
|
+
"<TokenDetails token=#{token} client_id=#{client_id} key_name=#{key_name} issued=#{issued} expires=#{expires} capability=#{capability} expired?=#{expired?}>"
|
107
|
+
end
|
102
108
|
end
|
103
109
|
end
|
@@ -1,8 +1,7 @@
|
|
1
1
|
module Ably::Models
|
2
2
|
# Convert token request argument to a {TokenRequest} object
|
3
3
|
#
|
4
|
-
# @param attributes
|
5
|
-
# @option attributes (see TokenRequest#initialize)
|
4
|
+
# @param attributes (see #initialize)
|
6
5
|
#
|
7
6
|
# @return [TokenRequest]
|
8
7
|
def self.TokenRequest(attributes)
|
@@ -7,9 +7,10 @@ module Ably
|
|
7
7
|
# Fallback hosts to use when a connection to rest/realtime.ably.io is not possible due to
|
8
8
|
# network failures either at the client, between the client and Ably, within an Ably data center, or at the IO domain registrar
|
9
9
|
#
|
10
|
-
FALLBACK_HOSTS = %w(A.ably-realtime.com B.ably-realtime.com C.ably-realtime.com D.ably-realtime.com E.ably-realtime.com)
|
10
|
+
FALLBACK_HOSTS = %w(A.ably-realtime.com B.ably-realtime.com C.ably-realtime.com D.ably-realtime.com E.ably-realtime.com).freeze
|
11
|
+
|
11
12
|
INTERNET_CHECK = {
|
12
13
|
url: '//internet-up.ably-realtime.com/is-the-internet-up.txt',
|
13
14
|
ok_text: 'yes'
|
14
|
-
}
|
15
|
+
}.freeze
|
15
16
|
end
|
@@ -50,10 +50,8 @@ module Ably::Modules
|
|
50
50
|
#
|
51
51
|
# @api private
|
52
52
|
def emit_message(name, payload)
|
53
|
-
raise 'Event name is required' unless name
|
54
|
-
|
55
53
|
message_emitter_subscriptions[:all].each { |cb| cb.call(payload) }
|
56
|
-
message_emitter_subscriptions[name].each { |cb| cb.call(payload) }
|
54
|
+
message_emitter_subscriptions[name].each { |cb| cb.call(payload) } if name
|
57
55
|
end
|
58
56
|
|
59
57
|
private
|
@@ -71,11 +71,11 @@ module Ably::Modules
|
|
71
71
|
# @yield block is called if the state is matched immediately or once when the state is reached
|
72
72
|
#
|
73
73
|
# @return [void]
|
74
|
-
def once_or_if(target_states, options = {})
|
74
|
+
def once_or_if(target_states, options = {}, &block)
|
75
75
|
raise ArgumentError, 'Block required' unless block_given?
|
76
76
|
|
77
77
|
if Array(target_states).any? { |target_state| state == target_state }
|
78
|
-
|
78
|
+
safe_yield block
|
79
79
|
else
|
80
80
|
failure_block = options.fetch(:else, nil)
|
81
81
|
failure_wrapper = nil
|
@@ -37,6 +37,8 @@ module Ably
|
|
37
37
|
include Ably::Modules::AsyncWrapper
|
38
38
|
|
39
39
|
def_delegators :auth_sync, :client_id
|
40
|
+
def_delegators :auth_sync, :token_client_id_allowed?, :configure_client_id, :client_id_validated?
|
41
|
+
def_delegators :auth_sync, :can_assume_client_id?, :has_client_id?
|
40
42
|
def_delegators :auth_sync, :current_token_details, :token
|
41
43
|
def_delegators :auth_sync, :key, :key_name, :key_secret, :options, :auth_options, :token_params
|
42
44
|
def_delegators :auth_sync, :using_basic_auth?, :using_token_auth?
|
@@ -68,6 +70,10 @@ module Ably
|
|
68
70
|
def authorise(token_params = {}, auth_options = {}, &success_callback)
|
69
71
|
async_wrap(success_callback) do
|
70
72
|
auth_sync.authorise(token_params, auth_options)
|
73
|
+
end.tap do |deferrable|
|
74
|
+
deferrable.errback do |error|
|
75
|
+
client.connection.transition_state_machine :failed, reason: error if error.kind_of?(Ably::Exceptions::IncompatibleClientId)
|
76
|
+
end
|
71
77
|
end
|
72
78
|
end
|
73
79
|
|
@@ -309,9 +309,20 @@ module Ably
|
|
309
309
|
|
310
310
|
# Queue messages and process queue if channel is attached.
|
311
311
|
# If channel is not yet attached, attempt to attach it before the message queue is processed.
|
312
|
-
# @
|
312
|
+
# @return [Ably::Util::SafeDeferrable]
|
313
313
|
def queue_messages(raw_messages)
|
314
|
-
messages = Array(raw_messages).map
|
314
|
+
messages = Array(raw_messages).map do |raw_msg|
|
315
|
+
create_message(raw_msg).tap do |message|
|
316
|
+
next if message.client_id.nil?
|
317
|
+
if message.client_id == '*'
|
318
|
+
raise Ably::Exceptions::IncompatibleClientId.new('Wildcard client_id is reserved and cannot be used when publishing messages', 400, 40012)
|
319
|
+
end
|
320
|
+
unless client.auth.can_assume_client_id?(message.client_id)
|
321
|
+
raise Ably::Exceptions::IncompatibleClientId.new("Cannot publish with client_id '#{message.client_id}' as it is incompatible with the current configured client_id '#{client.client_id}'", 400, 40012)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
315
326
|
queue.push *messages
|
316
327
|
|
317
328
|
if attached?
|
@@ -340,12 +351,12 @@ module Ably
|
|
340
351
|
Ably::Util::SafeDeferrable.new(logger).tap do |deferrable|
|
341
352
|
messages.each do |message|
|
342
353
|
message.callback do
|
343
|
-
|
354
|
+
next if failed
|
344
355
|
actual_deliveries += 1
|
345
356
|
deferrable.succeed messages if actual_deliveries == expected_deliveries
|
346
357
|
end
|
347
358
|
message.errback do |error|
|
348
|
-
|
359
|
+
next if failed
|
349
360
|
failed = true
|
350
361
|
deferrable.fail error, message
|
351
362
|
end
|
@@ -123,6 +123,8 @@ module Ably::Realtime
|
|
123
123
|
# It is up to Ably to ensure that duplicate messages are not retransmitted on the channel
|
124
124
|
# base on the serial numbers
|
125
125
|
#
|
126
|
+
# TODO: Move this into the Connection class, it does not belong in a Channel class
|
127
|
+
#
|
126
128
|
# @api private
|
127
129
|
def resend_pending_message_ack_queue
|
128
130
|
connection.__pending_message_ack_queue__.delete_if do |protocol_message|
|
@@ -30,6 +30,10 @@ module Ably
|
|
30
30
|
# (see Ably::Rest::Client#auth)
|
31
31
|
attr_reader :auth
|
32
32
|
|
33
|
+
# The underlying connection for this client
|
34
|
+
# @return [Aby::Realtime::Connection]
|
35
|
+
attr_reader :connection
|
36
|
+
|
33
37
|
# The {Ably::Rest::Client REST client} instantiated with the same credentials and configuration that is used for all REST operations such as authentication
|
34
38
|
# @return [Ably::Rest::Client]
|
35
39
|
attr_reader :rest_client
|
@@ -69,6 +73,9 @@ module Ably
|
|
69
73
|
# @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
|
70
74
|
# @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.
|
71
75
|
#
|
76
|
+
# @option options [Integer] :disconnected_retry_timeout (15 seconds). When the connection enters the DISCONNECTED state, after this delay in milliseconds, if the state is still DISCONNECTED, the client library will attempt to reconnect automatically
|
77
|
+
# @option options [Integer] :suspended_retry_timeout (30 seconds). When the connection enters the SUSPENDED state, after this delay in milliseconds, if the state is still SUSPENDED, the client library will attempt to reconnect automatically
|
78
|
+
#
|
72
79
|
# @return [Ably::Realtime::Client]
|
73
80
|
#
|
74
81
|
# @example
|
@@ -82,6 +89,7 @@ module Ably
|
|
82
89
|
@rest_client = Ably::Rest::Client.new(options)
|
83
90
|
@auth = Ably::Realtime::Auth.new(self)
|
84
91
|
@channels = Ably::Realtime::Channels.new(self)
|
92
|
+
@connection = Ably::Realtime::Connection.new(self, options)
|
85
93
|
@echo_messages = @rest_client.options.fetch(:echo_messages, true) == false ? false : true
|
86
94
|
@queue_messages = @rest_client.options.fetch(:queue_messages, true) == false ? false : true
|
87
95
|
@custom_realtime_host = @rest_client.options[:realtime_host] || @rest_client.options[:ws_host]
|
@@ -142,10 +150,9 @@ module Ably
|
|
142
150
|
endpoint_for_host(custom_realtime_host || [environment, DOMAIN].compact.join('-'))
|
143
151
|
end
|
144
152
|
|
145
|
-
|
146
|
-
# @return [Aby::Realtime::Connection] The underlying connection for this client
|
153
|
+
|
147
154
|
def connection
|
148
|
-
@connection
|
155
|
+
@connection
|
149
156
|
end
|
150
157
|
|
151
158
|
# (see Ably::Rest::Client#register_encoder)
|
@@ -73,7 +73,7 @@ module Ably::Realtime
|
|
73
73
|
elsif connection.connected?
|
74
74
|
logger.error "CONNECTED ProtocolMessage should not have been received when the connection is in the CONNECTED state"
|
75
75
|
else
|
76
|
-
|
76
|
+
process_connected_message protocol_message
|
77
77
|
end
|
78
78
|
|
79
79
|
when ACTION.Disconnect, ACTION.Disconnected
|
@@ -141,6 +141,16 @@ module Ably::Realtime
|
|
141
141
|
connection.manager.error_received_from_server protocol_message.error
|
142
142
|
end
|
143
143
|
|
144
|
+
def process_connected_message(protocol_message)
|
145
|
+
if client.auth.token_client_id_allowed?(protocol_message.connection_details.client_id)
|
146
|
+
client.auth.configure_client_id protocol_message.connection_details.client_id
|
147
|
+
connection.transition_state_machine :connected, reason: protocol_message.error, protocol_message: protocol_message
|
148
|
+
else
|
149
|
+
reason = Ably::Exceptions::IncompatibleClientId.new("Client ID '#{protocol_message.connection_details.client_id}' specified by the server is incompatible with the library's configured client ID '#{client.client_id}'", 400, 40012)
|
150
|
+
connection.transition_state_machine :failed, reason: reason, protocol_message: protocol_message
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
144
154
|
def update_connection_recovery_info(protocol_message)
|
145
155
|
connection.update_connection_serial protocol_message.connection_serial if protocol_message.has_connection_serial?
|
146
156
|
end
|
@@ -60,6 +60,14 @@ module Ably
|
|
60
60
|
# Expected format for a connection recover key
|
61
61
|
RECOVER_REGEX = /^(?<recover>[\w-]+):(?<connection_serial>\-?\w+)$/
|
62
62
|
|
63
|
+
# Defaults for automatic connection recovery and timeouts
|
64
|
+
DEFAULTS = {
|
65
|
+
disconnected_retry_timeout: 15, # when the connection enters the DISCONNECTED state, after this delay in milliseconds, if the state is still DISCONNECTED, the client library will attempt to reconnect automatically
|
66
|
+
suspended_retry_timeout: 30, # when the connection enters the SUSPENDED state, after this delay in milliseconds, if the state is still SUSPENDED, the client library will attempt to reconnect automatically
|
67
|
+
connection_state_ttl: 60, # the duration that Ably will persist the connection state when a Realtime client is abruptly disconnected
|
68
|
+
realtime_request_timeout: 10 # default timeout when establishing a connection, or sending a HEARTBEAT, CONNECT, ATTACH, DETACH or CLOSE ProtocolMessage
|
69
|
+
}.freeze
|
70
|
+
|
63
71
|
# A unique public identifier for this connection, used to identify this member in presence events and messages
|
64
72
|
# @return [String]
|
65
73
|
attr_reader :id
|
@@ -90,23 +98,35 @@ module Ably
|
|
90
98
|
# @api private
|
91
99
|
attr_reader :manager
|
92
100
|
|
93
|
-
# An internal queue used to manage unsent outgoing messages.
|
101
|
+
# An internal queue used to manage unsent outgoing messages. You should never interface with this array directly
|
94
102
|
# @return [Array]
|
95
103
|
# @api private
|
96
104
|
attr_reader :__outgoing_message_queue__
|
97
105
|
|
98
|
-
# An internal queue used to manage sent messages.
|
106
|
+
# An internal queue used to manage sent messages. You should never interface with this array directly
|
99
107
|
# @return [Array]
|
100
108
|
# @api private
|
101
109
|
attr_reader :__pending_message_ack_queue__
|
102
110
|
|
111
|
+
# Configured recovery and timeout defaults for this {Connection}.
|
112
|
+
# See the configurable options in {Ably::Realtime::Client#initialize}.
|
113
|
+
# The defaults are immutable
|
114
|
+
# @return [Hash]
|
115
|
+
attr_reader :defaults
|
116
|
+
|
103
117
|
# @api public
|
104
|
-
def initialize(client)
|
118
|
+
def initialize(client, options)
|
105
119
|
@client = client
|
106
120
|
@client_serial = -1
|
107
121
|
@__outgoing_message_queue__ = []
|
108
122
|
@__pending_message_ack_queue__ = []
|
109
123
|
|
124
|
+
@defaults = DEFAULTS.dup
|
125
|
+
options.each do |key, val|
|
126
|
+
@defaults[key] = val if DEFAULTS.has_key?(key)
|
127
|
+
end if options.kind_of?(Hash)
|
128
|
+
@defaults.freeze
|
129
|
+
|
110
130
|
Client::IncomingMessageDispatcher.new client, self
|
111
131
|
Client::OutgoingMessageDispatcher.new client, self
|
112
132
|
|
@@ -133,6 +153,11 @@ module Ably
|
|
133
153
|
|
134
154
|
# Causes the library to attempt connection. If it was previously explicitly
|
135
155
|
# closed by the user, or was closed as a result of an unrecoverable error, a new connection will be opened.
|
156
|
+
# Succeeds when connection is established i.e. state is @Connected@
|
157
|
+
# Fails when state becomes either @Closing@, @Closed@ or @Failed@
|
158
|
+
#
|
159
|
+
# Note that if the connection remains in the disconnected ans suspended states indefinitely,
|
160
|
+
# the Deferrable or block provided may never be called
|
136
161
|
#
|
137
162
|
# @yield block is called as soon as this connection is in the Connected state
|
138
163
|
#
|
@@ -143,14 +168,32 @@ module Ably
|
|
143
168
|
raise exception_for_state_change_to(:connecting) unless can_transition_to?(:connecting)
|
144
169
|
transition_state_machine :connecting
|
145
170
|
end
|
146
|
-
|
171
|
+
|
172
|
+
Ably::Util::SafeDeferrable.new(logger).tap do |deferrable|
|
173
|
+
deferrable.callback do
|
174
|
+
yield if block_given?
|
175
|
+
end
|
176
|
+
succeed_callback = deferrable.method(:succeed)
|
177
|
+
fail_callback = deferrable.method(:fail)
|
178
|
+
|
179
|
+
once(:connected) do
|
180
|
+
deferrable.succeed
|
181
|
+
off &fail_callback
|
182
|
+
end
|
183
|
+
|
184
|
+
once(:failed, :closed, :closing) do
|
185
|
+
deferrable.fail
|
186
|
+
off &succeed_callback
|
187
|
+
end
|
188
|
+
end
|
147
189
|
end
|
148
190
|
|
149
191
|
# Sends a ping to Ably and yields the provided block when a heartbeat ping request is echoed from the server.
|
150
192
|
# This can be useful for measuring true roundtrip client to Ably server latency for a simple message, or checking that an underlying transport is responding currently.
|
151
193
|
# The elapsed milliseconds is passed as an argument to the block and represents the time taken to echo a ping heartbeat once the connection is in the `:connected` state.
|
152
194
|
#
|
153
|
-
# @yield [Integer] if a block is passed to this method, then this block will be called once the ping heartbeat is received with the time elapsed in milliseconds
|
195
|
+
# @yield [Integer] if a block is passed to this method, then this block will be called once the ping heartbeat is received with the time elapsed in milliseconds.
|
196
|
+
# If the ping is not received within an acceptable timeframe, the block will be called with +nil+ as he first argument
|
154
197
|
#
|
155
198
|
# @example
|
156
199
|
# client = Ably::Rest::Client.new(key: 'key.id:secret')
|
@@ -165,9 +208,12 @@ module Ably
|
|
165
208
|
raise RuntimeError, 'Cannot send a ping when connection is in a closed or failed state' if closed? || failed?
|
166
209
|
|
167
210
|
started = nil
|
211
|
+
finished = false
|
168
212
|
|
169
213
|
wait_for_ping = Proc.new do |protocol_message|
|
214
|
+
next if finished
|
170
215
|
if protocol_message.action == Ably::Models::ProtocolMessage::ACTION.Heartbeat
|
216
|
+
finished = true
|
171
217
|
__incoming_protocol_msgbus__.unsubscribe(:protocol_message, &wait_for_ping)
|
172
218
|
time_passed = (Time.now.to_f * 1000 - started.to_f * 1000).to_i
|
173
219
|
safe_yield block, time_passed if block_given?
|
@@ -175,10 +221,19 @@ module Ably
|
|
175
221
|
end
|
176
222
|
|
177
223
|
once_or_if(STATE.Connected) do
|
224
|
+
next if finished
|
178
225
|
started = Time.now
|
179
226
|
send_protocol_message action: Ably::Models::ProtocolMessage::ACTION.Heartbeat.to_i
|
180
227
|
__incoming_protocol_msgbus__.subscribe :protocol_message, &wait_for_ping
|
181
228
|
end
|
229
|
+
|
230
|
+
EventMachine.add_timer(defaults.fetch(:realtime_request_timeout)) do
|
231
|
+
next if finished
|
232
|
+
finished = true
|
233
|
+
__incoming_protocol_msgbus__.unsubscribe(:protocol_message, &wait_for_ping)
|
234
|
+
logger.warn "Ping timed out after #{defaults.fetch(:realtime_request_timeout)}s"
|
235
|
+
safe_yield block, nil if block_given?
|
236
|
+
end
|
182
237
|
end
|
183
238
|
|
184
239
|
# @yield [Boolean] True if an internet connection check appears to be up following an HTTP request to a reliable CDN
|
@@ -327,11 +382,12 @@ module Ably
|
|
327
382
|
client.auth.auth_params.tap do |auth_deferrable|
|
328
383
|
auth_deferrable.callback do |auth_params|
|
329
384
|
url_params = auth_params.merge(
|
330
|
-
timestamp: as_since_epoch(Time.now),
|
331
385
|
format: client.protocol,
|
332
386
|
echo: client.echo_messages
|
333
387
|
)
|
334
388
|
|
389
|
+
url_params['clientId'] = client.auth.client_id if client.auth.has_client_id?
|
390
|
+
|
335
391
|
if connection_resumable?
|
336
392
|
url_params.merge! resume: key, connection_serial: serial
|
337
393
|
logger.debug "Resuming connection key #{key} with serial #{serial}"
|