ably-rest 1.0.5 → 1.1.3

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 (118) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +6 -3
  3. data/CHANGELOG.md +1 -1
  4. data/LICENSE +1 -1
  5. data/README.md +26 -7
  6. data/SPEC.md +2003 -1605
  7. data/ably-rest.gemspec +4 -2
  8. data/lib/submodules/ably-ruby/.editorconfig +14 -0
  9. data/lib/submodules/ably-ruby/.travis.yml +10 -8
  10. data/lib/submodules/ably-ruby/CHANGELOG.md +97 -1
  11. data/lib/submodules/ably-ruby/LICENSE +1 -3
  12. data/lib/submodules/ably-ruby/README.md +12 -7
  13. data/lib/submodules/ably-ruby/Rakefile +32 -0
  14. data/lib/submodules/ably-ruby/SPEC.md +1277 -835
  15. data/lib/submodules/ably-ruby/ably.gemspec +17 -11
  16. data/lib/submodules/ably-ruby/lib/ably/auth.rb +34 -8
  17. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +10 -4
  18. data/lib/submodules/ably-ruby/lib/ably/logger.rb +8 -2
  19. data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +1 -1
  20. data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +1 -1
  21. data/lib/submodules/ably-ruby/lib/ably/models/device_details.rb +87 -0
  22. data/lib/submodules/ably-ruby/lib/ably/models/device_push_details.rb +86 -0
  23. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +23 -2
  24. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +12 -12
  25. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +6 -4
  26. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +6 -4
  27. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +32 -2
  28. data/lib/submodules/ably-ruby/lib/ably/models/push_channel_subscription.rb +89 -0
  29. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +2 -2
  30. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +2 -2
  31. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +2 -2
  32. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +2 -2
  33. data/lib/submodules/ably-ruby/lib/ably/modules/exception_codes.rb +128 -0
  34. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +15 -2
  35. data/lib/submodules/ably-ruby/lib/ably/modules/safe_deferrable.rb +1 -1
  36. data/lib/submodules/ably-ruby/lib/ably/modules/safe_yield.rb +1 -1
  37. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +5 -5
  38. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -2
  39. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +1 -0
  40. data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +2 -2
  41. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +27 -105
  42. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +4 -8
  43. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
  44. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/publisher.rb +74 -0
  45. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/push_channel.rb +62 -0
  46. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +91 -3
  47. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +9 -4
  48. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  49. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +45 -26
  50. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +25 -9
  51. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +2 -2
  52. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +7 -7
  53. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +9 -9
  54. data/lib/submodules/ably-ruby/lib/ably/realtime/push.rb +40 -0
  55. data/lib/submodules/ably-ruby/lib/ably/realtime/push/admin.rb +61 -0
  56. data/lib/submodules/ably-ruby/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
  57. data/lib/submodules/ably-ruby/lib/ably/realtime/push/device_registrations.rb +105 -0
  58. data/lib/submodules/ably-ruby/lib/ably/rest.rb +1 -0
  59. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +54 -18
  60. data/lib/submodules/ably-ruby/lib/ably/rest/channel/push_channel.rb +62 -0
  61. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +171 -41
  62. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
  63. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -0
  64. data/lib/submodules/ably-ruby/lib/ably/rest/push.rb +42 -0
  65. data/lib/submodules/ably-ruby/lib/ably/rest/push/admin.rb +54 -0
  66. data/lib/submodules/ably-ruby/lib/ably/rest/push/channel_subscriptions.rb +121 -0
  67. data/lib/submodules/ably-ruby/lib/ably/rest/push/device_registrations.rb +103 -0
  68. data/lib/submodules/ably-ruby/lib/ably/version.rb +7 -2
  69. data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +253 -49
  70. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +33 -21
  71. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +180 -62
  72. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +155 -2
  73. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +293 -13
  74. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +142 -39
  75. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +38 -36
  76. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +12 -3
  77. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +207 -173
  78. data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_admin_spec.rb +736 -0
  79. data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_spec.rb +27 -0
  80. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +62 -51
  81. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +2 -2
  82. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +79 -4
  83. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +6 -0
  84. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +318 -74
  85. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +158 -6
  86. data/lib/submodules/ably-ruby/spec/acceptance/rest/push_admin_spec.rb +952 -0
  87. data/lib/submodules/ably-ruby/spec/acceptance/rest/push_spec.rb +25 -0
  88. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +1 -1
  89. data/lib/submodules/ably-ruby/spec/run_parallel_tests +33 -0
  90. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +1 -9
  91. data/lib/submodules/ably-ruby/spec/spec_helper.rb +3 -1
  92. data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +9 -5
  93. data/lib/submodules/ably-ruby/spec/support/event_emitter_helper.rb +31 -0
  94. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +1 -1
  95. data/lib/submodules/ably-ruby/spec/support/test_app.rb +2 -2
  96. data/lib/submodules/ably-ruby/spec/support/test_logger_helper.rb +42 -0
  97. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +11 -12
  98. data/lib/submodules/ably-ruby/spec/unit/models/device_details_spec.rb +102 -0
  99. data/lib/submodules/ably-ruby/spec/unit/models/device_push_details_spec.rb +101 -0
  100. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +51 -3
  101. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +17 -2
  102. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +1 -1
  103. data/lib/submodules/ably-ruby/spec/unit/models/push_channel_subscription_spec.rb +86 -0
  104. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +2 -2
  105. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +1 -1
  106. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +3 -3
  107. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +10 -10
  108. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +1 -1
  109. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +13 -1
  110. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +2 -2
  111. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +1 -1
  112. data/lib/submodules/ably-ruby/spec/unit/realtime/push_channel_spec.rb +36 -0
  113. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +30 -1
  114. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +30 -0
  115. data/lib/submodules/ably-ruby/spec/unit/rest/push_channel_spec.rb +36 -0
  116. data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +3 -3
  117. data/spec/spec_helper.rb +1 -0
  118. metadata +51 -10
@@ -0,0 +1,62 @@
1
+ module Ably::Realtime
2
+ class Channel
3
+ # A push channel used for push notifications
4
+ # Each PushChannel maps to exactly one Realtime Channel
5
+ #
6
+ # @!attribute [r] channel
7
+ # @return [Ably::Realtime::Channel] Underlying channel object
8
+ #
9
+ class PushChannel
10
+ attr_reader :channel
11
+
12
+ def initialize(channel)
13
+ raise ArgumentError, "Unsupported channel type '#{channel.class}'" unless channel.kind_of?(Ably::Realtime::Channel)
14
+ @channel = channel
15
+ end
16
+
17
+ def to_s
18
+ "<PushChannel: name=#{channel.name}>"
19
+ end
20
+
21
+ # Subscribe local device for push notifications on this channel
22
+ #
23
+ # @note This is unsupported in the Ruby library
24
+ def subscribe_device(*args)
25
+ raise_unsupported
26
+ end
27
+
28
+ # Subscribe all devices registered to this client's authenticated client_id for push notifications on this channel
29
+ #
30
+ # @note This is unsupported in the Ruby library
31
+ def subscribe_client_id(*args)
32
+ raise_unsupported
33
+ end
34
+
35
+ # Unsubscribe local device for push notifications on this channel
36
+ #
37
+ # @note This is unsupported in the Ruby library
38
+ def unsubscribe_device(*args)
39
+ raise_unsupported
40
+ end
41
+
42
+ # Unsubscribe all devices registered to this client's authenticated client_id for push notifications on this channel
43
+ #
44
+ # @note This is unsupported in the Ruby library
45
+ def unsubscribe_client_id(*args)
46
+ raise_unsupported
47
+ end
48
+
49
+ # Get list of subscriptions on this channel for this device or authenticate client_id
50
+ #
51
+ # @note This is unsupported in the Ruby library
52
+ def get_subscriptions(*args)
53
+ raise_unsupported
54
+ end
55
+
56
+ private
57
+ def raise_unsupported
58
+ raise Ably::Exceptions::PushNotificationsNotSupported, 'This device does not support receiving or subscribing to push notifications. All PushChannel methods are unavailable'
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,4 +1,5 @@
1
1
  require 'uri'
2
+ require 'ably/realtime/channel/publisher'
2
3
 
3
4
  module Ably
4
5
  module Realtime
@@ -21,6 +22,9 @@ module Ably
21
22
  #
22
23
  class Client
23
24
  include Ably::Modules::AsyncWrapper
25
+ include Ably::Realtime::Channel::Publisher
26
+ include Ably::Modules::Conversions
27
+
24
28
  extend Forwardable
25
29
 
26
30
  DOMAIN = 'realtime.ably.io'
@@ -38,6 +42,7 @@ module Ably
38
42
 
39
43
  # The {Ably::Rest::Client REST client} instantiated with the same credentials and configuration that is used for all REST operations such as authentication
40
44
  # @return [Ably::Rest::Client]
45
+ # @private
41
46
  attr_reader :rest_client
42
47
 
43
48
  # When false the client suppresses messages originating from this connection being echoed back on the same connection. Defaults to true
@@ -105,9 +110,6 @@ module Ably
105
110
  end
106
111
 
107
112
  @rest_client = Ably::Rest::Client.new(options.merge(realtime_client: self))
108
- @auth = Ably::Realtime::Auth.new(self)
109
- @channels = Ably::Realtime::Channels.new(self)
110
- @connection = Ably::Realtime::Connection.new(self, options)
111
113
  @echo_messages = rest_client.options.fetch(:echo_messages, true) == false ? false : true
112
114
  @queue_messages = rest_client.options.fetch(:queue_messages, true) == false ? false : true
113
115
  @custom_realtime_host = rest_client.options[:realtime_host] || rest_client.options[:ws_host]
@@ -115,6 +117,10 @@ module Ably
115
117
  @recover = rest_client.options[:recover]
116
118
 
117
119
  raise ArgumentError, "Recovery key '#{recover}' is invalid" if recover && !recover.match(Connection::RECOVER_REGEX)
120
+
121
+ @auth = Ably::Realtime::Auth.new(self)
122
+ @channels = Ably::Realtime::Channels.new(self)
123
+ @connection = Ably::Realtime::Connection.new(self, options)
118
124
  end
119
125
 
120
126
  # Return a {Ably::Realtime::Channel Realtime Channel} for the given name
@@ -162,6 +168,12 @@ module Ably
162
168
  connection.connect(&block)
163
169
  end
164
170
 
171
+ # Push notification object for publishing and managing push notifications
172
+ # @return [Ably::Realtime::Push]
173
+ def push
174
+ @push ||= Push.new(self)
175
+ end
176
+
165
177
  # (see Ably::Rest::Client#request)
166
178
  # @yield [Ably::Models::HttpPaginatedResponse<>] An Array of Stats
167
179
  #
@@ -172,6 +184,74 @@ module Ably
172
184
  end
173
185
  end
174
186
 
187
+ # Publish one or more messages to the specified channel.
188
+ #
189
+ # This method allows messages to be efficiently published to Ably without instancing a {Ably::Realtime::Channel} object.
190
+ # If you want to publish a high rate of messages to Ably without instancing channels or using the REST API, then this method
191
+ # is recommended. However, channel options such as encryption are not supported with this method. If you need to specify channel options
192
+ # we recommend you use the {Ably::Realtime::Channel} +publish+ method without attaching to each channel, unless you also want to subscribe
193
+ # to published messages on that channel.
194
+ #
195
+ # Note: This feature is still in beta. As such, we cannot guarantee the API will not change in future.
196
+ #
197
+ # @param channel [String] The channel name you want to publish the message(s) to
198
+ # @param name [String, Array<Ably::Models::Message|Hash>, 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
199
+ # @param data [String, ByteArray, nil] The message payload unless an Array of [Ably::Model::Message] objects passed in the first argument
200
+ # @param attributes [Hash, nil] Optional additional message attributes such as :client_id or :connection_id, applied when name attribute is nil or a string
201
+ #
202
+ # @yield [Ably::Models::Message,Array<Ably::Models::Message>] On success, will call the block with the {Ably::Models::Message} if a single message is published, or an Array of {Ably::Models::Message} when multiple messages are published
203
+ # @return [Ably::Util::SafeDeferrable] Deferrable that supports both success (callback) and failure (errback) callbacks
204
+ #
205
+ # @example
206
+ # # Publish a single message
207
+ # client.publish 'activityChannel', click', { x: 1, y: 2 }
208
+ #
209
+ # # Publish an array of message Hashes
210
+ # messages = [
211
+ # { name: 'click', { x: 1, y: 2 } },
212
+ # { name: 'click', { x: 2, y: 3 } }
213
+ # ]
214
+ # client.publish 'activityChannel', messages
215
+ #
216
+ # # Publish an array of Ably::Models::Message objects
217
+ # messages = [
218
+ # Ably::Models::Message(name: 'click', { x: 1, y: 2 })
219
+ # Ably::Models::Message(name: 'click', { x: 2, y: 3 })
220
+ # ]
221
+ # client.publish 'activityChannel', messages
222
+ #
223
+ # client.publish('activityChannel', 'click', 'body') do |message|
224
+ # puts "#{message.name} event received with #{message.data}"
225
+ # end
226
+ #
227
+ # client.publish('activityChannel', 'click', 'body').errback do |error, message|
228
+ # puts "#{message.name} was not received, error #{error.message}"
229
+ # end
230
+ #
231
+ def publish(channel_name, name, data = nil, attributes = {}, &success_block)
232
+ if !connection.can_publish_messages?
233
+ error = Ably::Exceptions::MessageQueueingDisabled.new("Message cannot be published. Client is not allowed to queue messages when connection is in state #{connection.state}")
234
+ return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
235
+ end
236
+
237
+ messages = if name.kind_of?(Enumerable)
238
+ name
239
+ else
240
+ name = ensure_utf_8(:name, name, allow_nil: true)
241
+ ensure_supported_payload data
242
+ [{ name: name, data: data }.merge(attributes)]
243
+ end
244
+
245
+ if messages.length > Realtime::Connection::MAX_PROTOCOL_MESSAGE_BATCH_SIZE
246
+ error = Ably::Exceptions::InvalidRequest.new("It is not possible to publish more than #{Realtime::Connection::MAX_PROTOCOL_MESSAGE_BATCH_SIZE} messages with a single publish request.")
247
+ return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
248
+ end
249
+
250
+ enqueue_messages_on_connection(self, messages, channel_name).tap do |deferrable|
251
+ deferrable.callback(&success_block) if block_given?
252
+ end
253
+ end
254
+
175
255
  # @!attribute [r] endpoint
176
256
  # @return [URI::Generic] Default Ably Realtime endpoint used for all requests
177
257
  def endpoint
@@ -214,6 +294,14 @@ module Ably
214
294
  @fallback_endpoints[fallback_endpoint_index % @fallback_endpoints.count]
215
295
  end
216
296
 
297
+ # The local device detilas
298
+ # @return [Ably::Models::LocalDevice]
299
+ #
300
+ # @note This is unsupported in the Ruby library
301
+ def device
302
+ raise Ably::Exceptions::PushNotificationsNotSupported, 'This device does not support receiving or subscribing to push notifications. The local device object is not unavailable'
303
+ end
304
+
217
305
  private
218
306
  def endpoint_for_host(host)
219
307
  port = if use_tls?
@@ -56,6 +56,7 @@ module Ably::Realtime
56
56
  end
57
57
 
58
58
  update_connection_recovery_info protocol_message
59
+ connection.set_connection_confirmed_alive
59
60
 
60
61
  case protocol_message.action
61
62
  when ACTION.Heartbeat
@@ -75,8 +76,10 @@ module Ably::Realtime
75
76
  elsif connection.connected?
76
77
  logger.debug { "Updated CONNECTED ProtocolMessage received (whilst connected)" }
77
78
  process_connected_update_message protocol_message
79
+ connection.set_connection_confirmed_alive # Connection protocol messages can change liveness settings such as max_idle_interval
78
80
  else
79
81
  process_connected_message protocol_message
82
+ connection.set_connection_confirmed_alive # Connection protocol messages can change liveness settings such as max_idle_interval
80
83
  end
81
84
 
82
85
  when ACTION.Disconnect, ACTION.Disconnected
@@ -99,7 +102,11 @@ module Ably::Realtime
99
102
  if channel.attached?
100
103
  channel.manager.duplicate_attached_received protocol_message
101
104
  else
102
- channel.transition_state_machine :attached, reason: protocol_message.error, resumed: protocol_message.has_channel_resumed_flag?, protocol_message: protocol_message
105
+ if channel.failed?
106
+ logger.warn "Ably::Realtime::Client::IncomingMessageDispatcher - Received an ATTACHED protocol message for FAILED channel #{channel.name}. Ignoring ATTACHED message"
107
+ else
108
+ channel.transition_state_machine :attached, reason: protocol_message.error, resumed: protocol_message.has_channel_resumed_flag?, protocol_message: protocol_message
109
+ end
103
110
  end
104
111
  end
105
112
 
@@ -129,11 +136,9 @@ module Ably::Realtime
129
136
  client.auth.authorize
130
137
 
131
138
  else
132
- error = Ably::Exceptions::ProtocolError.new("Protocol Message Action #{protocol_message.action} is unsupported by this MessageDispatcher", 400, 80013)
139
+ error = Ably::Exceptions::ProtocolError.new("Protocol Message Action #{protocol_message.action} is unsupported by this MessageDispatcher", 400, Ably::Exceptions::Codes::PROTOCOL_ERROR)
133
140
  logger.fatal error.message
134
141
  end
135
-
136
- connection.set_connection_confirmed_alive
137
142
  end
138
143
 
139
144
  def dispatch_channel_error(protocol_message)
@@ -52,7 +52,7 @@ module Ably::Realtime
52
52
  protocol_message = outgoing_queue.shift
53
53
 
54
54
  if (!connection.transport)
55
- protocol_message.fail Ably::Exceptions::TransportClosed.new('Transport disconnected unexpectedly', nil, 80003)
55
+ protocol_message.fail Ably::Exceptions::TransportClosed.new('Transport disconnected unexpectedly', nil, Ably::Exceptions::Codes::DISCONNECTED)
56
56
  next
57
57
  end
58
58
 
@@ -66,7 +66,7 @@ module Ably
66
66
  ensure_state_machine_emits 'Ably::Models::ConnectionStateChange'
67
67
 
68
68
  # Expected format for a connection recover key
69
- RECOVER_REGEX = /^(?<recover>[\w!-]+):(?<connection_serial>\-?\w+)$/
69
+ RECOVER_REGEX = /^(?<recover>[^:]+):(?<connection_serial>[^:]+):(?<msg_serial>\-?\d+)$/
70
70
 
71
71
  # Defaults for automatic connection recovery and timeouts
72
72
  DEFAULTS = {
@@ -79,6 +79,9 @@ module Ably
79
79
  websocket_heartbeats_disabled: false,
80
80
  }.freeze
81
81
 
82
+ # Max number of messages to bundle in a single ProtocolMessage
83
+ MAX_PROTOCOL_MESSAGE_BATCH_SIZE = 50
84
+
82
85
  # A unique public identifier for this connection, used to identify this member in presence events and messages
83
86
  # @return [String]
84
87
  attr_reader :id
@@ -134,7 +137,6 @@ module Ably
134
137
  @client = client
135
138
  @__outgoing_message_queue__ = []
136
139
  @__pending_message_ack_queue__ = []
137
- reset_client_serial
138
140
 
139
141
  @defaults = DEFAULTS.dup
140
142
  options.each do |key, val|
@@ -142,12 +144,25 @@ module Ably
142
144
  end if options.kind_of?(Hash)
143
145
  @defaults.freeze
144
146
 
147
+ # If a recover client options is provided, then we need to ensure that the msgSerial matches the
148
+ # recover serial immediately at client library instantiation. This is done immediately so that any queued
149
+ # publishes use the correct serial number for these queued messages as well.
150
+ # There is no harm if the msgSerial is higher than expected if the recover fails.
151
+ recovery_msg_serial = connection_recover_parts && connection_recover_parts[:msg_serial].to_i
152
+ if recovery_msg_serial
153
+ @client_msg_serial = recovery_msg_serial
154
+ else
155
+ reset_client_msg_serial
156
+ end
157
+
145
158
  Client::IncomingMessageDispatcher.new client, self
146
159
  Client::OutgoingMessageDispatcher.new client, self
147
160
 
148
161
  @state_machine = ConnectionStateMachine.new(self)
149
162
  @state = STATE(state_machine.current_state)
150
163
  @manager = ConnectionManager.new(self)
164
+
165
+ @current_host = client.endpoint.host
151
166
  end
152
167
 
153
168
  # Causes the connection to close, entering the closed state, from any state except
@@ -225,7 +240,7 @@ module Ably
225
240
  #
226
241
  def ping(&block)
227
242
  if initialized? || suspended? || closing? || closed? || failed?
228
- error = Ably::Models::ErrorInfo.new(message: "Cannot send a ping when the connection is #{state}", code: 80003)
243
+ error = Ably::Models::ErrorInfo.new(message: "Cannot send a ping when the connection is #{state}", code: Ably::Exceptions::Codes::DISCONNECTED)
229
244
  return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
230
245
  end
231
246
 
@@ -235,7 +250,7 @@ module Ably
235
250
  ping_id = SecureRandom.hex(16)
236
251
  heartbeat_action = Ably::Models::ProtocolMessage::ACTION.Heartbeat
237
252
 
238
- wait_for_ping = Proc.new do |protocol_message|
253
+ wait_for_ping = lambda do |protocol_message|
239
254
  next if finished
240
255
  if protocol_message.action == heartbeat_action && protocol_message.id == ping_id
241
256
  finished = true
@@ -256,7 +271,7 @@ module Ably
256
271
  once_or_if([:suspended, :closing, :closed, :failed]) do
257
272
  next if finished
258
273
  finished = true
259
- deferrable.fail Ably::Models::ErrorInfo.new(message: "Ping failed as connection has changed state to #{state}", code: 80003)
274
+ deferrable.fail Ably::Models::ErrorInfo.new(message: "Ping failed as connection has changed state to #{state}", code: Ably::Exceptions::Codes::DISCONNECTED)
260
275
  end
261
276
 
262
277
  EventMachine.add_timer(defaults.fetch(:realtime_request_timeout)) do
@@ -265,7 +280,7 @@ module Ably
265
280
  __incoming_protocol_msgbus__.unsubscribe(:protocol_message, &wait_for_ping)
266
281
  error_msg = "Ping timed out after #{defaults.fetch(:realtime_request_timeout)}s"
267
282
  logger.warn { error_msg }
268
- deferrable.fail Ably::Models::ErrorInfo.new(message: error_msg, code: 50003)
283
+ deferrable.fail Ably::Models::ErrorInfo.new(message: error_msg, code: Ably::Exceptions::Codes::TIMEOUT_ERROR)
269
284
  safe_yield block, nil if block_given?
270
285
  end
271
286
  end
@@ -280,7 +295,7 @@ module Ably
280
295
  EventMachine::HttpRequest.new(url).get.tap do |http|
281
296
  http.errback do
282
297
  yield false if block_given?
283
- deferrable.fail Ably::Exceptions::ConnectionFailed.new("Unable to connect to #{url}", nil, 80000)
298
+ deferrable.fail Ably::Exceptions::ConnectionFailed.new("Unable to connect to #{url}", nil, Ably::Exceptions::Codes::CONNECTION_FAILED)
284
299
  end
285
300
  http.callback do
286
301
  EventMachine.next_tick do
@@ -289,7 +304,7 @@ module Ably
289
304
  if result
290
305
  deferrable.succeed
291
306
  else
292
- deferrable.fail Ably::Exceptions::ConnectionFailed.new("Unexpected response from #{url} (#{http.response_header.status})", 400, 40000)
307
+ deferrable.fail Ably::Exceptions::ConnectionFailed.new("Unexpected response from #{url} (#{http.response_header.status})", 400, Ably::Exceptions::Codes::BAD_REQUEST)
293
308
  end
294
309
  end
295
310
  end
@@ -300,18 +315,17 @@ module Ably
300
315
  # @!attribute [r] recovery_key
301
316
  # @return [String] recovery key that can be used by another client to recover this connection with the :recover option
302
317
  def recovery_key
303
- "#{key}:#{serial}" if connection_resumable?
318
+ "#{key}:#{serial}:#{client_msg_serial}" if connection_resumable?
304
319
  end
305
320
 
306
321
  # Following a new connection being made, the connection ID, connection key
307
- # and message serial need to match the details provided by the server.
322
+ # and connection serial need to match the details provided by the server.
308
323
  #
309
324
  # @return [void]
310
325
  # @api private
311
326
  def configure_new(connection_id, connection_key, connection_serial)
312
327
  @id = connection_id
313
328
  @key = connection_key
314
- @client_serial = connection_serial
315
329
 
316
330
  update_connection_serial connection_serial
317
331
  end
@@ -423,9 +437,12 @@ module Ably
423
437
  lib: client.rest_client.lib_version_id,
424
438
  )
425
439
 
426
- # Use native websocket heartbeats if possible
427
- # TODO: Fix once https://github.com/ably/ably-ruby/issues/116 is resolved
428
- url_params['heartbeats'] = 'true' # unless defaults.fetch(:websocket_heartbeats_disabled)
440
+ # Use native websocket heartbeats if possible, but allow Ably protocol heartbeats
441
+ url_params['heartbeats'] = if defaults.fetch(:websocket_heartbeats_disabled)
442
+ 'true'
443
+ else
444
+ 'false'
445
+ end
429
446
 
430
447
  url_params['clientId'] = client.auth.client_id if client.auth.has_client_id?
431
448
 
@@ -536,11 +553,11 @@ module Ably
536
553
  defaults.fetch(:realtime_request_timeout)
537
554
  end
538
555
 
539
- # Resets the client serial (msgSerial) sent to Ably for each new {Ably::Models::ProtocolMessage}
540
- # (see #client_serial)
556
+ # Resets the client message serial (msgSerial) sent to Ably for each new {Ably::Models::ProtocolMessage}
557
+ # (see #client_msg_serial)
541
558
  # @api private
542
- def reset_client_serial
543
- @client_serial = -1
559
+ def reset_client_msg_serial
560
+ @client_msg_serial = -1
544
561
  end
545
562
 
546
563
  # When a hearbeat or any other message from Ably is received
@@ -562,15 +579,15 @@ module Ably
562
579
 
563
580
  private
564
581
 
565
- # The client serial is incremented for every message that is published that requires an ACK.
582
+ # The client message serial (msgSerial) is incremented for every message that is published that requires an ACK.
566
583
  # Note that this is different to the connection serial that contains the last known serial number
567
584
  # received from the server.
568
585
  #
569
586
  # A message serial number does not guarantee a message has been received, only sent.
570
587
  # A connection serial guarantees the server has received the message and is thus used for connection recovery and resumes.
571
588
  # @return [Integer] starting at -1 indicating no messages sent, 0 when the first message is sent
572
- def client_serial
573
- @client_serial
589
+ def client_msg_serial
590
+ @client_msg_serial
574
591
  end
575
592
 
576
593
  def resume_callbacks
@@ -579,7 +596,7 @@ module Ably
579
596
 
580
597
  def create_pub_sub_message_bus
581
598
  Ably::Util::PubSub.new(
582
- coerce_into: Proc.new do |event|
599
+ coerce_into: lambda do |event|
583
600
  raise KeyError, "Expected :protocol_message, :#{event} is disallowed" unless event == :protocol_message
584
601
  :protocol_message
585
602
  end
@@ -595,11 +612,11 @@ module Ably
595
612
  end
596
613
 
597
614
  def add_message_serial_to(protocol_message)
598
- @client_serial += 1
599
- protocol_message[:msgSerial] = client_serial
615
+ @client_msg_serial += 1
616
+ protocol_message[:msgSerial] = client_msg_serial
600
617
  yield
601
618
  rescue StandardError => e
602
- @client_serial -= 1
619
+ @client_msg_serial -= 1
603
620
  raise e
604
621
  end
605
622
 
@@ -615,8 +632,10 @@ module Ably
615
632
  def connection_state_available?
616
633
  return true if connected?
617
634
 
635
+ return false if time_since_connection_confirmed_alive? > connection_state_ttl + details.max_idle_interval
636
+
618
637
  connected_last = state_history.reverse.find { |connected| connected.fetch(:state) == :connected }
619
- if connected_last.nil? || (connected_last.fetch(:transitioned_at) < Time.now - connection_state_ttl)
638
+ if connected_last.nil?
620
639
  false
621
640
  else
622
641
  true