ably 1.1.4.rc → 1.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check.yml +41 -0
  3. data/CHANGELOG.md +85 -0
  4. data/COPYRIGHT +1 -0
  5. data/LICENSE +173 -10
  6. data/MAINTAINERS.md +1 -0
  7. data/README.md +24 -18
  8. data/SPEC.md +1020 -922
  9. data/ably.gemspec +13 -8
  10. data/lib/ably.rb +1 -0
  11. data/lib/ably/agent.rb +3 -0
  12. data/lib/ably/auth.rb +12 -2
  13. data/lib/ably/exceptions.rb +6 -0
  14. data/lib/ably/models/connection_details.rb +2 -0
  15. data/lib/ably/models/message.rb +14 -0
  16. data/lib/ably/models/presence_message.rb +14 -0
  17. data/lib/ably/models/protocol_message.rb +8 -0
  18. data/lib/ably/modules/ably.rb +11 -1
  19. data/lib/ably/realtime/channel.rb +7 -11
  20. data/lib/ably/realtime/channel/channel_manager.rb +3 -3
  21. data/lib/ably/realtime/channel/channel_properties.rb +24 -0
  22. data/lib/ably/realtime/channel/publisher.rb +5 -0
  23. data/lib/ably/realtime/client.rb +9 -0
  24. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -6
  25. data/lib/ably/realtime/connection.rb +9 -5
  26. data/lib/ably/realtime/connection/websocket_transport.rb +67 -1
  27. data/lib/ably/realtime/presence.rb +0 -14
  28. data/lib/ably/rest/channel.rb +10 -3
  29. data/lib/ably/rest/client.rb +22 -21
  30. data/lib/ably/version.rb +1 -13
  31. data/spec/acceptance/realtime/auth_spec.rb +1 -1
  32. data/spec/acceptance/realtime/channel_history_spec.rb +25 -0
  33. data/spec/acceptance/realtime/channel_spec.rb +24 -0
  34. data/spec/acceptance/realtime/client_spec.rb +72 -16
  35. data/spec/acceptance/realtime/connection_failures_spec.rb +29 -12
  36. data/spec/acceptance/realtime/connection_spec.rb +31 -33
  37. data/spec/acceptance/realtime/presence_history_spec.rb +3 -59
  38. data/spec/acceptance/realtime/presence_spec.rb +66 -157
  39. data/spec/acceptance/realtime/push_admin_spec.rb +3 -19
  40. data/spec/acceptance/rest/auth_spec.rb +6 -75
  41. data/spec/acceptance/rest/base_spec.rb +8 -4
  42. data/spec/acceptance/rest/channel_spec.rb +13 -0
  43. data/spec/acceptance/rest/client_spec.rb +144 -45
  44. data/spec/acceptance/rest/push_admin_spec.rb +3 -19
  45. data/spec/shared/client_initializer_behaviour.rb +131 -8
  46. data/spec/shared/model_behaviour.rb +1 -1
  47. data/spec/spec_helper.rb +12 -2
  48. data/spec/support/serialization_helper.rb +21 -0
  49. data/spec/unit/models/message_spec.rb +59 -0
  50. data/spec/unit/models/presence_message_spec.rb +49 -0
  51. data/spec/unit/models/protocol_message_spec.rb +48 -0
  52. data/spec/unit/realtime/channel_spec.rb +1 -1
  53. data/spec/unit/realtime/client_spec.rb +19 -6
  54. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +38 -0
  55. data/spec/unit/rest/channel_spec.rb +10 -0
  56. data/spec/unit/rest/client_spec.rb +20 -0
  57. metadata +52 -32
  58. data/.travis.yml +0 -18
@@ -278,28 +278,14 @@ module Ably::Realtime
278
278
 
279
279
  # Return the presence messages history for the channel
280
280
  #
281
- # Once attached to a channel, you can retrieve presence message history on the channel before the
282
- # channel was attached with the option <tt>until_attach: true</tt>. This is very useful for
283
- # developers who wish to capture new presence events as well as retrieve historical presence state with
284
- # the guarantee that no presence history has been missed.
285
- #
286
281
  # @param (see Ably::Rest::Presence#history)
287
282
  # @option options (see Ably::Rest::Presence#history)
288
- # @option options [Boolean] :until_attach When true, request for history will be limited only to messages published before the associated channel was attached. The associated channel must be attached.
289
283
  #
290
284
  # @yield [Ably::Models::PaginatedResult<Ably::Models::PresenceMessage>] First {Ably::Models::PaginatedResult page} of {Ably::Models::PresenceMessage} objects accessible with {Ably::Models::PaginatedResult#items #items}.
291
285
  #
292
286
  # @return [Ably::Util::SafeDeferrable]
293
287
  #
294
288
  def history(options = {}, &callback)
295
- if options.delete(:until_attach)
296
- unless channel.attached?
297
- error = Ably::Exceptions::InvalidRequest.new('option :until_attach is invalid as the channel is not attached')
298
- return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
299
- end
300
- options[:from_serial] = channel.attached_serial
301
- end
302
-
303
289
  async_wrap(callback) do
304
290
  rest_presence.history(options.merge(async_blocking_operations: true))
305
291
  end
@@ -23,6 +23,7 @@ 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
26
27
 
27
28
  # Initialize a new Channel object
28
29
  #
@@ -52,8 +53,8 @@ module Ably
52
53
  #
53
54
  # # Publish an array of message Hashes
54
55
  # messages = [
55
- # { name: 'click', { x: 1, y: 2 } },
56
- # { name: 'click', { x: 2, y: 3 } }
56
+ # { name: 'click', data: { x: 1, y: 2 } },
57
+ # { name: 'click', data: { x: 2, y: 3 } }
57
58
  # ]
58
59
  # channel.publish messages
59
60
  #
@@ -85,7 +86,13 @@ module Ably
85
86
  [[{ name: first, data: second }.merge(third)], nil]
86
87
  end
87
88
 
88
- payload = messages.each_with_index.map do |message, index|
89
+ messages.map! { |message| Ably::Models::Message(message.dup) }
90
+
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}.")
93
+ end
94
+
95
+ payload = messages.map do |message|
89
96
  Ably::Models::Message(message.dup).tap do |msg|
90
97
  msg.encode client.encoders, options
91
98
 
@@ -25,6 +25,8 @@ module Ably
25
25
  # Default Ably domain for REST
26
26
  DOMAIN = 'rest.ably.io'
27
27
 
28
+ MAX_FRAME_SIZE = 524288
29
+
28
30
  # Configuration for HTTP timeouts and HTTP request reattempts to fallback hosts
29
31
  HTTP_DEFAULTS = {
30
32
  open_timeout: 4,
@@ -52,6 +54,10 @@ module Ably
52
54
  # @return [Symbol]
53
55
  attr_reader :protocol
54
56
 
57
+ # Client agent i.e. `example-gem/1.2.0 ably-ruby/1.1.5 ruby/1.9.3`
58
+ # @return [String]
59
+ attr_reader :agent
60
+
55
61
  # {Ably::Auth} authentication object configured for this connection
56
62
  # @return [Ably::Auth]
57
63
  attr_reader :auth
@@ -168,6 +174,7 @@ module Ably
168
174
  end
169
175
  end
170
176
 
177
+ @agent = options.delete(:agent) || Ably::AGENT
171
178
  @realtime_client = options.delete(:realtime_client)
172
179
  @tls = options.delete(:tls) == false ? false : true
173
180
  @environment = options.delete(:environment) # nil is production
@@ -184,16 +191,18 @@ module Ably
184
191
  @idempotent_rest_publishing = options.delete(:idempotent_rest_publishing) || Ably.major_minor_version_numeric > 1.1
185
192
 
186
193
 
187
- if options[:fallback_hosts_use_default] && options[:fallback_jhosts]
188
- raise ArgumentError, "fallback_hosts_use_default cannot be set to trye when fallback_jhosts is also provided"
194
+ if options[:fallback_hosts_use_default] && options[:fallback_hosts]
195
+ raise ArgumentError, "fallback_hosts_use_default cannot be set to try when fallback_hosts is also provided"
189
196
  end
190
197
  @fallback_hosts = case
191
198
  when options.delete(:fallback_hosts_use_default)
192
199
  Ably::FALLBACK_HOSTS
193
200
  when options_fallback_hosts = options.delete(:fallback_hosts)
194
201
  options_fallback_hosts
195
- when environment || custom_host || options[:realtime_host] || custom_port || custom_tls_port
202
+ when custom_host || options[:realtime_host] || custom_port || custom_tls_port
196
203
  []
204
+ when environment
205
+ CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |host| "#{environment}#{host}" }
197
206
  else
198
207
  Ably::FALLBACK_HOSTS
199
208
  end
@@ -348,14 +357,17 @@ module Ably
348
357
  #
349
358
  # @return [Ably::Models::HttpPaginatedResponse<>]
350
359
  def request(method, path, params = {}, body = nil, headers = {}, options = {})
351
- raise "Method #{method.to_s.upcase} not supported" unless [:get, :put, :post].include?(method.to_sym)
360
+ raise "Method #{method.to_s.upcase} not supported" unless %i(get put patch post delete).include?(method.to_sym)
352
361
 
353
362
  response = case method.to_sym
354
- when :get
363
+ when :get, :delete
355
364
  reauthorize_on_authorization_failure do
356
365
  send_request(method, path, params, headers: headers)
357
366
  end
358
- when :post
367
+ 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.")
370
+ end
359
371
  path_with_params = Addressable::URI.new
360
372
  path_with_params.query_values = params || {}
361
373
  query = path_with_params.query
@@ -471,16 +483,6 @@ module Ably
471
483
  end
472
484
  end
473
485
 
474
- # Library Ably version user agent
475
- # @api private
476
- def lib_version_id
477
- @lib_version_id ||= [
478
- 'ruby',
479
- Ably.lib_variant,
480
- Ably::VERSION
481
- ].compact.join('-')
482
- end
483
-
484
486
  # Allowable duration for an external auth request
485
487
  # For REST client this defaults to request_timeout
486
488
  # For Realtime clients this defaults to 250ms less than the realtime_request_timeout
@@ -576,10 +578,9 @@ module Ably
576
578
  end
577
579
  unless options[:send_auth_header] == false
578
580
  request.headers[:authorization] = auth.auth_header
579
- if options[:headers]
580
- options[:headers].map do |key, val|
581
- request.headers[key] = val
582
- end
581
+
582
+ options[:headers].to_h.merge(auth.extra_auth_headers).map do |key, val|
583
+ request.headers[key] = val
583
584
  end
584
585
  end
585
586
  end.tap do
@@ -662,7 +663,7 @@ module Ably
662
663
  accept: mime_type,
663
664
  user_agent: user_agent,
664
665
  'X-Ably-Version' => Ably::PROTOCOL_VERSION,
665
- 'X-Ably-Lib' => lib_version_id
666
+ 'Ably-Agent' => agent
666
667
  },
667
668
  request: {
668
669
  open_timeout: http_defaults.fetch(:open_timeout),
data/lib/ably/version.rb CHANGED
@@ -1,19 +1,7 @@
1
1
  module Ably
2
- VERSION = '1.1.4.rc'
2
+ VERSION = '1.1.7'
3
3
  PROTOCOL_VERSION = '1.1'
4
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
16
-
17
5
  # @api private
18
6
  def self.major_minor_version_numeric
19
7
  VERSION.gsub(/\.\d+$/, '').to_f
@@ -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 }
@@ -83,6 +83,16 @@ describe Ably::Realtime::Channel, :event_machine do
83
83
  end
84
84
  end
85
85
 
86
+ it 'sets attach_serial property after the attachment (#RTL15a)' do
87
+ expect(channel.properties.attach_serial).to be_nil
88
+
89
+ channel.attach
90
+ channel.on(:attached) do
91
+ expect(channel.properties.attach_serial).to_not be_nil
92
+ stop_reactor
93
+ end
94
+ end
95
+
86
96
  it 'sends an ATTACH and waits for an ATTACHED (#RTL4c)' do
87
97
  connection.once(:connected) do
88
98
  attach_count = 0
@@ -1393,6 +1403,20 @@ describe Ably::Realtime::Channel, :event_machine do
1393
1403
  end
1394
1404
  end
1395
1405
  end
1406
+
1407
+ context 'message size exceeded (#TO3l8)' do
1408
+ let(:message) { 'x' * 700000 }
1409
+
1410
+ let(:client) { auto_close Ably::Realtime::Client.new(client_options) }
1411
+ let(:channel) { client.channels.get(channel_name) }
1412
+
1413
+ it 'should not allow to send a message' do
1414
+ channel.publish('event', message).errback do |error|
1415
+ expect(error).to be_instance_of(Ably::Exceptions::MaxMessageSizeExceeded)
1416
+ stop_reactor
1417
+ end
1418
+ end
1419
+ end
1396
1420
  end
1397
1421
 
1398
1422
  describe '#subscribe' do
@@ -87,22 +87,6 @@ describe Ably::Realtime::Client, :event_machine do
87
87
  end
88
88
  end
89
89
  end
90
-
91
- context 'with client_id' do
92
- let(:client_options) do
93
- default_options.merge(client_id: random_str)
94
- end
95
- it 'connects using token auth' do
96
- run_reactor do
97
- connection.on(:connected) do
98
- expect(connection.state).to eq(:connected)
99
- expect(auth_params[:access_token]).to_not be_nil
100
- expect(auth_params[:key]).to be_nil
101
- stop_reactor
102
- end
103
- end
104
- end
105
- end
106
90
  end
107
91
  end
108
92
 
@@ -249,6 +233,8 @@ describe Ably::Realtime::Client, :event_machine do
249
233
 
250
234
  context '#request (#RSC19*)' do
251
235
  let(:client_options) { default_options.merge(key: api_key) }
236
+ let(:device_id) { random_str }
237
+ let(:endpoint) { subject.rest_client.endpoint }
252
238
 
253
239
  context 'get' do
254
240
  it 'returns an HttpPaginatedResponse object' do
@@ -297,6 +283,76 @@ describe Ably::Realtime::Client, :event_machine do
297
283
  end
298
284
  end
299
285
  end
286
+
287
+
288
+ context 'post', :webmock do
289
+ before do
290
+ stub_request(:delete, "#{endpoint}/push/deviceRegistrations/#{device_id}/resetUpdateToken").
291
+ to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
292
+ end
293
+
294
+ it 'supports post' do
295
+ subject.request(:delete, "push/deviceRegistrations/#{device_id}/resetUpdateToken").callback do |response|
296
+ expect(response).to be_success
297
+ stop_reactor
298
+ end
299
+ end
300
+ end
301
+
302
+ context 'delete', :webmock do
303
+ before do
304
+ stub_request(:delete, "#{endpoint}/push/channelSubscriptions?deviceId=#{device_id}").
305
+ to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
306
+ end
307
+
308
+ it 'supports delete' do
309
+ subject.request(:delete, "/push/channelSubscriptions", { deviceId: device_id}).callback do |response|
310
+ expect(response).to be_success
311
+ stop_reactor
312
+ end
313
+ end
314
+ end
315
+
316
+ context 'patch', :webmock do
317
+ let(:body_params) { { 'metadata' => { 'key' => 'value' } } }
318
+
319
+ before do
320
+ stub_request(:patch, "#{endpoint}/push/deviceRegistrations/#{device_id}")
321
+ .with(body: serialize_body(body_params, protocol))
322
+ .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
323
+ end
324
+
325
+ it 'supports patch' do
326
+ subject.request(:patch, "/push/deviceRegistrations/#{device_id}", {}, body_params).callback do |response|
327
+ expect(response).to be_success
328
+ stop_reactor
329
+ end
330
+ end
331
+ end
332
+
333
+ context 'put', :webmock do
334
+ let(:body_params) do
335
+ {
336
+ 'id' => random_str,
337
+ 'platform' => 'ios',
338
+ 'formFactor' => 'phone',
339
+ 'metadata' => { 'key' => 'value' }
340
+ }
341
+ end
342
+
343
+ before do
344
+ stub_request(:put, "#{endpoint}/push/deviceRegistrations/#{device_id}")
345
+ .with(body: serialize_body(body_params, protocol))
346
+ .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
347
+ end
348
+
349
+ it 'supports put' do
350
+ subject.request(:put, "/push/deviceRegistrations/#{device_id}", {}, body_params).callback do |response|
351
+ expect(response).to be_success
352
+ stop_reactor
353
+ end
354
+ end
355
+ end
300
356
  end
301
357
 
302
358
  context '#publish (#TBC)' do
@@ -156,10 +156,20 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
156
156
 
157
157
  stub_request(:get, auth_url).
158
158
  to_return do |request|
159
- sleep Ably::Rest::Client::HTTP_DEFAULTS.fetch(:request_timeout)
160
- { status: [500, "Internal Server Error"] }
161
- end.then.
162
- to_return(:status => 201, :body => token_response.to_json, :headers => { 'Content-Type' => 'application/json' })
159
+ sleep Ably::Rest::Client::HTTP_DEFAULTS.fetch(:request_timeout)
160
+ { status: [500, "Internal Server Error"] }
161
+ end.then.
162
+ to_return(:status => 201, :body => token_response.to_json, :headers => { 'Content-Type' => 'application/json' })
163
+
164
+ stub_request(:get, 'https://internet-up.ably-realtime.com/is-the-internet-up.txt')
165
+ .with(
166
+ headers: {
167
+ 'Accept-Encoding' => 'gzip, compressed',
168
+ 'Connection' => 'close',
169
+ 'Host' => 'internet-up.ably-realtime.com',
170
+ 'User-Agent' => 'EventMachine HttpClient'
171
+ }
172
+ ).to_return(status: 200, body: 'yes\n', headers: { 'Content-Type' => 'text/plain' })
163
173
  end
164
174
 
165
175
  specify 'the connection moves to the disconnected state and tries again, returning again to the disconnected state (#RSA4c, #RSA4c1, #RSA4c2)' do
@@ -1310,7 +1320,9 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
1310
1320
  end)
1311
1321
  end
1312
1322
 
1313
- it 'triggers a re-authentication and then resumes the connection' do
1323
+ xit 'triggers a re-authentication and then resumes the connection' do
1324
+ # [PENDING] After sandbox env update connection isn't found and a new connection is created. Spec fails
1325
+ #
1314
1326
  connection.once(:connected) do
1315
1327
  connection_id = connection.id
1316
1328
 
@@ -1423,14 +1435,19 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
1423
1435
  let(:expected_host) { "#{environment}-#{Ably::Realtime::Client::DOMAIN}" }
1424
1436
  let(:client_options) { timeout_options.merge(environment: environment) }
1425
1437
 
1426
- it 'does not use a fallback host by default' do
1427
- expect(connection).to receive(:create_transport).exactly(retry_count_for_all_states).times do |host|
1428
- expect(host).to eql(expected_host)
1429
- raise EventMachine::ConnectionError
1430
- end
1438
+ context ':fallback_hosts_use_default is unset' do
1439
+ let(:max_time_in_state_for_tests) { 8 }
1440
+ let(:expected_hosts) { Ably::CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |suffix| "#{environment}#{suffix}" } + [expected_host] }
1441
+ let(:fallback_hosts_used) { Array.new }
1442
+
1443
+ it 'uses fallback hosts by default' do
1444
+ allow(connection).to receive(:create_transport) do |host|
1445
+ fallback_hosts_used << host
1446
+ raise EventMachine::ConnectionError
1447
+ end
1431
1448
 
1432
- connection.once(:suspended) do
1433
1449
  connection.once(:suspended) do
1450
+ expect(fallback_hosts_used.uniq).to match_array(expected_hosts)
1434
1451
  stop_reactor
1435
1452
  end
1436
1453
  end
@@ -1508,7 +1525,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
1508
1525
  end
1509
1526
 
1510
1527
  context 'with production environment' do
1511
- let(:custom_hosts) { %w(A.ably-realtime.com B.ably-realtime.com) }
1528
+ let(:custom_hosts) { %w(a.ably-realtime.com b.ably-realtime.com) }
1512
1529
  before do
1513
1530
  stub_const 'Ably::FALLBACK_HOSTS', custom_hosts
1514
1531
  end