ably 1.1.7 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
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