ably-rest 0.8.5 → 0.8.6
Sign up to get free protection for your applications and to get access to all the features.
- 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}"
|