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.
- checksums.yaml +4 -4
- data/.editorconfig +14 -0
- data/.travis.yml +10 -8
- data/CHANGELOG.md +58 -4
- data/LICENSE +1 -3
- data/README.md +9 -5
- data/Rakefile +32 -0
- data/SPEC.md +920 -565
- data/ably.gemspec +16 -11
- data/lib/ably/auth.rb +28 -2
- data/lib/ably/exceptions.rb +10 -4
- data/lib/ably/logger.rb +7 -1
- data/lib/ably/models/channel_state_change.rb +1 -1
- data/lib/ably/models/connection_state_change.rb +1 -1
- data/lib/ably/models/device_details.rb +87 -0
- data/lib/ably/models/device_push_details.rb +86 -0
- data/lib/ably/models/error_info.rb +23 -2
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -4
- data/lib/ably/models/protocol_message.rb +32 -2
- data/lib/ably/models/push_channel_subscription.rb +89 -0
- data/lib/ably/modules/conversions.rb +1 -1
- data/lib/ably/modules/encodeable.rb +1 -1
- data/lib/ably/modules/exception_codes.rb +128 -0
- data/lib/ably/modules/model_common.rb +15 -2
- data/lib/ably/modules/state_machine.rb +2 -2
- data/lib/ably/realtime.rb +1 -0
- data/lib/ably/realtime/auth.rb +1 -1
- data/lib/ably/realtime/channel.rb +24 -102
- data/lib/ably/realtime/channel/channel_manager.rb +2 -6
- data/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
- data/lib/ably/realtime/channel/publisher.rb +74 -0
- data/lib/ably/realtime/channel/push_channel.rb +62 -0
- data/lib/ably/realtime/client.rb +91 -3
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +6 -2
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
- data/lib/ably/realtime/connection.rb +34 -20
- data/lib/ably/realtime/connection/connection_manager.rb +25 -9
- data/lib/ably/realtime/connection/websocket_transport.rb +1 -1
- data/lib/ably/realtime/presence.rb +4 -4
- data/lib/ably/realtime/presence/members_map.rb +3 -3
- data/lib/ably/realtime/push.rb +40 -0
- data/lib/ably/realtime/push/admin.rb +61 -0
- data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
- data/lib/ably/realtime/push/device_registrations.rb +105 -0
- data/lib/ably/rest.rb +1 -0
- data/lib/ably/rest/channel.rb +53 -17
- data/lib/ably/rest/channel/push_channel.rb +62 -0
- data/lib/ably/rest/client.rb +161 -35
- data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -1
- data/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
- data/lib/ably/rest/presence.rb +1 -0
- data/lib/ably/rest/push.rb +42 -0
- data/lib/ably/rest/push/admin.rb +54 -0
- data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
- data/lib/ably/rest/push/device_registrations.rb +103 -0
- data/lib/ably/version.rb +7 -2
- data/spec/acceptance/realtime/auth_spec.rb +22 -21
- data/spec/acceptance/realtime/channel_history_spec.rb +26 -20
- data/spec/acceptance/realtime/channel_spec.rb +177 -59
- data/spec/acceptance/realtime/client_spec.rb +153 -0
- data/spec/acceptance/realtime/connection_failures_spec.rb +72 -6
- data/spec/acceptance/realtime/connection_spec.rb +129 -18
- data/spec/acceptance/realtime/message_spec.rb +36 -34
- data/spec/acceptance/realtime/presence_spec.rb +201 -167
- data/spec/acceptance/realtime/push_admin_spec.rb +736 -0
- data/spec/acceptance/realtime/push_spec.rb +27 -0
- data/spec/acceptance/rest/auth_spec.rb +4 -3
- data/spec/acceptance/rest/base_spec.rb +2 -2
- data/spec/acceptance/rest/channel_spec.rb +79 -4
- data/spec/acceptance/rest/channels_spec.rb +6 -0
- data/spec/acceptance/rest/client_spec.rb +129 -10
- data/spec/acceptance/rest/message_spec.rb +158 -6
- data/spec/acceptance/rest/push_admin_spec.rb +952 -0
- data/spec/acceptance/rest/push_spec.rb +25 -0
- data/spec/acceptance/rest/time_spec.rb +1 -1
- data/spec/run_parallel_tests +33 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/support/debug_failure_helper.rb +9 -5
- data/spec/support/test_app.rb +2 -2
- data/spec/unit/logger_spec.rb +10 -3
- data/spec/unit/models/device_details_spec.rb +102 -0
- data/spec/unit/models/device_push_details_spec.rb +101 -0
- data/spec/unit/models/error_info_spec.rb +51 -3
- data/spec/unit/models/message_spec.rb +17 -2
- data/spec/unit/models/presence_message_spec.rb +1 -1
- data/spec/unit/models/push_channel_subscription_spec.rb +86 -0
- data/spec/unit/modules/enum_spec.rb +1 -1
- data/spec/unit/realtime/client_spec.rb +13 -1
- data/spec/unit/realtime/connection_spec.rb +1 -1
- data/spec/unit/realtime/push_channel_spec.rb +36 -0
- data/spec/unit/rest/channel_spec.rb +8 -1
- data/spec/unit/rest/client_spec.rb +30 -0
- data/spec/unit/rest/push_channel_spec.rb +36 -0
- metadata +94 -31
@@ -111,16 +111,12 @@ module Ably::Realtime
|
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
114
|
-
# When a channel becomes
|
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:
|
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
|
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
|
data/lib/ably/realtime/client.rb
CHANGED
@@ -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.
|
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,
|
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,
|
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>[
|
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:
|
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:
|
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:
|
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,
|
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,
|
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
|
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 #
|
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
|
546
|
-
@
|
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
|
576
|
-
@
|
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
|
-
@
|
602
|
-
protocol_message[:msgSerial] =
|
615
|
+
@client_msg_serial += 1
|
616
|
+
protocol_message[:msgSerial] = client_msg_serial
|
603
617
|
yield
|
604
618
|
rescue StandardError => e
|
605
|
-
@
|
619
|
+
@client_msg_serial -= 1
|
606
620
|
raise e
|
607
621
|
end
|
608
622
|
|