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.
- checksums.yaml +4 -4
- data/.github/workflows/check.yml +15 -1
- data/CHANGELOG.md +131 -0
- data/COPYRIGHT +1 -1
- data/README.md +14 -2
- data/SPEC.md +0 -7
- data/UPDATING.md +30 -0
- data/ably.gemspec +12 -7
- data/lib/ably/agent.rb +3 -0
- data/lib/ably/auth.rb +3 -3
- data/lib/ably/exceptions.rb +6 -0
- data/lib/ably/models/channel_options.rb +97 -0
- data/lib/ably/models/connection_details.rb +8 -0
- data/lib/ably/models/delta_extras.rb +29 -0
- data/lib/ably/models/error_info.rb +6 -2
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -0
- data/lib/ably/models/message.rb +28 -3
- data/lib/ably/models/presence_message.rb +14 -0
- data/lib/ably/models/protocol_message.rb +29 -12
- data/lib/ably/models/token_details.rb +7 -2
- data/lib/ably/modules/channels_collection.rb +22 -2
- data/lib/ably/modules/conversions.rb +34 -0
- data/lib/ably/realtime/channel/channel_manager.rb +18 -6
- data/lib/ably/realtime/channel/channel_state_machine.rb +10 -1
- data/lib/ably/realtime/channel/publisher.rb +6 -0
- data/lib/ably/realtime/channel.rb +54 -22
- data/lib/ably/realtime/channels.rb +1 -1
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -6
- data/lib/ably/realtime/connection/connection_manager.rb +13 -4
- data/lib/ably/realtime/connection/connection_state_machine.rb +4 -0
- data/lib/ably/realtime/connection.rb +2 -2
- data/lib/ably/rest/channel.rb +31 -31
- data/lib/ably/rest/client.rb +27 -12
- data/lib/ably/util/crypto.rb +1 -1
- data/lib/ably/version.rb +2 -14
- data/lib/ably.rb +1 -0
- data/spec/acceptance/realtime/auth_spec.rb +1 -1
- data/spec/acceptance/realtime/channel_history_spec.rb +25 -0
- data/spec/acceptance/realtime/channel_spec.rb +466 -21
- data/spec/acceptance/realtime/channels_spec.rb +59 -7
- data/spec/acceptance/realtime/connection_failures_spec.rb +59 -2
- data/spec/acceptance/realtime/connection_spec.rb +256 -28
- data/spec/acceptance/realtime/message_spec.rb +77 -0
- data/spec/acceptance/realtime/presence_history_spec.rb +3 -1
- data/spec/acceptance/realtime/presence_spec.rb +31 -159
- data/spec/acceptance/rest/auth_spec.rb +18 -0
- data/spec/acceptance/rest/channel_spec.rb +84 -9
- data/spec/acceptance/rest/channels_spec.rb +23 -6
- data/spec/acceptance/rest/client_spec.rb +25 -21
- data/spec/acceptance/rest/message_spec.rb +61 -3
- data/spec/lib/unit/models/channel_options_spec.rb +52 -0
- data/spec/shared/model_behaviour.rb +1 -1
- data/spec/spec_helper.rb +11 -2
- data/spec/support/test_app.rb +1 -1
- data/spec/unit/models/delta_extras_spec.rb +14 -0
- data/spec/unit/models/error_info_spec.rb +17 -1
- data/spec/unit/models/message_spec.rb +97 -0
- data/spec/unit/models/presence_message_spec.rb +49 -0
- data/spec/unit/models/protocol_message_spec.rb +125 -27
- data/spec/unit/models/token_details_spec.rb +14 -0
- data/spec/unit/realtime/channel_spec.rb +3 -2
- data/spec/unit/realtime/channels_spec.rb +53 -15
- data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +38 -0
- data/spec/unit/rest/channel_spec.rb +44 -1
- data/spec/unit/rest/channels_spec.rb +81 -14
- data/spec/unit/rest/client_spec.rb +47 -0
- metadata +60 -24
data/lib/ably/rest/channel.rb
CHANGED
@@ -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]
|
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
|
-
|
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.
|
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,
|
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
|
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
|
68
|
-
#
|
69
|
-
# message
|
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(
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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.
|
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
|
-
#
|
161
|
-
|
162
|
-
|
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
|
data/lib/ably/rest/client.rb
CHANGED
@@ -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
|
-
@
|
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
|
-
'
|
681
|
+
'Ably-Agent' => agent
|
667
682
|
},
|
668
683
|
request: {
|
669
684
|
open_timeout: http_defaults.fetch(:open_timeout),
|
data/lib/ably/util/crypto.rb
CHANGED
@@ -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
|
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.
|
3
|
-
PROTOCOL_VERSION = '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
@@ -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 }
|