ably 0.8.3 → 0.8.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -3
- data/lib/ably/auth.rb +1 -1
- data/lib/ably/exceptions.rb +3 -0
- data/lib/ably/models/channel_state_change.rb +41 -0
- data/lib/ably/models/connection_state_change.rb +43 -0
- data/lib/ably/models/message.rb +1 -1
- data/lib/ably/models/presence_message.rb +1 -1
- data/lib/ably/models/protocol_message.rb +2 -1
- data/lib/ably/modules/state_emitter.rb +4 -1
- data/lib/ably/modules/uses_state_machine.rb +28 -4
- data/lib/ably/realtime/channel.rb +11 -3
- data/lib/ably/realtime/channel/channel_manager.rb +24 -4
- data/lib/ably/realtime/channel/channel_state_machine.rb +20 -11
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +4 -4
- data/lib/ably/realtime/connection.rb +1 -0
- data/lib/ably/realtime/connection/connection_manager.rb +33 -21
- data/lib/ably/realtime/connection/connection_state_machine.rb +24 -16
- data/lib/ably/rest/channel.rb +3 -2
- data/lib/ably/util/crypto.rb +15 -0
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/channel_spec.rb +155 -9
- data/spec/acceptance/realtime/client_spec.rb +2 -2
- data/spec/acceptance/realtime/connection_failures_spec.rb +8 -4
- data/spec/acceptance/realtime/connection_spec.rb +122 -11
- data/spec/acceptance/realtime/message_spec.rb +119 -3
- data/spec/acceptance/realtime/presence_spec.rb +34 -13
- data/spec/acceptance/rest/channel_spec.rb +9 -0
- data/spec/acceptance/rest/client_spec.rb +10 -0
- data/spec/unit/models/channel_state_change_spec.rb +44 -0
- data/spec/unit/models/connection_state_change_spec.rb +54 -0
- data/spec/unit/util/crypto_spec.rb +18 -0
- metadata +8 -2
@@ -55,6 +55,7 @@ module Ably
|
|
55
55
|
)
|
56
56
|
include Ably::Modules::StateEmitter
|
57
57
|
include Ably::Modules::UsesStateMachine
|
58
|
+
ensure_state_machine_emits 'Ably::Models::ConnectionStateChange'
|
58
59
|
|
59
60
|
# Expected format for a connection recover key
|
60
61
|
RECOVER_REGEX = /^(?<recover>[\w-]+):(?<connection_serial>\-?\w+)$/
|
@@ -53,7 +53,7 @@ module Ably::Realtime
|
|
53
53
|
end
|
54
54
|
|
55
55
|
unless client.auth.authentication_security_requirements_met?
|
56
|
-
connection.transition_state_machine :failed, Ably::Exceptions::InsecureRequest.new('Cannot use Basic Auth over non-TLS connections', 401, 40103)
|
56
|
+
connection.transition_state_machine :failed, reason: Ably::Exceptions::InsecureRequest.new('Cannot use Basic Auth over non-TLS connections', 401, 40103)
|
57
57
|
return
|
58
58
|
end
|
59
59
|
|
@@ -80,7 +80,8 @@ module Ably::Realtime
|
|
80
80
|
# @api private
|
81
81
|
def connection_opening_failed(error)
|
82
82
|
logger.warn "ConnectionManager: Connection to #{connection.current_host}:#{connection.port} failed; #{error.message}"
|
83
|
-
|
83
|
+
next_state = get_next_retry_state_info
|
84
|
+
connection.transition_state_machine next_state.fetch(:state), retry_in: next_state.fetch(:pause), reason: Ably::Exceptions::ConnectionError.new("Connection failed: #{error.message}", nil, 80000)
|
84
85
|
end
|
85
86
|
|
86
87
|
# Called whenever a new connection is made
|
@@ -155,11 +156,10 @@ module Ably::Realtime
|
|
155
156
|
# When a connection is disconnected whilst connecting, attempt reconnect and/or set state to :suspended or :failed
|
156
157
|
#
|
157
158
|
# @api private
|
158
|
-
def respond_to_transport_disconnected_when_connecting(
|
159
|
+
def respond_to_transport_disconnected_when_connecting(error)
|
159
160
|
return unless connection.disconnected? || connection.suspended? # do nothing if state has changed through an explicit request
|
160
161
|
return unless retry_connection? # do not always reattempt connection or change state as client may be re-authorising
|
161
162
|
|
162
|
-
error = current_transition.metadata
|
163
163
|
if error.kind_of?(Ably::Models::ErrorInfo)
|
164
164
|
renew_token_and_reconnect error if error.code == RESOLVABLE_ERROR_CODES.fetch(:token_expired)
|
165
165
|
return
|
@@ -172,23 +172,22 @@ module Ably::Realtime
|
|
172
172
|
return if connection_retry_for(:suspended)
|
173
173
|
|
174
174
|
# Fallback if no other criteria met
|
175
|
-
connection.transition_state_machine :failed,
|
175
|
+
connection.transition_state_machine :failed, reason: error
|
176
176
|
end
|
177
177
|
|
178
178
|
# When a connection is disconnected after connecting, attempt reconnect and/or set state to :suspended or :failed
|
179
179
|
#
|
180
180
|
# @api private
|
181
|
-
def respond_to_transport_disconnected_whilst_connected(
|
181
|
+
def respond_to_transport_disconnected_whilst_connected(error)
|
182
182
|
logger.warn "ConnectionManager: Connection to #{connection.transport.url} was disconnected unexpectedly"
|
183
183
|
|
184
|
-
error = current_transition.metadata
|
185
184
|
if error.kind_of?(Ably::Models::ErrorInfo) && error.code != RESOLVABLE_ERROR_CODES.fetch(:token_expired)
|
186
185
|
connection.emit :error, error
|
187
186
|
logger.error "ConnectionManager: Error in Disconnected ProtocolMessage received from the server - #{error}"
|
188
187
|
end
|
189
188
|
|
190
189
|
destroy_transport
|
191
|
-
respond_to_transport_disconnected_when_connecting
|
190
|
+
respond_to_transport_disconnected_when_connecting error
|
192
191
|
end
|
193
192
|
|
194
193
|
# {Ably::Models::ProtocolMessage ProtocolMessage Error} received from server.
|
@@ -198,13 +197,13 @@ module Ably::Realtime
|
|
198
197
|
def error_received_from_server(error)
|
199
198
|
case error.code
|
200
199
|
when RESOLVABLE_ERROR_CODES.fetch(:token_expired)
|
201
|
-
connection.transition_state_machine :disconnected
|
200
|
+
connection.transition_state_machine :disconnected, retry_in: 0
|
202
201
|
connection.unsafe_once_or_if(:disconnected) do
|
203
202
|
renew_token_and_reconnect error
|
204
203
|
end
|
205
204
|
else
|
206
205
|
logger.error "ConnectionManager: Error #{error.class.name} code #{error.code} received from server '#{error.message}', transitioning to failed state"
|
207
|
-
connection.transition_state_machine :failed, error
|
206
|
+
connection.transition_state_machine :failed, reason: error
|
208
207
|
end
|
209
208
|
end
|
210
209
|
|
@@ -249,12 +248,26 @@ module Ably::Realtime
|
|
249
248
|
timers.fetch(key, []).each(&:cancel)
|
250
249
|
end
|
251
250
|
|
252
|
-
def
|
253
|
-
if connection_retry_from_suspended_state? ||
|
251
|
+
def get_next_retry_state_info
|
252
|
+
retry_state = if connection_retry_from_suspended_state? || !can_reattempt_connect_for_state?(:disconnected)
|
254
253
|
:suspended
|
255
254
|
else
|
256
255
|
:disconnected
|
257
256
|
end
|
257
|
+
{
|
258
|
+
state: retry_state,
|
259
|
+
pause: next_retry_pause(retry_state)
|
260
|
+
}
|
261
|
+
end
|
262
|
+
|
263
|
+
def next_retry_pause(retry_state)
|
264
|
+
return nil unless CONNECT_RETRY_CONFIG.fetch(retry_state)
|
265
|
+
|
266
|
+
if retries_for_state(retry_state, ignore_states: [:connecting]).empty?
|
267
|
+
0
|
268
|
+
else
|
269
|
+
CONNECT_RETRY_CONFIG.fetch(retry_state).fetch(:retry_every)
|
270
|
+
end
|
258
271
|
end
|
259
272
|
|
260
273
|
def connection_retry_from_suspended_state?
|
@@ -348,13 +361,12 @@ module Ably::Realtime
|
|
348
361
|
connection.transition_state_machine :closed
|
349
362
|
elsif !connection.closed? && !connection.disconnected?
|
350
363
|
exception = if reason
|
351
|
-
Ably::Exceptions::
|
352
|
-
end
|
353
|
-
if connection_retry_from_suspended_state? || !can_reattempt_connect_for_state?(:disconnected)
|
354
|
-
connection.transition_state_machine :suspended, exception
|
364
|
+
Ably::Exceptions::TransportClosed.new(reason, nil, 80003)
|
355
365
|
else
|
356
|
-
|
366
|
+
Ably::Exceptions::TransportClosed.new('Transport disconnected unexpectedly', nil, 80003)
|
357
367
|
end
|
368
|
+
next_state = get_next_retry_state_info
|
369
|
+
connection.transition_state_machine next_state.fetch(:state), retry_in: next_state.fetch(:pause), reason: exception
|
358
370
|
end
|
359
371
|
end
|
360
372
|
end
|
@@ -362,7 +374,7 @@ module Ably::Realtime
|
|
362
374
|
def renew_token_and_reconnect(error)
|
363
375
|
if client.auth.token_renewable?
|
364
376
|
if @renewing_token
|
365
|
-
connection.transition_state_machine :failed, error
|
377
|
+
connection.transition_state_machine :failed, reason: error
|
366
378
|
return
|
367
379
|
end
|
368
380
|
|
@@ -383,18 +395,18 @@ module Ably::Realtime
|
|
383
395
|
if token_details && !token_details.expired?
|
384
396
|
connection.connect
|
385
397
|
else
|
386
|
-
connection.transition_state_machine :failed, error unless connection.failed?
|
398
|
+
connection.transition_state_machine :failed, reason: error unless connection.failed?
|
387
399
|
end
|
388
400
|
end
|
389
401
|
|
390
402
|
authorise_deferrable.errback do |auth_error|
|
391
403
|
logger.error "ConnectionManager: Error authorising following token expiry: #{auth_error}"
|
392
|
-
connection.transition_state_machine :failed, auth_error
|
404
|
+
connection.transition_state_machine :failed, reason: auth_error
|
393
405
|
end
|
394
406
|
end
|
395
407
|
else
|
396
408
|
logger.error "ConnectionManager: Token has expired and is not renewable - #{error}"
|
397
|
-
connection.transition_state_machine :failed, error
|
409
|
+
connection.transition_state_machine :failed, reason: error
|
398
410
|
end
|
399
411
|
end
|
400
412
|
|
@@ -41,23 +41,25 @@ module Ably::Realtime
|
|
41
41
|
end
|
42
42
|
|
43
43
|
before_transition(to: [:connected]) do |connection, current_transition|
|
44
|
-
connection.manager.connected current_transition.metadata
|
44
|
+
connection.manager.connected current_transition.metadata.protocol_message
|
45
45
|
end
|
46
46
|
|
47
47
|
after_transition(to: [:connected]) do |connection, current_transition|
|
48
|
-
|
49
|
-
if is_error_type?(
|
50
|
-
connection.logger.warn "ConnectionManager: Connected with error - #{
|
51
|
-
connection.emit :error,
|
48
|
+
error = current_transition.metadata.reason
|
49
|
+
if is_error_type?(error)
|
50
|
+
connection.logger.warn "ConnectionManager: Connected with error - #{error.message}"
|
51
|
+
connection.emit :error, error
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
55
|
after_transition(to: [:disconnected, :suspended], from: [:connecting]) do |connection, current_transition|
|
56
|
-
|
56
|
+
err = error_from_state_change(current_transition)
|
57
|
+
connection.manager.respond_to_transport_disconnected_when_connecting err
|
57
58
|
end
|
58
59
|
|
59
60
|
after_transition(to: [:disconnected], from: [:connected]) do |connection, current_transition|
|
60
|
-
|
61
|
+
err = error_from_state_change(current_transition)
|
62
|
+
connection.manager.respond_to_transport_disconnected_whilst_connected err
|
61
63
|
end
|
62
64
|
|
63
65
|
after_transition(to: [:disconnected, :suspended]) do |connection|
|
@@ -65,7 +67,8 @@ module Ably::Realtime
|
|
65
67
|
end
|
66
68
|
|
67
69
|
before_transition(to: [:failed]) do |connection, current_transition|
|
68
|
-
|
70
|
+
err = error_from_state_change(current_transition)
|
71
|
+
connection.manager.fail err
|
69
72
|
end
|
70
73
|
|
71
74
|
after_transition(to: [:closing], from: [:initialized, :disconnected, :suspended]) do |connection|
|
@@ -82,24 +85,29 @@ module Ably::Realtime
|
|
82
85
|
|
83
86
|
# Transitions responsible for updating connection#error_reason
|
84
87
|
before_transition(to: [:disconnected, :suspended, :failed]) do |connection, current_transition|
|
85
|
-
|
88
|
+
err = error_from_state_change(current_transition)
|
89
|
+
connection.set_failed_connection_error_reason err
|
86
90
|
end
|
87
91
|
|
88
92
|
before_transition(to: [:connected, :closed]) do |connection, current_transition|
|
89
|
-
|
90
|
-
current_transition.metadata.error
|
91
|
-
else
|
92
|
-
current_transition.metadata
|
93
|
-
end
|
93
|
+
err = error_from_state_change(current_transition)
|
94
94
|
|
95
|
-
if
|
96
|
-
connection.set_failed_connection_error_reason
|
95
|
+
if err
|
96
|
+
connection.set_failed_connection_error_reason err
|
97
97
|
else
|
98
98
|
# Connected & Closed are "healthy" final states so reset the error reason
|
99
99
|
connection.clear_error_reason
|
100
100
|
end
|
101
101
|
end
|
102
102
|
|
103
|
+
def self.error_from_state_change(current_transition)
|
104
|
+
# ConnectionStateChange object is always passed in current_transition metadata object
|
105
|
+
connection_state_change = current_transition.metadata
|
106
|
+
# Reason attribute contains errors
|
107
|
+
err = connection_state_change && connection_state_change.reason
|
108
|
+
err if is_error_type?(err)
|
109
|
+
end
|
110
|
+
|
103
111
|
private
|
104
112
|
def connection
|
105
113
|
object
|
data/lib/ably/rest/channel.rb
CHANGED
@@ -34,6 +34,7 @@ module Ably
|
|
34
34
|
#
|
35
35
|
# @param name [String, Array<Ably::Models::Message|Hash>, nil] The event name of the message to publish, or an Array of [Ably::Model::Message] objects or [Hash] objects with +:name+ and +:data+ pairs
|
36
36
|
# @param data [String, ByteArray, nil] The message payload unless an Array of [Ably::Model::Message] objects passed in the first argument
|
37
|
+
# @param attributes [Hash, nil] Optional additional message attributes such as :client_id or :connection_id, applied when name attribute is nil or a string
|
37
38
|
# @return [Boolean] true if the message was published, otherwise false
|
38
39
|
#
|
39
40
|
# @example
|
@@ -54,13 +55,13 @@ module Ably
|
|
54
55
|
# ]
|
55
56
|
# channel.publish messages
|
56
57
|
#
|
57
|
-
def publish(name, data = nil)
|
58
|
+
def publish(name, data = nil, attributes = {})
|
58
59
|
messages = if name.kind_of?(Enumerable)
|
59
60
|
name
|
60
61
|
else
|
61
62
|
ensure_utf_8 :name, name, allow_nil: true
|
62
63
|
ensure_supported_payload data
|
63
|
-
[{ name: name, data: data }]
|
64
|
+
[{ name: name, data: data }.merge(attributes)]
|
64
65
|
end
|
65
66
|
|
66
67
|
payload = messages.map do |message|
|
data/lib/ably/util/crypto.rb
CHANGED
@@ -37,6 +37,21 @@ module Ably::Util
|
|
37
37
|
@options = DEFAULTS.merge(options).freeze
|
38
38
|
end
|
39
39
|
|
40
|
+
# Obtain a default CipherParams. This uses default algorithm, mode and
|
41
|
+
# padding and key length. A key and IV are generated using the default
|
42
|
+
# system SecureRandom; the key may be obtained from the returned CipherParams
|
43
|
+
# for out-of-band distribution to other clients.
|
44
|
+
#
|
45
|
+
# @return [Hash] CipherParam options Hash with attributes :key, :algorithn, :mode, :key_length
|
46
|
+
#
|
47
|
+
def self.get_default_params(key = nil)
|
48
|
+
params = DEFAULTS.merge(key: key)
|
49
|
+
params[:key_length] = key.unpack('b*').first.length if params[:key]
|
50
|
+
cipher_type = "#{params[:algorithm]}-#{params[:key_length]}-#{params[:mode]}"
|
51
|
+
params[:key] = OpenSSL::Cipher.new(cipher_type.upcase).random_key unless params[:key]
|
52
|
+
params
|
53
|
+
end
|
54
|
+
|
40
55
|
# Encrypt payload using configured Cipher
|
41
56
|
#
|
42
57
|
# @param [String] payload the payload to be encrypted
|
data/lib/ably/version.rb
CHANGED
@@ -92,7 +92,7 @@ describe Ably::Realtime::Channel, :event_machine do
|
|
92
92
|
|
93
93
|
it 'reattaches' do
|
94
94
|
channel.attach do
|
95
|
-
channel.transition_state_machine :failed, RuntimeError.new
|
95
|
+
channel.transition_state_machine :failed, reason: RuntimeError.new
|
96
96
|
expect(channel).to be_failed
|
97
97
|
channel.attach do
|
98
98
|
expect(channel).to be_attached
|
@@ -154,9 +154,9 @@ describe Ably::Realtime::Channel, :event_machine do
|
|
154
154
|
|
155
155
|
it 'emits failed event' do
|
156
156
|
restricted_channel.attach
|
157
|
-
restricted_channel.on(:failed) do |
|
157
|
+
restricted_channel.on(:failed) do |connection_state|
|
158
158
|
expect(restricted_channel.state).to eq(:failed)
|
159
|
-
expect(
|
159
|
+
expect(connection_state.reason.status).to eq(401)
|
160
160
|
stop_reactor
|
161
161
|
end
|
162
162
|
end
|
@@ -262,7 +262,7 @@ describe Ably::Realtime::Channel, :event_machine do
|
|
262
262
|
|
263
263
|
it 'raises an exception' do
|
264
264
|
channel.attach do
|
265
|
-
channel.transition_state_machine :failed, RuntimeError.new
|
265
|
+
channel.transition_state_machine :failed, reason: RuntimeError.new
|
266
266
|
expect(channel).to be_failed
|
267
267
|
expect { channel.detach }.to raise_error Ably::Exceptions::InvalidStateChange
|
268
268
|
stop_reactor
|
@@ -433,6 +433,19 @@ describe Ably::Realtime::Channel, :event_machine do
|
|
433
433
|
end
|
434
434
|
end
|
435
435
|
end
|
436
|
+
|
437
|
+
context 'and additional attributes' do
|
438
|
+
let(:client_id) { random_str }
|
439
|
+
|
440
|
+
it 'publishes the message with the attributes and return true indicating success' do
|
441
|
+
channel.publish(name, data, client_id: client_id) do
|
442
|
+
channel.history do |page|
|
443
|
+
expect(page.items.first.client_id).to eql(client_id)
|
444
|
+
stop_reactor
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
436
449
|
end
|
437
450
|
|
438
451
|
context 'with an array of Hash objects with :name and :data attributes' do
|
@@ -666,7 +679,8 @@ describe Ably::Realtime::Channel, :event_machine do
|
|
666
679
|
context 'an :attached channel' do
|
667
680
|
it 'transitions state to :failed' do
|
668
681
|
channel.attach do
|
669
|
-
channel.on(:failed) do |
|
682
|
+
channel.on(:failed) do |connection_state_change|
|
683
|
+
error = connection_state_change.reason
|
670
684
|
expect(error).to be_a(Ably::Exceptions::ConnectionFailed)
|
671
685
|
expect(error.code).to eql(80002)
|
672
686
|
stop_reactor
|
@@ -688,7 +702,8 @@ describe Ably::Realtime::Channel, :event_machine do
|
|
688
702
|
|
689
703
|
it 'updates the channel error_reason' do
|
690
704
|
channel.attach do
|
691
|
-
channel.on(:failed) do |
|
705
|
+
channel.on(:failed) do |connection_state_change|
|
706
|
+
error = connection_state_change.reason
|
692
707
|
expect(error).to be_a(Ably::Exceptions::ConnectionFailed)
|
693
708
|
expect(error.code).to eql(80002)
|
694
709
|
stop_reactor
|
@@ -734,7 +749,7 @@ describe Ably::Realtime::Channel, :event_machine do
|
|
734
749
|
fake_error connection_error
|
735
750
|
end
|
736
751
|
|
737
|
-
channel.transition_state_machine :failed, original_error
|
752
|
+
channel.transition_state_machine :failed, reason: original_error
|
738
753
|
end
|
739
754
|
end
|
740
755
|
end
|
@@ -783,8 +798,8 @@ describe Ably::Realtime::Channel, :event_machine do
|
|
783
798
|
end
|
784
799
|
|
785
800
|
context 'a :failed channel' do
|
786
|
-
let(:original_error) { RuntimeError.new }
|
787
801
|
let(:client_options) { default_options.merge(log_level: :fatal) }
|
802
|
+
let(:original_error) { Ably::Models::ErrorInfo.new(message: 'Error') }
|
788
803
|
|
789
804
|
it 'remains in the :failed state and retains the error_reason' do
|
790
805
|
channel.attach do
|
@@ -801,7 +816,7 @@ describe Ably::Realtime::Channel, :event_machine do
|
|
801
816
|
client.connection.close
|
802
817
|
end
|
803
818
|
|
804
|
-
channel.transition_state_machine :failed, original_error
|
819
|
+
channel.transition_state_machine :failed, reason: original_error
|
805
820
|
end
|
806
821
|
end
|
807
822
|
end
|
@@ -830,6 +845,75 @@ describe Ably::Realtime::Channel, :event_machine do
|
|
830
845
|
end
|
831
846
|
end
|
832
847
|
end
|
848
|
+
|
849
|
+
context ':suspended' do
|
850
|
+
context 'an :attached channel' do
|
851
|
+
let(:client_options) { default_options.merge(log_level: :fatal) }
|
852
|
+
|
853
|
+
it 'transitions state to :detached' do
|
854
|
+
channel.attach do
|
855
|
+
channel.on(:detached) do
|
856
|
+
stop_reactor
|
857
|
+
end
|
858
|
+
client.connection.transition_state_machine :suspended
|
859
|
+
end
|
860
|
+
end
|
861
|
+
end
|
862
|
+
|
863
|
+
context 'a :detached channel' do
|
864
|
+
it 'remains in the :detached state' do
|
865
|
+
channel.attach do
|
866
|
+
channel.detach do
|
867
|
+
channel.on(:detached) { raise 'Detached state should not have been reached' }
|
868
|
+
channel.on(:error) { raise 'Error should not have been emitted' }
|
869
|
+
|
870
|
+
EventMachine.add_timer(1) do
|
871
|
+
expect(channel).to be_detached
|
872
|
+
stop_reactor
|
873
|
+
end
|
874
|
+
|
875
|
+
client.connection.transition_state_machine :suspended
|
876
|
+
end
|
877
|
+
end
|
878
|
+
end
|
879
|
+
end
|
880
|
+
|
881
|
+
context 'a :failed channel' do
|
882
|
+
let(:original_error) { RuntimeError.new }
|
883
|
+
let(:client_options) { default_options.merge(log_level: :fatal) }
|
884
|
+
|
885
|
+
it 'remains in the :failed state and retains the error_reason' do
|
886
|
+
channel.attach do
|
887
|
+
channel.once(:error) do
|
888
|
+
channel.on(:detached) { raise 'Detached state should not have been reached' }
|
889
|
+
channel.on(:error) { raise 'Error should not have been emitted' }
|
890
|
+
|
891
|
+
EventMachine.add_timer(1) do
|
892
|
+
expect(channel).to be_failed
|
893
|
+
expect(channel.error_reason).to eql(original_error)
|
894
|
+
stop_reactor
|
895
|
+
end
|
896
|
+
|
897
|
+
client.connection.transition_state_machine :suspended
|
898
|
+
end
|
899
|
+
|
900
|
+
channel.transition_state_machine :failed, reason: original_error
|
901
|
+
end
|
902
|
+
end
|
903
|
+
end
|
904
|
+
|
905
|
+
context 'a channel ATTACH request when connection SUSPENDED' do
|
906
|
+
it 'raises an exception' do
|
907
|
+
client.connect do
|
908
|
+
client.connection.once(:suspended) do
|
909
|
+
expect { channel.attach }.to raise_error Ably::Exceptions::InvalidStateChange
|
910
|
+
stop_reactor
|
911
|
+
end
|
912
|
+
client.connection.transition_state_machine :suspended
|
913
|
+
end
|
914
|
+
end
|
915
|
+
end
|
916
|
+
end
|
833
917
|
end
|
834
918
|
|
835
919
|
describe '#presence' do
|
@@ -838,5 +922,67 @@ describe Ably::Realtime::Channel, :event_machine do
|
|
838
922
|
stop_reactor
|
839
923
|
end
|
840
924
|
end
|
925
|
+
|
926
|
+
context 'channel state change' do
|
927
|
+
it 'emits a ChannelStateChange object' do
|
928
|
+
channel.on(:attached) do |channel_state_change|
|
929
|
+
expect(channel_state_change).to be_a(Ably::Models::ChannelStateChange)
|
930
|
+
stop_reactor
|
931
|
+
end
|
932
|
+
channel.attach
|
933
|
+
end
|
934
|
+
|
935
|
+
context 'ChannelStateChange object' do
|
936
|
+
it 'has current state' do
|
937
|
+
channel.on(:attached) do |channel_state_change|
|
938
|
+
expect(channel_state_change.current).to eq(:attached)
|
939
|
+
stop_reactor
|
940
|
+
end
|
941
|
+
channel.attach
|
942
|
+
end
|
943
|
+
|
944
|
+
it 'has a previous state' do
|
945
|
+
channel.on(:attached) do |channel_state_change|
|
946
|
+
expect(channel_state_change.previous).to eq(:attaching)
|
947
|
+
stop_reactor
|
948
|
+
end
|
949
|
+
channel.attach
|
950
|
+
end
|
951
|
+
|
952
|
+
it 'contains a private API protocol_message attribute that is used for special state change events', :api_private do
|
953
|
+
channel.on(:attached) do |channel_state_change|
|
954
|
+
expect(channel_state_change.protocol_message).to be_a(Ably::Models::ProtocolMessage)
|
955
|
+
expect(channel_state_change.reason).to be_nil
|
956
|
+
stop_reactor
|
957
|
+
end
|
958
|
+
channel.attach
|
959
|
+
end
|
960
|
+
|
961
|
+
it 'has an empty reason when there is no error' do
|
962
|
+
channel.on(:detached) do |channel_state_change|
|
963
|
+
expect(channel_state_change.reason).to be_nil
|
964
|
+
stop_reactor
|
965
|
+
end
|
966
|
+
channel.attach do
|
967
|
+
channel.detach
|
968
|
+
end
|
969
|
+
end
|
970
|
+
|
971
|
+
context 'on failure' do
|
972
|
+
let(:client_options) { default_options.merge(log_level: :none) }
|
973
|
+
|
974
|
+
it 'has a reason Error object when there is an error on the channel' do
|
975
|
+
channel.on(:failed) do |channel_state_change|
|
976
|
+
expect(channel_state_change.reason).to be_a(Ably::Exceptions::BaseAblyException)
|
977
|
+
stop_reactor
|
978
|
+
end
|
979
|
+
channel.attach do
|
980
|
+
error = Ably::Exceptions::ConnectionFailed.new('forced failure', 500, 50000)
|
981
|
+
client.connection.manager.error_received_from_server error
|
982
|
+
end
|
983
|
+
end
|
984
|
+
end
|
985
|
+
end
|
986
|
+
end
|
841
987
|
end
|
842
988
|
end
|