ably-rest 1.0.6 → 1.1.4.rc
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -1
- data/README.md +23 -15
- data/ably-rest.gemspec +6 -6
- data/lib/submodules/ably-ruby/.editorconfig +14 -0
- data/lib/submodules/ably-ruby/.travis.yml +10 -8
- data/lib/submodules/ably-ruby/CHANGELOG.md +75 -3
- data/lib/submodules/ably-ruby/LICENSE +1 -3
- data/lib/submodules/ably-ruby/README.md +12 -7
- data/lib/submodules/ably-ruby/Rakefile +32 -0
- data/lib/submodules/ably-ruby/SPEC.md +1277 -835
- data/lib/submodules/ably-ruby/ably.gemspec +15 -10
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +30 -4
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +10 -4
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +7 -1
- data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/device_details.rb +87 -0
- data/lib/submodules/ably-ruby/lib/ably/models/device_push_details.rb +86 -0
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +23 -2
- data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -4
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +32 -2
- data/lib/submodules/ably-ruby/lib/ably/models/push_channel_subscription.rb +89 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/exception_codes.rb +128 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +15 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +24 -102
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +2 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/publisher.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/push_channel.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +91 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +6 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +34 -20
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +25 -9
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +4 -4
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +3 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/push.rb +40 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/push/admin.rb +61 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/push/device_registrations.rb +105 -0
- data/lib/submodules/ably-ruby/lib/ably/rest.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +53 -17
- data/lib/submodules/ably-ruby/lib/ably/rest/channel/push_channel.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +162 -35
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -1
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push.rb +42 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push/admin.rb +54 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push/channel_subscriptions.rb +121 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push/device_registrations.rb +103 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +7 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +245 -17
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +26 -20
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +177 -59
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +153 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +72 -6
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +129 -18
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +36 -34
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +201 -167
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_admin_spec.rb +736 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_spec.rb +27 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +41 -3
- data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +79 -4
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +6 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +129 -10
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +158 -6
- data/lib/submodules/ably-ruby/spec/acceptance/rest/push_admin_spec.rb +952 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/push_spec.rb +25 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/run_parallel_tests +33 -0
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +1 -1
- data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +9 -5
- data/lib/submodules/ably-ruby/spec/support/test_app.rb +2 -2
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +10 -3
- data/lib/submodules/ably-ruby/spec/unit/models/device_details_spec.rb +102 -0
- data/lib/submodules/ably-ruby/spec/unit/models/device_push_details_spec.rb +101 -0
- data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +51 -3
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +17 -2
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/models/push_channel_subscription_spec.rb +86 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +13 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/push_channel_spec.rb +36 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +8 -1
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +30 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/push_channel_spec.rb +36 -0
- metadata +46 -21
@@ -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
|
@@ -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
|
|