ably 0.8.5 → 0.8.6
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/.travis.yml +1 -1
- data/CHANGELOG.md +42 -48
- data/SPEC.md +1099 -640
- data/ably.gemspec +10 -4
- data/lib/ably/auth.rb +155 -47
- data/lib/ably/exceptions.rb +2 -0
- data/lib/ably/models/channel_state_change.rb +2 -3
- data/lib/ably/models/connection_details.rb +54 -0
- data/lib/ably/models/protocol_message.rb +14 -4
- data/lib/ably/models/token_details.rb +13 -7
- data/lib/ably/models/token_request.rb +1 -2
- data/lib/ably/modules/ably.rb +3 -2
- data/lib/ably/modules/message_emitter.rb +1 -3
- data/lib/ably/modules/state_emitter.rb +2 -2
- data/lib/ably/realtime/auth.rb +6 -0
- data/lib/ably/realtime/channel/channel_manager.rb +2 -0
- data/lib/ably/realtime/channel.rb +15 -4
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +11 -1
- data/lib/ably/realtime/client.rb +10 -3
- data/lib/ably/realtime/connection/connection_manager.rb +58 -54
- data/lib/ably/realtime/connection.rb +62 -6
- data/lib/ably/realtime/presence.rb +18 -5
- data/lib/ably/rest/channel.rb +9 -1
- data/lib/ably/rest/client.rb +32 -14
- data/lib/ably/rest/presence.rb +1 -1
- data/lib/ably/version.rb +1 -1
- data/lib/ably.rb +2 -0
- data/spec/acceptance/realtime/auth_spec.rb +251 -11
- data/spec/acceptance/realtime/channel_history_spec.rb +12 -2
- data/spec/acceptance/realtime/channel_spec.rb +316 -24
- data/spec/acceptance/realtime/client_spec.rb +93 -1
- data/spec/acceptance/realtime/connection_failures_spec.rb +177 -86
- data/spec/acceptance/realtime/connection_spec.rb +284 -60
- data/spec/acceptance/realtime/message_spec.rb +45 -6
- data/spec/acceptance/realtime/presence_history_spec.rb +4 -0
- data/spec/acceptance/realtime/presence_spec.rb +181 -49
- data/spec/acceptance/realtime/time_spec.rb +13 -0
- data/spec/acceptance/rest/auth_spec.rb +222 -4
- data/spec/acceptance/rest/channel_spec.rb +132 -1
- data/spec/acceptance/rest/client_spec.rb +129 -28
- data/spec/acceptance/rest/presence_spec.rb +7 -7
- data/spec/acceptance/rest/time_spec.rb +10 -0
- data/spec/shared/client_initializer_behaviour.rb +41 -17
- data/spec/spec_helper.rb +1 -0
- data/spec/support/debug_failure_helper.rb +16 -0
- data/spec/unit/models/connection_details_spec.rb +60 -0
- data/spec/unit/models/protocol_message_spec.rb +45 -0
- data/spec/unit/modules/event_emitter_spec.rb +3 -1
- data/spec/unit/realtime/channel_spec.rb +6 -5
- data/spec/unit/realtime/client_spec.rb +5 -1
- data/spec/unit/realtime/connection_spec.rb +5 -1
- data/spec/unit/realtime/realtime_spec.rb +5 -1
- metadata +54 -7
data/ably.gemspec
CHANGED
@@ -8,10 +8,10 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Ably::VERSION
|
9
9
|
spec.authors = ['Lewis Marshall', "Matthew O'Riordan"]
|
10
10
|
spec.email = ['lewis@lmars.net', 'matt@ably.io']
|
11
|
-
spec.description = %q{A Ruby client library for ably.io
|
12
|
-
spec.summary = %q{A Ruby client library for ably.io
|
11
|
+
spec.description = %q{A Ruby client library for ably.io realtime messaging}
|
12
|
+
spec.summary = %q{A Ruby client library for ably.io realtime messaging implemented using EventMachine}
|
13
13
|
spec.homepage = 'http://github.com/ably/ably-ruby'
|
14
|
-
spec.license = '
|
14
|
+
spec.license = 'Apache 2'
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
@@ -25,14 +25,20 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.add_runtime_dependency 'json'
|
26
26
|
spec.add_runtime_dependency 'websocket-driver', '~> 0.3'
|
27
27
|
spec.add_runtime_dependency 'msgpack', '>= 0.6.2'
|
28
|
+
spec.add_runtime_dependency 'addressable', '>= 2.0.0'
|
28
29
|
|
29
30
|
spec.add_development_dependency 'bundler', '~> 1.3'
|
30
31
|
spec.add_development_dependency 'rake'
|
31
32
|
spec.add_development_dependency 'redcarpet'
|
32
|
-
spec.add_development_dependency 'rspec', '~> 3.
|
33
|
+
spec.add_development_dependency 'rspec', '~> 3.2.0' # version lock, see config.around(:example, :event_machine) in event_machine_helper.rb
|
33
34
|
spec.add_development_dependency 'rspec-retry'
|
34
35
|
spec.add_development_dependency 'yard'
|
35
36
|
spec.add_development_dependency 'webmock'
|
36
37
|
|
37
38
|
spec.add_development_dependency 'coveralls'
|
39
|
+
|
40
|
+
if RUBY_VERSION.match(/^2/)
|
41
|
+
spec.add_development_dependency 'pry'
|
42
|
+
spec.add_development_dependency 'pry-byebug'
|
43
|
+
end
|
38
44
|
end
|
data/lib/ably/auth.rb
CHANGED
@@ -13,8 +13,6 @@ module Ably
|
|
13
13
|
# @return [String] The provided client ID, used for identifying this client for presence purposes
|
14
14
|
# @!attribute [r] current_token_details
|
15
15
|
# @return [Ably::Models::TokenDetails] Current {Ably::Models::TokenDetails} issued by this library or one of the provided callbacks used to authenticate requests
|
16
|
-
# @!attribute [r] token
|
17
|
-
# @return [String] Token string provided to the {Ably::Client} constructor that is used to authenticate all requests
|
18
16
|
# @!attribute [r] key
|
19
17
|
# @return [String] Complete API key containing both the key name and key secret, if present
|
20
18
|
# @!attribute [r] key_name
|
@@ -61,6 +59,7 @@ module Ably
|
|
61
59
|
@client = client
|
62
60
|
@options = auth_options.dup
|
63
61
|
@token_params = token_params.dup
|
62
|
+
@token_option = options[:token] || options[:token_details]
|
64
63
|
|
65
64
|
@options.delete :force # Forcing token auth for every request is not a valid default
|
66
65
|
|
@@ -74,11 +73,25 @@ module Ably
|
|
74
73
|
raise ArgumentError, 'key is missing. Either an API key, token, or token auth method must be provided'
|
75
74
|
end
|
76
75
|
|
77
|
-
if
|
78
|
-
raise ArgumentError, '
|
76
|
+
if options[:client_id] == '*'
|
77
|
+
raise ArgumentError, 'A client cannot be configured with a wildcard client_id'
|
78
|
+
end
|
79
|
+
|
80
|
+
if has_client_id? && !token_creatable_externally? && !token_option
|
81
|
+
raise ArgumentError, 'client_id cannot be provided without a complete API key or means to authenticate. An API key is needed to automatically authenticate with Ably and obtain a token' unless api_key_present?
|
79
82
|
ensure_utf_8 :client_id, client_id
|
80
83
|
end
|
81
84
|
|
85
|
+
# If a token details object or token string is provided in the initializer
|
86
|
+
# then the client can be authorised immediately using this token
|
87
|
+
if token_option
|
88
|
+
token_details = convert_to_token_details(token_option)
|
89
|
+
if token_details
|
90
|
+
token_details = authorise_with_token(token_details)
|
91
|
+
logger.debug "Auth: new token passed in to the initializer: #{token_details}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
82
95
|
@options.freeze
|
83
96
|
@token_params.freeze
|
84
97
|
end
|
@@ -120,7 +133,9 @@ module Ably
|
|
120
133
|
token_params = (auth_options.delete(:token_params) || {}).merge(token_params)
|
121
134
|
@token_params = @token_params.merge(token_params) # update defaults
|
122
135
|
|
123
|
-
|
136
|
+
authorise_with_token(request_token(token_params, auth_options)).tap do |new_token_details|
|
137
|
+
logger.debug "Auth: new token following authorisation: #{new_token_details}"
|
138
|
+
end
|
124
139
|
end
|
125
140
|
|
126
141
|
# Request a {Ably::Models::TokenDetails} which can be used to make authenticated token based requests
|
@@ -154,34 +169,29 @@ module Ably
|
|
154
169
|
def request_token(token_params = {}, auth_options = {})
|
155
170
|
ensure_valid_auth_attributes auth_options
|
156
171
|
|
157
|
-
|
158
|
-
token_params
|
172
|
+
# Token param precedence (lowest to highest):
|
173
|
+
# Auth default => client_id => auth_options[:token_params] arg => token_params arg
|
174
|
+
token_params = self.token_params.merge(
|
175
|
+
(client_id ? { client_id: client_id } : {}).
|
176
|
+
merge(auth_options[:token_params] || {}).
|
177
|
+
merge(token_params)
|
178
|
+
)
|
179
|
+
|
159
180
|
auth_options = self.options.merge(auth_options)
|
160
181
|
|
161
182
|
token_request = if auth_callback = auth_options.delete(:auth_callback)
|
162
183
|
auth_callback.call(token_params)
|
163
184
|
elsif auth_url = auth_options.delete(:auth_url)
|
164
|
-
token_request_from_auth_url(auth_url, auth_options)
|
185
|
+
token_request_from_auth_url(auth_url, auth_options, token_params)
|
165
186
|
else
|
166
187
|
create_token_request(token_params, auth_options)
|
167
188
|
end
|
168
189
|
|
169
|
-
|
170
|
-
|
171
|
-
return token_request
|
172
|
-
when Hash
|
173
|
-
return Ably::Models::TokenDetails.new(token_request) if IdiomaticRubyWrapper(token_request).has_key?(:issued)
|
174
|
-
when String
|
175
|
-
return Ably::Models::TokenDetails.new(token: token_request)
|
190
|
+
convert_to_token_details(token_request).tap do |token_details|
|
191
|
+
return token_details if token_details
|
176
192
|
end
|
177
193
|
|
178
|
-
|
179
|
-
|
180
|
-
response = client.post("/keys/#{token_request.key_name}/requestToken",
|
181
|
-
token_request.hash, send_auth_header: false,
|
182
|
-
disable_automatic_reauthorise: true)
|
183
|
-
|
184
|
-
Ably::Models::TokenDetails.new(response.body)
|
194
|
+
send_token_request(token_request)
|
185
195
|
end
|
186
196
|
|
187
197
|
# Creates and signs a token request that can then subsequently be used by any client to request a token
|
@@ -277,21 +287,23 @@ module Ably
|
|
277
287
|
# True when Token Auth is being used to authenticate with Ably
|
278
288
|
def using_token_auth?
|
279
289
|
return options[:use_token_auth] if options.has_key?(:use_token_auth)
|
280
|
-
!!(
|
290
|
+
!!(token_option || current_token_details || has_client_id? || token_creatable_externally?)
|
281
291
|
end
|
282
292
|
|
283
293
|
def client_id
|
284
|
-
options[:client_id]
|
294
|
+
@client_id || options[:client_id]
|
285
295
|
end
|
286
296
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
297
|
+
# When a client has authenticated with Ably and the client is either anonymous (cannot assume a +client_id+)
|
298
|
+
# or has an assigned +client_id+ (implicit in all operations), then this client has a validated +client_id+, even
|
299
|
+
# if that client_id is +nil+ (anonymous)
|
300
|
+
#
|
301
|
+
# Once validated by Ably, the client library will enforce the use of the +client_id+ identity provided by Ably, rejecting
|
302
|
+
# messages with an invalid +client_id+ immediately
|
303
|
+
#
|
304
|
+
# @return [Boolean]
|
305
|
+
def client_id_validated?
|
306
|
+
!!@client_id_validated
|
295
307
|
end
|
296
308
|
|
297
309
|
# Auth header string used in HTTP requests to Ably
|
@@ -327,7 +339,7 @@ module Ably
|
|
327
339
|
#
|
328
340
|
# @return [Boolean]
|
329
341
|
def token_renewable?
|
330
|
-
token_creatable_externally? || (api_key_present? && !
|
342
|
+
token_creatable_externally? || (api_key_present? && !token_option)
|
331
343
|
end
|
332
344
|
|
333
345
|
# Returns false when attempting to send an API Key over a non-secure connection
|
@@ -338,8 +350,59 @@ module Ably
|
|
338
350
|
client.use_tls? || using_token_auth?
|
339
351
|
end
|
340
352
|
|
353
|
+
# True if token provided client_id is compatible with the client's configured +client_id+, when applicable
|
354
|
+
#
|
355
|
+
# @return [Boolean]
|
356
|
+
# @api private
|
357
|
+
def token_client_id_allowed?(token_client_id)
|
358
|
+
return true if client_id.nil? # no explicit client_id specified for this client
|
359
|
+
return true if client_id == '*' || token_client_id == '*' # wildcard supported always
|
360
|
+
token_client_id == client_id
|
361
|
+
end
|
362
|
+
|
363
|
+
# True if assumed_client_id is compatible with the client's configured or Ably assigned +client_id+
|
364
|
+
#
|
365
|
+
# @return [Boolean]
|
366
|
+
# @api private
|
367
|
+
def can_assume_client_id?(assumed_client_id)
|
368
|
+
if client_id_validated?
|
369
|
+
client_id == '*' || (client_id == assumed_client_id)
|
370
|
+
elsif !options[:client_id] || options[:client_id] == '*'
|
371
|
+
true # client ID is unknown
|
372
|
+
else
|
373
|
+
options[:client_id] == assumed_client_id
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
# Configures the client ID for this client
|
378
|
+
# Typically this occurs following an Auth or receiving a {Ably::Models::ProtocolMessage} with a +client_id+ in the {Ably::Models::ConnectionDetails}
|
379
|
+
#
|
380
|
+
# @api private
|
381
|
+
def configure_client_id(new_client_id)
|
382
|
+
# If new client ID from Ably is a wildcard, but preconfigured clientId is set, then keep the existing clientId
|
383
|
+
if has_client_id? && new_client_id == '*'
|
384
|
+
@client_id_validated = true
|
385
|
+
return
|
386
|
+
end
|
387
|
+
|
388
|
+
# If client_id is defined and not a wildcard, prevent it changing, this is not supported
|
389
|
+
if client_id && client_id != '*' && new_client_id != client_id
|
390
|
+
raise Ably::Exceptions::IncompatibleClientId.new("Client ID is immutable once configured for a client. Client ID cannot be changed to '#{new_client_id}'", 400, 40012)
|
391
|
+
end
|
392
|
+
@client_id_validated = true
|
393
|
+
@client_id = new_client_id
|
394
|
+
end
|
395
|
+
|
396
|
+
# True when a client_id other than a wildcard is configured for Auth
|
397
|
+
#
|
398
|
+
# @api private
|
399
|
+
def has_client_id?
|
400
|
+
client_id && (client_id != '*')
|
401
|
+
end
|
402
|
+
|
341
403
|
private
|
342
404
|
attr_reader :client
|
405
|
+
attr_reader :token_option
|
343
406
|
|
344
407
|
def ensure_valid_auth_attributes(attributes)
|
345
408
|
if attributes[:timestamp]
|
@@ -401,17 +464,21 @@ module Ably
|
|
401
464
|
|
402
465
|
# Returns the current token if it exists or authorises and retrieves a token
|
403
466
|
def token_auth_string
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
token
|
467
|
+
if !current_token_details && token_option
|
468
|
+
# A TokenRequest was configured in the ClientOptions +:token field+ and no current token exists
|
469
|
+
# Note: If a Token or TokenDetails is provided in the initializer, the token is stored in +current_token_details+
|
470
|
+
authorise_with_token send_token_request(token_option)
|
471
|
+
current_token_details.token
|
410
472
|
else
|
473
|
+
# Authorise will use the current token if one exists and is not expired, otherwise a new token will be issued
|
411
474
|
authorise.token
|
412
475
|
end
|
413
476
|
end
|
414
477
|
|
478
|
+
def configure_current_token_details(token_details)
|
479
|
+
@current_token_details = token_details
|
480
|
+
end
|
481
|
+
|
415
482
|
# Token Auth HTTP Authorization header value
|
416
483
|
def token_auth_header
|
417
484
|
"Bearer #{encode64(token_auth_string)}"
|
@@ -455,15 +522,20 @@ module Ably
|
|
455
522
|
# Retrieve a token request from a specified URL, expects a JSON response
|
456
523
|
#
|
457
524
|
# @return [Hash]
|
458
|
-
def token_request_from_auth_url(auth_url, auth_options)
|
525
|
+
def token_request_from_auth_url(auth_url, auth_options, token_params)
|
459
526
|
uri = URI.parse(auth_url)
|
460
527
|
connection = Faraday.new("#{uri.scheme}://#{uri.host}", connection_options)
|
461
|
-
method = auth_options[:auth_method] || :get
|
528
|
+
method = auth_options[:auth_method] || options[:auth_method] || :get
|
529
|
+
params = (auth_options[:auth_params] || options[:auth_method] || {}).merge(token_params)
|
462
530
|
|
463
531
|
response = connection.send(method) do |request|
|
464
532
|
request.url uri.path
|
465
|
-
request.params = CGI.parse(uri.query || '').merge(auth_options[:auth_params] || {})
|
466
533
|
request.headers = auth_options[:auth_headers] || {}
|
534
|
+
if method.to_s.downcase == 'post'
|
535
|
+
request.body = params
|
536
|
+
else
|
537
|
+
request.params = (Addressable::URI.parse(uri.to_s).query_values || {}).merge(params)
|
538
|
+
end
|
467
539
|
end
|
468
540
|
|
469
541
|
if !response.body.kind_of?(Hash) && !response.headers['Content-Type'].to_s.match(%r{text/plain}i)
|
@@ -474,6 +546,42 @@ module Ably
|
|
474
546
|
response.body
|
475
547
|
end
|
476
548
|
|
549
|
+
# Use the provided token to authenticate immediately and store the token details in +current_token_details+
|
550
|
+
def authorise_with_token(new_token_details)
|
551
|
+
if new_token_details && !new_token_details.from_token_string?
|
552
|
+
if !token_client_id_allowed?(new_token_details.client_id)
|
553
|
+
raise Ably::Exceptions::IncompatibleClientId.new("Client ID '#{new_token_details.client_id}' in the token is incompatible with the current client ID '#{client_id}'", 400, 40012)
|
554
|
+
end
|
555
|
+
configure_client_id new_token_details.client_id
|
556
|
+
end
|
557
|
+
configure_current_token_details new_token_details
|
558
|
+
end
|
559
|
+
|
560
|
+
# Returns a TokenDetails object if the provided token_details_obj argument is a TokenDetails object, Token String
|
561
|
+
# or TokenDetails JSON object.
|
562
|
+
# If the token_details_obj is not a Token or TokenDetails +nil+ is returned
|
563
|
+
def convert_to_token_details(token_details_obj)
|
564
|
+
case token_details_obj
|
565
|
+
when Ably::Models::TokenDetails
|
566
|
+
return token_details_obj
|
567
|
+
when Hash
|
568
|
+
return Ably::Models::TokenDetails.new(token_details_obj) if IdiomaticRubyWrapper(token_details_obj).has_key?(:issued)
|
569
|
+
when String
|
570
|
+
return Ably::Models::TokenDetails.new(token: token_details_obj)
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
# @return [Ably::Models::TokenDetails]
|
575
|
+
def send_token_request(token_request)
|
576
|
+
token_request = Ably::Models::TokenRequest(token_request)
|
577
|
+
|
578
|
+
response = client.post("/keys/#{token_request.key_name}/requestToken",
|
579
|
+
token_request.hash, send_auth_header: false,
|
580
|
+
disable_automatic_reauthorise: true)
|
581
|
+
|
582
|
+
Ably::Models::TokenDetails.new(response.body)
|
583
|
+
end
|
584
|
+
|
477
585
|
# Return a Hash of connection options to initiate the Faraday::Connection with
|
478
586
|
#
|
479
587
|
# @return [Hash]
|
@@ -501,7 +609,7 @@ module Ably
|
|
501
609
|
# Raise exceptions if response code is invalid
|
502
610
|
builder.use Ably::Rest::Middleware::ExternalExceptions
|
503
611
|
|
504
|
-
setup_incoming_middleware builder,
|
612
|
+
setup_incoming_middleware builder, logger
|
505
613
|
|
506
614
|
# Set Faraday's HTTP adapter
|
507
615
|
builder.adapter Faraday.default_adapter
|
@@ -520,12 +628,12 @@ module Ably
|
|
520
628
|
auth_callback_present? || token_url_present?
|
521
629
|
end
|
522
630
|
|
523
|
-
def has_client_id?
|
524
|
-
!!client_id
|
525
|
-
end
|
526
|
-
|
527
631
|
def api_key_present?
|
528
632
|
key_name && key_secret
|
529
633
|
end
|
634
|
+
|
635
|
+
def logger
|
636
|
+
client.logger
|
637
|
+
end
|
530
638
|
end
|
531
639
|
end
|
data/lib/ably/exceptions.rb
CHANGED
@@ -3,9 +3,9 @@ module Ably::Models
|
|
3
3
|
# when a state change occurs
|
4
4
|
#
|
5
5
|
# @!attribute [r] current
|
6
|
-
# @return [Connection::STATE] Current
|
6
|
+
# @return [Connection::STATE] Current channel state
|
7
7
|
# @!attribute [r] previous
|
8
|
-
# @return [Connection::STATE] Previous
|
8
|
+
# @return [Connection::STATE] Previous channel state
|
9
9
|
# @!attribute [r] reason
|
10
10
|
# @return [Ably::Models::ErrorInfo] Object describing the reason for a state change when not initiated by the consumer of the client library
|
11
11
|
#
|
@@ -20,7 +20,6 @@ module Ably::Models
|
|
20
20
|
@hash_object = {
|
21
21
|
current: hash_object.fetch(:current),
|
22
22
|
previous: hash_object.fetch(:previous),
|
23
|
-
retry_in: hash_object[:retry_in],
|
24
23
|
reason: hash_object[:reason],
|
25
24
|
protocol_message: hash_object[:protocol_message]
|
26
25
|
}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Ably::Models
|
2
|
+
# Convert connection details attributes to a {ConnectionDetails} object
|
3
|
+
#
|
4
|
+
# @param attributes (see #initialize)
|
5
|
+
#
|
6
|
+
# @return [ConnectionDetails]
|
7
|
+
def self.ConnectionDetails(attributes)
|
8
|
+
case attributes
|
9
|
+
when ConnectionDetails
|
10
|
+
return attributes
|
11
|
+
else
|
12
|
+
ConnectionDetails.new(attributes || {})
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# ConnectionDetails are optionally passed to the client library in the +CONNECTED+ {Ably::Models::ProtocolMessage#connectionDetails} attribute
|
17
|
+
# to inform the client about any constraints it should adhere to and provide additional metadata about the connection.
|
18
|
+
# For example, if a request is made to publish a message that exceeds the +maxMessageSize+, the client library can reject
|
19
|
+
# the message immediately, without communicating with the Ably service
|
20
|
+
#
|
21
|
+
class ConnectionDetails
|
22
|
+
include Ably::Modules::ModelCommon
|
23
|
+
|
24
|
+
# @param attributes [Hash]
|
25
|
+
# @option attributes [String] :client_id contains the client ID assigned to the connection
|
26
|
+
# @option attributes [String] :connection_key the connection secret key string that is used to resume a connection and its state
|
27
|
+
# @option attributes [Integer] :max_message_size maximum individual message size in bytes
|
28
|
+
# @option attributes [Integer] :max_frame_size maximum size for a single frame of data sent to Ably. This restriction applies to a {Ably::Models::ProtocolMessage} sent over a realtime connection, or the total body size for a REST request
|
29
|
+
# @option attributes [Integer] :max_inbound_rate maximum allowable number of requests per second from a client
|
30
|
+
# @option attributes [Integer] :connection_state_ttl duration in seconds that Ably will persist the connection state when a Realtime client is abruptly disconnected
|
31
|
+
#
|
32
|
+
def initialize(attributes = {})
|
33
|
+
@hash_object = IdiomaticRubyWrapper(attributes.clone)
|
34
|
+
hash[:connection_state_ttl] = (hash[:connection_state_ttl].to_f / 1000).round if hash[:connection_state_ttl]
|
35
|
+
hash.freeze
|
36
|
+
end
|
37
|
+
|
38
|
+
%w(client_id connection_key max_message_size max_frame_size max_inbound_rate connection_state_ttl).each do |attribute|
|
39
|
+
define_method attribute do
|
40
|
+
hash[attribute.to_sym]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_client_id?
|
45
|
+
hash.has_key?(:client_id)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @!attribute [r] hash
|
49
|
+
# @return [Hash] Access the token details Hash object ruby'fied to use symbolized keys
|
50
|
+
def hash
|
51
|
+
@hash_object
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -10,12 +10,12 @@ module Ably::Models
|
|
10
10
|
# @return [ACTION] Protocol Message action {Ably::Modules::Enum} from list of {ACTION}. Returns nil if action is unsupported by protocol
|
11
11
|
# @!attribute [r] count
|
12
12
|
# @return [Integer] The count field is used for ACK and NACK actions. See {http://docs.ably.io/client-lib-development-guide/protocol/#message-acknowledgement message acknowledgement protocol}
|
13
|
-
# @!attribute [r]
|
13
|
+
# @!attribute [r] error
|
14
14
|
# @return [ErrorInfo] Contains error information
|
15
15
|
# @!attribute [r] channel
|
16
16
|
# @return [String] Channel name for messages
|
17
17
|
# @!attribute [r] channel_serial
|
18
|
-
# @return [String] Contains a serial number for a message on the current
|
18
|
+
# @return [String] Contains a serial number for a message on the current channelƒ
|
19
19
|
# @!attribute [r] connection_id
|
20
20
|
# @return [String] Contains a string public identifier for the connection
|
21
21
|
# @!attribute [r] connection_key
|
@@ -88,12 +88,18 @@ module Ably::Models
|
|
88
88
|
@hash_object.freeze
|
89
89
|
end
|
90
90
|
|
91
|
-
%w(id channel channel_serial connection_id
|
91
|
+
%w(id channel channel_serial connection_id).each do |attribute|
|
92
92
|
define_method attribute do
|
93
93
|
hash[attribute.to_sym]
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
+
def connection_key
|
98
|
+
# connection_key in connection details takes precedence over connection_key on the ProtocolMessage
|
99
|
+
# connection_key in the ProtocolMessage will be deprecated in future protocol versions > 0.8
|
100
|
+
connection_details.connection_key || hash[:connection_key]
|
101
|
+
end
|
102
|
+
|
97
103
|
def id!
|
98
104
|
raise RuntimeError, 'ProtocolMessage #id is nil' unless id
|
99
105
|
id
|
@@ -106,7 +112,7 @@ module Ably::Models
|
|
106
112
|
end
|
107
113
|
|
108
114
|
def error
|
109
|
-
@
|
115
|
+
@error ||= ErrorInfo.new(hash[:error]) if hash[:error]
|
110
116
|
end
|
111
117
|
|
112
118
|
def timestamp
|
@@ -181,6 +187,10 @@ module Ably::Models
|
|
181
187
|
flags & 1 == 1
|
182
188
|
end
|
183
189
|
|
190
|
+
def connection_details
|
191
|
+
@connection_details ||= Ably::Models::ConnectionDetails(hash[:connection_details])
|
192
|
+
end
|
193
|
+
|
184
194
|
# Indicates this protocol message will generate an ACK response when sent
|
185
195
|
# Examples of protocol messages required ACK include :message and :presence
|
186
196
|
def ack_required?
|
@@ -1,8 +1,7 @@
|
|
1
1
|
module Ably::Models
|
2
2
|
# Convert token details argument to a {TokenDetails} object
|
3
3
|
#
|
4
|
-
# @param attributes
|
5
|
-
# @option attributes (see TokenDetails#initialize)
|
4
|
+
# @param attributes (see #initialize)
|
6
5
|
#
|
7
6
|
# @return [TokenDetails]
|
8
7
|
def self.TokenDetails(attributes)
|
@@ -27,10 +26,6 @@ module Ably::Models
|
|
27
26
|
# For example, if buffer is 10s, the token can no longer be used for new requests 9s before it expires
|
28
27
|
TOKEN_EXPIRY_BUFFER = 15
|
29
28
|
|
30
|
-
def initialize(attributes)
|
31
|
-
@hash_object = IdiomaticRubyWrapper(attributes.clone.freeze)
|
32
|
-
end
|
33
|
-
|
34
29
|
# @param attributes
|
35
30
|
# @option attributes [String] :token token used to authenticate requests
|
36
31
|
# @option attributes [String] :key_name API key name used to create this token
|
@@ -76,7 +71,7 @@ module Ably::Models
|
|
76
71
|
# @!attribute [r] capability
|
77
72
|
# @return [Hash] Capabilities assigned to this token
|
78
73
|
def capability
|
79
|
-
JSON.parse(hash.fetch(:capability)) if hash.
|
74
|
+
JSON.parse(hash.fetch(:capability)) if hash.has_key?(:capability)
|
80
75
|
end
|
81
76
|
|
82
77
|
# @!attribute [r] client_id
|
@@ -94,10 +89,21 @@ module Ably::Models
|
|
94
89
|
expires < Time.now + TOKEN_EXPIRY_BUFFER
|
95
90
|
end
|
96
91
|
|
92
|
+
# True if the TokenDetails was created from an opaque string i.e. no metadata exists for this token
|
93
|
+
# @return [Boolean]
|
94
|
+
# @api private
|
95
|
+
def from_token_string?
|
96
|
+
hash.keys == [:token]
|
97
|
+
end
|
98
|
+
|
97
99
|
# @!attribute [r] hash
|
98
100
|
# @return [Hash] Access the token details Hash object ruby'fied to use symbolized keys
|
99
101
|
def hash
|
100
102
|
@hash_object
|
101
103
|
end
|
104
|
+
|
105
|
+
def to_s
|
106
|
+
"<TokenDetails token=#{token} client_id=#{client_id} key_name=#{key_name} issued=#{issued} expires=#{expires} capability=#{capability} expired?=#{expired?}>"
|
107
|
+
end
|
102
108
|
end
|
103
109
|
end
|
@@ -1,8 +1,7 @@
|
|
1
1
|
module Ably::Models
|
2
2
|
# Convert token request argument to a {TokenRequest} object
|
3
3
|
#
|
4
|
-
# @param attributes
|
5
|
-
# @option attributes (see TokenRequest#initialize)
|
4
|
+
# @param attributes (see #initialize)
|
6
5
|
#
|
7
6
|
# @return [TokenRequest]
|
8
7
|
def self.TokenRequest(attributes)
|
data/lib/ably/modules/ably.rb
CHANGED
@@ -7,9 +7,10 @@ module Ably
|
|
7
7
|
# Fallback hosts to use when a connection to rest/realtime.ably.io is not possible due to
|
8
8
|
# network failures either at the client, between the client and Ably, within an Ably data center, or at the IO domain registrar
|
9
9
|
#
|
10
|
-
FALLBACK_HOSTS = %w(A.ably-realtime.com B.ably-realtime.com C.ably-realtime.com D.ably-realtime.com E.ably-realtime.com)
|
10
|
+
FALLBACK_HOSTS = %w(A.ably-realtime.com B.ably-realtime.com C.ably-realtime.com D.ably-realtime.com E.ably-realtime.com).freeze
|
11
|
+
|
11
12
|
INTERNET_CHECK = {
|
12
13
|
url: '//internet-up.ably-realtime.com/is-the-internet-up.txt',
|
13
14
|
ok_text: 'yes'
|
14
|
-
}
|
15
|
+
}.freeze
|
15
16
|
end
|
@@ -50,10 +50,8 @@ module Ably::Modules
|
|
50
50
|
#
|
51
51
|
# @api private
|
52
52
|
def emit_message(name, payload)
|
53
|
-
raise 'Event name is required' unless name
|
54
|
-
|
55
53
|
message_emitter_subscriptions[:all].each { |cb| cb.call(payload) }
|
56
|
-
message_emitter_subscriptions[name].each { |cb| cb.call(payload) }
|
54
|
+
message_emitter_subscriptions[name].each { |cb| cb.call(payload) } if name
|
57
55
|
end
|
58
56
|
|
59
57
|
private
|
@@ -71,11 +71,11 @@ module Ably::Modules
|
|
71
71
|
# @yield block is called if the state is matched immediately or once when the state is reached
|
72
72
|
#
|
73
73
|
# @return [void]
|
74
|
-
def once_or_if(target_states, options = {})
|
74
|
+
def once_or_if(target_states, options = {}, &block)
|
75
75
|
raise ArgumentError, 'Block required' unless block_given?
|
76
76
|
|
77
77
|
if Array(target_states).any? { |target_state| state == target_state }
|
78
|
-
|
78
|
+
safe_yield block
|
79
79
|
else
|
80
80
|
failure_block = options.fetch(:else, nil)
|
81
81
|
failure_wrapper = nil
|
data/lib/ably/realtime/auth.rb
CHANGED
@@ -37,6 +37,8 @@ module Ably
|
|
37
37
|
include Ably::Modules::AsyncWrapper
|
38
38
|
|
39
39
|
def_delegators :auth_sync, :client_id
|
40
|
+
def_delegators :auth_sync, :token_client_id_allowed?, :configure_client_id, :client_id_validated?
|
41
|
+
def_delegators :auth_sync, :can_assume_client_id?, :has_client_id?
|
40
42
|
def_delegators :auth_sync, :current_token_details, :token
|
41
43
|
def_delegators :auth_sync, :key, :key_name, :key_secret, :options, :auth_options, :token_params
|
42
44
|
def_delegators :auth_sync, :using_basic_auth?, :using_token_auth?
|
@@ -68,6 +70,10 @@ module Ably
|
|
68
70
|
def authorise(token_params = {}, auth_options = {}, &success_callback)
|
69
71
|
async_wrap(success_callback) do
|
70
72
|
auth_sync.authorise(token_params, auth_options)
|
73
|
+
end.tap do |deferrable|
|
74
|
+
deferrable.errback do |error|
|
75
|
+
client.connection.transition_state_machine :failed, reason: error if error.kind_of?(Ably::Exceptions::IncompatibleClientId)
|
76
|
+
end
|
71
77
|
end
|
72
78
|
end
|
73
79
|
|
@@ -123,6 +123,8 @@ module Ably::Realtime
|
|
123
123
|
# It is up to Ably to ensure that duplicate messages are not retransmitted on the channel
|
124
124
|
# base on the serial numbers
|
125
125
|
#
|
126
|
+
# TODO: Move this into the Connection class, it does not belong in a Channel class
|
127
|
+
#
|
126
128
|
# @api private
|
127
129
|
def resend_pending_message_ack_queue
|
128
130
|
connection.__pending_message_ack_queue__.delete_if do |protocol_message|
|
@@ -309,9 +309,20 @@ module Ably
|
|
309
309
|
|
310
310
|
# Queue messages and process queue if channel is attached.
|
311
311
|
# If channel is not yet attached, attempt to attach it before the message queue is processed.
|
312
|
-
# @
|
312
|
+
# @return [Ably::Util::SafeDeferrable]
|
313
313
|
def queue_messages(raw_messages)
|
314
|
-
messages = Array(raw_messages).map
|
314
|
+
messages = Array(raw_messages).map do |raw_msg|
|
315
|
+
create_message(raw_msg).tap do |message|
|
316
|
+
next if message.client_id.nil?
|
317
|
+
if message.client_id == '*'
|
318
|
+
raise Ably::Exceptions::IncompatibleClientId.new('Wildcard client_id is reserved and cannot be used when publishing messages', 400, 40012)
|
319
|
+
end
|
320
|
+
unless client.auth.can_assume_client_id?(message.client_id)
|
321
|
+
raise Ably::Exceptions::IncompatibleClientId.new("Cannot publish with client_id '#{message.client_id}' as it is incompatible with the current configured client_id '#{client.client_id}'", 400, 40012)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
315
326
|
queue.push *messages
|
316
327
|
|
317
328
|
if attached?
|
@@ -340,12 +351,12 @@ module Ably
|
|
340
351
|
Ably::Util::SafeDeferrable.new(logger).tap do |deferrable|
|
341
352
|
messages.each do |message|
|
342
353
|
message.callback do
|
343
|
-
|
354
|
+
next if failed
|
344
355
|
actual_deliveries += 1
|
345
356
|
deferrable.succeed messages if actual_deliveries == expected_deliveries
|
346
357
|
end
|
347
358
|
message.errback do |error|
|
348
|
-
|
359
|
+
next if failed
|
349
360
|
failed = true
|
350
361
|
deferrable.fail error, message
|
351
362
|
end
|