ably 0.8.15 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|