ably 1.1.4 → 1.1.8

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 (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