ably 0.8.15 → 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/.travis.yml +6 -4
- data/CHANGELOG.md +6 -2
- data/README.md +5 -1
- data/SPEC.md +1473 -852
- data/ably.gemspec +11 -8
- data/lib/ably/auth.rb +90 -53
- data/lib/ably/exceptions.rb +37 -8
- data/lib/ably/logger.rb +10 -1
- data/lib/ably/models/auth_details.rb +42 -0
- data/lib/ably/models/channel_state_change.rb +18 -4
- data/lib/ably/models/connection_details.rb +6 -3
- data/lib/ably/models/connection_state_change.rb +4 -3
- data/lib/ably/models/error_info.rb +1 -1
- data/lib/ably/models/message.rb +17 -1
- data/lib/ably/models/message_encoders/base.rb +103 -82
- data/lib/ably/models/message_encoders/base64.rb +1 -1
- data/lib/ably/models/presence_message.rb +16 -1
- data/lib/ably/models/protocol_message.rb +20 -3
- data/lib/ably/models/token_details.rb +11 -1
- data/lib/ably/models/token_request.rb +16 -6
- data/lib/ably/modules/async_wrapper.rb +7 -3
- data/lib/ably/modules/encodeable.rb +51 -12
- data/lib/ably/modules/enum.rb +17 -7
- data/lib/ably/modules/event_emitter.rb +29 -14
- data/lib/ably/modules/model_common.rb +13 -21
- data/lib/ably/modules/state_emitter.rb +7 -4
- data/lib/ably/modules/state_machine.rb +2 -4
- data/lib/ably/modules/uses_state_machine.rb +7 -3
- data/lib/ably/realtime.rb +2 -0
- data/lib/ably/realtime/auth.rb +102 -42
- data/lib/ably/realtime/channel.rb +68 -26
- data/lib/ably/realtime/channel/channel_manager.rb +154 -65
- data/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
- data/lib/ably/realtime/client.rb +18 -3
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
- data/lib/ably/realtime/connection.rb +108 -49
- data/lib/ably/realtime/connection/connection_manager.rb +167 -61
- data/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
- data/lib/ably/realtime/connection/websocket_transport.rb +19 -10
- data/lib/ably/realtime/presence.rb +70 -45
- data/lib/ably/realtime/presence/members_map.rb +201 -36
- data/lib/ably/realtime/presence/presence_manager.rb +30 -6
- data/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
- data/lib/ably/rest.rb +2 -2
- data/lib/ably/rest/channel.rb +5 -5
- data/lib/ably/rest/client.rb +31 -27
- data/lib/ably/rest/middleware/exceptions.rb +1 -3
- data/lib/ably/rest/middleware/logger.rb +2 -2
- data/lib/ably/rest/presence.rb +2 -2
- data/lib/ably/util/pub_sub.rb +1 -1
- data/lib/ably/util/safe_deferrable.rb +26 -0
- data/lib/ably/version.rb +2 -2
- data/spec/acceptance/realtime/auth_spec.rb +470 -111
- data/spec/acceptance/realtime/channel_history_spec.rb +5 -3
- data/spec/acceptance/realtime/channel_spec.rb +1017 -168
- data/spec/acceptance/realtime/client_spec.rb +6 -6
- data/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
- data/spec/acceptance/realtime/connection_spec.rb +424 -105
- data/spec/acceptance/realtime/message_spec.rb +52 -23
- data/spec/acceptance/realtime/presence_history_spec.rb +5 -3
- data/spec/acceptance/realtime/presence_spec.rb +1110 -96
- data/spec/acceptance/rest/auth_spec.rb +222 -59
- data/spec/acceptance/rest/base_spec.rb +1 -1
- data/spec/acceptance/rest/channel_spec.rb +1 -2
- data/spec/acceptance/rest/client_spec.rb +104 -48
- data/spec/acceptance/rest/message_spec.rb +42 -15
- data/spec/acceptance/rest/presence_spec.rb +4 -11
- data/spec/rspec_config.rb +2 -1
- data/spec/shared/client_initializer_behaviour.rb +2 -2
- data/spec/shared/safe_deferrable_behaviour.rb +6 -2
- data/spec/spec_helper.rb +4 -2
- data/spec/support/debug_failure_helper.rb +20 -4
- data/spec/support/event_machine_helper.rb +32 -1
- data/spec/unit/auth_spec.rb +4 -11
- data/spec/unit/logger_spec.rb +28 -2
- data/spec/unit/models/auth_details_spec.rb +49 -0
- data/spec/unit/models/channel_state_change_spec.rb +23 -3
- data/spec/unit/models/connection_details_spec.rb +12 -1
- data/spec/unit/models/connection_state_change_spec.rb +15 -4
- data/spec/unit/models/message_encoders/base64_spec.rb +2 -1
- data/spec/unit/models/message_spec.rb +153 -0
- data/spec/unit/models/presence_message_spec.rb +192 -0
- data/spec/unit/models/protocol_message_spec.rb +64 -6
- data/spec/unit/models/token_details_spec.rb +75 -0
- data/spec/unit/models/token_request_spec.rb +74 -0
- data/spec/unit/modules/async_wrapper_spec.rb +2 -1
- data/spec/unit/modules/enum_spec.rb +69 -0
- data/spec/unit/modules/event_emitter_spec.rb +149 -22
- data/spec/unit/modules/state_emitter_spec.rb +9 -3
- data/spec/unit/realtime/client_spec.rb +1 -1
- data/spec/unit/realtime/connection_spec.rb +8 -5
- data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
- data/spec/unit/realtime/presence_spec.rb +4 -3
- data/spec/unit/rest/client_spec.rb +1 -1
- data/spec/unit/util/crypto_spec.rb +3 -3
- metadata +22 -19
@@ -12,8 +12,6 @@ module Ably::Realtime
|
|
12
12
|
def initialize(channel, connection)
|
13
13
|
@channel = channel
|
14
14
|
@connection = connection
|
15
|
-
|
16
|
-
setup_connection_event_handlers
|
17
15
|
end
|
18
16
|
|
19
17
|
# Commence attachment
|
@@ -25,64 +23,130 @@ module Ably::Realtime
|
|
25
23
|
end
|
26
24
|
|
27
25
|
# Commence attachment
|
28
|
-
def detach(error
|
26
|
+
def detach(error, previous_state)
|
29
27
|
if connection.closed? || connection.connecting? || connection.suspended?
|
30
28
|
channel.transition_state_machine :detached, reason: error
|
31
29
|
elsif can_transition_to?(:detached)
|
32
|
-
send_detach_protocol_message
|
30
|
+
send_detach_protocol_message previous_state
|
33
31
|
end
|
34
32
|
end
|
35
33
|
|
36
34
|
# Channel is attached, notify presence if sync is expected
|
37
35
|
def attached(attached_protocol_message)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
36
|
+
# If no attached ProtocolMessage then this attached request was triggered by the client
|
37
|
+
# library, such as returning to attached whne detach has failed
|
38
|
+
if attached_protocol_message
|
39
|
+
update_presence_sync_state_following_attached attached_protocol_message
|
40
|
+
channel.set_attached_serial attached_protocol_message.channel_serial
|
42
41
|
end
|
43
|
-
channel.set_attached_serial attached_protocol_message.channel_serial
|
44
42
|
end
|
45
43
|
|
46
44
|
# An error has occurred on the channel
|
47
|
-
def
|
48
|
-
logger.error "ChannelManager: Channel '#{channel.name}' error: #{error}"
|
49
|
-
channel.emit :error, error
|
45
|
+
def log_channel_error(error)
|
46
|
+
logger.error { "ChannelManager: Channel '#{channel.name}' error: #{error}" }
|
50
47
|
end
|
51
48
|
|
52
|
-
#
|
53
|
-
|
54
|
-
|
49
|
+
# Request channel to be reattached by sending an attach protocol message
|
50
|
+
# @param [Hash] options
|
51
|
+
# @option options [Ably::Models::ErrorInfo] :reason
|
52
|
+
def request_reattach(options = {})
|
53
|
+
reason = options[:reason]
|
54
|
+
send_attach_protocol_message
|
55
|
+
logger.debug { "Explicit channel reattach request sent to Ably due to #{reason}" }
|
56
|
+
channel.set_channel_error_reason(reason) if reason
|
57
|
+
channel.transition_state_machine! :attaching, reason: reason unless channel.attaching?
|
55
58
|
end
|
56
59
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
60
|
+
def duplicate_attached_received(protocol_message)
|
61
|
+
if protocol_message.error
|
62
|
+
channel.set_channel_error_reason protocol_message.error
|
63
|
+
log_channel_error protocol_message.error
|
64
|
+
end
|
65
|
+
|
66
|
+
if protocol_message.has_channel_resumed_flag?
|
67
|
+
logger.debug { "ChannelManager: Additional resumed ATTACHED message received for #{channel.state} channel '#{channel.name}'" }
|
68
|
+
else
|
69
|
+
channel.emit :update, Ably::Models::ChannelStateChange.new(
|
70
|
+
current: channel.state,
|
71
|
+
previous: channel.state,
|
72
|
+
event: Ably::Realtime::Channel::EVENT(:update),
|
73
|
+
reason: protocol_message.error,
|
74
|
+
resumed: false,
|
75
|
+
)
|
76
|
+
update_presence_sync_state_following_attached protocol_message
|
77
|
+
end
|
78
|
+
|
79
|
+
channel.set_attached_serial protocol_message.channel_serial
|
80
|
+
end
|
81
|
+
|
82
|
+
# Handle DETACED messages, see #RTL13 for server-initated detaches
|
83
|
+
def detached_received(reason)
|
84
|
+
case channel.state.to_sym
|
85
|
+
when :detaching
|
86
|
+
channel.transition_state_machine :detached, reason: reason
|
87
|
+
when :attached, :suspended
|
88
|
+
channel.transition_state_machine :attaching, reason: reason
|
89
|
+
else
|
90
|
+
logger.debug { "ChannelManager: DETACHED ProtocolMessage received, but no action to take as not DETACHING, ATTACHED OR SUSPENDED" }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# When continuity on the connection is interrupted or channel becomes suspended (implying loss of continuity)
|
95
|
+
# then all messages published but awaiting an ACK from Ably should be failed with a NACK
|
96
|
+
# @param [Hash] options
|
97
|
+
# @option options [Boolean] :immediately
|
98
|
+
def fail_messages_awaiting_ack(error, options = {})
|
99
|
+
immediately = options[:immediately] || false
|
100
|
+
|
101
|
+
fail_proc = Proc.new do
|
102
|
+
error = Ably::Exceptions::MessageDeliveryFailed.new("Continuity of connection was lost so published messages awaiting ACK have failed") unless error
|
63
103
|
fail_messages_in_queue connection.__pending_message_ack_queue__, error
|
64
|
-
fail_messages_in_queue connection.__outgoing_message_queue__, error
|
65
104
|
end
|
105
|
+
|
106
|
+
# Allow a short time for other queued operations to complete before failing all messages
|
107
|
+
if immediately
|
108
|
+
fail_proc.call
|
109
|
+
else
|
110
|
+
EventMachine.add_timer(0.1) { fail_proc.call }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# When a channel becomes detached, suspended or failed,
|
115
|
+
# all queued messages should be failed immediately as we don't queue in
|
116
|
+
# any of those states
|
117
|
+
def fail_queued_messages(error)
|
118
|
+
error = Ably::Exceptions::MessageDeliveryFailed.new("Queued messages on channel '#{channel.name}' in state '#{channel.state}' will never be delivered") unless error
|
119
|
+
fail_messages_in_queue connection.__outgoing_message_queue__, error
|
120
|
+
channel.__queue__.each do |message|
|
121
|
+
nack_message message, error
|
122
|
+
end
|
123
|
+
channel.__queue__.clear
|
66
124
|
end
|
67
125
|
|
68
126
|
def fail_messages_in_queue(queue, error)
|
69
127
|
queue.delete_if do |protocol_message|
|
70
|
-
if protocol_message.
|
71
|
-
|
72
|
-
|
128
|
+
if protocol_message.action.match_any?(:presence, :message)
|
129
|
+
if protocol_message.channel == channel.name
|
130
|
+
nack_messages protocol_message, error
|
131
|
+
true
|
132
|
+
end
|
73
133
|
end
|
74
134
|
end
|
75
135
|
end
|
76
136
|
|
77
137
|
def nack_messages(protocol_message, error)
|
78
138
|
(protocol_message.messages + protocol_message.presence).each do |message|
|
79
|
-
|
80
|
-
message.fail error
|
139
|
+
nack_message message, error, protocol_message
|
81
140
|
end
|
82
|
-
logger.debug "Calling NACK failure callbacks for #{protocol_message.class.name} - #{protocol_message.
|
141
|
+
logger.debug { "Calling NACK failure callbacks for #{protocol_message.class.name} - #{protocol_message.to_json}" }
|
83
142
|
protocol_message.fail error
|
84
143
|
end
|
85
144
|
|
145
|
+
def nack_message(message, error, protocol_message = nil)
|
146
|
+
logger.debug { "Calling NACK failure callbacks for #{message.class.name} - #{message.to_json} #{"protocol message: #{protocol_message}" if protocol_message}" }
|
147
|
+
message.fail error
|
148
|
+
end
|
149
|
+
|
86
150
|
def drop_pending_queue_from_ack(ack_protocol_message)
|
87
151
|
message_serial_up_to = ack_protocol_message.message_serial + ack_protocol_message.count - 1
|
88
152
|
connection.__pending_message_ack_queue__.drop_while do |protocol_message|
|
@@ -93,7 +157,23 @@ module Ably::Realtime
|
|
93
157
|
end
|
94
158
|
end
|
95
159
|
|
160
|
+
# If the connection is still connected and the channel still suspended after
|
161
|
+
# channel_retry_timeout has passed, then attempt to reattach automatically, see #RTL13b
|
162
|
+
def start_attach_from_suspended_timer
|
163
|
+
cancel_attach_from_suspended_timer
|
164
|
+
if connection.connected?
|
165
|
+
channel.unsafe_once { |event| cancel_attach_from_suspended_timer unless event == :update }
|
166
|
+
connection.unsafe_once { |event| cancel_attach_from_suspended_timer unless event == :update }
|
167
|
+
|
168
|
+
@attach_from_suspended_timer = EventMachine::Timer.new(channel_retry_timeout) do
|
169
|
+
channel.transition_state_machine! :attaching
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
96
174
|
private
|
175
|
+
attr_reader :pending_state_change_timer
|
176
|
+
|
97
177
|
def channel
|
98
178
|
@channel
|
99
179
|
end
|
@@ -104,63 +184,72 @@ module Ably::Realtime
|
|
104
184
|
|
105
185
|
def_delegators :channel, :can_transition_to?
|
106
186
|
|
187
|
+
def cancel_attach_from_suspended_timer
|
188
|
+
@attach_from_suspended_timer.cancel if @attach_from_suspended_timer
|
189
|
+
@attach_from_suspended_timer = nil
|
190
|
+
end
|
191
|
+
|
107
192
|
# If the connection has not previously connected, connect now
|
108
193
|
def connect_if_connection_initialized
|
109
194
|
connection.connect if connection.initialized?
|
110
195
|
end
|
111
196
|
|
112
|
-
def
|
113
|
-
|
197
|
+
def realtime_request_timeout
|
198
|
+
connection.defaults.fetch(:realtime_request_timeout)
|
114
199
|
end
|
115
200
|
|
116
|
-
def
|
117
|
-
|
201
|
+
def channel_retry_timeout
|
202
|
+
connection.defaults.fetch(:channel_retry_timeout)
|
118
203
|
end
|
119
204
|
|
120
|
-
def
|
121
|
-
|
122
|
-
action: state.to_i,
|
123
|
-
channel: channel.name
|
124
|
-
)
|
205
|
+
def send_attach_protocol_message
|
206
|
+
send_state_change_protocol_message Ably::Models::ProtocolMessage::ACTION.Attach, :suspended # move to suspended
|
125
207
|
end
|
126
208
|
|
127
|
-
|
128
|
-
|
129
|
-
# It is up to Ably to ensure that duplicate messages are not retransmitted on the channel
|
130
|
-
# base on the serial numbers
|
131
|
-
#
|
132
|
-
# TODO: Move this into the Connection class, it does not belong in a Channel class
|
133
|
-
#
|
134
|
-
# @api private
|
135
|
-
def resend_pending_message_ack_queue
|
136
|
-
connection.__pending_message_ack_queue__.delete_if do |protocol_message|
|
137
|
-
if protocol_message.channel == channel.name
|
138
|
-
connection.__outgoing_message_queue__ << protocol_message
|
139
|
-
connection.__outgoing_protocol_msgbus__.publish :protocol_message
|
140
|
-
true
|
141
|
-
end
|
142
|
-
end
|
209
|
+
def send_detach_protocol_message(previous_state)
|
210
|
+
send_state_change_protocol_message Ably::Models::ProtocolMessage::ACTION.Detach, previous_state # return to previous state if failed
|
143
211
|
end
|
144
212
|
|
145
|
-
def
|
146
|
-
|
147
|
-
|
213
|
+
def send_state_change_protocol_message(new_state, state_if_failed)
|
214
|
+
state_at_time_of_request = channel.state
|
215
|
+
@pending_state_change_timer = EventMachine::Timer.new(realtime_request_timeout) do
|
216
|
+
if channel.state == state_at_time_of_request
|
217
|
+
error = Ably::Models::ErrorInfo.new(code: 90007, message: "Channel #{new_state} operation failed (timed out)")
|
218
|
+
channel.transition_state_machine state_if_failed, reason: error
|
219
|
+
end
|
148
220
|
end
|
149
221
|
|
150
|
-
|
151
|
-
if
|
152
|
-
|
153
|
-
end
|
222
|
+
channel.once_state_changed do
|
223
|
+
@pending_state_change_timer.cancel if @pending_state_change_timer
|
224
|
+
@pending_state_change_timer = nil
|
154
225
|
end
|
155
226
|
|
156
|
-
|
157
|
-
|
158
|
-
|
227
|
+
resend_if_disconnected_and_connected = Proc.new do
|
228
|
+
connection.unsafe_once(:disconnected) do
|
229
|
+
next unless pending_state_change_timer
|
230
|
+
connection.unsafe_once(:connected) do
|
231
|
+
next unless pending_state_change_timer
|
232
|
+
connection.send_protocol_message(
|
233
|
+
action: new_state.to_i,
|
234
|
+
channel: channel.name
|
235
|
+
)
|
236
|
+
resend_if_disconnected_and_connected.call
|
237
|
+
end
|
159
238
|
end
|
160
239
|
end
|
240
|
+
resend_if_disconnected_and_connected.call
|
161
241
|
|
162
|
-
connection.
|
163
|
-
|
242
|
+
connection.send_protocol_message(
|
243
|
+
action: new_state.to_i,
|
244
|
+
channel: channel.name
|
245
|
+
)
|
246
|
+
end
|
247
|
+
|
248
|
+
def update_presence_sync_state_following_attached(attached_protocol_message)
|
249
|
+
if attached_protocol_message.has_presence_flag?
|
250
|
+
channel.presence.manager.sync_expected
|
251
|
+
else
|
252
|
+
channel.presence.manager.sync_not_expected
|
164
253
|
end
|
165
254
|
end
|
166
255
|
|
@@ -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
|
data/lib/ably/realtime/client.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
1
3
|
module Ably
|
2
4
|
module Realtime
|
3
5
|
# Client for the Ably Realtime API
|
@@ -76,8 +78,10 @@ module Ably
|
|
76
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
|
77
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.
|
78
80
|
#
|
79
|
-
# @option options [Integer] :
|
80
|
-
# @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
|
81
85
|
#
|
82
86
|
# @return [Ably::Realtime::Client]
|
83
87
|
#
|
@@ -89,7 +93,18 @@ module Ably
|
|
89
93
|
# client = Ably::Realtime::Client.new(key: 'key.id:secret', client_id: 'john')
|
90
94
|
#
|
91
95
|
def initialize(options)
|
92
|
-
|
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))
|
93
108
|
@auth = Ably::Realtime::Auth.new(self)
|
94
109
|
@channels = Ably::Realtime::Channels.new(self)
|
95
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.
|
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.
|
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
|