ably 0.8.3 → 0.8.4
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/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
|