ably 1.0.7 → 1.1.4.rc

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +14 -0
  3. data/.travis.yml +10 -8
  4. data/CHANGELOG.md +58 -4
  5. data/LICENSE +1 -3
  6. data/README.md +9 -5
  7. data/Rakefile +32 -0
  8. data/SPEC.md +920 -565
  9. data/ably.gemspec +16 -11
  10. data/lib/ably/auth.rb +28 -2
  11. data/lib/ably/exceptions.rb +10 -4
  12. data/lib/ably/logger.rb +7 -1
  13. data/lib/ably/models/channel_state_change.rb +1 -1
  14. data/lib/ably/models/connection_state_change.rb +1 -1
  15. data/lib/ably/models/device_details.rb +87 -0
  16. data/lib/ably/models/device_push_details.rb +86 -0
  17. data/lib/ably/models/error_info.rb +23 -2
  18. data/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -4
  19. data/lib/ably/models/protocol_message.rb +32 -2
  20. data/lib/ably/models/push_channel_subscription.rb +89 -0
  21. data/lib/ably/modules/conversions.rb +1 -1
  22. data/lib/ably/modules/encodeable.rb +1 -1
  23. data/lib/ably/modules/exception_codes.rb +128 -0
  24. data/lib/ably/modules/model_common.rb +15 -2
  25. data/lib/ably/modules/state_machine.rb +2 -2
  26. data/lib/ably/realtime.rb +1 -0
  27. data/lib/ably/realtime/auth.rb +1 -1
  28. data/lib/ably/realtime/channel.rb +24 -102
  29. data/lib/ably/realtime/channel/channel_manager.rb +2 -6
  30. data/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
  31. data/lib/ably/realtime/channel/publisher.rb +74 -0
  32. data/lib/ably/realtime/channel/push_channel.rb +62 -0
  33. data/lib/ably/realtime/client.rb +91 -3
  34. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +6 -2
  35. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  36. data/lib/ably/realtime/connection.rb +34 -20
  37. data/lib/ably/realtime/connection/connection_manager.rb +25 -9
  38. data/lib/ably/realtime/connection/websocket_transport.rb +1 -1
  39. data/lib/ably/realtime/presence.rb +4 -4
  40. data/lib/ably/realtime/presence/members_map.rb +3 -3
  41. data/lib/ably/realtime/push.rb +40 -0
  42. data/lib/ably/realtime/push/admin.rb +61 -0
  43. data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
  44. data/lib/ably/realtime/push/device_registrations.rb +105 -0
  45. data/lib/ably/rest.rb +1 -0
  46. data/lib/ably/rest/channel.rb +53 -17
  47. data/lib/ably/rest/channel/push_channel.rb +62 -0
  48. data/lib/ably/rest/client.rb +161 -35
  49. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -1
  50. data/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
  51. data/lib/ably/rest/presence.rb +1 -0
  52. data/lib/ably/rest/push.rb +42 -0
  53. data/lib/ably/rest/push/admin.rb +54 -0
  54. data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
  55. data/lib/ably/rest/push/device_registrations.rb +103 -0
  56. data/lib/ably/version.rb +7 -2
  57. data/spec/acceptance/realtime/auth_spec.rb +22 -21
  58. data/spec/acceptance/realtime/channel_history_spec.rb +26 -20
  59. data/spec/acceptance/realtime/channel_spec.rb +177 -59
  60. data/spec/acceptance/realtime/client_spec.rb +153 -0
  61. data/spec/acceptance/realtime/connection_failures_spec.rb +72 -6
  62. data/spec/acceptance/realtime/connection_spec.rb +129 -18
  63. data/spec/acceptance/realtime/message_spec.rb +36 -34
  64. data/spec/acceptance/realtime/presence_spec.rb +201 -167
  65. data/spec/acceptance/realtime/push_admin_spec.rb +736 -0
  66. data/spec/acceptance/realtime/push_spec.rb +27 -0
  67. data/spec/acceptance/rest/auth_spec.rb +4 -3
  68. data/spec/acceptance/rest/base_spec.rb +2 -2
  69. data/spec/acceptance/rest/channel_spec.rb +79 -4
  70. data/spec/acceptance/rest/channels_spec.rb +6 -0
  71. data/spec/acceptance/rest/client_spec.rb +129 -10
  72. data/spec/acceptance/rest/message_spec.rb +158 -6
  73. data/spec/acceptance/rest/push_admin_spec.rb +952 -0
  74. data/spec/acceptance/rest/push_spec.rb +25 -0
  75. data/spec/acceptance/rest/time_spec.rb +1 -1
  76. data/spec/run_parallel_tests +33 -0
  77. data/spec/spec_helper.rb +1 -1
  78. data/spec/support/debug_failure_helper.rb +9 -5
  79. data/spec/support/test_app.rb +2 -2
  80. data/spec/unit/logger_spec.rb +10 -3
  81. data/spec/unit/models/device_details_spec.rb +102 -0
  82. data/spec/unit/models/device_push_details_spec.rb +101 -0
  83. data/spec/unit/models/error_info_spec.rb +51 -3
  84. data/spec/unit/models/message_spec.rb +17 -2
  85. data/spec/unit/models/presence_message_spec.rb +1 -1
  86. data/spec/unit/models/push_channel_subscription_spec.rb +86 -0
  87. data/spec/unit/modules/enum_spec.rb +1 -1
  88. data/spec/unit/realtime/client_spec.rb +13 -1
  89. data/spec/unit/realtime/connection_spec.rb +1 -1
  90. data/spec/unit/realtime/push_channel_spec.rb +36 -0
  91. data/spec/unit/rest/channel_spec.rb +8 -1
  92. data/spec/unit/rest/client_spec.rb +30 -0
  93. data/spec/unit/rest/push_channel_spec.rb +36 -0
  94. metadata +94 -31
@@ -111,16 +111,12 @@ module Ably::Realtime
111
111
  end
112
112
  end
113
113
 
114
- # When a channel becomes detached, suspended or failed,
114
+ # When a channel becomes suspended or failed,
115
115
  # all queued messages should be failed immediately as we don't queue in
116
116
  # any of those states
117
117
  def fail_queued_messages(error)
118
118
  error = Ably::Exceptions::MessageDeliveryFailed.new("Queued messages on channel '#{channel.name}' in state '#{channel.state}' will never be delivered") unless error
119
119
  fail_messages_in_queue connection.__outgoing_message_queue__, error
120
- channel.__queue__.each do |message|
121
- nack_message message, error
122
- end
123
- channel.__queue__.clear
124
120
  end
125
121
 
126
122
  def fail_messages_in_queue(queue, error)
@@ -214,7 +210,7 @@ module Ably::Realtime
214
210
  state_at_time_of_request = channel.state
215
211
  @pending_state_change_timer = EventMachine::Timer.new(realtime_request_timeout) do
216
212
  if channel.state == state_at_time_of_request
217
- error = Ably::Models::ErrorInfo.new(code: 90007, message: "Channel #{new_state} operation failed (timed out)")
213
+ error = Ably::Models::ErrorInfo.new(code: Ably::Exceptions::Codes::CHANNEL_OPERATION_FAILED_NO_RESPONSE_FROM_SERVER, message: "Channel #{new_state} operation failed (timed out)")
218
214
  channel.transition_state_machine state_if_failed, reason: error
219
215
  end
220
216
  end
@@ -25,7 +25,7 @@ module Ably::Realtime
25
25
  transition :from => :attached, :to => [:attaching, :detaching, :detached, :failed, :suspended]
26
26
  transition :from => :detaching, :to => [:detached, :attaching, :attached, :failed, :suspended]
27
27
  transition :from => :detached, :to => [:attaching, :attached, :failed]
28
- transition :from => :suspended, :to => [:attaching, :detached, :failed]
28
+ transition :from => :suspended, :to => [:attaching, :attached, :detached, :failed]
29
29
  transition :from => :failed, :to => [:attaching]
30
30
 
31
31
  after_transition do |channel, transition|
@@ -47,7 +47,7 @@ module Ably::Realtime
47
47
 
48
48
  after_transition(to: [:detached, :failed, :suspended]) do |channel, current_transition|
49
49
  err = error_from_state_change(current_transition)
50
- channel.manager.fail_queued_messages err
50
+ channel.manager.fail_queued_messages(err) if channel.failed? or channel.suspended? #RTL11
51
51
  channel.manager.log_channel_error err if err
52
52
  end
53
53
 
@@ -0,0 +1,74 @@
1
+ module Ably::Realtime
2
+ class Channel
3
+ # Publisher module adds publishing capabilities to the current object
4
+ module Publisher
5
+ private
6
+
7
+ # Prepare and queue messages on the connection queue immediately
8
+ # @return [Ably::Util::SafeDeferrable]
9
+ def enqueue_messages_on_connection(client, raw_messages, channel_name, channel_options = {})
10
+ messages = Array(raw_messages).map do |raw_msg|
11
+ create_message(client, raw_msg, channel_options).tap do |message|
12
+ next if message.client_id.nil?
13
+ if message.client_id == '*'
14
+ raise Ably::Exceptions::IncompatibleClientId.new('Wildcard client_id is reserved and cannot be used when publishing messages')
15
+ end
16
+ if message.client_id && !message.client_id.kind_of?(String)
17
+ raise Ably::Exceptions::IncompatibleClientId.new('client_id must be a String when publishing messages')
18
+ end
19
+ unless client.auth.can_assume_client_id?(message.client_id)
20
+ raise Ably::Exceptions::IncompatibleClientId.new("Cannot publish with client_id '#{message.client_id}' as it is incompatible with the current configured client_id '#{client.client_id}'")
21
+ end
22
+ end
23
+ end
24
+
25
+ connection.send_protocol_message(
26
+ action: Ably::Models::ProtocolMessage::ACTION.Message.to_i,
27
+ channel: channel_name,
28
+ messages: messages
29
+ )
30
+
31
+ if messages.count == 1
32
+ # A message is a Deferrable so, if publishing only one message, simply return that Deferrable
33
+ messages.first
34
+ else
35
+ deferrable_for_multiple_messages(messages)
36
+ end
37
+ end
38
+
39
+ # A deferrable object that calls the success callback once all messages are delivered
40
+ # If any message fails, the errback is called immediately
41
+ # Only one callback or errback is ever called i.e. if a group of messages all fail, only once
42
+ # errback will be invoked
43
+ def deferrable_for_multiple_messages(messages)
44
+ expected_deliveries = messages.count
45
+ actual_deliveries = 0
46
+ failed = false
47
+
48
+ Ably::Util::SafeDeferrable.new(logger).tap do |deferrable|
49
+ messages.each do |message|
50
+ message.callback do
51
+ next if failed
52
+ actual_deliveries += 1
53
+ deferrable.succeed messages if actual_deliveries == expected_deliveries
54
+ end
55
+ message.errback do |error|
56
+ next if failed
57
+ failed = true
58
+ deferrable.fail error, message
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ def create_message(client, message, channel_options)
65
+ Ably::Models::Message(message.dup).tap do |msg|
66
+ msg.encode(client.encoders, channel_options) do |encode_error, error_message|
67
+ client.logger.error error_message
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+
@@ -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?
@@ -102,7 +102,11 @@ module Ably::Realtime
102
102
  if channel.attached?
103
103
  channel.manager.duplicate_attached_received protocol_message
104
104
  else
105
- 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
106
110
  end
107
111
  end
108
112
 
@@ -132,7 +136,7 @@ module Ably::Realtime
132
136
  client.auth.authorize
133
137
 
134
138
  else
135
- 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)
136
140
  logger.fatal error.message
137
141
  end
138
142
  end
@@ -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
 
@@ -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
@@ -539,11 +553,11 @@ module Ably
539
553
  defaults.fetch(:realtime_request_timeout)
540
554
  end
541
555
 
542
- # Resets the client serial (msgSerial) sent to Ably for each new {Ably::Models::ProtocolMessage}
543
- # (see #client_serial)
556
+ # Resets the client message serial (msgSerial) sent to Ably for each new {Ably::Models::ProtocolMessage}
557
+ # (see #client_msg_serial)
544
558
  # @api private
545
- def reset_client_serial
546
- @client_serial = -1
559
+ def reset_client_msg_serial
560
+ @client_msg_serial = -1
547
561
  end
548
562
 
549
563
  # When a hearbeat or any other message from Ably is received
@@ -565,15 +579,15 @@ module Ably
565
579
 
566
580
  private
567
581
 
568
- # 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.
569
583
  # Note that this is different to the connection serial that contains the last known serial number
570
584
  # received from the server.
571
585
  #
572
586
  # A message serial number does not guarantee a message has been received, only sent.
573
587
  # A connection serial guarantees the server has received the message and is thus used for connection recovery and resumes.
574
588
  # @return [Integer] starting at -1 indicating no messages sent, 0 when the first message is sent
575
- def client_serial
576
- @client_serial
589
+ def client_msg_serial
590
+ @client_msg_serial
577
591
  end
578
592
 
579
593
  def resume_callbacks
@@ -598,11 +612,11 @@ module Ably
598
612
  end
599
613
 
600
614
  def add_message_serial_to(protocol_message)
601
- @client_serial += 1
602
- protocol_message[:msgSerial] = client_serial
615
+ @client_msg_serial += 1
616
+ protocol_message[:msgSerial] = client_msg_serial
603
617
  yield
604
618
  rescue StandardError => e
605
- @client_serial -= 1
619
+ @client_msg_serial -= 1
606
620
  raise e
607
621
  end
608
622