ably-rest 0.9.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ably-rest.gemspec +2 -1
- data/lib/submodules/ably-ruby/.travis.yml +6 -4
- data/lib/submodules/ably-ruby/CHANGELOG.md +52 -61
- data/lib/submodules/ably-ruby/README.md +10 -0
- data/lib/submodules/ably-ruby/SPEC.md +1473 -852
- data/lib/submodules/ably-ruby/ably.gemspec +2 -1
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +57 -25
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +34 -8
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +10 -1
- data/lib/submodules/ably-ruby/lib/ably/models/auth_details.rb +42 -0
- data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +18 -4
- data/lib/submodules/ably-ruby/lib/ably/models/connection_details.rb +6 -3
- data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +4 -3
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +12 -1
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +101 -97
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +13 -1
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +20 -3
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +7 -3
- data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +17 -7
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +29 -14
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +7 -4
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -4
- data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +7 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +2 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +79 -31
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +62 -26
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +154 -65
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +16 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +108 -49
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +165 -59
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +19 -10
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +67 -45
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +198 -36
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_manager.rb +30 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +3 -3
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +21 -8
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +1 -3
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/util/safe_deferrable.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +416 -99
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +5 -3
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +1011 -160
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +436 -97
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +52 -23
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +5 -3
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1160 -105
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +151 -22
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +88 -27
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +42 -15
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +4 -4
- data/lib/submodules/ably-ruby/spec/rspec_config.rb +2 -1
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +2 -2
- data/lib/submodules/ably-ruby/spec/shared/safe_deferrable_behaviour.rb +6 -2
- data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +20 -4
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +32 -1
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +4 -11
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +28 -2
- data/lib/submodules/ably-ruby/spec/unit/models/auth_details_spec.rb +49 -0
- data/lib/submodules/ably-ruby/spec/unit/models/channel_state_change_spec.rb +23 -3
- data/lib/submodules/ably-ruby/spec/unit/models/connection_details_spec.rb +12 -1
- data/lib/submodules/ably-ruby/spec/unit/models/connection_state_change_spec.rb +15 -4
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +34 -2
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +73 -2
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +64 -6
- data/lib/submodules/ably-ruby/spec/unit/models/token_details_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/models/token_request_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +2 -1
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +69 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +149 -22
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +9 -3
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +8 -5
- data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +4 -3
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +3 -3
- metadata +7 -5
|
@@ -28,6 +28,25 @@ module Ably::Realtime
|
|
|
28
28
|
presence.members.change_state :sync_starting
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
# Process presence messages from SYNC messages. Sync can be server-initiated or triggered following ATTACH
|
|
32
|
+
#
|
|
33
|
+
# @return [void]
|
|
34
|
+
#
|
|
35
|
+
# @api private
|
|
36
|
+
def sync_process_messages(serial, presence_messages)
|
|
37
|
+
unless presence.members.sync_starting?
|
|
38
|
+
presence.members.change_state :sync_starting
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
presence.members.update_sync_serial serial
|
|
42
|
+
|
|
43
|
+
presence_messages.each do |presence_message|
|
|
44
|
+
presence.__incoming_msgbus__.publish :sync, presence_message
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
presence.members.change_state :finalizing_sync if presence.members.sync_serial_cursor_at_end?
|
|
48
|
+
end
|
|
49
|
+
|
|
31
50
|
# There server has indicated that there are no SYNC ProtocolMessages to come because
|
|
32
51
|
# there are no members on this channel
|
|
33
52
|
#
|
|
@@ -35,7 +54,8 @@ module Ably::Realtime
|
|
|
35
54
|
#
|
|
36
55
|
# @api private
|
|
37
56
|
def sync_not_expected
|
|
38
|
-
|
|
57
|
+
logger.debug { "#{self.class.name}: Emitting leave events for all members as a SYNC is not expected and thus there are no members on the channel" }
|
|
58
|
+
presence.members.change_state :sync_none
|
|
39
59
|
end
|
|
40
60
|
|
|
41
61
|
private
|
|
@@ -43,16 +63,20 @@ module Ably::Realtime
|
|
|
43
63
|
|
|
44
64
|
def setup_channel_event_handlers
|
|
45
65
|
channel.unsafe_on(:detached) do
|
|
46
|
-
|
|
66
|
+
if !presence.initialized?
|
|
67
|
+
presence.transition_state_machine :left if presence.can_transition_to?(:left)
|
|
68
|
+
end
|
|
47
69
|
end
|
|
48
70
|
|
|
49
71
|
channel.unsafe_on(:failed) do |metadata|
|
|
50
|
-
|
|
72
|
+
if !presence.initialized?
|
|
73
|
+
presence.transition_state_machine :left, metadata if presence.can_transition_to?(:left)
|
|
74
|
+
end
|
|
51
75
|
end
|
|
76
|
+
end
|
|
52
77
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
end
|
|
78
|
+
def logger
|
|
79
|
+
presence.channel.client.logger
|
|
56
80
|
end
|
|
57
81
|
end
|
|
58
82
|
end
|
|
@@ -15,19 +15,16 @@ module Ably::Realtime
|
|
|
15
15
|
# :entered
|
|
16
16
|
# :leaving
|
|
17
17
|
# :left
|
|
18
|
-
# :failed
|
|
19
18
|
Presence::STATE.each_with_index do |state_enum, index|
|
|
20
19
|
state state_enum.to_sym, initial: index == 0
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
# Entering or entered states can skip leaving and go straight to left if a channel is detached
|
|
24
23
|
# A channel that detaches very quickly will also go straight to :left from :initialized
|
|
25
|
-
# Failed states only occur when present and the channel fails or presence fails
|
|
26
24
|
transition :from => :initialized, :to => [:entering, :left]
|
|
27
|
-
transition :from => :entering, :to => [:entered, :leaving, :left
|
|
28
|
-
transition :from => :entered, :to => [:leaving, :left
|
|
29
|
-
transition :from => :leaving, :to => [:left, :entering
|
|
30
|
-
transition :from => :failed, :to => [:entering]
|
|
25
|
+
transition :from => :entering, :to => [:entered, :leaving, :left]
|
|
26
|
+
transition :from => :entered, :to => [:leaving, :left]
|
|
27
|
+
transition :from => :leaving, :to => [:left, :entering]
|
|
31
28
|
|
|
32
29
|
after_transition do |presence, transition|
|
|
33
30
|
presence.synchronize_state_with_statemachine
|
|
@@ -41,13 +38,9 @@ module Ably::Realtime
|
|
|
41
38
|
presence.manager.leave current_transition.metadata
|
|
42
39
|
end
|
|
43
40
|
|
|
44
|
-
after_transition(to: [:failed]) do |presence, current_transition|
|
|
45
|
-
presence.manager.emit_error current_transition.metadata
|
|
46
|
-
end
|
|
47
|
-
|
|
48
41
|
# Transitions responsible for updating channel#error_reason
|
|
49
|
-
before_transition(to: [:left
|
|
50
|
-
presence.channel.
|
|
42
|
+
before_transition(to: [:left]) do |presence, current_transition|
|
|
43
|
+
presence.channel.set_channel_error_reason current_transition.metadata if is_error_type?(current_transition.metadata)
|
|
51
44
|
end
|
|
52
45
|
|
|
53
46
|
private
|
|
@@ -69,10 +69,10 @@ module Ably
|
|
|
69
69
|
|
|
70
70
|
next if msg.client_id.nil?
|
|
71
71
|
if msg.client_id == '*'
|
|
72
|
-
raise Ably::Exceptions::IncompatibleClientId.new('Wildcard client_id is reserved and cannot be used when publishing messages'
|
|
72
|
+
raise Ably::Exceptions::IncompatibleClientId.new('Wildcard client_id is reserved and cannot be used when publishing messages')
|
|
73
73
|
end
|
|
74
74
|
unless client.auth.can_assume_client_id?(msg.client_id)
|
|
75
|
-
raise Ably::Exceptions::IncompatibleClientId.new("Cannot publish with client_id '#{msg.client_id}' as it is incompatible with the current configured client_id '#{client.client_id}'"
|
|
75
|
+
raise Ably::Exceptions::IncompatibleClientId.new("Cannot publish with client_id '#{msg.client_id}' as it is incompatible with the current configured client_id '#{client.client_id}'")
|
|
76
76
|
end
|
|
77
77
|
end.as_json
|
|
78
78
|
end
|
|
@@ -136,7 +136,7 @@ module Ably
|
|
|
136
136
|
def decode_message(message)
|
|
137
137
|
message.decode client.encoders, options
|
|
138
138
|
rescue Ably::Exceptions::CipherError, Ably::Exceptions::EncoderError => e
|
|
139
|
-
client.logger.error "Decoding Error on channel '#{name}', message event name '#{message.name}'. #{e.class.name}: #{e.message}"
|
|
139
|
+
client.logger.error { "Decoding Error on channel '#{name}', message event name '#{message.name}'. #{e.class.name}: #{e.message}" }
|
|
140
140
|
end
|
|
141
141
|
end
|
|
142
142
|
end
|
|
@@ -25,8 +25,8 @@ module Ably
|
|
|
25
25
|
# Configuration for HTTP timeouts and HTTP request reattempts to fallback hosts
|
|
26
26
|
HTTP_DEFAULTS = {
|
|
27
27
|
open_timeout: 4,
|
|
28
|
-
request_timeout:
|
|
29
|
-
max_retry_duration:
|
|
28
|
+
request_timeout: 10,
|
|
29
|
+
max_retry_duration: 15,
|
|
30
30
|
max_retry_count: 3
|
|
31
31
|
}.freeze
|
|
32
32
|
|
|
@@ -104,12 +104,12 @@ module Ably
|
|
|
104
104
|
# @option options [Proc] :auth_callback when provided, the Proc will be called with the token params hash as the first argument, whenever a new token is required.
|
|
105
105
|
# The Proc should return a token string, {Ably::Models::TokenDetails} or JSON equivalent, {Ably::Models::TokenRequest} or JSON equivalent
|
|
106
106
|
# @option options [Boolean] :query_time when true will query the {https://www.ably.io Ably} system for the current time instead of using the local time
|
|
107
|
-
# @option options [Hash] :
|
|
107
|
+
# @option options [Hash] :default_token_params convenience to pass in +token_params+ that will be used as a default for all token requests. See {Auth#create_token_request}
|
|
108
108
|
#
|
|
109
109
|
# @option options [Integer] :http_open_timeout (4 seconds) timeout in seconds for opening an HTTP connection for all HTTP requests
|
|
110
|
-
# @option options [Integer] :http_request_timeout (
|
|
110
|
+
# @option options [Integer] :http_request_timeout (10 seconds) timeout in seconds for any single complete HTTP request and response
|
|
111
111
|
# @option options [Integer] :http_max_retry_count (3) maximum number of fallback host retries for HTTP requests that fail due to network issues or server problems
|
|
112
|
-
# @option options [Integer] :http_max_retry_duration (
|
|
112
|
+
# @option options [Integer] :http_max_retry_duration (15 seconds) maximum elapsed time in which fallback host retries for HTTP requests will be attempted i.e. if the first default host attempt takes 5s, and then the subsequent fallback retry attempt takes 7s, no further fallback host attempts will be made as the total elapsed time of 12s exceeds the default 10s limit
|
|
113
113
|
#
|
|
114
114
|
# @option options [Boolean] :fallback_hosts_use_default (false) When true, forces the user of fallback hosts even if a non-default production endpoint is being used
|
|
115
115
|
# @option options [Array<String>] :fallback_hosts When an array of fallback hosts are provided, these fallback hosts are always used if a request fails to the primary endpoint. If an empty array is provided, the fallback host functionality is disabled
|
|
@@ -128,13 +128,14 @@ module Ably
|
|
|
128
128
|
|
|
129
129
|
options = options.clone
|
|
130
130
|
if options.kind_of?(String)
|
|
131
|
-
options = if options.match(
|
|
131
|
+
options = if options.match(Auth::API_KEY_REGEX)
|
|
132
132
|
{ key: options }
|
|
133
133
|
else
|
|
134
134
|
{ token: options }
|
|
135
135
|
end
|
|
136
136
|
end
|
|
137
137
|
|
|
138
|
+
@realtime_client = options.delete(:realtime_client)
|
|
138
139
|
@tls = options.delete(:tls) == false ? false : true
|
|
139
140
|
@environment = options.delete(:environment) # nil is production
|
|
140
141
|
@environment = nil if [:production, 'production'].include?(@environment)
|
|
@@ -183,7 +184,7 @@ module Ably
|
|
|
183
184
|
end
|
|
184
185
|
raise ArgumentError, 'Protocol is invalid. Must be either :msgpack or :json' unless [:msgpack, :json].include?(@protocol)
|
|
185
186
|
|
|
186
|
-
token_params = options.delete(:
|
|
187
|
+
token_params = options.delete(:default_token_params) || {}
|
|
187
188
|
@options = options
|
|
188
189
|
@auth = Auth.new(self, token_params, options)
|
|
189
190
|
@channels = Ably::Rest::Channels.new(self)
|
|
@@ -402,6 +403,18 @@ module Ably
|
|
|
402
403
|
].compact.join('-')
|
|
403
404
|
end
|
|
404
405
|
|
|
406
|
+
# Allowable duration for an external auth request
|
|
407
|
+
# For REST client this defaults to request_timeout
|
|
408
|
+
# For Realtime clients this defaults to realtime_request_timeout
|
|
409
|
+
# @api private
|
|
410
|
+
def auth_request_timeout
|
|
411
|
+
if @realtime_client
|
|
412
|
+
@realtime_client.connection.defaults.fetch(:realtime_request_timeout)
|
|
413
|
+
else
|
|
414
|
+
http_defaults.fetch(:request_timeout)
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
405
418
|
private
|
|
406
419
|
def raw_request(method, path, params = {}, options = {})
|
|
407
420
|
options = options.clone
|
|
@@ -440,7 +453,7 @@ module Ably
|
|
|
440
453
|
time_passed = Time.now - requested_at
|
|
441
454
|
if can_fallback_to_alternate_ably_host? && retry_count < max_retry_count && time_passed <= max_retry_duration
|
|
442
455
|
retry_count += 1
|
|
443
|
-
logger.warn "Ably::Rest::Client - Retry #{retry_count} for #{method} #{path} #{params} as initial attempt failed: #{error}"
|
|
456
|
+
logger.warn { "Ably::Rest::Client - Retry #{retry_count} for #{method} #{path} #{params} as initial attempt failed: #{error}" }
|
|
444
457
|
retry
|
|
445
458
|
end
|
|
446
459
|
|
|
@@ -7,8 +7,6 @@ module Ably
|
|
|
7
7
|
# HTTP exceptions raised by Ably due to an error status code
|
|
8
8
|
# Ably returns JSON/Msgpack error codes and messages so include this if possible in the exception messages
|
|
9
9
|
class Exceptions < Faraday::Response::Middleware
|
|
10
|
-
TOKEN_EXPIRED_CODE = 40140..40149
|
|
11
|
-
|
|
12
10
|
def on_complete(env)
|
|
13
11
|
if env.status >= 400
|
|
14
12
|
error_status_code = env.status
|
|
@@ -35,7 +33,7 @@ module Ably
|
|
|
35
33
|
if env.status >= 500
|
|
36
34
|
raise Ably::Exceptions::ServerError.new(*exception_args)
|
|
37
35
|
elsif env.status == 401
|
|
38
|
-
if TOKEN_EXPIRED_CODE.include?(error_code)
|
|
36
|
+
if Ably::Exceptions::TOKEN_EXPIRED_CODE.include?(error_code)
|
|
39
37
|
raise Ably::Exceptions::TokenExpired.new(*exception_args)
|
|
40
38
|
else
|
|
41
39
|
raise Ably::Exceptions::UnauthorizedRequest.new(*exception_args)
|
|
@@ -17,8 +17,8 @@ module Ably
|
|
|
17
17
|
def_delegators :@logger, :debug, :info, :warn, :error, :fatal
|
|
18
18
|
|
|
19
19
|
def call(env)
|
|
20
|
-
debug "=> URL: #{env.method} #{env.url}, Headers: #{dump_headers env.request_headers}"
|
|
21
|
-
debug "=> Body: #{body_for(env)}"
|
|
20
|
+
debug { "=> URL: #{env.method} #{env.url}, Headers: #{dump_headers env.request_headers}" }
|
|
21
|
+
debug { "=> Body: #{body_for(env)}" }
|
|
22
22
|
super
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -90,7 +90,7 @@ module Ably
|
|
|
90
90
|
def decode_message(presence_message)
|
|
91
91
|
presence_message.decode client.encoders, channel.options
|
|
92
92
|
rescue Ably::Exceptions::CipherError, Ably::Exceptions::EncoderError => e
|
|
93
|
-
client.logger.error "Decoding Error on presence channel '#{channel.name}', presence message client_id '#{presence_message.client_id}'. #{e.class.name}: #{e.message}"
|
|
93
|
+
client.logger.error { "Decoding Error on presence channel '#{channel.name}', presence message client_id '#{presence_message.client_id}'. #{e.class.name}: #{e.message}" }
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
96
|
end
|
|
@@ -14,5 +14,31 @@ module Ably::Util
|
|
|
14
14
|
def initialize(logger)
|
|
15
15
|
@logger = logger
|
|
16
16
|
end
|
|
17
|
+
|
|
18
|
+
# Create a new {SafeDeferrable} and fail immediately with the provided error in the next eventloop cycle
|
|
19
|
+
#
|
|
20
|
+
# @param error [Ably::Exceptions::BaseAblyException, Ably::Models::ErrorInfo] The error used to fail the newly created {SafeDeferrable}
|
|
21
|
+
#
|
|
22
|
+
# @return [SafeDeferrable]
|
|
23
|
+
#
|
|
24
|
+
def self.new_and_fail_immediately(logger, error)
|
|
25
|
+
new(logger).tap do |deferrable|
|
|
26
|
+
EventMachine.next_tick do
|
|
27
|
+
deferrable.fail error
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Create a new {SafeDeferrable} and succeed immediately with the provided arguments in the next eventloop cycle
|
|
33
|
+
#
|
|
34
|
+
# @return [SafeDeferrable]
|
|
35
|
+
#
|
|
36
|
+
def self.new_and_succeed_immediately(logger, *args)
|
|
37
|
+
new(logger).tap do |deferrable|
|
|
38
|
+
EventMachine.next_tick do
|
|
39
|
+
deferrable.succeed *args
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
17
43
|
end
|
|
18
44
|
end
|
|
@@ -5,6 +5,14 @@ require 'spec_helper'
|
|
|
5
5
|
# wrapper around the Ably::Auth object
|
|
6
6
|
#
|
|
7
7
|
describe Ably::Realtime::Auth, :event_machine do
|
|
8
|
+
def disconnect_transport(connection)
|
|
9
|
+
if connection.transport
|
|
10
|
+
connection.transport.close_connection_after_writing
|
|
11
|
+
else
|
|
12
|
+
EventMachine.next_tick { disconnect_transport connection }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
8
16
|
vary_by_protocol do
|
|
9
17
|
let(:default_options) { { key: api_key, environment: environment, protocol: protocol } }
|
|
10
18
|
let(:client_options) { default_options }
|
|
@@ -88,8 +96,9 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
|
88
96
|
end
|
|
89
97
|
|
|
90
98
|
context '#options (auth_options)' do
|
|
99
|
+
let(:token_str) { auth.request_token_sync.token }
|
|
91
100
|
let(:auth_url) { "https://echo.ably.io/?type=text" }
|
|
92
|
-
let(:auth_params) { { :body =>
|
|
101
|
+
let(:auth_params) { { :body => token_str } }
|
|
93
102
|
let(:client_options) { default_options.merge(auto_connect: false) }
|
|
94
103
|
|
|
95
104
|
it 'contains the configured auth options' do
|
|
@@ -130,7 +139,7 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
|
130
139
|
end
|
|
131
140
|
end
|
|
132
141
|
|
|
133
|
-
context do
|
|
142
|
+
context 'methods' do
|
|
134
143
|
let(:custom_ttl) { 33 }
|
|
135
144
|
let(:custom_client_id) { random_str }
|
|
136
145
|
|
|
@@ -177,12 +186,16 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
|
177
186
|
end
|
|
178
187
|
|
|
179
188
|
context '#authorize' do
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
189
|
+
context 'with token auth' do
|
|
190
|
+
let(:client_options) { default_options.merge(use_token_auth: true) }
|
|
191
|
+
|
|
192
|
+
it 'returns a token asynchronously' do
|
|
193
|
+
auth.authorize(ttl: custom_ttl, client_id: custom_client_id) do |token_details|
|
|
194
|
+
expect(token_details).to be_a(Ably::Models::TokenDetails)
|
|
195
|
+
expect(token_details.expires.to_i).to be_within(3).of(Time.now.to_i + custom_ttl)
|
|
196
|
+
expect(token_details.client_id).to eql(custom_client_id)
|
|
197
|
+
stop_reactor
|
|
198
|
+
end
|
|
186
199
|
end
|
|
187
200
|
end
|
|
188
201
|
|
|
@@ -223,10 +236,11 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
|
223
236
|
context 'and an incompatible client_id in a TokenDetails object passed to the auth callback' do
|
|
224
237
|
let(:auth_token_object) { rest_auth_client.auth.request_token }
|
|
225
238
|
|
|
226
|
-
it 'rejects a TokenDetails object with an incompatible client_id and
|
|
239
|
+
it 'rejects a TokenDetails object with an incompatible client_id and fails with an exception' do
|
|
227
240
|
client.connect
|
|
228
|
-
client.connection.on(:
|
|
229
|
-
expect(
|
|
241
|
+
client.connection.on(:failed) do |state_change|
|
|
242
|
+
expect(state_change.reason).to be_a(Ably::Exceptions::AuthenticationFailed)
|
|
243
|
+
expect(state_change.reason.code).to eql(40012)
|
|
230
244
|
EventMachine.add_timer(0.1) do
|
|
231
245
|
expect(client.connection).to be_failed
|
|
232
246
|
stop_reactor
|
|
@@ -235,13 +249,14 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
|
235
249
|
end
|
|
236
250
|
end
|
|
237
251
|
|
|
238
|
-
context 'and an incompatible client_id in a TokenRequest object passed to the auth callback and
|
|
252
|
+
context 'and an incompatible client_id in a TokenRequest object passed to the auth callback and fails with an exception' do
|
|
239
253
|
let(:auth_token_object) { rest_auth_client.auth.create_token_request }
|
|
240
254
|
|
|
241
|
-
it 'rejects a TokenRequests object with an incompatible client_id and
|
|
255
|
+
it 'rejects a TokenRequests object with an incompatible client_id and fails with an exception' do
|
|
242
256
|
client.connect
|
|
243
|
-
client.connection.on(:
|
|
244
|
-
expect(
|
|
257
|
+
client.connection.on(:failed) do |state_change|
|
|
258
|
+
expect(state_change.reason).to be_a(Ably::Exceptions::AuthenticationFailed)
|
|
259
|
+
expect(state_change.reason.code).to eql(40012)
|
|
245
260
|
EventMachine.add_timer(0.1) do
|
|
246
261
|
expect(client.connection).to be_failed
|
|
247
262
|
stop_reactor
|
|
@@ -269,11 +284,12 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
|
269
284
|
let(:invalid_auth_token) { Ably::Rest::Client.new(default_options.merge(key: api_key, client_id: 'invalid')).auth.request_token }
|
|
270
285
|
|
|
271
286
|
context 'and an incompatible client_id in a TokenDetails object passed to the auth callback' do
|
|
272
|
-
it 'rejects a TokenDetails object with an incompatible client_id and
|
|
287
|
+
it 'rejects a TokenDetails object with an incompatible client_id and fails with an exception' do
|
|
273
288
|
client.connection.once(:connected) do
|
|
274
289
|
client.auth.authorize({})
|
|
275
|
-
client.connection.on(:
|
|
276
|
-
expect(
|
|
290
|
+
client.connection.on(:failed) do |state_change|
|
|
291
|
+
expect(state_change.reason).to be_a(Ably::Exceptions::IncompatibleClientId)
|
|
292
|
+
expect(state_change.reason.code).to eql(40012)
|
|
277
293
|
EventMachine.add_timer(0.1) do
|
|
278
294
|
expect(client.connection).to be_failed
|
|
279
295
|
stop_reactor
|
|
@@ -304,29 +320,268 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
|
304
320
|
end }
|
|
305
321
|
|
|
306
322
|
let(:client_options) { default_options.merge(auth_callback: basic_token_cb) }
|
|
323
|
+
let(:connection) { client.connection }
|
|
324
|
+
|
|
325
|
+
context 'when INITIALIZED' do
|
|
326
|
+
let(:client_options) { default_options.merge(auth_callback: basic_token_cb, auto_connect: false) }
|
|
327
|
+
|
|
328
|
+
it 'obtains a token and connects to Ably (#RTC8c, #RTC8b1)' do
|
|
329
|
+
has_connected = false
|
|
330
|
+
EventMachine.add_timer(0.2) do
|
|
331
|
+
expect(client.connection).to be_initialized
|
|
332
|
+
connection.once(:connected) do
|
|
333
|
+
expect(client.auth.client_id).to_not be_nil
|
|
334
|
+
has_connected = true
|
|
335
|
+
end
|
|
336
|
+
client.auth.authorize(nil, auth_callback: identified_token_cb) do |token|
|
|
337
|
+
expect(token.client_id).to eql('bob')
|
|
338
|
+
EventMachine.add_timer(0.25) do
|
|
339
|
+
expect(has_connected).to be_truthy
|
|
340
|
+
stop_reactor
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end
|
|
307
346
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
347
|
+
context 'when CONNECTING' do
|
|
348
|
+
let(:client_options) { default_options.merge(auth_callback: basic_token_cb) }
|
|
349
|
+
|
|
350
|
+
it 'aborts the current connection process, obtains a token, and connects to Ably again (#RTC8b)' do
|
|
351
|
+
connected_count = 0
|
|
352
|
+
connection.once(:connecting) do
|
|
353
|
+
connection.once(:connected) { connected_count += 1 }
|
|
354
|
+
|
|
355
|
+
client.auth.authorize(nil, auth_callback: identified_token_cb) do |token|
|
|
356
|
+
expect(token.client_id).to eql('bob')
|
|
357
|
+
EventMachine.add_timer(0.25) do
|
|
358
|
+
expect(connected_count).to eql(1)
|
|
359
|
+
stop_reactor
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
context 'when FAILED' do
|
|
367
|
+
let(:client_options) { default_options.merge(token: 'this.token:is.invalid', log_level: :none) }
|
|
368
|
+
|
|
369
|
+
it 'obtains a token and connects to Ably (#RTC8c, #RTC8b1)' do
|
|
370
|
+
has_connected = false
|
|
371
|
+
connection.once(:failed) do
|
|
313
372
|
client.connection.once(:connected) do
|
|
314
|
-
|
|
315
|
-
|
|
373
|
+
has_connected = true
|
|
374
|
+
end
|
|
375
|
+
client.auth.authorize(nil, auth_callback: basic_token_cb) do
|
|
376
|
+
EventMachine.add_timer(0.25) do
|
|
377
|
+
expect(has_connected).to be_truthy
|
|
378
|
+
stop_reactor
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
context 'when CLOSED' do
|
|
386
|
+
it 'obtains a token and connects to Ably (#RTC8c, #RTC8b1, #RTC8a3)' do
|
|
387
|
+
has_connected = false
|
|
388
|
+
connection.once(:connected) do
|
|
389
|
+
connection.once(:closed) do
|
|
390
|
+
client.connection.once(:connected) do
|
|
391
|
+
has_connected = true
|
|
392
|
+
end
|
|
393
|
+
client.auth.authorize(nil, auth_callback: basic_token_cb) do
|
|
394
|
+
EventMachine.add_timer(0.25) do
|
|
395
|
+
expect(has_connected).to be_truthy
|
|
396
|
+
stop_reactor
|
|
397
|
+
end
|
|
398
|
+
end
|
|
316
399
|
end
|
|
400
|
+
connection.close
|
|
317
401
|
end
|
|
318
402
|
end
|
|
319
403
|
end
|
|
320
404
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
405
|
+
context 'when in the CONNECTED state' do
|
|
406
|
+
context 'with a valid token in the AUTH ProtocolMessage sent' do
|
|
407
|
+
let(:client_options) { default_options.merge(use_token_auth: true) }
|
|
408
|
+
|
|
409
|
+
it 'obtains a new token (that upgrades from anonymous to identified) and upgrades the connection after receiving an updated CONNECTED ProtocolMessage (#RTC8a, #RTC8a3)' do
|
|
410
|
+
skip "This capability to upgrade from anonymous to identified is not yet implemented, see https://github.com/ably/wiki/issues/182"
|
|
411
|
+
|
|
326
412
|
client.connection.once(:connected) do
|
|
327
|
-
|
|
328
|
-
|
|
413
|
+
existing_token = client.auth.current_token_details
|
|
414
|
+
expect(client.connection.details.client_id).to be_nil
|
|
415
|
+
auth_sent = false
|
|
416
|
+
has_updated = false
|
|
417
|
+
new_connected_message_received = false
|
|
418
|
+
|
|
419
|
+
client.connection.once(:disconnected) { raise "Should not disconnnect during auth process"}
|
|
420
|
+
client.connection.once(:update) do
|
|
421
|
+
expect(auth_sent).to be_truthy
|
|
422
|
+
expect(new_connected_message_received).to be_truthy
|
|
423
|
+
expect(existing_token).to_not eql(client.auth.current_token_details)
|
|
424
|
+
expect(client.auth.client_id).to_not be_nil
|
|
425
|
+
expect(client.connection.details.client_id).to_not be_nil
|
|
426
|
+
has_updated = true
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
|
430
|
+
auth_sent = true if protocol_message.action == :auth
|
|
431
|
+
end
|
|
432
|
+
connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
|
433
|
+
new_connected_message_received = true if protocol_message.action == :connected
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
client.auth.authorize(nil, auth_callback: identified_token_cb) do
|
|
437
|
+
EventMachine.add_timer(0.25) do
|
|
438
|
+
expect(has_updated).to be_truthy
|
|
439
|
+
stop_reactor
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
it 'obtains a new token (as anonymous user before & after) and upgrades the connection after receiving an updated CONNECTED ProtocolMessage (#RTC8a, #RTC8a3)' do
|
|
446
|
+
client.connection.once(:connected) do
|
|
447
|
+
existing_token = client.auth.current_token_details
|
|
448
|
+
auth_sent = false
|
|
449
|
+
has_updated = false
|
|
450
|
+
new_connected_message_received = false
|
|
451
|
+
|
|
452
|
+
client.connection.once(:disconnected) { raise "Should not disconnnect during auth process"}
|
|
453
|
+
client.connection.once(:update) do
|
|
454
|
+
EventMachine.next_tick do
|
|
455
|
+
expect(auth_sent).to be_truthy
|
|
456
|
+
expect(new_connected_message_received).to be_truthy
|
|
457
|
+
expect(existing_token).to_not eql(client.auth.current_token_details)
|
|
458
|
+
has_updated = true
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
|
463
|
+
auth_sent = true if protocol_message.action == :auth
|
|
464
|
+
end
|
|
465
|
+
connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
|
466
|
+
new_connected_message_received = true if protocol_message.action == :connected
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
client.auth.authorize do
|
|
470
|
+
EventMachine.add_timer(0.25) do
|
|
471
|
+
expect(has_updated).to be_truthy
|
|
472
|
+
stop_reactor
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
context 'when DISCONNECTED' do
|
|
481
|
+
it 'obtains a token, upgrades from anonymous to identified, and connects to Ably immediately (#RTC8c, #RTC8b1)' do
|
|
482
|
+
skip "This capability to upgrade from anonymous to identified is not yet implemented, see https://github.com/ably/wiki/issues/182"
|
|
483
|
+
|
|
484
|
+
disconnected_waiting = false
|
|
485
|
+
has_connected = false
|
|
486
|
+
|
|
487
|
+
connection.once(:connected) do
|
|
488
|
+
expect(client.auth.client_id).to be_nil
|
|
489
|
+
|
|
490
|
+
connection.on(:disconnected) do |connection_state_change|
|
|
491
|
+
# Once we detect the connection will remain DISCONNECTED for 15s, then we can call authorize
|
|
492
|
+
# else we can't be sure authorize was responsible for the reconnect
|
|
493
|
+
if connection_state_change.retry_in > 1
|
|
494
|
+
disconnected_waiting = true
|
|
495
|
+
|
|
496
|
+
client.connection.once(:connected) do
|
|
497
|
+
expect(client.auth.client_id).to_not be_nil
|
|
498
|
+
has_connected = true
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
client.auth.authorize(nil, auth_callback: identified_token_cb) do
|
|
502
|
+
EventMachine.add_timer(0.25) do
|
|
503
|
+
expect(has_connected).to be_truthy
|
|
504
|
+
stop_reactor
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
connection.on(:connecting) do
|
|
511
|
+
disconnect_transport connection unless disconnected_waiting
|
|
512
|
+
end
|
|
513
|
+
disconnect_transport connection
|
|
514
|
+
end
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
it 'obtains a similar anonymous token and connects to Ably immediately (#RTC8c, #RTC8b1)' do
|
|
518
|
+
disconnected_waiting = false
|
|
519
|
+
has_connected = false
|
|
520
|
+
|
|
521
|
+
connection.once(:connected) do
|
|
522
|
+
expect(client.auth.client_id).to be_nil
|
|
523
|
+
|
|
524
|
+
connection.on(:disconnected) do |connection_state_change|
|
|
525
|
+
# Once we detect the connection will remain DISCONNECTED for 15s, then we can call authorize
|
|
526
|
+
# else we can't be sure authorize was responsible for the reconnect
|
|
527
|
+
if connection_state_change.retry_in > 1
|
|
528
|
+
disconnected_waiting = true
|
|
529
|
+
|
|
530
|
+
client.connection.once(:connected) do
|
|
531
|
+
expect(client.auth.client_id).to be_nil
|
|
532
|
+
has_connected = true
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
client.auth.authorize do
|
|
536
|
+
EventMachine.add_timer(0.25) do
|
|
537
|
+
expect(has_connected).to be_truthy
|
|
538
|
+
stop_reactor
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
connection.on(:connecting) do
|
|
545
|
+
disconnect_transport connection unless disconnected_waiting
|
|
546
|
+
end
|
|
547
|
+
disconnect_transport connection
|
|
548
|
+
end
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
context 'when SUSPENDED' do
|
|
553
|
+
let(:client_options) do
|
|
554
|
+
default_options.merge(
|
|
555
|
+
disconnected_retry_timeout: 0.1,
|
|
556
|
+
max_connection_state_ttl: 0.3,
|
|
557
|
+
use_token_auth: true
|
|
558
|
+
)
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
it 'obtains a token and connects to Ably immediately (#RTC8c, #RTC8b1)' do
|
|
562
|
+
been_suspended = false
|
|
563
|
+
has_connected = false
|
|
564
|
+
|
|
565
|
+
connection.once(:connected) do
|
|
566
|
+
connection.on(:suspended) do |connection_state_change|
|
|
567
|
+
been_suspended = true
|
|
568
|
+
|
|
569
|
+
client.connection.once(:connected) do
|
|
570
|
+
has_connected = true
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
client.auth.authorize do
|
|
574
|
+
EventMachine.add_timer(0.25) do
|
|
575
|
+
expect(has_connected).to be_truthy
|
|
576
|
+
stop_reactor
|
|
577
|
+
end
|
|
578
|
+
end
|
|
329
579
|
end
|
|
580
|
+
|
|
581
|
+
connection.on(:connecting) do
|
|
582
|
+
disconnect_transport connection unless been_suspended
|
|
583
|
+
end
|
|
584
|
+
disconnect_transport connection
|
|
330
585
|
end
|
|
331
586
|
end
|
|
332
587
|
end
|
|
@@ -338,44 +593,97 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
|
338
593
|
rest_client.auth.create_token_request({ client_id: 'mike', capability: basic_capability })
|
|
339
594
|
end }
|
|
340
595
|
|
|
341
|
-
it '
|
|
596
|
+
it 'transitions the connection state to FAILED if the client_id changes (#RSA15c, #RTC8a2)' do
|
|
342
597
|
client.connection.once(:connected) do
|
|
343
598
|
client.auth.authorize(nil, auth_callback: identified_token_cb)
|
|
344
599
|
client.connection.once(:failed) do
|
|
345
|
-
expect(client.connection.error_reason.message).to match(/incompatible.*
|
|
600
|
+
expect(client.connection.error_reason.message).to match(/incompatible.*clientId/i)
|
|
601
|
+
expect(client.connection.error_reason.code).to eql(40012)
|
|
346
602
|
stop_reactor
|
|
347
603
|
end
|
|
348
604
|
end
|
|
349
605
|
end
|
|
350
606
|
end
|
|
351
607
|
|
|
608
|
+
context 'when auth fails' do
|
|
609
|
+
let(:client_options) { default_options.merge(auth_callback: basic_token_cb, log_level: :none) }
|
|
610
|
+
|
|
611
|
+
it 'transitions the connection state to the FAILED state (#RSA15c, #RTC8a2, #RTC8a3)' do
|
|
612
|
+
connection_failed = false
|
|
613
|
+
|
|
614
|
+
client.connection.once(:connected) do
|
|
615
|
+
client.auth.authorize(nil, auth_callback: Proc.new { 'invalid.token:will.cause.failure' }).tap do |deferrable|
|
|
616
|
+
deferrable.errback do |error|
|
|
617
|
+
EventMachine.add_timer(0.2) do
|
|
618
|
+
expect(connection_failed).to eql(true)
|
|
619
|
+
expect(error.message).to match(/Invalid accessToken/i)
|
|
620
|
+
expect(error.code).to eql(40005)
|
|
621
|
+
stop_reactor
|
|
622
|
+
end
|
|
623
|
+
end
|
|
624
|
+
deferrable.callback { raise "Authorize should not succed" }
|
|
625
|
+
end
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
client.connection.once(:failed) do
|
|
629
|
+
expect(client.connection.error_reason.message).to match(/Invalid accessToken/i)
|
|
630
|
+
expect(client.connection.error_reason.code).to eql(40005)
|
|
631
|
+
connection_failed = true
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
context 'when the authCallback fails' do
|
|
637
|
+
let(:client_options) { default_options.merge(auth_callback: basic_token_cb, log_level: :none) }
|
|
638
|
+
|
|
639
|
+
it 'calls the error callback of authorize and leaves the connection intact (#RSA4c3)' do
|
|
640
|
+
client.connection.once(:connected) do
|
|
641
|
+
client.auth.authorize(nil, auth_callback: Proc.new { raise 'Exception raised' }).errback do |error|
|
|
642
|
+
EventMachine.add_timer(0.2) do
|
|
643
|
+
expect(connection).to be_connected
|
|
644
|
+
expect(error.message).to match(/Exception raised/i)
|
|
645
|
+
stop_reactor
|
|
646
|
+
end
|
|
647
|
+
end
|
|
648
|
+
client.connection.once(:failed) do
|
|
649
|
+
raise "Connection should not fail"
|
|
650
|
+
end
|
|
651
|
+
end
|
|
652
|
+
end
|
|
653
|
+
end
|
|
654
|
+
|
|
352
655
|
context 'when upgrading capabilities' do
|
|
353
656
|
let(:client_options) { default_options.merge(auth_callback: basic_token_cb, log_level: :error) }
|
|
354
657
|
|
|
355
|
-
it 'is allowed' do
|
|
658
|
+
it 'is allowed (#RTC8a1)' do
|
|
356
659
|
client.connection.once(:connected) do
|
|
660
|
+
client.connection.once(:disconnected) { raise 'Upgrade does not require a disconnect' }
|
|
661
|
+
|
|
357
662
|
channel = client.channels.get('foo')
|
|
358
663
|
channel.publish('not-allowed').errback do |error|
|
|
359
664
|
expect(error.code).to eql(40160)
|
|
360
665
|
expect(error.message).to match(/permission denied/)
|
|
666
|
+
|
|
361
667
|
client.auth.authorize(nil, auth_callback: upgraded_token_cb)
|
|
362
|
-
client.connection.once(:
|
|
668
|
+
client.connection.once(:update) do
|
|
363
669
|
expect(client.connection.error_reason).to be_nil
|
|
364
|
-
channel.subscribe('allowed') do |message|
|
|
670
|
+
channel.subscribe('now-allowed') do |message|
|
|
365
671
|
stop_reactor
|
|
366
672
|
end
|
|
367
|
-
channel.publish 'allowed'
|
|
673
|
+
channel.publish 'now-allowed'
|
|
368
674
|
end
|
|
369
675
|
end
|
|
370
676
|
end
|
|
371
677
|
end
|
|
372
678
|
end
|
|
373
679
|
|
|
374
|
-
context 'when downgrading capabilities' do
|
|
680
|
+
context 'when downgrading capabilities (#RTC8a1)' do
|
|
375
681
|
let(:client_options) { default_options.merge(auth_callback: basic_token_cb, log_level: :none) }
|
|
376
682
|
|
|
377
683
|
it 'is allowed and channels are detached' do
|
|
378
684
|
client.connection.once(:connected) do
|
|
685
|
+
client.connection.once(:disconnected) { raise 'Upgrade does not require a disconnect' }
|
|
686
|
+
|
|
379
687
|
channel = client.channels.get('foo')
|
|
380
688
|
channel.attach do
|
|
381
689
|
client.auth.authorize(nil, auth_callback: downgraded_token_cb)
|
|
@@ -389,11 +697,13 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
|
389
697
|
end
|
|
390
698
|
end
|
|
391
699
|
|
|
392
|
-
it 'ensures message delivery continuity whilst upgrading' do
|
|
700
|
+
it 'ensures message delivery continuity whilst upgrading (#RTC8a1)' do
|
|
393
701
|
received_messages = []
|
|
394
702
|
subscriber_channel = client.channels.get('foo')
|
|
395
703
|
publisher_channel = client_publisher.channels.get('foo')
|
|
396
704
|
subscriber_channel.attach do
|
|
705
|
+
client.connection.once(:disconnected) { raise 'Upgrade does not require a disconnect' }
|
|
706
|
+
|
|
397
707
|
subscriber_channel.subscribe do |message|
|
|
398
708
|
received_messages << message
|
|
399
709
|
end
|
|
@@ -401,73 +711,19 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
|
401
711
|
publisher_channel.publish('foo') do
|
|
402
712
|
EventMachine.add_timer(2) do
|
|
403
713
|
expect(received_messages.length).to eql(1)
|
|
404
|
-
|
|
405
|
-
client.connection.once(:
|
|
406
|
-
publisher_channel.publish('bar') do
|
|
407
|
-
expect(received_messages.length).to eql(1)
|
|
408
|
-
end
|
|
409
|
-
end
|
|
410
|
-
client.connection.once(:connected) do
|
|
714
|
+
|
|
715
|
+
client.connection.once(:update) do
|
|
411
716
|
EventMachine.add_timer(2) do
|
|
412
717
|
expect(received_messages.length).to eql(2)
|
|
413
718
|
stop_reactor
|
|
414
719
|
end
|
|
415
720
|
end
|
|
416
|
-
end
|
|
417
|
-
end
|
|
418
|
-
end
|
|
419
|
-
end
|
|
420
|
-
end
|
|
421
721
|
|
|
422
|
-
|
|
423
|
-
client.connection.once(:connected) do
|
|
424
|
-
client.connection.once(:closing) do
|
|
425
|
-
client.auth.authorize(nil)
|
|
426
|
-
client.connection.once(:connected) do
|
|
427
|
-
raise "Should not reconnect following #authorize"
|
|
428
|
-
end
|
|
429
|
-
EventMachine.add_timer(4) do
|
|
430
|
-
expect(client.connection).to be_closed
|
|
431
|
-
stop_reactor
|
|
432
|
-
end
|
|
433
|
-
end
|
|
434
|
-
client.connection.close
|
|
435
|
-
end
|
|
436
|
-
end
|
|
437
|
-
|
|
438
|
-
it 'does not change the connection state if current connection state is closed' do
|
|
439
|
-
client.connection.once(:connected) do
|
|
440
|
-
client.connection.once(:closed) do
|
|
441
|
-
client.auth.authorize(nil)
|
|
442
|
-
client.connection.once(:connected) do
|
|
443
|
-
raise "Should not reconnect following #authorize"
|
|
444
|
-
end
|
|
445
|
-
EventMachine.add_timer(4) do
|
|
446
|
-
expect(client.connection).to be_closed
|
|
447
|
-
stop_reactor
|
|
448
|
-
end
|
|
449
|
-
end
|
|
450
|
-
client.connection.close
|
|
451
|
-
end
|
|
452
|
-
end
|
|
453
|
-
|
|
454
|
-
context 'when state is failed' do
|
|
455
|
-
let(:client_options) { default_options.merge(auth_callback: basic_token_cb, log_level: :none) }
|
|
722
|
+
client.auth.authorize(nil)
|
|
456
723
|
|
|
457
|
-
|
|
458
|
-
client.connection.once(:connected) do
|
|
459
|
-
client.connection.once(:failed) do
|
|
460
|
-
client.auth.authorize(nil)
|
|
461
|
-
client.connection.once(:connected) do
|
|
462
|
-
raise "Should not reconnect following #authorize"
|
|
463
|
-
end
|
|
464
|
-
EventMachine.add_timer(4) do
|
|
465
|
-
expect(client.connection).to be_failed
|
|
466
|
-
stop_reactor
|
|
724
|
+
publisher_channel.publish('bar')
|
|
467
725
|
end
|
|
468
726
|
end
|
|
469
|
-
protocol_message = Ably::Models::ProtocolMessage.new(action: Ably::Models::ProtocolMessage::ACTION.Error.to_i)
|
|
470
|
-
client.connection.__incoming_protocol_msgbus__.publish :protocol_message, protocol_message
|
|
471
727
|
end
|
|
472
728
|
end
|
|
473
729
|
end
|
|
@@ -486,6 +742,67 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
|
486
742
|
end
|
|
487
743
|
end
|
|
488
744
|
|
|
745
|
+
context 'server initiated AUTH ProtocolMessage' do
|
|
746
|
+
before do
|
|
747
|
+
stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', 0 # allow token to be used even if about to expire
|
|
748
|
+
stub_const 'Ably::Auth::TOKEN_DEFAULTS', Ably::Auth::TOKEN_DEFAULTS.merge(renew_token_buffer: 0) # Ensure tokens issued expire immediately after issue
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
context 'when received' do
|
|
752
|
+
# Ably in all environments other than locla will send AUTH 30 seconds before expiry
|
|
753
|
+
# We set the TTL to 33s and wait (3s window)
|
|
754
|
+
# In local env, that window is 5 seconds instead of 30 seconds
|
|
755
|
+
let(:local_offset) { ENV['ABLY_ENV'] == 'local' ? 25 : 0 }
|
|
756
|
+
let(:client_options) { default_options.merge(use_token_auth: :true, default_token_params: { ttl: 33 - local_offset }) }
|
|
757
|
+
|
|
758
|
+
it 'should immediately start a new authentication process (#RTN22)' do
|
|
759
|
+
client.connection.once(:connected) do
|
|
760
|
+
original_token = auth.current_token_details
|
|
761
|
+
received_auth = false
|
|
762
|
+
|
|
763
|
+
client.connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
|
764
|
+
received_auth = true if protocol_message.action == :auth
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
client.connection.once(:update) do
|
|
768
|
+
expect(received_auth).to be_truthy
|
|
769
|
+
expect(original_token).to_not eql(auth.current_token_details)
|
|
770
|
+
stop_reactor
|
|
771
|
+
end
|
|
772
|
+
end
|
|
773
|
+
end
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
context 'when not received' do
|
|
777
|
+
# Ably in all environments other than production will send AUTH 5 seconds before expiry, so
|
|
778
|
+
# set TTL to 5s so that the window for Realtime to send has passed
|
|
779
|
+
let(:client_options) { default_options.merge(use_token_auth: :true, default_token_params: { ttl: 5 }) }
|
|
780
|
+
|
|
781
|
+
it 'should expect the connection to be disconnected by the server but should resume automatically (#RTN22a)' do
|
|
782
|
+
client.connection.once(:connected) do
|
|
783
|
+
original_token = auth.current_token_details
|
|
784
|
+
original_conn_id = client.connection.id
|
|
785
|
+
received_auth = false
|
|
786
|
+
|
|
787
|
+
client.connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
|
788
|
+
received_auth = true if protocol_message.action == :auth
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
client.connection.once(:disconnected) do |state_change|
|
|
792
|
+
expect(state_change.reason.code).to eql(40142)
|
|
793
|
+
|
|
794
|
+
client.connection.once(:connected) do
|
|
795
|
+
expect(received_auth).to be_falsey
|
|
796
|
+
expect(original_token).to_not eql(auth.current_token_details)
|
|
797
|
+
expect(original_conn_id).to eql(client.connection.id)
|
|
798
|
+
stop_reactor
|
|
799
|
+
end
|
|
800
|
+
end
|
|
801
|
+
end
|
|
802
|
+
end
|
|
803
|
+
end
|
|
804
|
+
end
|
|
805
|
+
|
|
489
806
|
context '#auth_params' do
|
|
490
807
|
it 'returns the auth params asynchronously' do
|
|
491
808
|
auth.auth_params do |auth_params|
|
|
@@ -698,7 +1015,7 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
|
698
1015
|
end
|
|
699
1016
|
|
|
700
1017
|
context 'deprecated #authorise' do
|
|
701
|
-
let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object) }
|
|
1018
|
+
let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, use_token_auth: true) }
|
|
702
1019
|
let(:custom_logger) do
|
|
703
1020
|
Class.new do
|
|
704
1021
|
def initialize
|