ably 1.1.6 → 1.2.0

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check.yml +15 -1
  3. data/CHANGELOG.md +131 -0
  4. data/COPYRIGHT +1 -1
  5. data/README.md +14 -2
  6. data/SPEC.md +0 -7
  7. data/UPDATING.md +30 -0
  8. data/ably.gemspec +12 -7
  9. data/lib/ably/agent.rb +3 -0
  10. data/lib/ably/auth.rb +3 -3
  11. data/lib/ably/exceptions.rb +6 -0
  12. data/lib/ably/models/channel_options.rb +97 -0
  13. data/lib/ably/models/connection_details.rb +8 -0
  14. data/lib/ably/models/delta_extras.rb +29 -0
  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 +28 -3
  18. data/lib/ably/models/presence_message.rb +14 -0
  19. data/lib/ably/models/protocol_message.rb +29 -12
  20. data/lib/ably/models/token_details.rb +7 -2
  21. data/lib/ably/modules/channels_collection.rb +22 -2
  22. data/lib/ably/modules/conversions.rb +34 -0
  23. data/lib/ably/realtime/channel/channel_manager.rb +18 -6
  24. data/lib/ably/realtime/channel/channel_state_machine.rb +10 -1
  25. data/lib/ably/realtime/channel/publisher.rb +6 -0
  26. data/lib/ably/realtime/channel.rb +54 -22
  27. data/lib/ably/realtime/channels.rb +1 -1
  28. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -6
  29. data/lib/ably/realtime/connection/connection_manager.rb +13 -4
  30. data/lib/ably/realtime/connection/connection_state_machine.rb +4 -0
  31. data/lib/ably/realtime/connection.rb +2 -2
  32. data/lib/ably/rest/channel.rb +31 -31
  33. data/lib/ably/rest/client.rb +27 -12
  34. data/lib/ably/util/crypto.rb +1 -1
  35. data/lib/ably/version.rb +2 -14
  36. data/lib/ably.rb +1 -0
  37. data/spec/acceptance/realtime/auth_spec.rb +1 -1
  38. data/spec/acceptance/realtime/channel_history_spec.rb +25 -0
  39. data/spec/acceptance/realtime/channel_spec.rb +466 -21
  40. data/spec/acceptance/realtime/channels_spec.rb +59 -7
  41. data/spec/acceptance/realtime/connection_failures_spec.rb +59 -2
  42. data/spec/acceptance/realtime/connection_spec.rb +256 -28
  43. data/spec/acceptance/realtime/message_spec.rb +77 -0
  44. data/spec/acceptance/realtime/presence_history_spec.rb +3 -1
  45. data/spec/acceptance/realtime/presence_spec.rb +31 -159
  46. data/spec/acceptance/rest/auth_spec.rb +18 -0
  47. data/spec/acceptance/rest/channel_spec.rb +84 -9
  48. data/spec/acceptance/rest/channels_spec.rb +23 -6
  49. data/spec/acceptance/rest/client_spec.rb +25 -21
  50. data/spec/acceptance/rest/message_spec.rb +61 -3
  51. data/spec/lib/unit/models/channel_options_spec.rb +52 -0
  52. data/spec/shared/model_behaviour.rb +1 -1
  53. data/spec/spec_helper.rb +11 -2
  54. data/spec/support/test_app.rb +1 -1
  55. data/spec/unit/models/delta_extras_spec.rb +14 -0
  56. data/spec/unit/models/error_info_spec.rb +17 -1
  57. data/spec/unit/models/message_spec.rb +97 -0
  58. data/spec/unit/models/presence_message_spec.rb +49 -0
  59. data/spec/unit/models/protocol_message_spec.rb +125 -27
  60. data/spec/unit/models/token_details_spec.rb +14 -0
  61. data/spec/unit/realtime/channel_spec.rb +3 -2
  62. data/spec/unit/realtime/channels_spec.rb +53 -15
  63. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +38 -0
  64. data/spec/unit/rest/channel_spec.rb +44 -1
  65. data/spec/unit/rest/channels_spec.rb +81 -14
  66. data/spec/unit/rest/client_spec.rb +47 -0
  67. metadata +60 -24
@@ -28,21 +28,20 @@ module Ably
28
28
  #
29
29
  # @param client [Ably::Rest::Client]
30
30
  # @param name [String] The name of the channel
31
- # @param channel_options [Hash] Channel options, currently reserved for Encryption options
32
- # @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}
33
32
  #
34
33
  def initialize(client, name, channel_options = {})
35
34
  name = (ensure_utf_8 :name, name)
36
35
 
37
- update_options channel_options
36
+ @options = Ably::Models::ChannelOptions(channel_options)
38
37
  @client = client
39
38
  @name = name
40
39
  @push = PushChannel.new(self)
41
40
  end
42
41
 
43
- # Publish one or more messages to the channel. Three overloaded forms
42
+ # Publish one or more messages to the channel. Five overloaded forms
44
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
45
- # @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
46
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)
47
46
  # @return [Boolean] true if the message was published, otherwise false
48
47
  #
@@ -50,42 +49,39 @@ module Ably
50
49
  # # Publish a single message with (name, data) form
51
50
  # channel.publish 'click', { x: 1, y: 2 }
52
51
  #
53
- # # 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
54
57
  # messages = [
55
58
  # { name: 'click', data: { x: 1, y: 2 } },
56
59
  # { name: 'click', data: { x: 2, y: 3 } }
57
60
  # ]
58
61
  # channel.publish messages
59
62
  #
60
- # # Publish an array of Ably::Models::Message objects
63
+ # # Publish an array of Ably::Models::Message objects form
61
64
  # messages = [
62
- # Ably::Models::Message(name: 'click', { x: 1, y: 2 })
63
- # 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 })
64
67
  # ]
65
68
  # channel.publish messages
66
69
  #
67
- # # Publish a single Ably::Models::Message object, with a query params
68
- # # specifying quickAck: true
69
- # message = Ably::Models::Message(name: 'click', { x: 1, y: 2 })
70
- # 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
71
73
  #
72
- def publish(first, second = nil, third = {})
73
- messages, qs_params = if first.kind_of?(Enumerable)
74
- # ([Message], qs_params) form
75
- [first, second]
76
- elsif first.kind_of?(Ably::Models::Message)
77
- # (Message, qs_params) form
78
- [[first], second]
79
- else
80
- # (name, data, attributes) form
81
- first = ensure_utf_8(:name, first, allow_nil: true)
82
- ensure_supported_payload second
83
- # RSL1h - attributes as an extra method parameter is extra-spec but need to
84
- # keep it for backcompat until version 2
85
- [[{ name: first, data: second }.merge(third)], nil]
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)
77
+
78
+ messages = build_messages(name, data, attributes) # (RSL1a, RSL1b)
79
+
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.")
86
82
  end
87
83
 
88
- payload = messages.each_with_index.map do |message, index|
84
+ payload = messages.map do |message|
89
85
  Ably::Models::Message(message.dup).tap do |msg|
90
86
  msg.encode client.encoders, options
91
87
 
@@ -157,12 +153,16 @@ module Ably
157
153
  @presence ||= Presence.new(client, self)
158
154
  end
159
155
 
160
- # @api private
161
- def update_options(channel_options)
162
- @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)
163
161
  end
162
+ alias options= set_options
164
163
 
165
164
  private
165
+
166
166
  def base_path
167
167
  "/channels/#{URI.encode_www_form_component(name)}"
168
168
  end
@@ -25,6 +25,9 @@ module Ably
25
25
  # Default Ably domain for REST
26
26
  DOMAIN = 'rest.ably.io'
27
27
 
28
+ MAX_MESSAGE_SIZE = 65536 # See spec TO3l8
29
+ MAX_FRAME_SIZE = 524288 # See spec TO3l8
30
+
28
31
  # Configuration for HTTP timeouts and HTTP request reattempts to fallback hosts
29
32
  HTTP_DEFAULTS = {
30
33
  open_timeout: 4,
@@ -52,6 +55,10 @@ module Ably
52
55
  # @return [Symbol]
53
56
  attr_reader :protocol
54
57
 
58
+ # Client agent i.e. `example-gem/1.2.0 ably-ruby/1.1.5 ruby/1.9.3`
59
+ # @return [String]
60
+ attr_reader :agent
61
+
55
62
  # {Ably::Auth} authentication object configured for this connection
56
63
  # @return [Ably::Auth]
57
64
  attr_reader :auth
@@ -112,6 +119,14 @@ module Ably
112
119
  # @return [Boolean]
113
120
  attr_reader :idempotent_rest_publishing
114
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
+
115
130
  # Creates a {Ably::Rest::Client Rest Client} and configures the {Ably::Auth} object for the connection.
116
131
  #
117
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
@@ -146,6 +161,8 @@ module Ably
146
161
  #
147
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.
148
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
149
166
  #
150
167
  # @return [Ably::Rest::Client]
151
168
  #
@@ -168,6 +185,7 @@ module Ably
168
185
  end
169
186
  end
170
187
 
188
+ @agent = options.delete(:agent) || Ably::AGENT
171
189
  @realtime_client = options.delete(:realtime_client)
172
190
  @tls = options.delete(:tls) == false ? false : true
173
191
  @environment = options.delete(:environment) # nil is production
@@ -181,8 +199,12 @@ module Ably
181
199
  @custom_tls_port = options.delete(:tls_port)
182
200
  @add_request_ids = options.delete(:add_request_ids)
183
201
  @log_retries_as_info = options.delete(:log_retries_as_info)
184
- @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
185
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
186
208
 
187
209
  if options[:fallback_hosts_use_default] && options[:fallback_hosts]
188
210
  raise ArgumentError, "fallback_hosts_use_default cannot be set to try when fallback_hosts is also provided"
@@ -358,6 +380,9 @@ module Ably
358
380
  send_request(method, path, params, headers: headers)
359
381
  end
360
382
  when :post, :patch, :put
383
+ if body.to_json.bytesize > max_frame_size
384
+ raise Ably::Exceptions::MaxFrameSizeExceeded.new("Maximum frame size exceeded #{max_frame_size} bytes.")
385
+ end
361
386
  path_with_params = Addressable::URI.new
362
387
  path_with_params.query_values = params || {}
363
388
  query = path_with_params.query
@@ -473,16 +498,6 @@ module Ably
473
498
  end
474
499
  end
475
500
 
476
- # Library Ably version user agent
477
- # @api private
478
- def lib_version_id
479
- @lib_version_id ||= [
480
- 'ruby',
481
- Ably.lib_variant,
482
- Ably::VERSION
483
- ].compact.join('-')
484
- end
485
-
486
501
  # Allowable duration for an external auth request
487
502
  # For REST client this defaults to request_timeout
488
503
  # For Realtime clients this defaults to 250ms less than the realtime_request_timeout
@@ -663,7 +678,7 @@ module Ably
663
678
  accept: mime_type,
664
679
  user_agent: user_agent,
665
680
  'X-Ably-Version' => Ably::PROTOCOL_VERSION,
666
- 'X-Ably-Lib' => lib_version_id
681
+ 'Ably-Agent' => agent
667
682
  },
668
683
  request: {
669
684
  open_timeout: http_defaults.fetch(:open_timeout),
@@ -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,18 +1,6 @@
1
1
  module Ably
2
- VERSION = '1.1.6'
3
- PROTOCOL_VERSION = '1.1'
4
-
5
- # Allow a variant to be configured for all instances of this client library
6
- # such as ruby-rest-[VERSION]
7
-
8
- # @api private
9
- def self.lib_variant=(variant)
10
- @lib_variant = variant
11
- end
12
-
13
- def self.lib_variant
14
- @lib_variant
15
- end
2
+ VERSION = '1.2.0'
3
+ PROTOCOL_VERSION = '1.2'
16
4
 
17
5
  # @api private
18
6
  def self.major_minor_version_numeric
data/lib/ably.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'addressable/uri'
2
2
 
3
3
  require 'ably/version'
4
+ require 'ably/agent'
4
5
 
5
6
  %w(modules util).each do |namespace|
6
7
  Dir.glob(File.expand_path("ably/#{namespace}/*.rb", File.dirname(__FILE__))).sort.each do |file|
@@ -1237,7 +1237,7 @@ describe Ably::Realtime::Auth, :event_machine do
1237
1237
  let(:basic_capability) { JSON.dump(channel_name => ['subscribe'], channel_with_publish_permissions => ['publish']) }
1238
1238
  let(:auth_callback) do
1239
1239
  lambda do |token_params|
1240
- Faraday.get("#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}&capability=#{URI.escape(basic_capability)}").body
1240
+ Faraday.get("#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}&capability=#{URI::Parser.new.escape(basic_capability)}").body
1241
1241
  end
1242
1242
  end
1243
1243
  let(:client_options) { default_options.merge(auth_callback: auth_callback, log_level: :error) }
@@ -183,6 +183,31 @@ describe Ably::Realtime::Channel, '#history', :event_machine do
183
183
  end
184
184
  end
185
185
 
186
+ context 'when channel receives update event after an attachment' do
187
+ before do
188
+ channel.on(:attached) do
189
+ channel.publish(event, message_after_attach) do
190
+ subsequent_serial = channel.properties.attach_serial.dup.tap { |serial| serial[-1] = '1' }
191
+ attached_message = Ably::Models::ProtocolMessage.new(action: 11, channel: channel_name, flags: 0, channel_serial: subsequent_serial)
192
+ client.connection.__incoming_protocol_msgbus__.publish :protocol_message, attached_message
193
+ end
194
+ end
195
+ end
196
+
197
+ it 'updates attach_serial' do
198
+ rest_channel.publish event, message_before_attach
199
+
200
+ channel.on(:update) do
201
+ channel.history(until_attach: true) do |messages|
202
+ expect(messages.items.count).to eql(2)
203
+ stop_reactor
204
+ end
205
+ end
206
+
207
+ channel.attach
208
+ end
209
+ end
210
+
186
211
  context 'and two pages of messages' do
187
212
  it 'retrieves two pages of messages before channel was attached' do
188
213
  10.times { rest_channel.publish event, message_before_attach }