ably 0.8.15 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -4
- data/CHANGELOG.md +6 -2
- data/README.md +5 -1
- data/SPEC.md +1473 -852
- data/ably.gemspec +11 -8
- data/lib/ably/auth.rb +90 -53
- data/lib/ably/exceptions.rb +37 -8
- data/lib/ably/logger.rb +10 -1
- data/lib/ably/models/auth_details.rb +42 -0
- data/lib/ably/models/channel_state_change.rb +18 -4
- data/lib/ably/models/connection_details.rb +6 -3
- data/lib/ably/models/connection_state_change.rb +4 -3
- data/lib/ably/models/error_info.rb +1 -1
- data/lib/ably/models/message.rb +17 -1
- data/lib/ably/models/message_encoders/base.rb +103 -82
- data/lib/ably/models/message_encoders/base64.rb +1 -1
- data/lib/ably/models/presence_message.rb +16 -1
- data/lib/ably/models/protocol_message.rb +20 -3
- data/lib/ably/models/token_details.rb +11 -1
- data/lib/ably/models/token_request.rb +16 -6
- data/lib/ably/modules/async_wrapper.rb +7 -3
- data/lib/ably/modules/encodeable.rb +51 -12
- data/lib/ably/modules/enum.rb +17 -7
- data/lib/ably/modules/event_emitter.rb +29 -14
- data/lib/ably/modules/model_common.rb +13 -21
- data/lib/ably/modules/state_emitter.rb +7 -4
- data/lib/ably/modules/state_machine.rb +2 -4
- data/lib/ably/modules/uses_state_machine.rb +7 -3
- data/lib/ably/realtime.rb +2 -0
- data/lib/ably/realtime/auth.rb +102 -42
- data/lib/ably/realtime/channel.rb +68 -26
- data/lib/ably/realtime/channel/channel_manager.rb +154 -65
- data/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
- data/lib/ably/realtime/client.rb +18 -3
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
- data/lib/ably/realtime/connection.rb +108 -49
- data/lib/ably/realtime/connection/connection_manager.rb +167 -61
- data/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
- data/lib/ably/realtime/connection/websocket_transport.rb +19 -10
- data/lib/ably/realtime/presence.rb +70 -45
- data/lib/ably/realtime/presence/members_map.rb +201 -36
- data/lib/ably/realtime/presence/presence_manager.rb +30 -6
- data/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
- data/lib/ably/rest.rb +2 -2
- data/lib/ably/rest/channel.rb +5 -5
- data/lib/ably/rest/client.rb +31 -27
- data/lib/ably/rest/middleware/exceptions.rb +1 -3
- data/lib/ably/rest/middleware/logger.rb +2 -2
- data/lib/ably/rest/presence.rb +2 -2
- data/lib/ably/util/pub_sub.rb +1 -1
- data/lib/ably/util/safe_deferrable.rb +26 -0
- data/lib/ably/version.rb +2 -2
- data/spec/acceptance/realtime/auth_spec.rb +470 -111
- data/spec/acceptance/realtime/channel_history_spec.rb +5 -3
- data/spec/acceptance/realtime/channel_spec.rb +1017 -168
- data/spec/acceptance/realtime/client_spec.rb +6 -6
- data/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
- data/spec/acceptance/realtime/connection_spec.rb +424 -105
- data/spec/acceptance/realtime/message_spec.rb +52 -23
- data/spec/acceptance/realtime/presence_history_spec.rb +5 -3
- data/spec/acceptance/realtime/presence_spec.rb +1110 -96
- data/spec/acceptance/rest/auth_spec.rb +222 -59
- data/spec/acceptance/rest/base_spec.rb +1 -1
- data/spec/acceptance/rest/channel_spec.rb +1 -2
- data/spec/acceptance/rest/client_spec.rb +104 -48
- data/spec/acceptance/rest/message_spec.rb +42 -15
- data/spec/acceptance/rest/presence_spec.rb +4 -11
- data/spec/rspec_config.rb +2 -1
- data/spec/shared/client_initializer_behaviour.rb +2 -2
- data/spec/shared/safe_deferrable_behaviour.rb +6 -2
- data/spec/spec_helper.rb +4 -2
- data/spec/support/debug_failure_helper.rb +20 -4
- data/spec/support/event_machine_helper.rb +32 -1
- data/spec/unit/auth_spec.rb +4 -11
- data/spec/unit/logger_spec.rb +28 -2
- data/spec/unit/models/auth_details_spec.rb +49 -0
- data/spec/unit/models/channel_state_change_spec.rb +23 -3
- data/spec/unit/models/connection_details_spec.rb +12 -1
- data/spec/unit/models/connection_state_change_spec.rb +15 -4
- data/spec/unit/models/message_encoders/base64_spec.rb +2 -1
- data/spec/unit/models/message_spec.rb +153 -0
- data/spec/unit/models/presence_message_spec.rb +192 -0
- data/spec/unit/models/protocol_message_spec.rb +64 -6
- data/spec/unit/models/token_details_spec.rb +75 -0
- data/spec/unit/models/token_request_spec.rb +74 -0
- data/spec/unit/modules/async_wrapper_spec.rb +2 -1
- data/spec/unit/modules/enum_spec.rb +69 -0
- data/spec/unit/modules/event_emitter_spec.rb +149 -22
- data/spec/unit/modules/state_emitter_spec.rb +9 -3
- data/spec/unit/realtime/client_spec.rb +1 -1
- data/spec/unit/realtime/connection_spec.rb +8 -5
- data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
- data/spec/unit/realtime/presence_spec.rb +4 -3
- data/spec/unit/rest/client_spec.rb +1 -1
- data/spec/unit/util/crypto_spec.rb +3 -3
- metadata +22 -19
@@ -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
|
data/lib/ably/rest.rb
CHANGED
@@ -3,12 +3,12 @@ require 'ably/rest/channels'
|
|
3
3
|
require 'ably/rest/client'
|
4
4
|
require 'ably/rest/presence'
|
5
5
|
|
6
|
+
require 'ably/models/message_encoders/base'
|
7
|
+
|
6
8
|
Dir.glob(File.expand_path("models/*.rb", File.dirname(__FILE__))).each do |file|
|
7
9
|
require file
|
8
10
|
end
|
9
11
|
|
10
|
-
require 'ably/models/message_encoders/base'
|
11
|
-
|
12
12
|
module Ably
|
13
13
|
# Rest provides the top-level class to be instanced for the Ably Rest library
|
14
14
|
#
|
data/lib/ably/rest/channel.rb
CHANGED
@@ -65,14 +65,14 @@ module Ably
|
|
65
65
|
|
66
66
|
payload = messages.map do |message|
|
67
67
|
Ably::Models::Message(message.dup).tap do |msg|
|
68
|
-
msg.encode
|
68
|
+
msg.encode client.encoders, options
|
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
|
@@ -134,9 +134,9 @@ module Ably
|
|
134
134
|
end
|
135
135
|
|
136
136
|
def decode_message(message)
|
137
|
-
message.decode
|
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
|
data/lib/ably/rest/client.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'faraday'
|
2
2
|
require 'json'
|
3
3
|
require 'logger'
|
4
|
+
require 'uri'
|
4
5
|
|
5
6
|
require 'ably/rest/middleware/exceptions'
|
6
7
|
|
@@ -24,8 +25,8 @@ module Ably
|
|
24
25
|
# Configuration for HTTP timeouts and HTTP request reattempts to fallback hosts
|
25
26
|
HTTP_DEFAULTS = {
|
26
27
|
open_timeout: 4,
|
27
|
-
request_timeout:
|
28
|
-
max_retry_duration:
|
28
|
+
request_timeout: 10,
|
29
|
+
max_retry_duration: 15,
|
29
30
|
max_retry_count: 3
|
30
31
|
}.freeze
|
31
32
|
|
@@ -103,12 +104,12 @@ module Ably
|
|
103
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.
|
104
105
|
# The Proc should return a token string, {Ably::Models::TokenDetails} or JSON equivalent, {Ably::Models::TokenRequest} or JSON equivalent
|
105
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
|
106
|
-
# @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}
|
107
108
|
#
|
108
109
|
# @option options [Integer] :http_open_timeout (4 seconds) timeout in seconds for opening an HTTP connection for all HTTP requests
|
109
|
-
# @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
|
110
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
|
111
|
-
# @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
|
112
113
|
#
|
113
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
|
114
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
|
@@ -127,13 +128,14 @@ module Ably
|
|
127
128
|
|
128
129
|
options = options.clone
|
129
130
|
if options.kind_of?(String)
|
130
|
-
options = if options.match(
|
131
|
+
options = if options.match(Auth::API_KEY_REGEX)
|
131
132
|
{ key: options }
|
132
133
|
else
|
133
134
|
{ token: options }
|
134
135
|
end
|
135
136
|
end
|
136
137
|
|
138
|
+
@realtime_client = options.delete(:realtime_client)
|
137
139
|
@tls = options.delete(:tls) == false ? false : true
|
138
140
|
@environment = options.delete(:environment) # nil is production
|
139
141
|
@environment = nil if [:production, 'production'].include?(@environment)
|
@@ -182,7 +184,7 @@ module Ably
|
|
182
184
|
end
|
183
185
|
raise ArgumentError, 'Protocol is invalid. Must be either :msgpack or :json' unless [:msgpack, :json].include?(@protocol)
|
184
186
|
|
185
|
-
token_params = options.delete(:
|
187
|
+
token_params = options.delete(:default_token_params) || {}
|
186
188
|
@options = options
|
187
189
|
@auth = Auth.new(self, token_params, options)
|
188
190
|
@channels = Ably::Rest::Channels.new(self)
|
@@ -285,14 +287,14 @@ module Ably
|
|
285
287
|
|
286
288
|
response = case method.to_sym
|
287
289
|
when :get
|
288
|
-
|
290
|
+
reauthorize_on_authorisation_failure do
|
289
291
|
send_request(method, path, params, headers: headers)
|
290
292
|
end
|
291
293
|
when :post
|
292
294
|
path_with_params = Addressable::URI.new
|
293
295
|
path_with_params.query_values = params || {}
|
294
296
|
query = path_with_params.query
|
295
|
-
|
297
|
+
reauthorize_on_authorisation_failure do
|
296
298
|
send_request(method, "#{path}#{"?#{query}" unless query.nil? || query.empty?}", body, headers: headers)
|
297
299
|
end
|
298
300
|
end
|
@@ -346,18 +348,8 @@ module Ably
|
|
346
348
|
# @return [void]
|
347
349
|
#
|
348
350
|
# @api private
|
349
|
-
def register_encoder(encoder)
|
350
|
-
|
351
|
-
encoder.split('::').inject(Kernel) do |base, klass_name|
|
352
|
-
base.public_send(:const_get, klass_name)
|
353
|
-
end
|
354
|
-
else
|
355
|
-
encoder
|
356
|
-
end
|
357
|
-
|
358
|
-
raise "Encoder must inherit from `Ably::Models::MessageEncoders::Base`" unless encoder_klass.ancestors.include?(Ably::Models::MessageEncoders::Base)
|
359
|
-
|
360
|
-
encoders << encoder_klass.new(self)
|
351
|
+
def register_encoder(encoder, options = {})
|
352
|
+
encoders << Ably::Models::MessageEncoders.encoder_from(encoder, options)
|
361
353
|
end
|
362
354
|
|
363
355
|
# @!attribute [r] protocol_binary?
|
@@ -411,13 +403,25 @@ module Ably
|
|
411
403
|
].compact.join('-')
|
412
404
|
end
|
413
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
|
+
|
414
418
|
private
|
415
419
|
def raw_request(method, path, params = {}, options = {})
|
416
420
|
options = options.clone
|
417
|
-
if options.delete(:
|
421
|
+
if options.delete(:disable_automatic_reauthorize) == true
|
418
422
|
send_request(method, path, params, options)
|
419
423
|
else
|
420
|
-
|
424
|
+
reauthorize_on_authorisation_failure do
|
421
425
|
send_request(method, path, params, options)
|
422
426
|
end
|
423
427
|
end
|
@@ -449,7 +453,7 @@ module Ably
|
|
449
453
|
time_passed = Time.now - requested_at
|
450
454
|
if can_fallback_to_alternate_ably_host? && retry_count < max_retry_count && time_passed <= max_retry_duration
|
451
455
|
retry_count += 1
|
452
|
-
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}" }
|
453
457
|
retry
|
454
458
|
end
|
455
459
|
|
@@ -464,11 +468,11 @@ module Ably
|
|
464
468
|
end
|
465
469
|
end
|
466
470
|
|
467
|
-
def
|
471
|
+
def reauthorize_on_authorisation_failure
|
468
472
|
yield
|
469
473
|
rescue Ably::Exceptions::TokenExpired => e
|
470
474
|
if auth.token_renewable?
|
471
|
-
auth.
|
475
|
+
auth.authorize
|
472
476
|
yield
|
473
477
|
else
|
474
478
|
raise e
|
@@ -535,7 +539,7 @@ module Ably
|
|
535
539
|
end
|
536
540
|
|
537
541
|
def initialize_default_encoders
|
538
|
-
Ably::Models::MessageEncoders.register_default_encoders self
|
542
|
+
Ably::Models::MessageEncoders.register_default_encoders self, binary_protocol: protocol == :msgpack
|
539
543
|
end
|
540
544
|
end
|
541
545
|
end
|
@@ -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
|
|
data/lib/ably/rest/presence.rb
CHANGED
@@ -88,9 +88,9 @@ module Ably
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def decode_message(presence_message)
|
91
|
-
presence_message.decode channel
|
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
|
data/lib/ably/util/pub_sub.rb
CHANGED
@@ -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
|
data/lib/ably/version.rb
CHANGED
@@ -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 }
|
@@ -73,7 +81,7 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
73
81
|
context '#current_token_details' do
|
74
82
|
it 'contains the current token after auth' do
|
75
83
|
expect(auth.current_token_details).to be_nil
|
76
|
-
auth.
|
84
|
+
auth.authorize do
|
77
85
|
expect(auth.current_token_details).to be_a(Ably::Models::TokenDetails)
|
78
86
|
stop_reactor
|
79
87
|
end
|
@@ -88,12 +96,13 @@ 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
|
96
|
-
auth.
|
105
|
+
auth.authorize({}, auth_url: auth_url, auth_params: auth_params) do
|
97
106
|
expect(auth.options[:auth_url]).to eql(auth_url)
|
98
107
|
stop_reactor
|
99
108
|
end
|
@@ -104,7 +113,7 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
104
113
|
let(:custom_ttl) { 33 }
|
105
114
|
|
106
115
|
it 'contains the configured auth options' do
|
107
|
-
auth.
|
116
|
+
auth.authorize(ttl: custom_ttl) do
|
108
117
|
expect(auth.token_params[:ttl]).to eql(custom_ttl)
|
109
118
|
stop_reactor
|
110
119
|
end
|
@@ -113,7 +122,7 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
113
122
|
|
114
123
|
context '#using_basic_auth?' do
|
115
124
|
it 'is false when using Token Auth' do
|
116
|
-
auth.
|
125
|
+
auth.authorize do
|
117
126
|
expect(auth).to_not be_using_basic_auth
|
118
127
|
stop_reactor
|
119
128
|
end
|
@@ -122,7 +131,7 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
122
131
|
|
123
132
|
context '#using_token_auth?' do
|
124
133
|
it 'is true when using Token Auth' do
|
125
|
-
auth.
|
134
|
+
auth.authorize do
|
126
135
|
expect(auth).to be_using_token_auth
|
127
136
|
stop_reactor
|
128
137
|
end
|
@@ -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
|
|
@@ -176,13 +185,17 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
176
185
|
end
|
177
186
|
end
|
178
187
|
|
179
|
-
context '#
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
188
|
+
context '#authorize' do
|
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
|
-
client.auth.
|
275
|
-
client.connection.on(:
|
276
|
-
expect(
|
289
|
+
client.auth.authorize({})
|
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
|
@@ -284,7 +300,7 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
284
300
|
end
|
285
301
|
end
|
286
302
|
|
287
|
-
context '
|
303
|
+
context 'when already authenticated with a valid token' do
|
288
304
|
let(:rest_client) { Ably::Rest::Client.new(default_options) }
|
289
305
|
let(:client_publisher) { auto_close Ably::Realtime::Client.new(default_options) }
|
290
306
|
let(:basic_capability) { JSON.dump("foo" => ["subscribe"]) }
|
@@ -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
|
579
|
+
end
|
580
|
+
|
581
|
+
connection.on(:connecting) do
|
582
|
+
disconnect_transport connection unless been_suspended
|
329
583
|
end
|
584
|
+
disconnect_transport connection
|
330
585
|
end
|
331
586
|
end
|
332
587
|
end
|
@@ -338,47 +593,100 @@ 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
|
-
client.auth.
|
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/)
|
361
|
-
|
362
|
-
client.
|
666
|
+
|
667
|
+
client.auth.authorize(nil, auth_callback: upgraded_token_cb)
|
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
|
-
client.auth.
|
689
|
+
client.auth.authorize(nil, auth_callback: downgraded_token_cb)
|
382
690
|
channel.once(:failed) do
|
383
691
|
expect(channel.error_reason.code).to eql(40160)
|
384
692
|
expect(channel.error_reason.message).to match(/Channel denied access/)
|
@@ -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,86 +711,93 @@ 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
|
721
|
+
|
722
|
+
client.auth.authorize(nil)
|
723
|
+
|
724
|
+
publisher_channel.publish('bar')
|
416
725
|
end
|
417
726
|
end
|
418
727
|
end
|
419
728
|
end
|
420
729
|
end
|
730
|
+
end
|
731
|
+
end
|
421
732
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
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
|
733
|
+
context '#authorize_async' do
|
734
|
+
it 'returns a token synchronously' do
|
735
|
+
auth.authorize_sync(ttl: custom_ttl, client_id: custom_client_id).tap do |token_details|
|
736
|
+
expect(auth.authorize_sync).to be_a(Ably::Models::TokenDetails)
|
737
|
+
expect(token_details.expires.to_i).to be_within(3).of(Time.now.to_i + custom_ttl)
|
738
|
+
expect(token_details.client_id).to eql(custom_client_id)
|
739
|
+
stop_reactor
|
436
740
|
end
|
741
|
+
end
|
742
|
+
end
|
743
|
+
end
|
437
744
|
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
raise "Should not reconnect following auth force: true"
|
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
|
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
|
453
750
|
|
454
|
-
|
455
|
-
|
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 }) }
|
456
757
|
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
client.connection.__incoming_protocol_msgbus__.publish :protocol_message, protocol_message
|
471
|
-
end
|
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
|
472
771
|
end
|
473
772
|
end
|
474
773
|
end
|
475
774
|
end
|
476
775
|
|
477
|
-
context '
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
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
|
484
801
|
end
|
485
802
|
end
|
486
803
|
end
|
@@ -696,5 +1013,47 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
696
1013
|
end
|
697
1014
|
end
|
698
1015
|
end
|
1016
|
+
|
1017
|
+
context 'deprecated #authorise' do
|
1018
|
+
let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, use_token_auth: true) }
|
1019
|
+
let(:custom_logger) do
|
1020
|
+
Class.new do
|
1021
|
+
def initialize
|
1022
|
+
@messages = []
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
[:fatal, :error, :warn, :info, :debug].each do |severity|
|
1026
|
+
define_method severity do |message|
|
1027
|
+
@messages << [severity, message]
|
1028
|
+
end
|
1029
|
+
end
|
1030
|
+
|
1031
|
+
def logs
|
1032
|
+
@messages
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
def level
|
1036
|
+
1
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
def level=(new_level)
|
1040
|
+
end
|
1041
|
+
end
|
1042
|
+
end
|
1043
|
+
let(:custom_logger_object) { custom_logger.new }
|
1044
|
+
|
1045
|
+
it 'logs a deprecation warning (#RSA10l)' do
|
1046
|
+
client.auth.authorise
|
1047
|
+
expect(custom_logger_object.logs.find { |severity, message| message.match(/authorise.*deprecated/i)} ).to_not be_nil
|
1048
|
+
stop_reactor
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
it 'returns a valid token (#RSA10l)' do
|
1052
|
+
client.auth.authorise do |response|
|
1053
|
+
expect(response).to be_a(Ably::Models::TokenDetails)
|
1054
|
+
stop_reactor
|
1055
|
+
end
|
1056
|
+
end
|
1057
|
+
end
|
699
1058
|
end
|
700
1059
|
end
|