ably-rest 1.0.0 → 1.0.5

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.
Files changed (25) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -4
  3. data/lib/submodules/ably-ruby/.travis.yml +3 -0
  4. data/lib/submodules/ably-ruby/CHANGELOG.md +49 -4
  5. data/lib/submodules/ably-ruby/LICENSE +2 -2
  6. data/lib/submodules/ably-ruby/README.md +25 -6
  7. data/lib/submodules/ably-ruby/ably.gemspec +1 -1
  8. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +4 -2
  9. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +1 -1
  10. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +18 -4
  11. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +1 -1
  12. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +22 -3
  13. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +2 -2
  14. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
  15. data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
  16. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +44 -0
  17. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +2 -0
  18. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +5 -2
  19. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +1 -1
  20. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +27 -2
  21. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +95 -0
  22. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +6 -3
  23. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +26 -7
  24. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +7 -0
  25. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: db6fb6a1083ab0675e93787c68613dfe825626f8
4
- data.tar.gz: caafe4849ad502083c994fedbb1ff502dcaba821
3
+ metadata.gz: a0d3b8547bb94eb6d8efd1847fee0cc7231916da
4
+ data.tar.gz: 34e92947e49eb6ff09f3bc608277f09ef1e9b390
5
5
  SHA512:
6
- metadata.gz: 4fe732572bdbf0a4d1369f766fa8a2845f18919eb4d362f050ae44abdb25f8ee8beb402cdd263a7fb8e5355356e96d2a35f32633a9434ef52a81a98b4a0aedb1
7
- data.tar.gz: 75d72d55a94cf6e60b4b50fa8b1f4bc60c53aa601e9d199b38a4ace1539c74a7eb08655856af382c6941daffd51910659fdacf02429a776244a81196afe66ffd
6
+ metadata.gz: 155934f7ef691a2248776199b86ab243aa8e872193b28c40afaa77f08dc939d24bb1cb7592cb0bda624f4418d4a2ce7e68f969ed57c7494d4c663a25fc90e9ec
7
+ data.tar.gz: d935e38fa7b0bbbbdafc071b63bfecedcfe5693160438f802a8a18a3db106c5aeada7845485b0d128037a09e3b69b84bf6f45ed3b7180a21bf0bd582e497d705
data/README.md CHANGED
@@ -5,11 +5,15 @@
5
5
 
6
6
  A Ruby REST client library for [www.ably.io](https://www.ably.io), the realtime messaging service.
7
7
 
8
- Note: This library was created solely for developers who do not want EventMachine as a dependency of their application. If this is not a requirement for you, then we recommended you use the combined [REST & Realtime gem](https://rubygems.org/gems/ably).
9
-
10
8
  ## Documentation
11
9
 
12
- Visit https://www.ably.io/documentation for a complete API reference and more examples. The examples and API below is not exhaustive.
10
+ Visit https://www.ably.io/documentation for a complete API reference and more examples. The examples and API below is not exhaustive, you should use the completely [Ably API documentation](https://www.ably.io/documentation).
11
+
12
+ ## Realtime vs REST
13
+
14
+ This REST only library was created for developers who do not want EventMachine as a dependency of their application. Typically developers who are using Ably within their Rails or Sinatra apps would prefer to use the REST library as it has less dependencies and offers a synchronous API.
15
+
16
+ If however you need to use a realtime library that offers an asynchronous evented AP, then we recommended you [take a look at the combined REST & Realtime gem](https://rubygems.org/gems/ably).
13
17
 
14
18
  ## Installation
15
19
 
@@ -142,6 +146,10 @@ To see what has changed in recent versions of Bundler, see the [CHANGELOG](CHANG
142
146
  4. Push to the branch (`git push origin my-new-feature`)
143
147
  5. Create a new Pull Request
144
148
 
149
+ ## Release Process
150
+
151
+ See the [Ably Ruby release process notes](https://github.com/ably/ably-ruby#release-process).
152
+
145
153
  ## License
146
154
 
147
- Copyright (c) 2016 Ably Real-time Ltd, Licensed under the Apache License, Version 2.0. Refer to [LICENSE](LICENSE) for the license terms.
155
+ Copyright (c) 2017 Ably Real-time Ltd, Licensed under the Apache License, Version 2.0. Refer to [LICENSE](LICENSE) for the license terms.
@@ -6,7 +6,10 @@ language: ruby
6
6
  rvm:
7
7
  - 1.9.3
8
8
  - 2.0.0
9
+ - 2.1.10
9
10
  - 2.2.0
11
+ - 2.3.6
12
+ - 2.4.1
10
13
  script: bundle exec rspec
11
14
  notifications:
12
15
  slack:
@@ -1,11 +1,56 @@
1
1
  # Change Log
2
2
 
3
- ## [v1.0.0](https://github.com/ably/ably-ruby/tree/v1.0.0) (2017-03-07)
4
- [Full Changelog](https://github.com/ably/ably-ruby/compare/v0.8.14...v1.0.0)
3
+ ## [Unreleased](https://github.com/ably/ably-ruby/tree/v1.0.5)
4
+
5
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v1.0.4...v1.0.5)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Add Ruby 2.1 and 2.3 to Travis tests [\#129](https://github.com/ably/ably-ruby/issues/129)
10
+ - Add supported platforms to README file [\#128](https://github.com/ably/ably-ruby/issues/128)
11
+ - Add Ruby 2.1 and 2.3 to Travis tests [\#130](https://github.com/ably/ably-ruby/pull/130) ([funkyboy](https://github.com/funkyboy))
12
+
13
+ **Closed issues:**
14
+
15
+ - Cannot get realtime to work [\#127](https://github.com/ably/ably-ruby/issues/127)
16
+
17
+ **Merged pull requests:**
18
+
19
+ - Improve pagination history test [\#138](https://github.com/ably/ably-ruby/pull/138) ([funkyboy](https://github.com/funkyboy))
20
+ - Fix failing auth test [\#135](https://github.com/ably/ably-ruby/pull/135) ([funkyboy](https://github.com/funkyboy))
21
+ - Add submodule instructions to Contributing section [\#134](https://github.com/ably/ably-ruby/pull/134) ([funkyboy](https://github.com/funkyboy))
22
+ - Add request\_id option to client [\#133](https://github.com/ably/ably-ruby/pull/133) ([funkyboy](https://github.com/funkyboy))
23
+ - Update README with supported platforms [\#131](https://github.com/ably/ably-ruby/pull/131) ([funkyboy](https://github.com/funkyboy))
24
+
25
+ ## [v1.0.4](https://github.com/ably/ably-ruby/tree/v1.0.4) (2017-05-31)
26
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v1.0.3...v1.0.4)
5
27
 
6
- ### v1.0 release and upgrade notes from v0.8
28
+ ## [v1.0.3](https://github.com/ably/ably-ruby/tree/v1.0.3) (2017-05-31)
29
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v1.0.2...v1.0.3)
30
+
31
+ ## [v1.0.2](https://github.com/ably/ably-ruby/tree/v1.0.2) (2017-05-16)
32
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v1.0.1...v1.0.2)
33
+
34
+ **Fixed bugs:**
35
+
36
+ - Reconnect following disconnection is hitting a 403 error [\#117](https://github.com/ably/ably-ruby/issues/117)
37
+
38
+ **Merged pull requests:**
39
+
40
+ - Fallback fixes [\#120](https://github.com/ably/ably-ruby/pull/120) ([mattheworiordan](https://github.com/mattheworiordan))
41
+ - Channel name encoding error for REST requests [\#119](https://github.com/ably/ably-ruby/pull/119) ([mattheworiordan](https://github.com/mattheworiordan))
42
+
43
+ ## [v1.0.1](https://github.com/ably/ably-ruby/tree/v1.0.1) (2017-05-11)
44
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v1.1.0-beta.push.1...v1.0.1)
45
+
46
+ ## [v1.1.0-beta.push.1](https://github.com/ably/ably-ruby/tree/v1.1.0-beta.push.1) (2017-04-25)
47
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v1.0.0...v1.1.0-beta.push.1)
48
+
49
+ ## [v1.0.0](https://github.com/ably/ably-ruby/tree/v1.0.0) (2017-03-07)
50
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v0.8.15...v1.0.0)
7
51
 
8
- - See https://github.com/ably/docs/issues/235
52
+ ## [v0.8.15](https://github.com/ably/ably-ruby/tree/v0.8.15) (2017-03-07)
53
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v0.8.14...v0.8.15)
9
54
 
10
55
  **Implemented enhancements:**
11
56
 
@@ -1,6 +1,6 @@
1
- Copyright (c) 2016 Ably
1
+ Copyright (c) 2015-2017 Ably
2
2
 
3
- Copyright 2016 Ably Real-time Ltd
3
+ Copyright 2015-2017 Ably Real-time Ltd
4
4
 
5
5
  Licensed under the Apache License, Version 2.0 (the "License");
6
6
  you may not use this file except in compliance with the License.
@@ -5,6 +5,14 @@
5
5
 
6
6
  A Ruby client library for [ably.io](https://www.ably.io), the realtime messaging service.
7
7
 
8
+ ## Supported platforms
9
+
10
+ This SDK supports Ruby 1.9.3+.
11
+
12
+ We regression-test the SDK against a selection of Ruby versions (which we update over time, but usually consists of mainstream and widely used versions). Please refer to [.travis.yml](./.travis.yml) for the set of versions that currently undergo CI testing.
13
+
14
+ If you find any compatibility issues, please [do raise an issue](https://github.com/ably/ably-ruby/issues/new) in this repository or [contact Ably customer support](https://support.ably.io/) for advice.
15
+
8
16
  ## Documentation
9
17
 
10
18
  Visit https://www.ably.io/documentation for a complete API reference and more examples.
@@ -296,12 +304,23 @@ To see what has changed in recent versions of Bundler, see the [CHANGELOG](CHANG
296
304
  ## Contributing
297
305
 
298
306
  1. Fork it
299
- 2. Create your feature branch (`git checkout -b my-new-feature`)
300
- 3. Commit your changes (`git commit -am 'Add some feature'`)
301
- 4. Ensure you have added suitable tests and the test suite is passing(`bundle exec rspec`)
302
- 4. Push to the branch (`git push origin my-new-feature`)
303
- 5. Create a new Pull Request
307
+ 2. When pulling to local, make sure to also pull the `ably-common` repo (`git submodule init && git submodule update`)
308
+ 3. Create your feature branch (`git checkout -b my-new-feature`)
309
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
310
+ 5. Ensure you have added suitable tests and the test suite is passing(`bundle exec rspec`)
311
+ 6. Push to the branch (`git push origin my-new-feature`)
312
+ 7. Create a new Pull Request
313
+
314
+ ## Release process
315
+
316
+ This library uses [semantic versioning](http://semver.org/). For each release, the following needs to be done:
317
+
318
+ * Update the version number in [version.rb](./lib/ably/version.rb) and commit the change.
319
+ * Run [`github_changelog_generator`](https://github.com/skywinder/Github-Changelog-Generator) to automate the update of the [CHANGELOG](./CHANGELOG.md). Once the `CHANGELOG` update has completed, manually change the `Unreleased` heading and link with the current version number such as `v1.0.0`. Also ensure that the `Full Changelog` link points to the new version tag instead of the `HEAD`. Commit this change.
320
+ * Add a tag and push to origin such as `git tag v1.0.0 && git push origin v1.0.0`
321
+ * Visit [https://github.com/ably/ably-ruby/tags](https://github.com/ably/ably-ruby/tags) and `Add release notes` for the release including links to the changelog entry.
322
+ * Run `rake release` to publish the gem to [Rubygems](http://www.rubydoc.info/gems/ably)
304
323
 
305
324
  ## License
306
325
 
307
- Copyright (c) 2016 Ably Real-time Ltd, Licensed under the Apache License, Version 2.0. Refer to [LICENSE](LICENSE) for the license terms.
326
+ Copyright (c) 2017 Ably Real-time Ltd, Licensed under the Apache License, Version 2.0. Refer to [LICENSE](LICENSE) for the license terms.
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.description = %q{A Ruby client library for ably.io realtime messaging}
12
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 = 'Apache 2'
14
+ spec.license = 'Apache-2.0'
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
@@ -13,7 +13,7 @@ module Ably
13
13
  # @!attribute [r] code
14
14
  # @return [String] Ably specific error code
15
15
  class BaseAblyException < StandardError
16
- attr_reader :status, :code
16
+ attr_reader :status, :code, :request_id
17
17
 
18
18
  def initialize(message, status = nil, code = nil, base_exception = nil, options = {})
19
19
  super message
@@ -25,6 +25,7 @@ module Ably
25
25
  @code = code
26
26
  @code ||= base_exception.code if base_exception && base_exception.respond_to?(:code)
27
27
  @code ||= options[:fallback_code]
28
+ @request_id ||= options[:request_id]
28
29
  end
29
30
 
30
31
  def to_s
@@ -34,12 +35,13 @@ module Ably
34
35
  additional_info << "code: #{code}" if code
35
36
  additional_info << "http status: #{status}" if status
36
37
  additional_info << "base exception: #{@base_exception.class}" if @base_exception
38
+ additional_info << "request_id: #{request_id}" if request_id
37
39
  message << "(#{additional_info.join(', ')})"
38
40
  end
39
41
  message.join(' ')
40
42
  end
41
43
 
42
- def as_json
44
+ def as_json(*args)
43
45
  {
44
46
  message: "#{self.class}: #{message}",
45
47
  status: @status,
@@ -26,7 +26,7 @@ module Ably::Modules
26
26
 
27
27
  # Return a JSON ready object from the underlying #attributes using Ably naming conventions for keys
28
28
  # @return [Hash]
29
- def as_json
29
+ def as_json(*args)
30
30
  attributes.as_json.reject { |key, val| val.nil? }
31
31
  end
32
32
 
@@ -351,7 +351,7 @@ module Ably
351
351
  def determine_host
352
352
  raise ArgumentError, 'Block required' unless block_given?
353
353
 
354
- if can_use_fallback_hosts?
354
+ if should_use_fallback_hosts?
355
355
  internet_up? do |internet_is_up_result|
356
356
  @current_host = if internet_is_up_result
357
357
  client.fallback_endpoint.host
@@ -424,7 +424,8 @@ module Ably
424
424
  )
425
425
 
426
426
  # Use native websocket heartbeats if possible
427
- url_params['heartbeats'] = 'false' unless defaults.fetch(:websocket_heartbeats_disabled)
427
+ # TODO: Fix once https://github.com/ably/ably-ruby/issues/116 is resolved
428
+ url_params['heartbeats'] = 'true' # unless defaults.fetch(:websocket_heartbeats_disabled)
428
429
 
429
430
  url_params['clientId'] = client.auth.client_id if client.auth.has_client_id?
430
431
 
@@ -444,6 +445,10 @@ module Ably
444
445
  end
445
446
 
446
447
  determine_host do |host|
448
+ # Ensure the hostname matches the fallback host name
449
+ url.hostname = host
450
+ url.port = port
451
+
447
452
  begin
448
453
  logger.debug { "Connection: Opening socket connection to #{host}:#{port}/#{url.path}?#{url.query}" }
449
454
  @transport = create_transport(host, port, url) do |websocket_transport|
@@ -509,6 +514,7 @@ module Ably
509
514
 
510
515
  # @api private
511
516
  def create_transport(host, port, url, &block)
517
+ logger.debug { "Connection: EventMachine connecting to #{host}:#{port} with URL: #{url}" }
512
518
  EventMachine.connect(host, port, WebsocketTransport, self, url.to_s, &block)
513
519
  end
514
520
 
@@ -641,14 +647,22 @@ module Ably
641
647
  !!client.custom_realtime_host
642
648
  end
643
649
 
644
- def can_use_fallback_hosts?
650
+ def should_use_fallback_hosts?
645
651
  if client.fallback_hosts && !client.fallback_hosts.empty?
646
- if connecting? && previous_state
652
+ if connecting? && previous_state && !disconnected_from_connected_state?
647
653
  use_fallback_if_disconnected? || use_fallback_if_suspended?
648
654
  end
649
655
  end
650
656
  end
651
657
 
658
+ def disconnected_from_connected_state?
659
+ most_recent_state_changes = state_history.last(3).first(2) # Ignore current state
660
+
661
+ # A valid connection was disconnected
662
+ most_recent_state_changes.last.fetch(:state) == Connection::STATE.Disconnected &&
663
+ most_recent_state_changes.first.fetch(:state) == Connection::STATE.Connected
664
+ end
665
+
652
666
  def use_fallback_if_disconnected?
653
667
  second_reconnect_attempt_for(:disconnected, 1)
654
668
  end
@@ -130,7 +130,7 @@ module Ably
130
130
 
131
131
  private
132
132
  def base_path
133
- "/channels/#{Addressable::URI.encode(name)}"
133
+ "/channels/#{URI.encode_www_form_component(name)}"
134
134
  end
135
135
 
136
136
  def decode_message(message)
@@ -83,6 +83,10 @@ module Ably
83
83
  # if empty or nil then fallback host functionality is disabled
84
84
  attr_reader :fallback_hosts
85
85
 
86
+ # Whethere the {Client} has to add a random identifier to the path of a request
87
+ # @return [Boolean]
88
+ attr_reader :add_request_ids
89
+
86
90
  # Creates a {Ably::Rest::Client Rest Client} and configures the {Ably::Auth} object for the connection.
87
91
  #
88
92
  # @param [Hash,String] options an options Hash used to configure the client and the authentication, or String with an API key or Token ID
@@ -146,6 +150,7 @@ module Ably
146
150
  @custom_host = options.delete(:rest_host)
147
151
  @custom_port = options.delete(:port)
148
152
  @custom_tls_port = options.delete(:tls_port)
153
+ @add_request_ids = options.delete(:add_request_ids)
149
154
 
150
155
  if options[:fallback_hosts_use_default] && options[:fallback_jhosts]
151
156
  raise ArgumentError, "fallback_hosts_use_default cannot be set to trye when fallback_jhosts is also provided"
@@ -434,11 +439,25 @@ module Ably
434
439
  max_retry_duration = http_defaults.fetch(:max_retry_duration)
435
440
  requested_at = Time.now
436
441
  retry_count = 0
442
+ request_id = nil
443
+ if add_request_ids
444
+ params = if params.nil?
445
+ {}
446
+ else
447
+ params.dup
448
+ end
449
+ request_id = SecureRandom.urlsafe_base64(10)
450
+ params[:request_id] = request_id
451
+ end
437
452
 
438
453
  begin
439
454
  use_fallback = can_fallback_to_alternate_ably_host? && retry_count > 0
440
455
 
441
456
  connection(use_fallback: use_fallback).send(method, path, params) do |request|
457
+ if add_request_ids
458
+ request.options.context = {} if request.options.context.nil?
459
+ request.options.context[:request_id] = request_id
460
+ end
442
461
  unless options[:send_auth_header] == false
443
462
  request.headers[:authorization] = auth.auth_header
444
463
  if options[:headers]
@@ -456,12 +475,12 @@ module Ably
456
475
  logger.warn { "Ably::Rest::Client - Retry #{retry_count} for #{method} #{path} #{params} as initial attempt failed: #{error}" }
457
476
  retry
458
477
  end
459
-
460
478
  case error
461
479
  when Faraday::TimeoutError
462
- raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil, 80014, error)
480
+ raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil, 80014, error, { request_id: request_id })
463
481
  when Faraday::ClientError
464
- raise Ably::Exceptions::ConnectionError.new(error.message, nil, 80000, error)
482
+ # request_id is also available in the request context
483
+ raise Ably::Exceptions::ConnectionError.new(error.message, nil, 80000, error, { request_id: request_id })
465
484
  else
466
485
  raise error
467
486
  end
@@ -27,8 +27,8 @@ module Ably
27
27
  end
28
28
 
29
29
  message = 'Unknown server error' if message.to_s.strip == ''
30
-
31
- exception_args = [message, error_status_code, error_code]
30
+ request_id = env.request.context[:request_id] if env.request.context
31
+ exception_args = [message, error_status_code, error_code, nil, { request_id: request_id }]
32
32
 
33
33
  if env.status >= 500
34
34
  raise Ably::Exceptions::ServerError.new(*exception_args)
@@ -84,7 +84,7 @@ module Ably
84
84
 
85
85
  private
86
86
  def base_path
87
- "/channels/#{Addressable::URI.encode(channel.name)}/presence"
87
+ "/channels/#{URI.encode_www_form_component(channel.name)}/presence"
88
88
  end
89
89
 
90
90
  def decode_message(presence_message)
@@ -1,5 +1,5 @@
1
1
  module Ably
2
- VERSION = '1.0.0'
2
+ VERSION = '1.0.5'
3
3
  PROTOCOL_VERSION = '1.0'
4
4
 
5
5
  # Allow a variant to be configured for all instances of this client library
@@ -1178,6 +1178,27 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
1178
1178
  stop_reactor
1179
1179
  end
1180
1180
  end
1181
+
1182
+ it 'does not use a fallback host if the connection connects on the default host and then later becomes disconnected', em_timeout: 25 do
1183
+ request = 0
1184
+
1185
+ allow(connection).to receive(:create_transport).and_wrap_original do |wrapped_proc, host, *args, &block|
1186
+ expect(host).to eql(expected_host)
1187
+ request += 1
1188
+ wrapped_proc.call(host, *args, &block)
1189
+ end
1190
+
1191
+ connection.on(:connected) do
1192
+ if request <= 2
1193
+ EventMachine.add_timer(3) do
1194
+ # Force a disconnect
1195
+ connection.transport.unbind
1196
+ end
1197
+ else
1198
+ stop_reactor
1199
+ end
1200
+ end
1201
+ end
1181
1202
  end
1182
1203
 
1183
1204
  context ':fallback_hosts array is provided' do
@@ -1286,6 +1307,29 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
1286
1307
  end
1287
1308
  end
1288
1309
  end
1310
+
1311
+ it 'uses the correct host name for the WebSocket requests to the fallback hosts' do
1312
+ request = 0
1313
+ expect(connection).to receive(:create_transport).at_least(:once) do |host, port, uri|
1314
+ if request == 0 || request == expected_retry_attempts + 1
1315
+ expect(uri.hostname).to eql(expected_host)
1316
+ else
1317
+ expect(custom_hosts + [expected_host]).to include(uri.hostname)
1318
+ fallback_hosts_used << host if @suspended > 0
1319
+ end
1320
+ request += 1
1321
+ raise EventMachine::ConnectionError
1322
+ end
1323
+
1324
+ connection.on(:suspended) do
1325
+ @suspended += 1
1326
+
1327
+ if @suspended > 4
1328
+ expect(fallback_hosts_used.uniq).to match_array(custom_hosts + [expected_host])
1329
+ stop_reactor
1330
+ end
1331
+ end
1332
+ end
1289
1333
  end
1290
1334
 
1291
1335
  context ':fallback_hosts array is provided by an empty array' do
@@ -980,6 +980,7 @@ describe Ably::Realtime::Connection, :event_machine do
980
980
 
981
981
  context 'transport-level heartbeats are supported in the websocket transport' do
982
982
  it 'provides the heartbeats argument in the websocket connection params (#RTN23b)' do
983
+ skip 'Native heartbeats not yet supported in the WS driver https://github.com/ably/ably-ruby/issues/116'
983
984
  expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
984
985
  uri = URI.parse(url)
985
986
  expect(CGI::parse(uri.query)['heartbeats'][0]).to eql('false')
@@ -1007,6 +1008,7 @@ describe Ably::Realtime::Connection, :event_machine do
1007
1008
  let(:client_options) { default_options.merge(websocket_heartbeats_disabled: true) }
1008
1009
 
1009
1010
  it 'does not provide the heartbeats argument in the websocket connection params (#RTN23b)' do
1011
+ skip 'Native heartbeats not yet supported in the WS driver https://github.com/ably/ably-ruby/issues/116'
1010
1012
  expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
1011
1013
  uri = URI.parse(url)
1012
1014
  expect(CGI::parse(uri.query)['heartbeats'][0]).to be_nil
@@ -76,6 +76,8 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
76
76
  end
77
77
 
78
78
  context 'with supported extra payload content type (#RTL6h, #RSL6a2)' do
79
+ let(:channel) { client.channel("pushenabled:#{random_str}") }
80
+
79
81
  def publish_and_check_extras(extras)
80
82
  channel.attach
81
83
  channel.publish 'event', {}, extras: extras
@@ -86,7 +88,7 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
86
88
  end
87
89
 
88
90
  context 'JSON Object (Hash)' do
89
- let(:data) { { 'push' => { 'title' => 'Testing' } } }
91
+ let(:data) { { 'push' => { 'notification' => { 'title' => 'Testing' } } } }
90
92
 
91
93
  it 'is encoded and decoded to the same hash' do
92
94
  publish_and_check_extras data
@@ -94,7 +96,8 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
94
96
  end
95
97
 
96
98
  context 'JSON Array' do
97
- let(:data) { { 'push' => [ nil, true, false, 55, 'string', { 'Hash' => true }, ['array'] ] } }
99
+ # TODO: Add nil type back in
100
+ let(:data) { { 'push' => { 'data' => { 'key' => [ true, false, 55, 'string', { 'Hash' => true }, ['array'] ] } } } }
98
101
 
99
102
  it 'is encoded and decoded to the same Array' do
100
103
  publish_and_check_extras data
@@ -1083,7 +1083,7 @@ describe Ably::Auth do
1083
1083
  expect { auth.request_token(timestamp: Time.now - 180) }.to raise_error do |error|
1084
1084
  expect(error).to be_a(Ably::Exceptions::UnauthorizedRequest)
1085
1085
  expect(error.status).to eql(401)
1086
- expect(error.code).to eql(40101)
1086
+ expect(error.code).to eql(40104)
1087
1087
  end
1088
1088
  end
1089
1089
 
@@ -256,6 +256,25 @@ describe Ably::Rest::Channel do
256
256
  end
257
257
  end
258
258
  end
259
+
260
+ context 'with a non ASCII channel name' do
261
+ let(:channel_name) { 'foo:¡€≤`☃' }
262
+ let(:channel_name_encoded) { 'foo%3A%C2%A1%E2%82%AC%E2%89%A4%60%E2%98%83' }
263
+ let(:endpoint) { client.endpoint }
264
+ let(:channel) { client.channels.get(channel_name) }
265
+
266
+ context 'stubbed', :webmock do
267
+ let!(:get_stub) {
268
+ stub_request(:post, "#{endpoint}/channels/#{channel_name_encoded}/publish").
269
+ to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
270
+ }
271
+
272
+ it 'correctly encodes the channel name' do
273
+ channel.publish('foo')
274
+ expect(get_stub).to have_been_requested
275
+ end
276
+ end
277
+ end
259
278
  end
260
279
 
261
280
  describe '#history' do
@@ -324,7 +343,13 @@ describe Ably::Rest::Channel do
324
343
 
325
344
  # Page 3
326
345
  expect(page_3.items.size).to eql(1)
327
- expect(page_3).to be_last
346
+ # This test should be deterministic but it's not.
347
+ # Sometimes the backend, to avoid too much work, returns a `next` link that contains empty reults.
348
+ if page_3.next
349
+ expect(page_3.next.items.length).to eql(0)
350
+ else
351
+ expect(page_3).to be_last
352
+ end
328
353
  end
329
354
 
330
355
  context 'direction' do
@@ -374,7 +399,7 @@ describe Ably::Rest::Channel do
374
399
  let!(:history_stub) {
375
400
  query_params = default_history_options
376
401
  .merge(option => milliseconds).map { |k, v| "#{k}=#{v}" }.join('&')
377
- stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/messages?#{query_params}").
402
+ stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/messages?#{query_params}").
378
403
  to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
379
404
  }
380
405
 
@@ -959,5 +959,100 @@ describe Ably::Rest::Client do
959
959
  end
960
960
  end
961
961
  end
962
+
963
+ context 'request_id generation' do
964
+ context 'Timeout error' do
965
+ context 'with request_id', :webmock do
966
+ let(:custom_logger) do
967
+ Class.new do
968
+ def initialize
969
+ @messages = []
970
+ end
971
+
972
+ [:fatal, :error, :warn, :info, :debug].each do |severity|
973
+ define_method severity do |message, &block|
974
+ message_val = [message]
975
+ message_val << block.call if block
976
+
977
+ @messages << [severity, message_val.compact.join(' ')]
978
+ end
979
+ end
980
+
981
+ def logs
982
+ @messages
983
+ end
984
+
985
+ def level
986
+ 1
987
+ end
988
+
989
+ def level=(new_level)
990
+ end
991
+ end
992
+ end
993
+ let(:custom_logger_object) { custom_logger.new }
994
+ let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, add_request_ids: true) }
995
+ before do
996
+ @request_id = nil
997
+ stub_request(:get, Addressable::Template.new("#{client.endpoint}/time{?request_id}")).with do |request|
998
+ @request_id = request.uri.query_values['request_id']
999
+ end.to_return do
1000
+ raise Faraday::TimeoutError.new('timeout error message')
1001
+ end
1002
+ end
1003
+ it 'has an error with the same request_id of the request' do
1004
+ expect{ client.time }.to raise_error(Ably::Exceptions::ConnectionTimeout, /#{@request_id}/)
1005
+ expect(custom_logger_object.logs.find { |severity, message| message.match(/#{@request_id}/i)} ).to_not be_nil
1006
+ end
1007
+ end
1008
+
1009
+ context 'when specifying fallback hosts', :webmock do
1010
+ let(:client_options) { { key: api_key, fallback_hosts_use_default: true, add_request_ids: true } }
1011
+ let(:requests) { [] }
1012
+ before do
1013
+ @request_id = nil
1014
+ hosts = Ably::FALLBACK_HOSTS + ['rest.ably.io']
1015
+ hosts.each do |host|
1016
+ stub_request(:get, Addressable::Template.new("https://#{host.downcase}/time{?request_id}")).with do |request|
1017
+ @request_id = request.uri.query_values['request_id']
1018
+ requests << @request_id
1019
+ end.to_return do
1020
+ raise Faraday::TimeoutError.new('timeout error message')
1021
+ end
1022
+ end
1023
+ end
1024
+ it 'request_id is the same across retries' do
1025
+ expect{ client.time }.to raise_error(Ably::Exceptions::ConnectionTimeout, /#{@request_id}/)
1026
+ expect(requests.uniq.count).to eql(1)
1027
+ expect(requests.uniq.first).to eql(@request_id)
1028
+ end
1029
+ end
1030
+
1031
+ context 'without request_id' do
1032
+ let(:client_options) { default_options.merge(key: api_key, http_request_timeout: 0) }
1033
+ it 'does not include request_id in ConnectionTimeout error' do
1034
+ begin
1035
+ client.stats
1036
+ rescue Ably::Exceptions::ConnectionTimeout => err
1037
+ expect(err.request_id).to eql(nil)
1038
+ end
1039
+ end
1040
+ end
1041
+ end
1042
+
1043
+ context 'UnauthorizedRequest nonce error' do
1044
+ let(:token_params) { { nonce: "samenonce_#{protocol}", timestamp: Time.now.to_i } }
1045
+ it 'includes request_id in UnauthorizedRequest error due to replayed nonce' do
1046
+ client1 = Ably::Rest::Client.new(default_options.merge(key: api_key))
1047
+ client2 = Ably::Rest::Client.new(default_options.merge(key: api_key, add_request_ids: true))
1048
+ expect { client1.auth.request_token(token_params) }.not_to raise_error
1049
+ begin
1050
+ client2.auth.request_token(token_params)
1051
+ rescue Ably::Exceptions::UnauthorizedRequest => err
1052
+ expect(err.request_id).to_not eql(nil)
1053
+ end
1054
+ end
1055
+ end
1056
+ end
962
1057
  end
963
1058
  end
@@ -62,8 +62,10 @@ describe Ably::Rest::Channel, 'messages' do
62
62
  end
63
63
 
64
64
  context 'with supported extra payload content type (#RSL1h, #RSL6a2)' do
65
+ let(:channel) { client.channel("pushenabled:#{random_str}") }
66
+
65
67
  context 'JSON Object (Hash)' do
66
- let(:data) { { 'push' => { 'title' => 'Testing' } } }
68
+ let(:data) { { 'push' => { 'notification' => { 'title' => 'Testing' } } } }
67
69
 
68
70
  it 'is encoded and decoded to the same hash' do
69
71
  channel.publish 'event', {}, extras: data
@@ -72,9 +74,10 @@ describe Ably::Rest::Channel, 'messages' do
72
74
  end
73
75
 
74
76
  context 'JSON Array' do
75
- let(:data) { { 'push' => [ nil, true, false, 55, 'string', { 'Hash' => true }, ['array'] ] } }
77
+ # TODO: Add nil type back in
78
+ let(:data) { { 'push' => { 'data' => { 'key' => [ true, false, 55, 'string', { 'Hash' => true }, ['array'] ] } } } }
76
79
 
77
- it 'is encoded and decoded to the same Array' do
80
+ it 'is encoded and decoded to the same deep multi-type object' do
78
81
  channel.publish 'event', {}, extras: data
79
82
  expect(channel.history.items.first.extras).to eql(data)
80
83
  end
@@ -73,7 +73,7 @@ describe Ably::Rest::Presence do
73
73
  end
74
74
  let!(:get_stub) {
75
75
  query_params = query_options.map { |k, v| "#{k}=#{v}" }.join('&')
76
- stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/presence?#{query_params}").
76
+ stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence?#{query_params}").
77
77
  to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
78
78
  }
79
79
  let(:channel_name) { random_str }
@@ -111,6 +111,25 @@ describe Ably::Rest::Presence do
111
111
  expect(fixtures_channel.presence.get(connection_id: 'does.not.exist').items).to be_empty
112
112
  end
113
113
  end
114
+
115
+ context 'with a non ASCII channel name' do
116
+ let(:channel_name) { 'foo:¡€≤`☃' }
117
+ let(:channel_name_encoded) { 'foo%3A%C2%A1%E2%82%AC%E2%89%A4%60%E2%98%83' }
118
+ let(:endpoint) { client.endpoint }
119
+ let(:channel) { client.channels.get(channel_name) }
120
+
121
+ context 'stubbed', :webmock do
122
+ let!(:get_stub) {
123
+ stub_request(:get, "#{endpoint}/channels/#{channel_name_encoded}/presence?limit=100").
124
+ to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
125
+ }
126
+
127
+ it 'correctly encodes the channel name' do
128
+ channel.presence.get
129
+ expect(get_stub).to have_been_requested
130
+ end
131
+ end
132
+ end
114
133
  end
115
134
 
116
135
  describe '#history' do
@@ -194,7 +213,7 @@ describe Ably::Rest::Presence do
194
213
  context 'limit options', :webmock do
195
214
  let!(:history_stub) {
196
215
  query_params = history_options.map { |k, v| "#{k}=#{v}" }.join('&')
197
- stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/presence/history?#{query_params}").
216
+ stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?#{query_params}").
198
217
  to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
199
218
  }
200
219
 
@@ -234,7 +253,7 @@ describe Ably::Rest::Presence do
234
253
  }
235
254
  let!(:history_stub) {
236
255
  query_params = history_options.map { |k, v| "#{k}=#{v}" }.join('&')
237
- stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/presence/history?#{query_params}").
256
+ stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?#{query_params}").
238
257
  to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
239
258
  }
240
259
 
@@ -338,7 +357,7 @@ describe Ably::Rest::Presence do
338
357
 
339
358
  context '#get' do
340
359
  let!(:get_stub) {
341
- stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/presence?limit=100").
360
+ stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence?limit=100").
342
361
  to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type })
343
362
  }
344
363
 
@@ -355,7 +374,7 @@ describe Ably::Rest::Presence do
355
374
 
356
375
  context '#history' do
357
376
  let!(:history_stub) {
358
- stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/presence/history?direction=backwards&limit=100").
377
+ stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?direction=backwards&limit=100").
359
378
  to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type })
360
379
  }
361
380
 
@@ -385,7 +404,7 @@ describe Ably::Rest::Presence do
385
404
  context '#get' do
386
405
  let(:client_options) { default_options.merge(log_level: :fatal) }
387
406
  let!(:get_stub) {
388
- stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/presence?limit=100").
407
+ stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence?limit=100").
389
408
  to_return(:body => serialized_encoded_message_with_invalid_encoding, :headers => { 'Content-Type' => content_type })
390
409
  }
391
410
  let(:presence_message) { presence.get.items.first }
@@ -409,7 +428,7 @@ describe Ably::Rest::Presence do
409
428
  context '#history' do
410
429
  let(:client_options) { default_options.merge(log_level: :fatal) }
411
430
  let!(:history_stub) {
412
- stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/presence/history?direction=backwards&limit=100").
431
+ stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?direction=backwards&limit=100").
413
432
  to_return(:body => serialized_encoded_message_with_invalid_encoding, :headers => { 'Content-Type' => content_type })
414
433
  }
415
434
  let(:presence_message) { presence.history.items.first }
@@ -50,4 +50,11 @@ describe Ably::Rest::Client do
50
50
  end
51
51
  end
52
52
  end
53
+
54
+ context 'request_id generation' do
55
+ let(:client_options) { { key: 'appid.keyuid:keysecret', add_request_ids: true } }
56
+ it 'includes request_id in URL' do
57
+ expect(subject.add_request_ids).to eql(true)
58
+ end
59
+ end
53
60
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ably-rest
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew O'Riordan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-07 00:00:00.000000000 Z
11
+ date: 2018-04-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday