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/lib/ably/rest/client.rb
CHANGED
@@ -21,12 +21,12 @@ module Ably
|
|
21
21
|
# Default Ably domain for REST
|
22
22
|
DOMAIN = 'rest.ably.io'
|
23
23
|
|
24
|
-
# Configuration for
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
# Configuration for HTTP timeouts and HTTP request reattempts to fallback hosts
|
25
|
+
HTTP_DEFAULTS = {
|
26
|
+
open_timeout: 4,
|
27
|
+
request_timeout: 15,
|
28
|
+
max_retry_duration: 10,
|
29
|
+
max_retry_count: 3
|
30
30
|
}.freeze
|
31
31
|
|
32
32
|
def_delegators :auth, :client_id, :auth_options
|
@@ -51,18 +51,23 @@ module Ably
|
|
51
51
|
# @return [Logger::Severity]
|
52
52
|
attr_reader :log_level
|
53
53
|
|
54
|
-
# The custom host that is being used if it was provided with the option
|
54
|
+
# The custom host that is being used if it was provided with the option +:rest_host+ when the {Client} was created
|
55
55
|
# @return [String,Nil]
|
56
56
|
attr_reader :custom_host
|
57
57
|
|
58
|
-
# The custom port for non-TLS requests if it was provided with the option
|
58
|
+
# The custom port for non-TLS requests if it was provided with the option +:port+ when the {Client} was created
|
59
59
|
# @return [Integer,Nil]
|
60
60
|
attr_reader :custom_port
|
61
61
|
|
62
|
-
# The custom TLS port for TLS requests if it was provided with the option
|
62
|
+
# The custom TLS port for TLS requests if it was provided with the option +:tls_port+ when the {Client} was created
|
63
63
|
# @return [Integer,Nil]
|
64
64
|
attr_reader :custom_tls_port
|
65
65
|
|
66
|
+
# The immutable configured HTTP defaults for this client.
|
67
|
+
# See {#initialize} for the configurable HTTP defaults prefixed with +http_+
|
68
|
+
# @return [Hash]
|
69
|
+
attr_reader :http_defaults
|
70
|
+
|
66
71
|
# The registered encoders that are used to encode and decode message payloads
|
67
72
|
# @return [Array<Ably::Models::MessageEncoder::Base>]
|
68
73
|
# @api private
|
@@ -96,6 +101,11 @@ module Ably
|
|
96
101
|
# @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
|
97
102
|
# @option options [Hash] :token_params convenience to pass in +token_params+ that will be used as a default for all token requests. See {Auth#create_token_request}
|
98
103
|
#
|
104
|
+
# @option options [Integer] :http_open_timeout (4 seconds) timeout in seconds for opening an HTTP connection for all HTTP requests
|
105
|
+
# @option options [Integer] :http_request_timeout (15 seconds) timeout in seconds for any single complete HTTP request and response
|
106
|
+
# @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
|
107
|
+
# @option options [Integer] :http_max_retry_duration (10 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
|
108
|
+
#
|
99
109
|
# @return [Ably::Rest::Client]
|
100
110
|
#
|
101
111
|
# @example
|
@@ -127,6 +137,14 @@ module Ably
|
|
127
137
|
@custom_port = options.delete(:port)
|
128
138
|
@custom_tls_port = options.delete(:tls_port)
|
129
139
|
|
140
|
+
@http_defaults = HTTP_DEFAULTS.dup
|
141
|
+
options.each do |key, val|
|
142
|
+
if http_key = key[/^http_(.+)/, 1]
|
143
|
+
@http_defaults[http_key.to_sym] = val if val && @http_defaults.has_key?(http_key.to_sym)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
@http_defaults.freeze
|
147
|
+
|
130
148
|
if @log_level == :none
|
131
149
|
@custom_logger = Ably::Models::NilLogger.new
|
132
150
|
else
|
@@ -322,8 +340,8 @@ module Ably
|
|
322
340
|
# Sends HTTP request to connection end point
|
323
341
|
# Connection failures will automatically be reattempted until thresholds are met
|
324
342
|
def send_request(method, path, params, options)
|
325
|
-
|
326
|
-
|
343
|
+
max_retry_count = http_defaults.fetch(:max_retry_count)
|
344
|
+
max_retry_duration = http_defaults.fetch(:max_retry_duration)
|
327
345
|
requested_at = Time.now
|
328
346
|
retry_count = 0
|
329
347
|
|
@@ -338,7 +356,7 @@ module Ably
|
|
338
356
|
|
339
357
|
rescue Faraday::TimeoutError, Faraday::ClientError, Ably::Exceptions::ServerError => error
|
340
358
|
time_passed = Time.now - requested_at
|
341
|
-
if can_fallback_to_alternate_ably_host? && retry_count <
|
359
|
+
if can_fallback_to_alternate_ably_host? && retry_count < max_retry_count && time_passed <= max_retry_duration
|
342
360
|
retry_count += 1
|
343
361
|
retry
|
344
362
|
end
|
@@ -395,8 +413,8 @@ module Ably
|
|
395
413
|
user_agent: user_agent
|
396
414
|
},
|
397
415
|
request: {
|
398
|
-
open_timeout:
|
399
|
-
timeout:
|
416
|
+
open_timeout: http_defaults.fetch(:open_timeout),
|
417
|
+
timeout: http_defaults.fetch(:request_timeout)
|
400
418
|
}
|
401
419
|
}
|
402
420
|
end
|
data/lib/ably/rest/presence.rb
CHANGED
data/lib/ably/version.rb
CHANGED
data/lib/ably.rb
CHANGED
@@ -70,15 +70,6 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
context '#token' do
|
74
|
-
let(:client_options) { default_options.merge(token: random_str) }
|
75
|
-
|
76
|
-
it 'contains the current token after auth' do
|
77
|
-
expect(auth.token).to_not be_nil
|
78
|
-
stop_reactor
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
73
|
context '#current_token_details' do
|
83
74
|
it 'contains the current token after auth' do
|
84
75
|
expect(auth.current_token_details).to be_nil
|
@@ -99,6 +90,7 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
99
90
|
context '#options (auth_options)' do
|
100
91
|
let(:auth_url) { "https://echo.ably.io/?type=text" }
|
101
92
|
let(:auth_params) { { :body => random_str } }
|
93
|
+
let(:client_options) { default_options.merge(auto_connect: false) }
|
102
94
|
|
103
95
|
it 'contains the configured auth options' do
|
104
96
|
auth.authorise({}, auth_url: auth_url, auth_params: auth_params) do
|
@@ -193,6 +185,75 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
193
185
|
stop_reactor
|
194
186
|
end
|
195
187
|
end
|
188
|
+
|
189
|
+
context 'when implicitly called, with an explicit ClientOptions client_id' do
|
190
|
+
let(:client_id) { random_str }
|
191
|
+
let(:client_options) { default_options.merge(auth_callback: Proc.new { auth_token_object }, client_id: client_id, log_level: :none) }
|
192
|
+
let(:rest_auth_client) { Ably::Rest::Client.new(default_options.merge(key: api_key, client_id: 'invalid')) }
|
193
|
+
|
194
|
+
context 'and an incompatible client_id in a TokenDetails object passed to the auth callback' do
|
195
|
+
let(:auth_token_object) { rest_auth_client.auth.request_token }
|
196
|
+
|
197
|
+
it 'rejects a TokenDetails object with an incompatible client_id and raises an exception' do
|
198
|
+
client.connect
|
199
|
+
client.connection.on(:error) do |error|
|
200
|
+
expect(error).to be_a(Ably::Exceptions::IncompatibleClientId)
|
201
|
+
EventMachine.add_timer(0.1) do
|
202
|
+
expect(client.connection).to be_failed
|
203
|
+
stop_reactor
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
context 'and an incompatible client_id in a TokenRequest object passed to the auth callback and raises an exception' do
|
210
|
+
let(:auth_token_object) { rest_auth_client.auth.create_token_request }
|
211
|
+
|
212
|
+
it 'rejects a TokenRequests object with an incompatible client_id and raises an exception' do
|
213
|
+
client.connect
|
214
|
+
client.connection.on(:error) do |error|
|
215
|
+
expect(error).to be_a(Ably::Exceptions::IncompatibleClientId)
|
216
|
+
EventMachine.add_timer(0.1) do
|
217
|
+
expect(client.connection).to be_failed
|
218
|
+
stop_reactor
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
context 'when explicitly called, with an explicit ClientOptions client_id' do
|
226
|
+
let(:auth_proc) do
|
227
|
+
Proc.new do
|
228
|
+
if !@requested
|
229
|
+
@requested = true
|
230
|
+
valid_auth_token
|
231
|
+
else
|
232
|
+
invalid_auth_token
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
let(:client_id) { random_str }
|
238
|
+
let(:client_options) { default_options.merge(auth_callback: auth_proc, client_id: client_id, log_level: :none) }
|
239
|
+
let(:valid_auth_token) { Ably::Rest::Client.new(default_options.merge(key: api_key, client_id: client_id)).auth.request_token }
|
240
|
+
let(:invalid_auth_token) { Ably::Rest::Client.new(default_options.merge(key: api_key, client_id: 'invalid')).auth.request_token }
|
241
|
+
|
242
|
+
context 'and an incompatible client_id in a TokenDetails object passed to the auth callback' do
|
243
|
+
it 'rejects a TokenDetails object with an incompatible client_id and raises an exception' do
|
244
|
+
client.connection.once(:connected) do
|
245
|
+
client.auth.authorise({}, force: true)
|
246
|
+
client.connection.on(:error) do |error|
|
247
|
+
expect(error).to be_a(Ably::Exceptions::IncompatibleClientId)
|
248
|
+
EventMachine.add_timer(0.1) do
|
249
|
+
expect(client.connection).to be_failed
|
250
|
+
stop_reactor
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
196
257
|
end
|
197
258
|
|
198
259
|
context '#authorise_async' do
|
@@ -216,7 +277,7 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
216
277
|
end
|
217
278
|
end
|
218
279
|
|
219
|
-
context '#
|
280
|
+
context '#auth_params_sync' do
|
220
281
|
it 'returns the auth params synchronously' do
|
221
282
|
expect(auth.auth_params_sync).to be_a(Hash)
|
222
283
|
stop_reactor
|
@@ -232,11 +293,190 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
232
293
|
end
|
233
294
|
end
|
234
295
|
|
235
|
-
context '#
|
296
|
+
context '#auth_header_sync' do
|
236
297
|
it 'returns an auth header synchronously' do
|
237
298
|
expect(auth.auth_header_sync).to be_a(String)
|
238
299
|
stop_reactor
|
239
300
|
end
|
240
301
|
end
|
302
|
+
|
303
|
+
describe '#client_id_validated?' do
|
304
|
+
let(:auth) { Ably::Rest::Client.new(default_options.merge(key: api_key)).auth }
|
305
|
+
|
306
|
+
context 'when using basic auth' do
|
307
|
+
let(:client_options) { default_options.merge(key: api_key) }
|
308
|
+
|
309
|
+
context 'before connected' do
|
310
|
+
it 'is false as basic auth users do not have an identity' do
|
311
|
+
expect(client.auth).to_not be_client_id_validated
|
312
|
+
stop_reactor
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
context 'once connected' do
|
317
|
+
it 'is true' do
|
318
|
+
client.connection.once(:connected) do
|
319
|
+
expect(client.auth).to be_client_id_validated
|
320
|
+
stop_reactor
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
it 'contains a validated wildcard client_id' do
|
325
|
+
client.connection.once(:connected) do
|
326
|
+
expect(client.auth.client_id).to eql('*')
|
327
|
+
stop_reactor
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
context 'when using a token string' do
|
334
|
+
context 'with a valid client_id' do
|
335
|
+
let(:client_options) { default_options.merge(token: auth.request_token(client_id: 'present').token) }
|
336
|
+
|
337
|
+
context 'before connected' do
|
338
|
+
it 'is false as identification is not possible from an opaque token string' do
|
339
|
+
expect(client.auth).to_not be_client_id_validated
|
340
|
+
stop_reactor
|
341
|
+
end
|
342
|
+
|
343
|
+
specify '#client_id is nil' do
|
344
|
+
expect(client.auth.client_id).to be_nil
|
345
|
+
stop_reactor
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
context 'once connected' do
|
350
|
+
it 'is true' do
|
351
|
+
client.connection.once(:connected) do
|
352
|
+
expect(client.auth).to be_client_id_validated
|
353
|
+
stop_reactor
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
specify '#client_id is populated' do
|
358
|
+
client.connection.once(:connected) do
|
359
|
+
expect(client.auth.client_id).to eql('present')
|
360
|
+
stop_reactor
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
context 'with no client_id (anonymous)' do
|
367
|
+
let(:client_options) { default_options.merge(token: auth.request_token(client_id: nil).token) }
|
368
|
+
|
369
|
+
context 'before connected' do
|
370
|
+
it 'is false as identification is not possible from an opaque token string' do
|
371
|
+
expect(client.auth).to_not be_client_id_validated
|
372
|
+
stop_reactor
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
context 'once connected' do
|
377
|
+
it 'is true' do
|
378
|
+
client.connection.once(:connected) do
|
379
|
+
expect(client.auth).to be_client_id_validated
|
380
|
+
stop_reactor
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
context 'with a wildcard client_id (anonymous)' do
|
387
|
+
let(:client_options) { default_options.merge(token: auth.request_token(client_id: '*').token) }
|
388
|
+
|
389
|
+
context 'before connected' do
|
390
|
+
it 'is false as identification is not possible from an opaque token string' do
|
391
|
+
expect(client.auth).to_not be_client_id_validated
|
392
|
+
stop_reactor
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
context 'once connected' do
|
397
|
+
it 'is true' do
|
398
|
+
client.connection.once(:connected) do
|
399
|
+
expect(client.auth).to be_client_id_validated
|
400
|
+
stop_reactor
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
context 'when using a token' do
|
408
|
+
context 'with a client_id' do
|
409
|
+
let(:client_options) { default_options.merge(token: auth.request_token(client_id: 'present')) }
|
410
|
+
|
411
|
+
it 'is true' do
|
412
|
+
expect(client.auth).to be_client_id_validated
|
413
|
+
stop_reactor
|
414
|
+
end
|
415
|
+
|
416
|
+
context 'once connected' do
|
417
|
+
it 'is true' do
|
418
|
+
client.connection.once(:connected) do
|
419
|
+
expect(client.auth).to be_client_id_validated
|
420
|
+
stop_reactor
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
context 'with no client_id (anonymous)' do
|
427
|
+
let(:client_options) { default_options.merge(token: auth.request_token(client_id: nil)) }
|
428
|
+
|
429
|
+
it 'is true' do
|
430
|
+
expect(client.auth).to be_client_id_validated
|
431
|
+
stop_reactor
|
432
|
+
end
|
433
|
+
|
434
|
+
context 'once connected' do
|
435
|
+
it 'is true' do
|
436
|
+
client.connection.once(:connected) do
|
437
|
+
expect(client.auth).to be_client_id_validated
|
438
|
+
stop_reactor
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
context 'with a wildcard client_id (anonymous)' do
|
445
|
+
let(:client_options) { default_options.merge(token: auth.request_token(client_id: '*')) }
|
446
|
+
|
447
|
+
it 'is true' do
|
448
|
+
expect(client.auth).to be_client_id_validated
|
449
|
+
stop_reactor
|
450
|
+
end
|
451
|
+
|
452
|
+
context 'once connected' do
|
453
|
+
it 'is true' do
|
454
|
+
client.connection.once(:connected) do
|
455
|
+
expect(client.auth).to be_client_id_validated
|
456
|
+
stop_reactor
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
context 'when using a token request with a client_id' do
|
464
|
+
let(:client_options) { default_options.merge(token: auth.create_token_request(client_id: 'present')) }
|
465
|
+
|
466
|
+
it 'is not true as identification is not confirmed until authenticated' do
|
467
|
+
expect(client.auth).to_not be_client_id_validated
|
468
|
+
stop_reactor
|
469
|
+
end
|
470
|
+
|
471
|
+
context 'once connected' do
|
472
|
+
it 'is true as identification is completed following CONNECTED ProtocolMessage' do
|
473
|
+
client.channel('test').publish('a') do
|
474
|
+
expect(client.auth).to be_client_id_validated
|
475
|
+
stop_reactor
|
476
|
+
end
|
477
|
+
end
|
478
|
+
end
|
479
|
+
end
|
480
|
+
end
|
241
481
|
end
|
242
482
|
end
|
@@ -186,9 +186,19 @@ describe Ably::Realtime::Channel, '#history', :event_machine do
|
|
186
186
|
messages.next do |next_page_messages|
|
187
187
|
expect(next_page_messages.items.count).to eql(5)
|
188
188
|
expect(next_page_messages.items.map(&:data).uniq.first).to eql(message_before_attach)
|
189
|
-
expect(next_page_messages).to be_last
|
190
189
|
|
191
|
-
|
190
|
+
if next_page_messages.last?
|
191
|
+
expect(next_page_messages).to be_last
|
192
|
+
stop_reactor
|
193
|
+
else
|
194
|
+
# If previous page said there is another page it is plausible and correct that
|
195
|
+
# the next page is empty and then the last, if the limit was satisfied
|
196
|
+
next_page_messages.next do |empty_page|
|
197
|
+
expect(empty_page.items.count).to eql(0)
|
198
|
+
expect(empty_page).to be_last
|
199
|
+
stop_reactor
|
200
|
+
end
|
201
|
+
end
|
192
202
|
end
|
193
203
|
end
|
194
204
|
end
|