ably 1.1.7 → 1.2.1

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check.yml +1 -1
  3. data/CHANGELOG.md +99 -0
  4. data/COPYRIGHT +1 -1
  5. data/README.md +2 -2
  6. data/SPEC.md +0 -7
  7. data/UPDATING.md +30 -0
  8. data/ably.gemspec +11 -24
  9. data/lib/ably/auth.rb +8 -8
  10. data/lib/ably/logger.rb +4 -4
  11. data/lib/ably/models/channel_options.rb +97 -0
  12. data/lib/ably/models/connection_details.rb +8 -2
  13. data/lib/ably/models/delta_extras.rb +29 -0
  14. data/lib/ably/models/device_details.rb +1 -1
  15. data/lib/ably/models/error_info.rb +6 -2
  16. data/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -0
  17. data/lib/ably/models/message.rb +14 -3
  18. data/lib/ably/models/protocol_message.rb +23 -14
  19. data/lib/ably/models/token_details.rb +7 -2
  20. data/lib/ably/models/token_request.rb +1 -1
  21. data/lib/ably/modules/ably.rb +1 -1
  22. data/lib/ably/modules/channels_collection.rb +22 -2
  23. data/lib/ably/modules/conversions.rb +34 -0
  24. data/lib/ably/realtime/auth.rb +2 -2
  25. data/lib/ably/realtime/channel/channel_manager.rb +16 -4
  26. data/lib/ably/realtime/channel/channel_state_machine.rb +10 -1
  27. data/lib/ably/realtime/channel/publisher.rb +3 -2
  28. data/lib/ably/realtime/channel.rb +54 -22
  29. data/lib/ably/realtime/channels.rb +1 -1
  30. data/lib/ably/realtime/connection/connection_manager.rb +13 -4
  31. data/lib/ably/realtime/connection/connection_state_machine.rb +4 -0
  32. data/lib/ably/realtime/connection.rb +0 -3
  33. data/lib/ably/rest/channel.rb +28 -35
  34. data/lib/ably/rest/client.rb +23 -8
  35. data/lib/ably/rest/middleware/encoder.rb +1 -1
  36. data/lib/ably/rest/middleware/exceptions.rb +1 -1
  37. data/lib/ably/rest/middleware/external_exceptions.rb +1 -1
  38. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +1 -1
  39. data/lib/ably/rest/middleware/logger.rb +1 -1
  40. data/lib/ably/rest/middleware/parse_json.rb +1 -1
  41. data/lib/ably/rest/middleware/parse_message_pack.rb +1 -1
  42. data/lib/ably/util/crypto.rb +1 -1
  43. data/lib/ably/version.rb +2 -2
  44. data/spec/acceptance/realtime/channel_spec.rb +458 -27
  45. data/spec/acceptance/realtime/channels_spec.rb +59 -7
  46. data/spec/acceptance/realtime/connection_failures_spec.rb +56 -1
  47. data/spec/acceptance/realtime/connection_spec.rb +270 -1
  48. data/spec/acceptance/realtime/message_spec.rb +77 -0
  49. data/spec/acceptance/realtime/presence_spec.rb +18 -1
  50. data/spec/acceptance/rest/auth_spec.rb +18 -0
  51. data/spec/acceptance/rest/channel_spec.rb +73 -11
  52. data/spec/acceptance/rest/channels_spec.rb +23 -6
  53. data/spec/acceptance/rest/client_spec.rb +3 -3
  54. data/spec/acceptance/rest/message_spec.rb +61 -3
  55. data/spec/lib/unit/models/channel_options_spec.rb +52 -0
  56. data/spec/run_parallel_tests +2 -7
  57. data/spec/support/test_app.rb +1 -1
  58. data/spec/unit/logger_spec.rb +6 -14
  59. data/spec/unit/models/delta_extras_spec.rb +14 -0
  60. data/spec/unit/models/error_info_spec.rb +17 -1
  61. data/spec/unit/models/message_spec.rb +38 -0
  62. data/spec/unit/models/protocol_message_spec.rb +77 -27
  63. data/spec/unit/models/token_details_spec.rb +14 -0
  64. data/spec/unit/realtime/channel_spec.rb +2 -1
  65. data/spec/unit/realtime/channels_spec.rb +53 -15
  66. data/spec/unit/rest/channel_spec.rb +40 -7
  67. data/spec/unit/rest/channels_spec.rb +81 -14
  68. data/spec/unit/rest/client_spec.rb +27 -0
  69. metadata +46 -11
@@ -23,27 +23,25 @@ module Ably
23
23
  attr_reader :push
24
24
 
25
25
  IDEMPOTENT_LIBRARY_GENERATED_ID_LENGTH = 9 # See spec RSL1k1
26
- MAX_MESSAGE_SIZE = 65536 # See spec TO3l8
27
26
 
28
27
  # Initialize a new Channel object
29
28
  #
30
29
  # @param client [Ably::Rest::Client]
31
30
  # @param name [String] The name of the channel
32
- # @param channel_options [Hash] Channel options, currently reserved for Encryption options
33
- # @option channel_options [Hash,Ably::Models::CipherParams] :cipher A hash of options or a {Ably::Models::CipherParams} to configure the encryption. *:key* is required, all other options are optional. See {Ably::Util::Crypto#initialize} for a list of +:cipher+ options
31
+ # @param channel_options [Hash, Ably::Models::ChannelOptions] A hash of options or a {Ably::Models::ChannelOptions}
34
32
  #
35
33
  def initialize(client, name, channel_options = {})
36
34
  name = (ensure_utf_8 :name, name)
37
35
 
38
- update_options channel_options
36
+ @options = Ably::Models::ChannelOptions(channel_options)
39
37
  @client = client
40
38
  @name = name
41
39
  @push = PushChannel.new(self)
42
40
  end
43
41
 
44
- # Publish one or more messages to the channel. Three overloaded forms
42
+ # Publish one or more messages to the channel. Five overloaded forms
45
43
  # @param name [String, Array<Ably::Models::Message|Hash>, Ably::Models::Message, nil] The event name of the message to publish, or an Array of [Ably::Model::Message] objects or [Hash] objects with +:name+ and +:data+ pairs, or a single Ably::Model::Message object
46
- # @param data [String, ByteArray, Hash, nil] The message payload unless an Array of [Ably::Model::Message] objects passed in the first argument, in which case an optional hash of query parameters
44
+ # @param data [String, Array, Hash, nil] The message payload unless an Array of [Ably::Model::Message] objects passed in the first argument, in which case an optional hash of query parameters
47
45
  # @param attributes [Hash, nil] Optional additional message attributes such as :extras, :id, :client_id or :connection_id, applied when name attribute is nil or a string (Deprecated, will be removed in 2.0 in favour of constructing a Message object)
48
46
  # @return [Boolean] true if the message was published, otherwise false
49
47
  #
@@ -51,45 +49,36 @@ module Ably
51
49
  # # Publish a single message with (name, data) form
52
50
  # channel.publish 'click', { x: 1, y: 2 }
53
51
  #
54
- # # Publish an array of message Hashes
52
+ # # Publish a single message with single Hash form
53
+ # message = { name: 'click', data: { x: 1, y: 2 } }
54
+ # channel.publish message
55
+ #
56
+ # # Publish an array of message Hashes form
55
57
  # messages = [
56
58
  # { name: 'click', data: { x: 1, y: 2 } },
57
59
  # { name: 'click', data: { x: 2, y: 3 } }
58
60
  # ]
59
61
  # channel.publish messages
60
62
  #
61
- # # Publish an array of Ably::Models::Message objects
63
+ # # Publish an array of Ably::Models::Message objects form
62
64
  # messages = [
63
- # Ably::Models::Message(name: 'click', { x: 1, y: 2 })
64
- # Ably::Models::Message(name: 'click', { x: 2, y: 3 })
65
+ # Ably::Models::Message(name: 'click', data: { x: 1, y: 2 })
66
+ # Ably::Models::Message(name: 'click', data: { x: 2, y: 3 })
65
67
  # ]
66
68
  # channel.publish messages
67
69
  #
68
- # # Publish a single Ably::Models::Message object, with a query params
69
- # # specifying quickAck: true
70
- # message = Ably::Models::Message(name: 'click', { x: 1, y: 2 })
71
- # channel.publish message, quickAck: 'true'
70
+ # # Publish a single Ably::Models::Message object form
71
+ # message = Ably::Models::Message(name: 'click', data: { x: 1, y: 2 })
72
+ # channel.publish message
72
73
  #
73
- def publish(first, second = nil, third = {})
74
- messages, qs_params = if first.kind_of?(Enumerable)
75
- # ([Message], qs_params) form
76
- [first, second]
77
- elsif first.kind_of?(Ably::Models::Message)
78
- # (Message, qs_params) form
79
- [[first], second]
80
- else
81
- # (name, data, attributes) form
82
- first = ensure_utf_8(:name, first, allow_nil: true)
83
- ensure_supported_payload second
84
- # RSL1h - attributes as an extra method parameter is extra-spec but need to
85
- # keep it for backcompat until version 2
86
- [[{ name: first, data: second }.merge(third)], nil]
87
- end
74
+ def publish(name, data = nil, attributes = {})
75
+ qs_params = nil
76
+ qs_params = data if name.kind_of?(Enumerable) || name.kind_of?(Ably::Models::Message)
88
77
 
89
- messages.map! { |message| Ably::Models::Message(message.dup) }
78
+ messages = build_messages(name, data, attributes) # (RSL1a, RSL1b)
90
79
 
91
- if messages.sum(&:size) > Ably::Rest::Channel::MAX_MESSAGE_SIZE
92
- raise Ably::Exceptions::MaxMessageSizeExceeded.new("Maximum message size exceeded #{Ably::Rest::Channel::MAX_MESSAGE_SIZE}.")
80
+ if messages.sum(&:size) > (max_message_size = client.max_message_size || Ably::Rest::Client::MAX_MESSAGE_SIZE)
81
+ raise Ably::Exceptions::MaxMessageSizeExceeded.new("Maximum message size exceeded #{max_message_size} bytes.")
93
82
  end
94
83
 
95
84
  payload = messages.map do |message|
@@ -164,12 +153,16 @@ module Ably
164
153
  @presence ||= Presence.new(client, self)
165
154
  end
166
155
 
167
- # @api private
168
- def update_options(channel_options)
169
- @options = channel_options.clone.freeze
156
+ # Sets or updates the stored channel options. (#RSL7)
157
+ # @param channel_options [Hash, Ably::Models::ChannelOptions] A hash of options or a {Ably::Models::ChannelOptions}
158
+ # @return [Ably::Models::ChannelOptions]
159
+ def set_options(channel_options)
160
+ @options = Ably::Models::ChannelOptions(channel_options)
170
161
  end
162
+ alias options= set_options
171
163
 
172
164
  private
165
+
173
166
  def base_path
174
167
  "/channels/#{URI.encode_www_form_component(name)}"
175
168
  end
@@ -4,7 +4,7 @@ require 'logger'
4
4
  require 'uri'
5
5
 
6
6
  require 'typhoeus'
7
- require 'typhoeus/adapters/faraday'
7
+ require 'faraday/typhoeus'
8
8
 
9
9
  require 'ably/rest/middleware/exceptions'
10
10
 
@@ -25,7 +25,8 @@ module Ably
25
25
  # Default Ably domain for REST
26
26
  DOMAIN = 'rest.ably.io'
27
27
 
28
- MAX_FRAME_SIZE = 524288
28
+ MAX_MESSAGE_SIZE = 65536 # See spec TO3l8
29
+ MAX_FRAME_SIZE = 524288 # See spec TO3l8
29
30
 
30
31
  # Configuration for HTTP timeouts and HTTP request reattempts to fallback hosts
31
32
  HTTP_DEFAULTS = {
@@ -54,7 +55,7 @@ module Ably
54
55
  # @return [Symbol]
55
56
  attr_reader :protocol
56
57
 
57
- # Client agent i.e. `example-gem/1.2.0 ably-ruby/1.1.5 ruby/1.9.3`
58
+ # Client agent i.e. `example-gem/1.2.0 ably-ruby/1.1.5 ruby/3.1.1`
58
59
  # @return [String]
59
60
  attr_reader :agent
60
61
 
@@ -118,6 +119,14 @@ module Ably
118
119
  # @return [Boolean]
119
120
  attr_reader :idempotent_rest_publishing
120
121
 
122
+ # Max message size (TO2, TO3l8) by default (65536 bytes) 64KiB
123
+ # @return [Integer]
124
+ attr_reader :max_message_size
125
+
126
+ # Max frame size (TO2, TO3l8) by default (524288 bytes) 512KiB
127
+ # @return [Integer]
128
+ attr_reader :max_frame_size
129
+
121
130
  # Creates a {Ably::Rest::Client Rest Client} and configures the {Ably::Auth} object for the connection.
122
131
  #
123
132
  # @param [Hash,String] options an options Hash used to configure the client and the authentication, or String with an API key or Token ID
@@ -130,7 +139,7 @@ module Ably
130
139
  # @option options [Symbol] :protocol (:msgpack) Protocol used to communicate with Ably, :json and :msgpack currently supported
131
140
  # @option options [Boolean] :use_binary_protocol (true) When true will use the MessagePack binary protocol, when false it will use JSON encoding. This option will overide :protocol option
132
141
  # @option options [Logger::Severity,Symbol] :log_level (Logger::WARN) Log level for the standard Logger that outputs to STDOUT. Can be set to :fatal (Logger::FATAL), :error (Logger::ERROR), :warn (Logger::WARN), :info (Logger::INFO), :debug (Logger::DEBUG) or :none
133
- # @option options [Logger] :logger A custom logger can be used however it must adhere to the Ruby Logger interface, see http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html
142
+ # @option options [Logger] :logger A custom logger can be used however it must adhere to the Ruby Logger interface, see http://www.ruby-doc.org/stdlib-3.1.1/libdoc/logger/rdoc/Logger.html
134
143
  # @option options [String] :client_id client ID identifying this connection to other clients
135
144
  # @option options [String] :auth_url a URL to be used to GET or POST a set of token request params, to obtain a signed token request
136
145
  # @option options [Hash] :auth_headers a set of application-specific headers to be added to any request made to the +auth_url+
@@ -138,7 +147,7 @@ module Ably
138
147
  # @option options [Symbol] :auth_method (:get) HTTP method to use with +auth_url+, must be either +:get+ or +:post+
139
148
  # @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.
140
149
  # The Proc should return a token string, {Ably::Models::TokenDetails} or JSON equivalent, {Ably::Models::TokenRequest} or JSON equivalent
141
- # @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
150
+ # @option options [Boolean] :query_time when true will query the {https://www.ably.com Ably} system for the current time instead of using the local time
142
151
  # @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}
143
152
  #
144
153
  # @option options [Integer] :http_open_timeout (4 seconds) timeout in seconds for opening an HTTP connection for all HTTP requests
@@ -152,6 +161,8 @@ module Ably
152
161
  #
153
162
  # @option options [Boolean] :add_request_ids (false) When true, adds a unique request_id to each request sent to Ably servers. This is handy when reporting issues, because you can refer to a specific request.
154
163
  # @option options [Boolean] :idempotent_rest_publishing (false if ver < 1.2) When true, idempotent publishing is enabled for all messages published via REST
164
+ # @option options [Integer] :max_message_size (65536 bytes) Maximum size of all messages when publishing via REST publish()
165
+ # @option options [Integer] :max_frame_size (524288 bytes) Maximum size of frame
155
166
  #
156
167
  # @return [Ably::Rest::Client]
157
168
  #
@@ -188,8 +199,12 @@ module Ably
188
199
  @custom_tls_port = options.delete(:tls_port)
189
200
  @add_request_ids = options.delete(:add_request_ids)
190
201
  @log_retries_as_info = options.delete(:log_retries_as_info)
191
- @idempotent_rest_publishing = options.delete(:idempotent_rest_publishing) || Ably.major_minor_version_numeric > 1.1
202
+ @max_message_size = options.delete(:max_message_size) || MAX_MESSAGE_SIZE
203
+ @max_frame_size = options.delete(:max_frame_size) || MAX_FRAME_SIZE
192
204
 
205
+ if (@idempotent_rest_publishing = options.delete(:idempotent_rest_publishing)).nil?
206
+ @idempotent_rest_publishing = Ably::PROTOCOL_VERSION.to_f > 1.1
207
+ end
193
208
 
194
209
  if options[:fallback_hosts_use_default] && options[:fallback_hosts]
195
210
  raise ArgumentError, "fallback_hosts_use_default cannot be set to try when fallback_hosts is also provided"
@@ -365,8 +380,8 @@ module Ably
365
380
  send_request(method, path, params, headers: headers)
366
381
  end
367
382
  when :post, :patch, :put
368
- if body.to_json.bytesize > MAX_FRAME_SIZE
369
- raise Ably::Exceptions::MaxFrameSizeExceeded.new("Maximum frame size exceeded #{MAX_FRAME_SIZE} bytes.")
383
+ if body.to_json.bytesize > max_frame_size
384
+ raise Ably::Exceptions::MaxFrameSizeExceeded.new("Maximum frame size exceeded #{max_frame_size} bytes.")
370
385
  end
371
386
  path_with_params = Addressable::URI.new
372
387
  path_with_params.query_values = params || {}
@@ -5,7 +5,7 @@ module Ably
5
5
  module Rest
6
6
  module Middleware
7
7
  # Encode the body of the message according to the mime type
8
- class Encoder < ::Faraday::Response::Middleware
8
+ class Encoder < Faraday::Middleware
9
9
  CONTENT_TYPE = 'Content-Type'.freeze unless defined? CONTENT_TYPE
10
10
 
11
11
  def call(env)
@@ -6,7 +6,7 @@ module Ably
6
6
  module Middleware
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
- class Exceptions < Faraday::Response::Middleware
9
+ class Exceptions < Faraday::Middleware
10
10
  def on_complete(env)
11
11
  if env.status >= 400
12
12
  error_status_code = env.status
@@ -5,7 +5,7 @@ module Ably
5
5
  module Middleware
6
6
  # HTTP exceptions raised due to a status code error on a 3rd party site
7
7
  # Used by auth calls
8
- class ExternalExceptions < Faraday::Response::Middleware
8
+ class ExternalExceptions < Faraday::Middleware
9
9
  def on_complete(env)
10
10
  if env.status >= 400
11
11
  error_status_code = env.status
@@ -4,7 +4,7 @@ require 'json'
4
4
  module Ably
5
5
  module Rest
6
6
  module Middleware
7
- class FailIfUnsupportedMimeType < Faraday::Response::Middleware
7
+ class FailIfUnsupportedMimeType < Faraday::Middleware
8
8
  def on_complete(env)
9
9
  unless env.response_headers['Ably-Middleware-Parsed'] == true
10
10
  # Ignore empty body with success status code for no body response
@@ -3,7 +3,7 @@ require 'faraday'
3
3
  module Ably
4
4
  module Rest
5
5
  module Middleware
6
- class Logger < Faraday::Response::Middleware
6
+ class Logger < Faraday::Middleware
7
7
  extend Forwardable
8
8
 
9
9
  def initialize(app, logger = nil)
@@ -4,7 +4,7 @@ require 'json'
4
4
  module Ably
5
5
  module Rest
6
6
  module Middleware
7
- class ParseJson < Faraday::Response::Middleware
7
+ class ParseJson < Faraday::Middleware
8
8
  def on_complete(env)
9
9
  if env.response_headers['Content-Type'] == 'application/json'
10
10
  env.body = parse(env.body) unless env.response_headers['Ably-Middleware-Parsed'] == true
@@ -4,7 +4,7 @@ require 'msgpack'
4
4
  module Ably
5
5
  module Rest
6
6
  module Middleware
7
- class ParseMessagePack < Faraday::Response::Middleware
7
+ class ParseMessagePack < Faraday::Middleware
8
8
  def on_complete(env)
9
9
  if env.response_headers['Content-Type'] == 'application/x-msgpack'
10
10
  env.body = parse(env.body) unless env.response_headers['Ably-Middleware-Parsed'] == true
@@ -30,7 +30,7 @@ module Ably::Util
30
30
  # crypto.decrypt(decrypted) # => 'secret text'
31
31
  #
32
32
  def initialize(params)
33
- @fixed_iv = params.delete(:fixed_iv) if params.kind_of?(Hash)
33
+ @fixed_iv = params[:fixed_iv]
34
34
  @cipher_params = Ably::Models::CipherParams(params)
35
35
  end
36
36
 
data/lib/ably/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Ably
2
- VERSION = '1.1.7'
3
- PROTOCOL_VERSION = '1.1'
2
+ VERSION = '1.2.1'
3
+ PROTOCOL_VERSION = '1.2'
4
4
 
5
5
  # @api private
6
6
  def self.major_minor_version_numeric