ably 1.1.4 → 1.1.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check.yml +15 -1
  3. data/CHANGELOG.md +109 -0
  4. data/COPYRIGHT +1 -1
  5. data/README.md +23 -9
  6. data/SPEC.md +289 -228
  7. data/ably.gemspec +14 -9
  8. data/lib/ably/agent.rb +3 -0
  9. data/lib/ably/exceptions.rb +6 -0
  10. data/lib/ably/models/connection_details.rb +8 -0
  11. data/lib/ably/models/delta_extras.rb +29 -0
  12. data/lib/ably/models/error_info.rb +6 -2
  13. data/lib/ably/models/message.rb +25 -0
  14. data/lib/ably/models/presence_message.rb +14 -0
  15. data/lib/ably/models/protocol_message.rb +13 -8
  16. data/lib/ably/modules/ably.rb +11 -1
  17. data/lib/ably/realtime/channel/channel_manager.rb +2 -2
  18. data/lib/ably/realtime/channel/channel_state_machine.rb +5 -1
  19. data/lib/ably/realtime/channel/publisher.rb +6 -0
  20. data/lib/ably/realtime/channel.rb +2 -0
  21. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -6
  22. data/lib/ably/realtime/client.rb +1 -0
  23. data/lib/ably/realtime/connection/connection_manager.rb +13 -4
  24. data/lib/ably/realtime/connection/connection_state_machine.rb +4 -0
  25. data/lib/ably/realtime/connection.rb +2 -2
  26. data/lib/ably/rest/channel.rb +11 -3
  27. data/lib/ably/rest/client.rb +37 -18
  28. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -1
  29. data/lib/ably/version.rb +1 -13
  30. data/lib/ably.rb +1 -0
  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 +220 -1
  34. data/spec/acceptance/realtime/connection_failures_spec.rb +85 -13
  35. data/spec/acceptance/realtime/connection_spec.rb +263 -32
  36. data/spec/acceptance/realtime/presence_history_spec.rb +3 -1
  37. data/spec/acceptance/realtime/presence_spec.rb +31 -159
  38. data/spec/acceptance/rest/base_spec.rb +8 -4
  39. data/spec/acceptance/rest/channel_spec.rb +84 -9
  40. data/spec/acceptance/rest/channels_spec.rb +1 -1
  41. data/spec/acceptance/rest/client_spec.rb +72 -33
  42. data/spec/shared/client_initializer_behaviour.rb +131 -0
  43. data/spec/shared/model_behaviour.rb +1 -1
  44. data/spec/spec_helper.rb +11 -2
  45. data/spec/support/test_app.rb +1 -1
  46. data/spec/unit/models/delta_extras_spec.rb +14 -0
  47. data/spec/unit/models/error_info_spec.rb +17 -1
  48. data/spec/unit/models/message_spec.rb +83 -0
  49. data/spec/unit/models/presence_message_spec.rb +49 -0
  50. data/spec/unit/models/protocol_message_spec.rb +72 -20
  51. data/spec/unit/realtime/channel_spec.rb +3 -2
  52. data/spec/unit/realtime/channels_spec.rb +3 -3
  53. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +38 -0
  54. data/spec/unit/rest/channel_spec.rb +44 -1
  55. data/spec/unit/rest/client_spec.rb +47 -0
  56. metadata +48 -36
data/lib/ably/version.rb CHANGED
@@ -1,19 +1,7 @@
1
1
  module Ably
2
- VERSION = '1.1.4'
2
+ VERSION = '1.1.8'
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
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 }
@@ -750,6 +750,107 @@ describe Ably::Realtime::Channel, :event_machine do
750
750
  end
751
751
  end
752
752
 
753
+ describe '#(RTL17)' do
754
+ context 'when channel is initialized' do
755
+ it 'sends messages only on attach' do
756
+ expect(channel).to be_initialized
757
+ channel.publish('event', payload)
758
+
759
+ channel.subscribe do |message|
760
+ stop_reactor if message.data == payload && channel.attached?
761
+ end
762
+
763
+ channel.attach
764
+ end
765
+ end
766
+
767
+ context 'when channel is attaching' do
768
+ it 'sends messages only on attach' do
769
+ channel.publish('event', payload)
770
+
771
+ sent_message = nil
772
+ channel.subscribe do |message|
773
+ return if message.data != payload
774
+ sent_message = message
775
+
776
+ stop_reactor if channel.attached?
777
+ end
778
+
779
+ channel.on(:attaching) do
780
+ expect(channel).to be_attaching
781
+ expect(sent_message).to be_nil
782
+ end
783
+
784
+ channel.attach
785
+ end
786
+ end
787
+
788
+ context 'when channel is detaching' do
789
+ it 'stops sending message' do
790
+ sent_message = nil
791
+ event_published = false
792
+ channel.subscribe do |message|
793
+ sent_message = message if message.data == payload
794
+ end
795
+
796
+ channel.on(:detaching) do
797
+ channel.publish('event', payload)
798
+ event_published = true
799
+ end
800
+
801
+ channel.on(:detaching) do
802
+ EventMachine.next_tick do
803
+ expect(sent_message).to be_nil
804
+ stop_reactor if event_published
805
+ end
806
+ end
807
+
808
+ channel.attach do
809
+ channel.detach
810
+ end
811
+ end
812
+ end
813
+
814
+ context 'when channel is detached' do
815
+ it 'stops sending message' do
816
+ sent_message = nil
817
+ event_published = false
818
+ channel.subscribe do |message|
819
+ sent_message = message if message.data == payload
820
+ end
821
+
822
+ channel.on(:detaching) do
823
+ channel.publish('event', payload)
824
+ event_published = true
825
+ end
826
+
827
+ channel.on(:detached) do
828
+ expect(sent_message).to be_nil
829
+ stop_reactor if event_published
830
+ end
831
+
832
+ channel.attach do
833
+ channel.detach
834
+ end
835
+ end
836
+ end
837
+
838
+ context 'when channel is failed' do
839
+ it 'errors when trying to send a message' do
840
+ channel.once(:failed) do
841
+ channel.publish('event', payload).errback do |error|
842
+ expect(error).to be_a(Ably::Exceptions::ChannelInactive)
843
+ stop_reactor
844
+ end
845
+ end
846
+
847
+ channel.attach do
848
+ channel.transition_state_machine(:failed)
849
+ end
850
+ end
851
+ end
852
+ end
853
+
753
854
  context 'when channel is not attached in state Initializing (#RTL6c1)' do
754
855
  it 'publishes messages immediately and does not implicitly attach (#RTL6c1)' do
755
856
  sub_channel.attach do
@@ -1181,7 +1282,7 @@ describe Ably::Realtime::Channel, :event_machine do
1181
1282
  end
1182
1283
 
1183
1284
  context 'with more than allowed messages in a single publish' do
1184
- let(:channel_name) { random_str }
1285
+ 65536
1185
1286
 
1186
1287
  it 'rejects the publish' do
1187
1288
  messages = (Ably::Realtime::Connection::MAX_PROTOCOL_MESSAGE_BATCH_SIZE + 1).times.map do
@@ -1403,6 +1504,103 @@ describe Ably::Realtime::Channel, :event_machine do
1403
1504
  end
1404
1505
  end
1405
1506
  end
1507
+
1508
+ context 'message size exceeded (#TO3l8)' do
1509
+ let(:client) { auto_close Ably::Realtime::Client.new(client_options) }
1510
+ let(:channel) { client.channels.get(channel_name) }
1511
+
1512
+ context 'and max_message_size is default (65536 bytes)' do
1513
+ let(:channel_name) { random_str }
1514
+ let(:max_message_size) { 65536 }
1515
+
1516
+ it 'should allow to send a message (32 bytes)' do
1517
+ client.connection.once(:connected) do
1518
+ channel.subscribe('event') do |msg|
1519
+ expect(msg.data).to eq('x' * 32)
1520
+ stop_reactor
1521
+ end
1522
+ channel.publish('event', 'x' * 32)
1523
+ end
1524
+ end
1525
+
1526
+ it 'should not allow to send a message (700000 bytes)' do
1527
+ client.connection.once(:connected) do
1528
+ connection_details = Ably::Models::ConnectionDetails.new(
1529
+ client.connection.details.attributes.attributes.merge('maxMessageSize' => max_message_size)
1530
+ )
1531
+ client.connection.set_connection_details(connection_details)
1532
+ expect(client.connection.details.max_message_size).to eq(65536)
1533
+ channel.publish('event', 'x' * 700000).errback do |error|
1534
+ expect(error).to be_instance_of(Ably::Exceptions::MaxMessageSizeExceeded)
1535
+ stop_reactor
1536
+ end
1537
+ end
1538
+ end
1539
+ end
1540
+
1541
+ context 'and max_message_size is customized (11 bytes)' do
1542
+ let(:max_message_size) { 11 }
1543
+
1544
+ context 'and the message size is 30 bytes' do
1545
+ let(:channel_name) { random_str }
1546
+
1547
+ it 'should not allow to send a message' do
1548
+ client.connection.once(:connected) do
1549
+ connection_details = Ably::Models::ConnectionDetails.new(
1550
+ client.connection.details.attributes.attributes.merge('maxMessageSize' => max_message_size)
1551
+ )
1552
+ client.connection.set_connection_details(connection_details)
1553
+ expect(client.connection.details.max_message_size).to eq(11)
1554
+ channel.publish('event', 'x' * 30).errback do |error|
1555
+ expect(error).to be_instance_of(Ably::Exceptions::MaxMessageSizeExceeded)
1556
+ stop_reactor
1557
+ end
1558
+ end
1559
+ end
1560
+ end
1561
+ end
1562
+
1563
+ context 'and max_message_size is nil' do
1564
+ let(:max_message_size) { nil }
1565
+
1566
+ context 'and the message size is 30 bytes' do
1567
+ let(:channel_name) { random_str }
1568
+
1569
+ it 'should allow to send a message' do
1570
+ client.connection.once(:connected) do
1571
+ connection_details = Ably::Models::ConnectionDetails.new(
1572
+ client.connection.details.attributes.attributes.merge('maxMessageSize' => max_message_size)
1573
+ )
1574
+ client.connection.set_connection_details(connection_details)
1575
+ expect(client.connection.details.max_message_size).to eq(65536)
1576
+ channel.subscribe('event') do |msg|
1577
+ expect(msg.data).to eq('x' * 30)
1578
+ stop_reactor
1579
+ end
1580
+ channel.publish('event', 'x' * 30)
1581
+ end
1582
+ end
1583
+ end
1584
+
1585
+ context 'and the message size is 65537 bytes' do
1586
+ let(:channel_name) { random_str }
1587
+
1588
+ it 'should not allow to send a message' do
1589
+ client.connection.once(:connected) do
1590
+ connection_details = Ably::Models::ConnectionDetails.new(
1591
+ client.connection.details.attributes.attributes.merge('maxMessageSize' => max_message_size)
1592
+ )
1593
+ client.connection.set_connection_details(connection_details)
1594
+ expect(client.connection.details.max_message_size).to eq(65536)
1595
+ channel.publish('event', 'x' * 65537).errback do |error|
1596
+ expect(error).to be_instance_of(Ably::Exceptions::MaxMessageSizeExceeded)
1597
+ stop_reactor
1598
+ end
1599
+ end
1600
+ end
1601
+ end
1602
+ end
1603
+ end
1406
1604
  end
1407
1605
 
1408
1606
  describe '#subscribe' do
@@ -2210,6 +2408,27 @@ describe Ably::Realtime::Channel, :event_machine do
2210
2408
  channel.transition_state_machine! :suspended
2211
2409
  end
2212
2410
  end
2411
+
2412
+ context 'when connection is no longer connected' do
2413
+ it 'will not attempt to reattach (#RTL13c)' do
2414
+ channel.attach do
2415
+ connection.once(:closing) do
2416
+ channel.once(:attaching) do |state_change|
2417
+ raise 'Channel should not attempt to reattach'
2418
+ end
2419
+
2420
+ channel.transition_state_machine! :suspended
2421
+ end
2422
+
2423
+ connection.once(:closed) do
2424
+ expect(channel).to be_suspended
2425
+ stop_reactor
2426
+ end
2427
+
2428
+ connection.close
2429
+ end
2430
+ end
2431
+ end
2213
2432
  end
2214
2433
 
2215
2434
  context 'and channel is attaching' 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
@@ -560,7 +570,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
560
570
  end
561
571
 
562
572
  context 'when DISCONNECTED ProtocolMessage received from the server' do
563
- it 'reconnects automatically and immediately' do
573
+ it 'reconnects automatically and immediately (#RTN15a)' do
564
574
  fail_if_suspended_or_failed
565
575
 
566
576
  connection.once(:connected) do
@@ -580,6 +590,61 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
580
590
  end
581
591
  end
582
592
 
593
+ context 'when protocolMessage contains token error' do
594
+ context "library does not have a means to renew the token (#RTN15h1)" do
595
+ let(:auth_url) { 'https://echo.ably.io/createJWT' }
596
+ let(:token) { Faraday.get("#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}").body }
597
+ let(:client_options) { default_options.merge(token: token, log_level: :none) }
598
+
599
+ let(:error_message) { 'error_message' }
600
+
601
+ it 'moves connection state to failed' do
602
+ connection.on(:failed) do |connection_state_change|
603
+ expect(connection.error_reason.message).to eq(error_message)
604
+ stop_reactor
605
+ end
606
+
607
+ connection.on(:connected) do
608
+ protocol_message = Ably::Models::ProtocolMessage.new(action: Ably::Models::ProtocolMessage::ACTION.Disconnected.to_i, error: { code: 40140, message: error_message })
609
+ connection.__incoming_protocol_msgbus__.publish :protocol_message, protocol_message
610
+ end
611
+
612
+ connection.connect
613
+ end
614
+ end
615
+
616
+ context "library have a means to renew the token (#RTN15h2)" do
617
+ let(:client_options) { default_options.merge(log_level: :none) }
618
+ let(:error_message) { 'error_message' }
619
+
620
+ def send_disconnect_message
621
+ protocol_message = Ably::Models::ProtocolMessage.new(action: Ably::Models::ProtocolMessage::ACTION.Disconnected.to_i, error: { code: 40140, message: error_message })
622
+ connection.__incoming_protocol_msgbus__.publish :protocol_message, protocol_message
623
+ end
624
+
625
+ it 'attempts to reconnect' do
626
+ connection.on(:failed) do |connection_state_change|
627
+ raise "Connection shouldn't be failed"
628
+ end
629
+
630
+ connection.on(:connected) do
631
+ connection.once(:connecting) do
632
+ connection.once(:disconnected) do
633
+ expect(connection.error_reason.message).to eq(error_message)
634
+ stop_reactor
635
+ end
636
+
637
+ send_disconnect_message
638
+ end
639
+
640
+ send_disconnect_message
641
+ end
642
+
643
+ connection.connect
644
+ end
645
+ end
646
+ end
647
+
583
648
  context 'connection state freshness is monitored' do
584
649
  it 'resumes connections when disconnected within the connection_state_ttl period (#RTN15g)' do
585
650
  connection.once(:connected) do
@@ -1310,7 +1375,9 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
1310
1375
  end)
1311
1376
  end
1312
1377
 
1313
- it 'triggers a re-authentication and then resumes the connection' do
1378
+ xit 'triggers a re-authentication and then resumes the connection' do
1379
+ # [PENDING] After sandbox env update connection isn't found and a new connection is created. Spec fails
1380
+ #
1314
1381
  connection.once(:connected) do
1315
1382
  connection_id = connection.id
1316
1383
 
@@ -1423,14 +1490,19 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
1423
1490
  let(:expected_host) { "#{environment}-#{Ably::Realtime::Client::DOMAIN}" }
1424
1491
  let(:client_options) { timeout_options.merge(environment: environment) }
1425
1492
 
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
1493
+ context ':fallback_hosts_use_default is unset' do
1494
+ let(:max_time_in_state_for_tests) { 8 }
1495
+ let(:expected_hosts) { Ably::CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |suffix| "#{environment}#{suffix}" } + [expected_host] }
1496
+ let(:fallback_hosts_used) { Array.new }
1497
+
1498
+ it 'uses fallback hosts by default' do
1499
+ allow(connection).to receive(:create_transport) do |host|
1500
+ fallback_hosts_used << host
1501
+ raise EventMachine::ConnectionError
1502
+ end
1431
1503
 
1432
- connection.once(:suspended) do
1433
1504
  connection.once(:suspended) do
1505
+ expect(fallback_hosts_used.uniq).to match_array(expected_hosts)
1434
1506
  stop_reactor
1435
1507
  end
1436
1508
  end
@@ -1508,7 +1580,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
1508
1580
  end
1509
1581
 
1510
1582
  context 'with production environment' do
1511
- let(:custom_hosts) { %w(A.ably-realtime.com B.ably-realtime.com) }
1583
+ let(:custom_hosts) { %w(a.ably-realtime.com b.ably-realtime.com) }
1512
1584
  before do
1513
1585
  stub_const 'Ably::FALLBACK_HOSTS', custom_hosts
1514
1586
  end