ably-rest 0.9.3 → 1.0.0
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/ably-rest.gemspec +2 -1
- data/lib/submodules/ably-ruby/.travis.yml +6 -4
- data/lib/submodules/ably-ruby/CHANGELOG.md +52 -61
- data/lib/submodules/ably-ruby/README.md +10 -0
- data/lib/submodules/ably-ruby/SPEC.md +1473 -852
- data/lib/submodules/ably-ruby/ably.gemspec +2 -1
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +57 -25
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +34 -8
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +10 -1
- data/lib/submodules/ably-ruby/lib/ably/models/auth_details.rb +42 -0
- data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +18 -4
- data/lib/submodules/ably-ruby/lib/ably/models/connection_details.rb +6 -3
- data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +4 -3
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +12 -1
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +101 -97
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +13 -1
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +20 -3
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +7 -3
- data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +17 -7
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +29 -14
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +7 -4
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -4
- data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +7 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +2 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +79 -31
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +62 -26
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +154 -65
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +16 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +108 -49
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +165 -59
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +19 -10
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +67 -45
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +198 -36
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_manager.rb +30 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +3 -3
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +21 -8
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +1 -3
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/util/safe_deferrable.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +416 -99
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +5 -3
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +1011 -160
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +436 -97
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +52 -23
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +5 -3
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1160 -105
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +151 -22
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +88 -27
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +42 -15
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +4 -4
- data/lib/submodules/ably-ruby/spec/rspec_config.rb +2 -1
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +2 -2
- data/lib/submodules/ably-ruby/spec/shared/safe_deferrable_behaviour.rb +6 -2
- data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +20 -4
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +32 -1
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +4 -11
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +28 -2
- data/lib/submodules/ably-ruby/spec/unit/models/auth_details_spec.rb +49 -0
- data/lib/submodules/ably-ruby/spec/unit/models/channel_state_change_spec.rb +23 -3
- data/lib/submodules/ably-ruby/spec/unit/models/connection_details_spec.rb +12 -1
- data/lib/submodules/ably-ruby/spec/unit/models/connection_state_change_spec.rb +15 -4
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +34 -2
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +73 -2
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +64 -6
- data/lib/submodules/ably-ruby/spec/unit/models/token_details_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/models/token_request_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +2 -1
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +69 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +149 -22
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +9 -3
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +8 -5
- data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +4 -3
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +3 -3
- metadata +7 -5
|
@@ -20,11 +20,12 @@ module Ably::Realtime
|
|
|
20
20
|
state state_enum.to_sym, initial: index == 0
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
transition :from => :initialized, :to => [:attaching]
|
|
24
|
-
transition :from => :attaching, :to => [:attached, :detaching, :failed]
|
|
25
|
-
transition :from => :attached, :to => [:detaching, :detached, :failed]
|
|
26
|
-
transition :from => :detaching, :to => [:detached, :attaching, :failed]
|
|
23
|
+
transition :from => :initialized, :to => [:attaching, :failed]
|
|
24
|
+
transition :from => :attaching, :to => [:attached, :detaching, :failed, :suspended]
|
|
25
|
+
transition :from => :attached, :to => [:attaching, :detaching, :detached, :failed, :suspended]
|
|
26
|
+
transition :from => :detaching, :to => [:detached, :attaching, :attached, :failed, :suspended]
|
|
27
27
|
transition :from => :detached, :to => [:attaching, :attached, :failed]
|
|
28
|
+
transition :from => :suspended, :to => [:attaching, :detached, :failed]
|
|
28
29
|
transition :from => :failed, :to => [:attaching]
|
|
29
30
|
|
|
30
31
|
after_transition do |channel, transition|
|
|
@@ -41,31 +42,29 @@ module Ably::Realtime
|
|
|
41
42
|
|
|
42
43
|
after_transition(to: [:detaching]) do |channel, current_transition|
|
|
43
44
|
err = error_from_state_change(current_transition)
|
|
44
|
-
channel.manager.detach err
|
|
45
|
+
channel.manager.detach err, current_transition.metadata.previous
|
|
45
46
|
end
|
|
46
47
|
|
|
47
|
-
after_transition(to: [:detached]) do |channel, current_transition|
|
|
48
|
+
after_transition(to: [:detached, :failed, :suspended]) do |channel, current_transition|
|
|
48
49
|
err = error_from_state_change(current_transition)
|
|
49
|
-
channel.manager.
|
|
50
|
-
channel.manager.
|
|
50
|
+
channel.manager.fail_queued_messages err
|
|
51
|
+
channel.manager.log_channel_error err if err
|
|
51
52
|
end
|
|
52
53
|
|
|
53
|
-
after_transition(to: [:
|
|
54
|
-
|
|
55
|
-
channel.manager.fail_messages_awaiting_ack err
|
|
56
|
-
channel.manager.emit_error err if err
|
|
54
|
+
after_transition(to: [:suspended]) do |channel, current_transition|
|
|
55
|
+
channel.manager.start_attach_from_suspended_timer
|
|
57
56
|
end
|
|
58
57
|
|
|
59
58
|
# Transitions responsible for updating channel#error_reason
|
|
60
|
-
before_transition(to: [:failed]) do |channel, current_transition|
|
|
59
|
+
before_transition(to: [:failed, :suspended]) do |channel, current_transition|
|
|
61
60
|
err = error_from_state_change(current_transition)
|
|
62
|
-
channel.
|
|
61
|
+
channel.set_channel_error_reason err if err
|
|
63
62
|
end
|
|
64
63
|
|
|
65
64
|
before_transition(to: [:attached, :detached]) do |channel, current_transition|
|
|
66
65
|
err = error_from_state_change(current_transition)
|
|
67
66
|
if err
|
|
68
|
-
channel.
|
|
67
|
+
channel.set_channel_error_reason err
|
|
69
68
|
else
|
|
70
69
|
# Attached & Detached are "healthy" final states so reset the error reason
|
|
71
70
|
channel.clear_error_reason
|
|
@@ -78,8 +78,10 @@ module Ably
|
|
|
78
78
|
# @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
|
|
79
79
|
# @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.
|
|
80
80
|
#
|
|
81
|
-
# @option options [Integer] :
|
|
82
|
-
# @option options [Integer] :
|
|
81
|
+
# @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
|
|
82
|
+
# @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
|
|
83
|
+
# @option options [Integer] :suspended_retry_timeout (30 seconds). When the connection enters the SUSPENDED state, after this delay in seconds, if the state is still SUSPENDED, the client library will attempt to reconnect automatically
|
|
84
|
+
# @option options [Boolean] :disable_websocket_heartbeats WebSocket heartbeats are more efficient than protocol level heartbeats, however they can be disabled for development purposes
|
|
83
85
|
#
|
|
84
86
|
# @return [Ably::Realtime::Client]
|
|
85
87
|
#
|
|
@@ -91,7 +93,18 @@ module Ably
|
|
|
91
93
|
# client = Ably::Realtime::Client.new(key: 'key.id:secret', client_id: 'john')
|
|
92
94
|
#
|
|
93
95
|
def initialize(options)
|
|
94
|
-
|
|
96
|
+
raise ArgumentError, 'Options Hash is expected' if options.nil?
|
|
97
|
+
|
|
98
|
+
options = options.clone
|
|
99
|
+
if options.kind_of?(String)
|
|
100
|
+
options = if options.match(Ably::Auth::API_KEY_REGEX)
|
|
101
|
+
{ key: options }
|
|
102
|
+
else
|
|
103
|
+
{ token: options }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
@rest_client = Ably::Rest::Client.new(options.merge(realtime_client: self))
|
|
95
108
|
@auth = Ably::Realtime::Auth.new(self)
|
|
96
109
|
@channels = Ably::Realtime::Channels.new(self)
|
|
97
110
|
@connection = Ably::Realtime::Connection.new(self, options)
|
|
@@ -27,7 +27,7 @@ module Ably::Realtime
|
|
|
27
27
|
|
|
28
28
|
def get_channel(channel_name)
|
|
29
29
|
channels.fetch(channel_name) do
|
|
30
|
-
logger.warn "Received channel message for non-existent channel"
|
|
30
|
+
logger.warn { "Received channel message for non-existent channel" }
|
|
31
31
|
Ably::Realtime::Models::NilChannel.new
|
|
32
32
|
end
|
|
33
33
|
end
|
|
@@ -43,19 +43,13 @@ module Ably::Realtime
|
|
|
43
43
|
raise ArgumentError, "Expected a ProtocolMessage. Received #{protocol_message}"
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
unless
|
|
47
|
-
logger.debug "#{protocol_message.action} received: #{protocol_message}"
|
|
46
|
+
unless protocol_message.action.match_any?(:nack, :error)
|
|
47
|
+
logger.debug { "#{protocol_message.action} received: #{protocol_message}" }
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
if
|
|
50
|
+
if protocol_message.action.match_any?(:sync, :presence, :message)
|
|
51
51
|
if connection.serial && protocol_message.has_connection_serial? && protocol_message.connection_serial <= connection.serial
|
|
52
|
-
error_target = if protocol_message.channel
|
|
53
|
-
get_channel(protocol_message.channel)
|
|
54
|
-
else
|
|
55
|
-
connection
|
|
56
|
-
end
|
|
57
52
|
error_message = "Protocol error, duplicate message received for serial #{protocol_message.connection_serial}"
|
|
58
|
-
error_target.emit :error, Ably::Exceptions::ProtocolError.new(error_message, 400, 80013)
|
|
59
53
|
logger.error error_message
|
|
60
54
|
return
|
|
61
55
|
end
|
|
@@ -69,15 +63,18 @@ module Ably::Realtime
|
|
|
69
63
|
ack_pending_queue_for_message_serial(protocol_message) if protocol_message.has_message_serial?
|
|
70
64
|
|
|
71
65
|
when ACTION.Nack
|
|
72
|
-
logger.warn "NACK received: #{protocol_message}"
|
|
66
|
+
logger.warn { "NACK received: #{protocol_message}" }
|
|
73
67
|
nack_pending_queue_for_message_serial(protocol_message) if protocol_message.has_message_serial?
|
|
74
68
|
|
|
75
69
|
when ACTION.Connect
|
|
76
70
|
when ACTION.Connected
|
|
77
|
-
if connection.
|
|
78
|
-
logger.debug "
|
|
71
|
+
if connection.closing?
|
|
72
|
+
logger.debug { "Out-of-order incoming CONNECTED ProtocolMessage discarded as connection has moved on and is in state: #{connection.state}" }
|
|
73
|
+
elsif connection.disconnected? || connection.closing? || connection.closed? || connection.failed?
|
|
74
|
+
logger.warn { "Out-of-order incoming CONNECTED ProtocolMessage discarded as connection has moved on and is in state: #{connection.state}" }
|
|
79
75
|
elsif connection.connected?
|
|
80
|
-
logger.
|
|
76
|
+
logger.debug { "Updated CONNECTED ProtocolMessage received (whilst connected)" }
|
|
77
|
+
process_connected_update_message protocol_message
|
|
81
78
|
else
|
|
82
79
|
process_connected_message protocol_message
|
|
83
80
|
end
|
|
@@ -90,7 +87,7 @@ module Ably::Realtime
|
|
|
90
87
|
connection.transition_state_machine :closed unless connection.closed?
|
|
91
88
|
|
|
92
89
|
when ACTION.Error
|
|
93
|
-
if protocol_message.channel
|
|
90
|
+
if protocol_message.channel
|
|
94
91
|
dispatch_channel_error protocol_message
|
|
95
92
|
else
|
|
96
93
|
process_connection_error protocol_message
|
|
@@ -99,21 +96,22 @@ module Ably::Realtime
|
|
|
99
96
|
when ACTION.Attach
|
|
100
97
|
when ACTION.Attached
|
|
101
98
|
get_channel(protocol_message.channel).tap do |channel|
|
|
102
|
-
|
|
99
|
+
if channel.attached?
|
|
100
|
+
channel.manager.duplicate_attached_received protocol_message
|
|
101
|
+
else
|
|
102
|
+
channel.transition_state_machine :attached, reason: protocol_message.error, resumed: protocol_message.has_channel_resumed_flag?, protocol_message: protocol_message
|
|
103
|
+
end
|
|
103
104
|
end
|
|
104
105
|
|
|
105
106
|
when ACTION.Detach
|
|
106
107
|
when ACTION.Detached
|
|
107
108
|
get_channel(protocol_message.channel).tap do |channel|
|
|
108
|
-
channel.
|
|
109
|
+
channel.manager.detached_received protocol_message.error
|
|
109
110
|
end
|
|
110
111
|
|
|
111
112
|
when ACTION.Sync
|
|
112
113
|
presence = get_channel(protocol_message.channel).presence
|
|
113
|
-
|
|
114
|
-
presence.__incoming_msgbus__.publish :sync, presence_message
|
|
115
|
-
end
|
|
116
|
-
presence.members.update_sync_serial protocol_message.channel_serial
|
|
114
|
+
presence.manager.sync_process_messages protocol_message.channel_serial, protocol_message.presence
|
|
117
115
|
|
|
118
116
|
when ACTION.Presence
|
|
119
117
|
presence = get_channel(protocol_message.channel).presence
|
|
@@ -127,19 +125,23 @@ module Ably::Realtime
|
|
|
127
125
|
channel.__incoming_msgbus__.publish :message, message
|
|
128
126
|
end
|
|
129
127
|
|
|
128
|
+
when ACTION.Auth
|
|
129
|
+
client.auth.authorize
|
|
130
|
+
|
|
130
131
|
else
|
|
131
132
|
error = Ably::Exceptions::ProtocolError.new("Protocol Message Action #{protocol_message.action} is unsupported by this MessageDispatcher", 400, 80013)
|
|
132
|
-
client.connection.emit :error, error
|
|
133
133
|
logger.fatal error.message
|
|
134
134
|
end
|
|
135
|
+
|
|
136
|
+
connection.set_connection_confirmed_alive
|
|
135
137
|
end
|
|
136
138
|
|
|
137
139
|
def dispatch_channel_error(protocol_message)
|
|
138
|
-
logger.warn "Channel Error message received: #{protocol_message.error}"
|
|
140
|
+
logger.warn { "Channel Error message received: #{protocol_message.error}" }
|
|
139
141
|
if !protocol_message.has_message_serial?
|
|
140
142
|
get_channel(protocol_message.channel).transition_state_machine :failed, reason: protocol_message.error
|
|
141
143
|
else
|
|
142
|
-
logger.fatal "Cannot process ProtocolMessage as not yet implemented: #{protocol_message}"
|
|
144
|
+
logger.fatal { "Cannot process ProtocolMessage ERROR with message serial as not yet implemented: #{protocol_message}" }
|
|
143
145
|
end
|
|
144
146
|
end
|
|
145
147
|
|
|
@@ -149,11 +151,18 @@ module Ably::Realtime
|
|
|
149
151
|
|
|
150
152
|
def process_connected_message(protocol_message)
|
|
151
153
|
if client.auth.token_client_id_allowed?(protocol_message.connection_details.client_id)
|
|
152
|
-
client.auth.configure_client_id protocol_message.connection_details.client_id
|
|
153
|
-
client.connection.set_connection_details protocol_message.connection_details
|
|
154
154
|
connection.transition_state_machine :connected, reason: protocol_message.error, protocol_message: protocol_message
|
|
155
155
|
else
|
|
156
|
-
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}'"
|
|
156
|
+
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}'")
|
|
157
|
+
connection.transition_state_machine :failed, reason: reason, protocol_message: protocol_message
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def process_connected_update_message(protocol_message)
|
|
162
|
+
if client.auth.token_client_id_allowed?(protocol_message.connection_details.client_id)
|
|
163
|
+
connection.manager.connected_update protocol_message
|
|
164
|
+
else
|
|
165
|
+
reason = Ably::Exceptions::IncompatibleClientId.new("Client ID '#{protocol_message.connection_details.client_id}' in CONNECTED update specified by the server is incompatible with the library's configured client ID '#{client.client_id}'")
|
|
157
166
|
connection.transition_state_machine :failed, reason: reason, protocol_message: protocol_message
|
|
158
167
|
end
|
|
159
168
|
end
|
|
@@ -178,14 +187,14 @@ module Ably::Realtime
|
|
|
178
187
|
|
|
179
188
|
def ack_messages(messages)
|
|
180
189
|
messages.each do |message|
|
|
181
|
-
logger.debug "Calling ACK success callbacks for #{message.class.name} - #{message.to_json}"
|
|
190
|
+
logger.debug { "Calling ACK success callbacks for #{message.class.name} - #{message.to_json}" }
|
|
182
191
|
message.succeed message
|
|
183
192
|
end
|
|
184
193
|
end
|
|
185
194
|
|
|
186
195
|
def nack_messages(messages, protocol_message)
|
|
187
196
|
messages.each do |message|
|
|
188
|
-
logger.debug "Calling NACK failure callbacks for #{message.class.name} - #{message.to_json}, protocol message: #{protocol_message}"
|
|
197
|
+
logger.debug { "Calling NACK failure callbacks for #{message.class.name} - #{message.to_json}, protocol message: #{protocol_message}" }
|
|
189
198
|
message.fail protocol_message.error
|
|
190
199
|
end
|
|
191
200
|
end
|
|
@@ -74,7 +74,12 @@ module Ably::Realtime
|
|
|
74
74
|
|
|
75
75
|
def setup_event_handlers
|
|
76
76
|
connection.unsafe_on(:connected) do
|
|
77
|
-
|
|
77
|
+
# Give connection manager enough time to prevent message delivery if necessary
|
|
78
|
+
# For example, if reconnecting and connection and channel state is lost,
|
|
79
|
+
# then the queued messages must be NACK'd
|
|
80
|
+
EventMachine.next_tick do
|
|
81
|
+
deliver_queued_protocol_messages
|
|
82
|
+
end
|
|
78
83
|
end
|
|
79
84
|
end
|
|
80
85
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'securerandom'
|
|
2
|
+
|
|
1
3
|
module Ably
|
|
2
4
|
module Realtime
|
|
3
5
|
# The Connection class represents the connection associated with an Ably Realtime instance.
|
|
@@ -25,8 +27,6 @@ module Ably
|
|
|
25
27
|
# Connection::STATE.Closed
|
|
26
28
|
# Connection::STATE.Failed
|
|
27
29
|
#
|
|
28
|
-
# Connection emit errors - use `on(:error)` to subscribe to errors
|
|
29
|
-
#
|
|
30
30
|
# @example
|
|
31
31
|
# client = Ably::Realtime::Client.new('key.id:secret')
|
|
32
32
|
# client.connection.on(:connected) do
|
|
@@ -42,7 +42,8 @@ module Ably
|
|
|
42
42
|
include Ably::Modules::SafeYield
|
|
43
43
|
extend Ably::Modules::Enum
|
|
44
44
|
|
|
45
|
-
#
|
|
45
|
+
# ConnectionState
|
|
46
|
+
# The permited states for this connection
|
|
46
47
|
STATE = ruby_enum('STATE',
|
|
47
48
|
:initialized,
|
|
48
49
|
:connecting,
|
|
@@ -53,6 +54,13 @@ module Ably
|
|
|
53
54
|
:closed,
|
|
54
55
|
:failed
|
|
55
56
|
)
|
|
57
|
+
|
|
58
|
+
# ConnectionEvent
|
|
59
|
+
# The permitted connection events that are emitted for this connection
|
|
60
|
+
EVENT = ruby_enum('EVENT',
|
|
61
|
+
STATE.to_sym_arr + [:update]
|
|
62
|
+
)
|
|
63
|
+
|
|
56
64
|
include Ably::Modules::StateEmitter
|
|
57
65
|
include Ably::Modules::UsesStateMachine
|
|
58
66
|
ensure_state_machine_emits 'Ably::Models::ConnectionStateChange'
|
|
@@ -62,11 +70,13 @@ module Ably
|
|
|
62
70
|
|
|
63
71
|
# Defaults for automatic connection recovery and timeouts
|
|
64
72
|
DEFAULTS = {
|
|
73
|
+
channel_retry_timeout: 15, # when a channel becomes SUSPENDED, after this delay in seconds, the channel will automatically attempt to reattach if the connection is CONNECTED
|
|
65
74
|
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
75
|
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
76
|
connection_state_ttl: 120, # the duration that Ably will persist the connection state when a Realtime client is abruptly disconnected
|
|
68
|
-
max_connection_state_ttl: nil, # allow a max TTL to be passed in for CI test purposes thus overiding any connection_state_ttl sent from Ably
|
|
77
|
+
max_connection_state_ttl: nil, # allow a max TTL to be passed in, usually for CI test purposes thus overiding any connection_state_ttl sent from Ably
|
|
69
78
|
realtime_request_timeout: 10, # default timeout when establishing a connection, or sending a HEARTBEAT, CONNECT, ATTACH, DETACH or CLOSE ProtocolMessage
|
|
79
|
+
websocket_heartbeats_disabled: false,
|
|
70
80
|
}.freeze
|
|
71
81
|
|
|
72
82
|
# A unique public identifier for this connection, used to identify this member in presence events and messages
|
|
@@ -122,9 +132,9 @@ module Ably
|
|
|
122
132
|
# @api public
|
|
123
133
|
def initialize(client, options)
|
|
124
134
|
@client = client
|
|
125
|
-
@client_serial = -1
|
|
126
135
|
@__outgoing_message_queue__ = []
|
|
127
136
|
@__pending_message_ack_queue__ = []
|
|
137
|
+
reset_client_serial
|
|
128
138
|
|
|
129
139
|
@defaults = DEFAULTS.dup
|
|
130
140
|
options.each do |key, val|
|
|
@@ -150,7 +160,9 @@ module Ably
|
|
|
150
160
|
#
|
|
151
161
|
def close(&success_block)
|
|
152
162
|
unless closing? || closed?
|
|
153
|
-
|
|
163
|
+
unless can_transition_to?(:closing)
|
|
164
|
+
return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, exception_for_state_change_to(:closing))
|
|
165
|
+
end
|
|
154
166
|
transition_state_machine :closing
|
|
155
167
|
end
|
|
156
168
|
deferrable_for_state_change_to(STATE.Closed, &success_block)
|
|
@@ -170,8 +182,11 @@ module Ably
|
|
|
170
182
|
#
|
|
171
183
|
def connect(&success_block)
|
|
172
184
|
unless connecting? || connected?
|
|
173
|
-
|
|
174
|
-
|
|
185
|
+
unless can_transition_to?(:connecting)
|
|
186
|
+
return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, exception_for_state_change_to(:connecting))
|
|
187
|
+
end
|
|
188
|
+
# If connect called in a suspended block, we want to ensure the other callbacks have finished their work first
|
|
189
|
+
EventMachine.next_tick { transition_state_machine :connecting if can_transition_to?(:connecting) }
|
|
175
190
|
end
|
|
176
191
|
|
|
177
192
|
Ably::Util::SafeDeferrable.new(logger).tap do |deferrable|
|
|
@@ -181,12 +196,12 @@ module Ably
|
|
|
181
196
|
succeed_callback = deferrable.method(:succeed)
|
|
182
197
|
fail_callback = deferrable.method(:fail)
|
|
183
198
|
|
|
184
|
-
|
|
199
|
+
unsafe_once(:connected) do
|
|
185
200
|
deferrable.succeed
|
|
186
201
|
off(&fail_callback)
|
|
187
202
|
end
|
|
188
203
|
|
|
189
|
-
|
|
204
|
+
unsafe_once(:failed, :closed, :closing) do
|
|
190
205
|
deferrable.fail
|
|
191
206
|
off(&succeed_callback)
|
|
192
207
|
end
|
|
@@ -206,38 +221,53 @@ module Ably
|
|
|
206
221
|
# puts "Ping took #{elapsed_s}s"
|
|
207
222
|
# end
|
|
208
223
|
#
|
|
209
|
-
# @return [
|
|
224
|
+
# @return [Ably::Util::SafeDeferrable]
|
|
210
225
|
#
|
|
211
226
|
def ping(&block)
|
|
212
|
-
|
|
213
|
-
|
|
227
|
+
if initialized? || suspended? || closing? || closed? || failed?
|
|
228
|
+
error = Ably::Models::ErrorInfo.new(message: "Cannot send a ping when the connection is #{state}", code: 80003)
|
|
229
|
+
return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
|
|
230
|
+
end
|
|
214
231
|
|
|
215
|
-
|
|
216
|
-
|
|
232
|
+
Ably::Util::SafeDeferrable.new(logger).tap do |deferrable|
|
|
233
|
+
started = nil
|
|
234
|
+
finished = false
|
|
235
|
+
ping_id = SecureRandom.hex(16)
|
|
236
|
+
heartbeat_action = Ably::Models::ProtocolMessage::ACTION.Heartbeat
|
|
237
|
+
|
|
238
|
+
wait_for_ping = Proc.new do |protocol_message|
|
|
239
|
+
next if finished
|
|
240
|
+
if protocol_message.action == heartbeat_action && protocol_message.id == ping_id
|
|
241
|
+
finished = true
|
|
242
|
+
__incoming_protocol_msgbus__.unsubscribe(:protocol_message, &wait_for_ping)
|
|
243
|
+
time_passed = Time.now.to_f - started.to_f
|
|
244
|
+
deferrable.succeed time_passed
|
|
245
|
+
safe_yield block, time_passed if block_given?
|
|
246
|
+
end
|
|
247
|
+
end
|
|
217
248
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
__incoming_protocol_msgbus__.
|
|
223
|
-
time_passed = Time.now.to_f - started.to_f
|
|
224
|
-
safe_yield block, time_passed if block_given?
|
|
249
|
+
once_or_if(STATE.Connected) do
|
|
250
|
+
next if finished
|
|
251
|
+
started = Time.now
|
|
252
|
+
send_protocol_message action: heartbeat_action.to_i, id: ping_id
|
|
253
|
+
__incoming_protocol_msgbus__.subscribe :protocol_message, &wait_for_ping
|
|
225
254
|
end
|
|
226
|
-
end
|
|
227
255
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
end
|
|
256
|
+
once_or_if([:suspended, :closing, :closed, :failed]) do
|
|
257
|
+
next if finished
|
|
258
|
+
finished = true
|
|
259
|
+
deferrable.fail Ably::Models::ErrorInfo.new(message: "Ping failed as connection has changed state to #{state}", code: 80003)
|
|
260
|
+
end
|
|
234
261
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
262
|
+
EventMachine.add_timer(defaults.fetch(:realtime_request_timeout)) do
|
|
263
|
+
next if finished
|
|
264
|
+
finished = true
|
|
265
|
+
__incoming_protocol_msgbus__.unsubscribe(:protocol_message, &wait_for_ping)
|
|
266
|
+
error_msg = "Ping timed out after #{defaults.fetch(:realtime_request_timeout)}s"
|
|
267
|
+
logger.warn { error_msg }
|
|
268
|
+
deferrable.fail Ably::Models::ErrorInfo.new(message: error_msg, code: 50003)
|
|
269
|
+
safe_yield block, nil if block_given?
|
|
270
|
+
end
|
|
241
271
|
end
|
|
242
272
|
end
|
|
243
273
|
|
|
@@ -364,7 +394,7 @@ module Ably
|
|
|
364
394
|
Ably::Models::ProtocolMessage.new(protocol_message, logger: logger).tap do |message|
|
|
365
395
|
add_message_to_outgoing_queue message
|
|
366
396
|
notify_message_dispatcher_of_new_message message
|
|
367
|
-
logger.debug
|
|
397
|
+
logger.debug { "Connection: Prot msg queued =>: #{message.action} #{message}" }
|
|
368
398
|
end
|
|
369
399
|
end
|
|
370
400
|
end
|
|
@@ -387,21 +417,24 @@ module Ably
|
|
|
387
417
|
client.auth.auth_params.tap do |auth_deferrable|
|
|
388
418
|
auth_deferrable.callback do |auth_params|
|
|
389
419
|
url_params = auth_params.merge(
|
|
390
|
-
format:
|
|
391
|
-
echo:
|
|
392
|
-
v:
|
|
393
|
-
lib:
|
|
420
|
+
format: client.protocol,
|
|
421
|
+
echo: client.echo_messages,
|
|
422
|
+
v: Ably::PROTOCOL_VERSION,
|
|
423
|
+
lib: client.rest_client.lib_version_id,
|
|
394
424
|
)
|
|
395
425
|
|
|
426
|
+
# Use native websocket heartbeats if possible
|
|
427
|
+
url_params['heartbeats'] = 'false' unless defaults.fetch(:websocket_heartbeats_disabled)
|
|
428
|
+
|
|
396
429
|
url_params['clientId'] = client.auth.client_id if client.auth.has_client_id?
|
|
397
430
|
|
|
398
431
|
if connection_resumable?
|
|
399
432
|
url_params.merge! resume: key, connection_serial: serial
|
|
400
|
-
logger.debug "Resuming connection key #{key} with serial #{serial}"
|
|
433
|
+
logger.debug { "Resuming connection key #{key} with serial #{serial}" }
|
|
401
434
|
elsif connection_recoverable?
|
|
402
|
-
url_params.merge! recover: connection_recover_parts[:recover],
|
|
403
|
-
logger.debug "Recovering connection with key #{client.recover}"
|
|
404
|
-
|
|
435
|
+
url_params.merge! recover: connection_recover_parts[:recover], connectionSerial: connection_recover_parts[:connection_serial]
|
|
436
|
+
logger.debug { "Recovering connection with key #{client.recover}" }
|
|
437
|
+
unsafe_once(:connected, :closed, :failed) do
|
|
405
438
|
client.disable_automatic_connection_recovery
|
|
406
439
|
end
|
|
407
440
|
end
|
|
@@ -412,7 +445,7 @@ module Ably
|
|
|
412
445
|
|
|
413
446
|
determine_host do |host|
|
|
414
447
|
begin
|
|
415
|
-
logger.debug "Connection: Opening socket connection to #{host}:#{port}/#{url.path}?#{url.query}"
|
|
448
|
+
logger.debug { "Connection: Opening socket connection to #{host}:#{port}/#{url.path}?#{url.query}" }
|
|
416
449
|
@transport = create_transport(host, port, url) do |websocket_transport|
|
|
417
450
|
websocket_deferrable.succeed websocket_transport
|
|
418
451
|
end
|
|
@@ -451,7 +484,7 @@ module Ably
|
|
|
451
484
|
|
|
452
485
|
# Executes registered callbacks for a successful connection resume event
|
|
453
486
|
# @api private
|
|
454
|
-
def
|
|
487
|
+
def trigger_resumed
|
|
455
488
|
resume_callbacks.each(&:call)
|
|
456
489
|
end
|
|
457
490
|
|
|
@@ -490,6 +523,33 @@ module Ably
|
|
|
490
523
|
@connection_state_ttl = val
|
|
491
524
|
end
|
|
492
525
|
|
|
526
|
+
# @api private
|
|
527
|
+
def heartbeat_interval
|
|
528
|
+
# See RTN23a
|
|
529
|
+
(details && details.max_idle_interval).to_i +
|
|
530
|
+
defaults.fetch(:realtime_request_timeout)
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
# Resets the client serial (msgSerial) sent to Ably for each new {Ably::Models::ProtocolMessage}
|
|
534
|
+
# (see #client_serial)
|
|
535
|
+
# @api private
|
|
536
|
+
def reset_client_serial
|
|
537
|
+
@client_serial = -1
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
# When a hearbeat or any other message from Ably is received
|
|
541
|
+
# we know it's alive, see #RTN23
|
|
542
|
+
# @api private
|
|
543
|
+
def set_connection_confirmed_alive
|
|
544
|
+
@last_liveness_event = Time.now
|
|
545
|
+
manager.reset_liveness_timer
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
# @api private
|
|
549
|
+
def time_since_connection_confirmed_alive?
|
|
550
|
+
Time.now.to_i - @last_liveness_event.to_i
|
|
551
|
+
end
|
|
552
|
+
|
|
493
553
|
# As we are using a state machine, do not allow change_state to be used
|
|
494
554
|
# #transition_state_machine must be used instead
|
|
495
555
|
private :change_state
|
|
@@ -500,9 +560,8 @@ module Ably
|
|
|
500
560
|
# Note that this is different to the connection serial that contains the last known serial number
|
|
501
561
|
# received from the server.
|
|
502
562
|
#
|
|
503
|
-
# A
|
|
504
|
-
# A connection serial guarantees the server has received the message and is thus used for connection
|
|
505
|
-
# recovery and resumes.
|
|
563
|
+
# A message serial number does not guarantee a message has been received, only sent.
|
|
564
|
+
# A connection serial guarantees the server has received the message and is thus used for connection recovery and resumes.
|
|
506
565
|
# @return [Integer] starting at -1 indicating no messages sent, 0 when the first message is sent
|
|
507
566
|
def client_serial
|
|
508
567
|
@client_serial
|