ably-rest 0.9.3 → 1.0.0
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/ably-rest.gemspec +2 -1
- data/lib/submodules/ably-ruby/.travis.yml +6 -4
- data/lib/submodules/ably-ruby/CHANGELOG.md +52 -61
- data/lib/submodules/ably-ruby/README.md +10 -0
- data/lib/submodules/ably-ruby/SPEC.md +1473 -852
- data/lib/submodules/ably-ruby/ably.gemspec +2 -1
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +57 -25
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +34 -8
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +10 -1
- data/lib/submodules/ably-ruby/lib/ably/models/auth_details.rb +42 -0
- data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +18 -4
- data/lib/submodules/ably-ruby/lib/ably/models/connection_details.rb +6 -3
- data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +4 -3
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +12 -1
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +101 -97
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +13 -1
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +20 -3
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +7 -3
- data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +17 -7
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +29 -14
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +7 -4
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -4
- data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +7 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +2 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +79 -31
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +62 -26
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +154 -65
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +16 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +108 -49
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +165 -59
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +19 -10
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +67 -45
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +198 -36
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_manager.rb +30 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +3 -3
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +21 -8
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +1 -3
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/util/safe_deferrable.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +416 -99
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +5 -3
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +1011 -160
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +436 -97
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +52 -23
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +5 -3
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1160 -105
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +151 -22
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +88 -27
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +42 -15
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +4 -4
- data/lib/submodules/ably-ruby/spec/rspec_config.rb +2 -1
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +2 -2
- data/lib/submodules/ably-ruby/spec/shared/safe_deferrable_behaviour.rb +6 -2
- data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +20 -4
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +32 -1
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +4 -11
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +28 -2
- data/lib/submodules/ably-ruby/spec/unit/models/auth_details_spec.rb +49 -0
- data/lib/submodules/ably-ruby/spec/unit/models/channel_state_change_spec.rb +23 -3
- data/lib/submodules/ably-ruby/spec/unit/models/connection_details_spec.rb +12 -1
- data/lib/submodules/ably-ruby/spec/unit/models/connection_state_change_spec.rb +15 -4
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +34 -2
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +73 -2
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +64 -6
- data/lib/submodules/ably-ruby/spec/unit/models/token_details_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/models/token_request_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +2 -1
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +69 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +149 -22
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +9 -3
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +8 -5
- data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +4 -3
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +3 -3
- metadata +7 -5
|
@@ -4,7 +4,8 @@ module Ably::Modules
|
|
|
4
4
|
# module, and the class is an {EventEmitter}. It then emits state changes.
|
|
5
5
|
#
|
|
6
6
|
# It also ensures the EventEmitter is configured to retrict permitted events to the
|
|
7
|
-
# the available STATEs
|
|
7
|
+
# the available STATEs or EVENTs if defined i.e. if EVENTs includes an additional type such as
|
|
8
|
+
# :update, then it will support all EVENTs being emitted. EVENTs must be a superset of STATEs
|
|
8
9
|
#
|
|
9
10
|
# @note This module requires that the method #logger is defined.
|
|
10
11
|
#
|
|
@@ -51,7 +52,7 @@ module Ably::Modules
|
|
|
51
52
|
# @api private
|
|
52
53
|
def state=(new_state, *args)
|
|
53
54
|
if state != new_state
|
|
54
|
-
logger.debug
|
|
55
|
+
logger.debug { "#{self.class}: StateEmitter changed from #{state} => #{new_state}" } if respond_to?(:logger, true)
|
|
55
56
|
@state = STATE(new_state)
|
|
56
57
|
emit @state, *args
|
|
57
58
|
end
|
|
@@ -153,8 +154,10 @@ module Ably::Modules
|
|
|
153
154
|
|
|
154
155
|
def self.included(klass)
|
|
155
156
|
klass.configure_event_emitter coerce_into: Proc.new { |event|
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
# Special case allows EVENT instead of STATE to be emitted
|
|
158
|
+
# Relies on the assumption that EVENT is a superset of STATE
|
|
159
|
+
if klass.const_defined?(:EVENT)
|
|
160
|
+
klass::EVENT(event)
|
|
158
161
|
else
|
|
159
162
|
klass::STATE(event)
|
|
160
163
|
end
|
|
@@ -18,14 +18,12 @@ module Ably::Modules
|
|
|
18
18
|
|
|
19
19
|
# Alternative to Statesman's #transition_to that:
|
|
20
20
|
# * log state change failures to {Logger}
|
|
21
|
-
# * raise an exception on the {Ably::Realtime::Channel}
|
|
22
21
|
#
|
|
23
22
|
# @return [void]
|
|
24
23
|
def transition_state(state, *args)
|
|
25
|
-
unless result = transition_to(state, *args)
|
|
24
|
+
unless result = transition_to(state.to_sym, *args)
|
|
26
25
|
exception = exception_for_state_change_to(state)
|
|
27
|
-
|
|
28
|
-
logger.fatal "#{self.class}: #{exception.message}"
|
|
26
|
+
logger.fatal { "#{self.class}: #{exception.message}" }
|
|
29
27
|
end
|
|
30
28
|
result
|
|
31
29
|
end
|
|
@@ -67,15 +67,19 @@ module Ably::Modules
|
|
|
67
67
|
|
|
68
68
|
def log_state_machine_state_change
|
|
69
69
|
if state_machine.previous_state
|
|
70
|
-
logger.debug "#{self.class.name}: Transitioned from #{state_machine.previous_state} => #{state_machine.current_state}"
|
|
70
|
+
logger.debug { "#{self.class.name}: Transitioned from #{state_machine.previous_state} => #{state_machine.current_state}" }
|
|
71
71
|
else
|
|
72
|
-
logger.debug "#{self.class.name}: Transitioned to #{state_machine.current_state}"
|
|
72
|
+
logger.debug { "#{self.class.name}: Transitioned to #{state_machine.current_state}" }
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def emit_object(new_state, emit_params)
|
|
77
77
|
if self.class.emits_klass
|
|
78
|
-
self.class.emits_klass.new((emit_params || {}).merge(
|
|
78
|
+
self.class.emits_klass.new((emit_params || {}).merge(
|
|
79
|
+
current: STATE(new_state),
|
|
80
|
+
previous: STATE(state_machine.current_state),
|
|
81
|
+
event: EVENT(new_state)
|
|
82
|
+
))
|
|
79
83
|
else
|
|
80
84
|
emit_params
|
|
81
85
|
end
|
|
@@ -11,6 +11,8 @@ require 'ably/realtime/client'
|
|
|
11
11
|
require 'ably/realtime/connection'
|
|
12
12
|
require 'ably/realtime/presence'
|
|
13
13
|
|
|
14
|
+
require 'ably/models/message_encoders/base'
|
|
15
|
+
|
|
14
16
|
Dir.glob(File.expand_path("models/*.rb", File.dirname(__FILE__))).each do |file|
|
|
15
17
|
require file
|
|
16
18
|
end
|
|
@@ -44,13 +44,15 @@ module Ably
|
|
|
44
44
|
def_delegators :auth_sync, :using_basic_auth?, :using_token_auth?
|
|
45
45
|
def_delegators :auth_sync, :token_renewable?, :authentication_security_requirements_met?
|
|
46
46
|
def_delegators :client, :logger
|
|
47
|
+
def_delegators :client, :connection
|
|
47
48
|
|
|
48
49
|
def initialize(client)
|
|
49
50
|
@client = client
|
|
50
51
|
@auth_sync = client.rest_client.auth
|
|
51
52
|
end
|
|
52
53
|
|
|
53
|
-
#
|
|
54
|
+
# For new connections, ensures valid auth credentials are present for the library instance. This may rely on an already-known and valid token, and will obtain a new token if necessary.
|
|
55
|
+
# If a connection is already established, the connection will be upgraded with a new token
|
|
54
56
|
#
|
|
55
57
|
# In the event that a new token request is made, the provided options are used
|
|
56
58
|
#
|
|
@@ -68,41 +70,86 @@ module Ably
|
|
|
68
70
|
# end
|
|
69
71
|
#
|
|
70
72
|
def authorize(token_params = nil, auth_options = nil, &success_callback)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
Ably::Util::SafeDeferrable.new(logger).tap do |authorize_method_deferrable|
|
|
74
|
+
# Wrap the sync authorize method and wait for the result from the deferrable
|
|
75
|
+
async_wrap do
|
|
76
|
+
authorize_sync(token_params, auth_options)
|
|
77
|
+
end.tap do |auth_operation|
|
|
78
|
+
# Authorize operation succeeded and we have a new token, now let's perform inline authentication
|
|
79
|
+
auth_operation.callback do |token|
|
|
80
|
+
case connection.state.to_sym
|
|
81
|
+
when :initialized, :disconnected, :suspended, :closed, :closing, :failed
|
|
82
|
+
connection.connect
|
|
83
|
+
when :connected
|
|
84
|
+
perform_inline_auth token
|
|
85
|
+
when :connecting
|
|
86
|
+
# Fail all current connection attempts and try again with the new token, see #RTC8b
|
|
87
|
+
connection.manager.release_and_establish_new_transport
|
|
88
|
+
else
|
|
89
|
+
logger.fatal { "Auth#authorize: unsupported state #{connection.state}" }
|
|
90
|
+
authorize_method_deferrable.fail Ably::Exceptions::InvalidState.new("Unsupported state #{connection.state} for Auth#authorize")
|
|
91
|
+
next
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Indicate success or failure based on response from realtime, see #RTC8b1
|
|
95
|
+
auth_deferrable_resolved = false
|
|
96
|
+
|
|
97
|
+
connection.unsafe_once(:connected, :update) do
|
|
98
|
+
auth_deferrable_resolved = true
|
|
99
|
+
authorize_method_deferrable.succeed token
|
|
100
|
+
end
|
|
101
|
+
connection.unsafe_once(:suspended, :closed, :failed) do |state_change|
|
|
102
|
+
auth_deferrable_resolved = true
|
|
103
|
+
authorize_method_deferrable.fail state_change.reason
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Authorize failed, likely due to auth_url or auth_callback failing
|
|
108
|
+
auth_operation.errback do |error|
|
|
109
|
+
client.connection.transition_state_machine :failed, reason: error if error.kind_of?(Ably::Exceptions::IncompatibleClientId)
|
|
110
|
+
authorize_method_deferrable.fail error
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Call the block provided to this method upon success of this deferrable
|
|
115
|
+
authorize_method_deferrable.callback do |token|
|
|
116
|
+
yield token if block_given?
|
|
76
117
|
end
|
|
77
118
|
end
|
|
78
119
|
end
|
|
79
120
|
|
|
80
121
|
# @deprecated Use {#authorize} instead
|
|
81
122
|
def authorise(*args, &block)
|
|
82
|
-
logger.warn "Auth#authorise is deprecated and will be removed in 1.0. Please use Auth#authorize instead"
|
|
123
|
+
logger.warn { "Auth#authorise is deprecated and will be removed in 1.0. Please use Auth#authorize instead" }
|
|
83
124
|
authorize(*args, &block)
|
|
84
125
|
end
|
|
85
126
|
|
|
86
127
|
# Synchronous version of {#authorize}. See {Ably::Auth#authorize} for method definition
|
|
128
|
+
# Please note that authorize_sync will however not upgrade the current connection's token as this requires
|
|
129
|
+
# an synchronous operation to send the new authentication details to Ably over a realtime connection
|
|
130
|
+
#
|
|
87
131
|
# @param (see Ably::Auth#authorize)
|
|
88
132
|
# @option (see Ably::Auth#authorize)
|
|
89
133
|
# @return [Ably::Models::TokenDetails]
|
|
90
134
|
#
|
|
91
135
|
def authorize_sync(token_params = nil, auth_options = nil)
|
|
92
|
-
|
|
136
|
+
@authorization_in_flight = true
|
|
137
|
+
auth_sync.authorize(token_params, auth_options)
|
|
138
|
+
ensure
|
|
139
|
+
@authorization_in_flight = false
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @api private
|
|
143
|
+
def authorization_in_flight?
|
|
144
|
+
@authorization_in_flight
|
|
93
145
|
end
|
|
94
146
|
|
|
95
147
|
# @deprecated Use {#authorize_sync} instead
|
|
96
148
|
def authorise_sync(*args)
|
|
97
|
-
logger.warn "Auth#authorise_sync is deprecated and will be removed in 1.0. Please use Auth#authorize_sync instead"
|
|
149
|
+
logger.warn { "Auth#authorise_sync is deprecated and will be removed in 1.0. Please use Auth#authorize_sync instead" }
|
|
98
150
|
authorize_sync(*args)
|
|
99
151
|
end
|
|
100
152
|
|
|
101
|
-
# def_delegator :auth_sync, :request_token, :request_token_sync
|
|
102
|
-
# def_delegator :auth_sync, :create_token_request, :create_token_request_sync
|
|
103
|
-
# def_delegator :auth_sync, :auth_header, :auth_header_sync
|
|
104
|
-
# def_delegator :auth_sync, :auth_params, :auth_params_sync
|
|
105
|
-
|
|
106
153
|
# Request a {Ably::Models::TokenDetails} which can be used to make authenticated token based requests
|
|
107
154
|
#
|
|
108
155
|
# @param (see Ably::Auth#request_token)
|
|
@@ -186,7 +233,16 @@ module Ably
|
|
|
186
233
|
# @yield [Hash] Auth params for a new Realtime connection
|
|
187
234
|
#
|
|
188
235
|
def auth_params(&success_callback)
|
|
189
|
-
|
|
236
|
+
fail_callback = Proc.new do |error, deferrable|
|
|
237
|
+
logger.error { "Failed to authenticate: #{error}" }
|
|
238
|
+
if error.kind_of?(Ably::Exceptions::BaseAblyException)
|
|
239
|
+
# Use base exception if it exists carrying forward the status codes
|
|
240
|
+
deferrable.fail Ably::Exceptions::AuthenticationFailed.new(error.message, nil, nil, error)
|
|
241
|
+
else
|
|
242
|
+
deferrable.fail Ably::Exceptions::AuthenticationFailed.new(error.message, 500, 80019)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
async_wrap(success_callback, fail_callback) do
|
|
190
246
|
auth_params_sync
|
|
191
247
|
end
|
|
192
248
|
end
|
|
@@ -209,22 +265,14 @@ module Ably
|
|
|
209
265
|
@client
|
|
210
266
|
end
|
|
211
267
|
|
|
212
|
-
#
|
|
213
|
-
#
|
|
214
|
-
def
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
logger.debug "Realtime::Auth - current transport disconnected"
|
|
221
|
-
client.connection.transport.disconnect
|
|
222
|
-
else
|
|
223
|
-
EventMachine.add_timer(0.1, &block)
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
block.call
|
|
227
|
-
end
|
|
268
|
+
# Sends an AUTH ProtocolMessage on the existing connection triggering
|
|
269
|
+
# an inline AUTH process, see #RTC8a
|
|
270
|
+
def perform_inline_auth(token)
|
|
271
|
+
logger.debug { "Performing inline AUTH with Ably using token #{token}" }
|
|
272
|
+
connection.send_protocol_message(
|
|
273
|
+
action: Ably::Models::ProtocolMessage::ACTION.Auth.to_i,
|
|
274
|
+
auth: { access_token: token.token }
|
|
275
|
+
)
|
|
228
276
|
end
|
|
229
277
|
end
|
|
230
278
|
end
|
|
@@ -23,8 +23,6 @@ module Ably
|
|
|
23
23
|
# Channel::STATE.Detached
|
|
24
24
|
# Channel::STATE.Failed
|
|
25
25
|
#
|
|
26
|
-
# Channels emit errors - use +on(:error)+ to subscribe to errors
|
|
27
|
-
#
|
|
28
26
|
# @!attribute [r] state
|
|
29
27
|
# @return {Ably::Realtime::Connection::STATE} channel state
|
|
30
28
|
#
|
|
@@ -36,14 +34,24 @@ module Ably
|
|
|
36
34
|
include Ably::Modules::MessageEmitter
|
|
37
35
|
extend Ably::Modules::Enum
|
|
38
36
|
|
|
37
|
+
# ChannelState
|
|
38
|
+
# The permited states for this channel
|
|
39
39
|
STATE = ruby_enum('STATE',
|
|
40
40
|
:initialized,
|
|
41
41
|
:attaching,
|
|
42
42
|
:attached,
|
|
43
43
|
:detaching,
|
|
44
44
|
:detached,
|
|
45
|
+
:suspended,
|
|
45
46
|
:failed
|
|
46
47
|
)
|
|
48
|
+
|
|
49
|
+
# ChannelEvent
|
|
50
|
+
# The permitted channel events that are emitted for this channel
|
|
51
|
+
EVENT = ruby_enum('EVENT',
|
|
52
|
+
STATE.to_sym_arr + [:update]
|
|
53
|
+
)
|
|
54
|
+
|
|
47
55
|
include Ably::Modules::StateEmitter
|
|
48
56
|
include Ably::Modules::UsesStateMachine
|
|
49
57
|
ensure_state_machine_emits 'Ably::Models::ChannelStateChange'
|
|
@@ -138,11 +146,14 @@ module Ably
|
|
|
138
146
|
# end
|
|
139
147
|
#
|
|
140
148
|
def publish(name, data = nil, attributes = {}, &success_block)
|
|
141
|
-
|
|
142
|
-
|
|
149
|
+
if detached? || detaching? || failed?
|
|
150
|
+
error = Ably::Exceptions::ChannelInactive.new("Cannot publish messages on a channel in state #{state}")
|
|
151
|
+
return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
|
|
152
|
+
end
|
|
143
153
|
|
|
144
154
|
if !connection.can_publish_messages?
|
|
145
|
-
|
|
155
|
+
error = Ably::Exceptions::MessageQueueingDisabled.new("Message cannot be published. Client is configured to disallow queueing of messages and connection is currently #{connection.state}")
|
|
156
|
+
return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
|
|
146
157
|
end
|
|
147
158
|
|
|
148
159
|
messages = if name.kind_of?(Enumerable)
|
|
@@ -192,10 +203,19 @@ module Ably
|
|
|
192
203
|
#
|
|
193
204
|
def attach(&success_block)
|
|
194
205
|
if connection.closing? || connection.closed? || connection.suspended? || connection.failed?
|
|
195
|
-
|
|
206
|
+
error = Ably::Exceptions::InvalidStateChange.new("Cannot ATTACH channel when the connection is in a closed, suspended or failed state. Connection state: #{connection.state}")
|
|
207
|
+
return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
if !attached?
|
|
211
|
+
if detaching?
|
|
212
|
+
# Let the pending operation complete (#RTL4h)
|
|
213
|
+
once_state_changed { transition_state_machine :attaching if can_transition_to?(:attaching) }
|
|
214
|
+
else
|
|
215
|
+
transition_state_machine :attaching if can_transition_to?(:attaching)
|
|
216
|
+
end
|
|
196
217
|
end
|
|
197
218
|
|
|
198
|
-
transition_state_machine :attaching if can_transition_to?(:attaching)
|
|
199
219
|
deferrable_for_state_change_to(STATE.Attached, &success_block)
|
|
200
220
|
end
|
|
201
221
|
|
|
@@ -207,14 +227,24 @@ module Ably
|
|
|
207
227
|
def detach(&success_block)
|
|
208
228
|
if initialized?
|
|
209
229
|
success_block.call if block_given?
|
|
210
|
-
return Ably::Util::SafeDeferrable.
|
|
211
|
-
|
|
212
|
-
|
|
230
|
+
return Ably::Util::SafeDeferrable.new_and_succeed_immediately(logger)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
if failed? || connection.closing? || connection.failed?
|
|
234
|
+
return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, exception_for_state_change_to(:detaching))
|
|
213
235
|
end
|
|
214
236
|
|
|
215
|
-
|
|
237
|
+
if !detached?
|
|
238
|
+
if attaching?
|
|
239
|
+
# Let the pending operation complete (#RTL5i)
|
|
240
|
+
once_state_changed { transition_state_machine :detaching if can_transition_to?(:detaching) }
|
|
241
|
+
elsif can_transition_to?(:detaching)
|
|
242
|
+
transition_state_machine :detaching
|
|
243
|
+
else
|
|
244
|
+
transition_state_machine! :detached
|
|
245
|
+
end
|
|
246
|
+
end
|
|
216
247
|
|
|
217
|
-
transition_state_machine :detaching if can_transition_to?(:detaching)
|
|
218
248
|
deferrable_for_state_change_to(STATE.Detached, &success_block)
|
|
219
249
|
end
|
|
220
250
|
|
|
@@ -244,7 +274,10 @@ module Ably
|
|
|
244
274
|
#
|
|
245
275
|
def history(options = {}, &callback)
|
|
246
276
|
if options.delete(:until_attach)
|
|
247
|
-
|
|
277
|
+
unless attached?
|
|
278
|
+
error = Ably::Exceptions::InvalidRequest.new('option :until_attach is invalid as the channel is not attached' )
|
|
279
|
+
return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
|
|
280
|
+
end
|
|
248
281
|
options[:from_serial] = attached_serial
|
|
249
282
|
end
|
|
250
283
|
|
|
@@ -263,7 +296,7 @@ module Ably
|
|
|
263
296
|
end
|
|
264
297
|
|
|
265
298
|
# @api private
|
|
266
|
-
def
|
|
299
|
+
def set_channel_error_reason(error)
|
|
267
300
|
@error_reason = error
|
|
268
301
|
end
|
|
269
302
|
|
|
@@ -288,25 +321,26 @@ module Ably
|
|
|
288
321
|
client.logger
|
|
289
322
|
end
|
|
290
323
|
|
|
324
|
+
# Internal queue used for messages published that cannot yet be enqueued on the connection
|
|
325
|
+
# @api private
|
|
326
|
+
def __queue__
|
|
327
|
+
@queue
|
|
328
|
+
end
|
|
329
|
+
|
|
291
330
|
# As we are using a state machine, do not allow change_state to be used
|
|
292
331
|
# #transition_state_machine must be used instead
|
|
293
332
|
private :change_state
|
|
294
333
|
|
|
295
334
|
private
|
|
296
|
-
def queue
|
|
297
|
-
@queue
|
|
298
|
-
end
|
|
299
|
-
|
|
300
335
|
def setup_event_handlers
|
|
301
336
|
__incoming_msgbus__.subscribe(:message) do |message|
|
|
302
337
|
message.decode(client.encoders, options) do |encode_error, error_message|
|
|
303
338
|
client.logger.error error_message
|
|
304
|
-
emit :error, encode_error
|
|
305
339
|
end
|
|
306
340
|
emit_message message.name, message
|
|
307
341
|
end
|
|
308
342
|
|
|
309
|
-
|
|
343
|
+
unsafe_on(STATE.Attached) do
|
|
310
344
|
process_queue
|
|
311
345
|
end
|
|
312
346
|
end
|
|
@@ -319,15 +353,18 @@ module Ably
|
|
|
319
353
|
create_message(raw_msg).tap do |message|
|
|
320
354
|
next if message.client_id.nil?
|
|
321
355
|
if message.client_id == '*'
|
|
322
|
-
raise Ably::Exceptions::IncompatibleClientId.new('Wildcard client_id is reserved and cannot be used when publishing messages'
|
|
356
|
+
raise Ably::Exceptions::IncompatibleClientId.new('Wildcard client_id is reserved and cannot be used when publishing messages')
|
|
357
|
+
end
|
|
358
|
+
if message.client_id && !message.client_id.kind_of?(String)
|
|
359
|
+
raise Ably::Exceptions::IncompatibleClientId.new('client_id must be a String when publishing messages')
|
|
323
360
|
end
|
|
324
361
|
unless client.auth.can_assume_client_id?(message.client_id)
|
|
325
|
-
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}'"
|
|
362
|
+
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}'")
|
|
326
363
|
end
|
|
327
364
|
end
|
|
328
365
|
end
|
|
329
366
|
|
|
330
|
-
|
|
367
|
+
__queue__.push(*messages)
|
|
331
368
|
|
|
332
369
|
if attached?
|
|
333
370
|
process_queue
|
|
@@ -369,14 +406,14 @@ module Ably
|
|
|
369
406
|
end
|
|
370
407
|
|
|
371
408
|
def messages_in_queue?
|
|
372
|
-
!
|
|
409
|
+
!__queue__.empty?
|
|
373
410
|
end
|
|
374
411
|
|
|
375
412
|
# Move messages from Channel Queue into Outgoing Connection Queue
|
|
376
413
|
def process_queue
|
|
377
414
|
condition = -> { attached? && messages_in_queue? }
|
|
378
415
|
non_blocking_loop_while(condition) do
|
|
379
|
-
send_messages_within_protocol_message
|
|
416
|
+
send_messages_within_protocol_message __queue__.shift(MAX_PROTOCOL_MESSAGE_BATCH_SIZE)
|
|
380
417
|
end
|
|
381
418
|
end
|
|
382
419
|
|
|
@@ -392,7 +429,6 @@ module Ably
|
|
|
392
429
|
Ably::Models::Message(message.dup).tap do |msg|
|
|
393
430
|
msg.encode(client.encoders, options) do |encode_error, error_message|
|
|
394
431
|
client.logger.error error_message
|
|
395
|
-
emit :error, encode_error
|
|
396
432
|
end
|
|
397
433
|
end
|
|
398
434
|
end
|
|
@@ -12,8 +12,6 @@ module Ably::Realtime
|
|
|
12
12
|
def initialize(channel, connection)
|
|
13
13
|
@channel = channel
|
|
14
14
|
@connection = connection
|
|
15
|
-
|
|
16
|
-
setup_connection_event_handlers
|
|
17
15
|
end
|
|
18
16
|
|
|
19
17
|
# Commence attachment
|
|
@@ -25,64 +23,130 @@ module Ably::Realtime
|
|
|
25
23
|
end
|
|
26
24
|
|
|
27
25
|
# Commence attachment
|
|
28
|
-
def detach(error
|
|
26
|
+
def detach(error, previous_state)
|
|
29
27
|
if connection.closed? || connection.connecting? || connection.suspended?
|
|
30
28
|
channel.transition_state_machine :detached, reason: error
|
|
31
29
|
elsif can_transition_to?(:detached)
|
|
32
|
-
send_detach_protocol_message
|
|
30
|
+
send_detach_protocol_message previous_state
|
|
33
31
|
end
|
|
34
32
|
end
|
|
35
33
|
|
|
36
34
|
# Channel is attached, notify presence if sync is expected
|
|
37
35
|
def attached(attached_protocol_message)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
# If no attached ProtocolMessage then this attached request was triggered by the client
|
|
37
|
+
# library, such as returning to attached whne detach has failed
|
|
38
|
+
if attached_protocol_message
|
|
39
|
+
update_presence_sync_state_following_attached attached_protocol_message
|
|
40
|
+
channel.set_attached_serial attached_protocol_message.channel_serial
|
|
42
41
|
end
|
|
43
|
-
channel.set_attached_serial attached_protocol_message.channel_serial
|
|
44
42
|
end
|
|
45
43
|
|
|
46
44
|
# An error has occurred on the channel
|
|
47
|
-
def
|
|
48
|
-
logger.error "ChannelManager: Channel '#{channel.name}' error: #{error}"
|
|
49
|
-
channel.emit :error, error
|
|
45
|
+
def log_channel_error(error)
|
|
46
|
+
logger.error { "ChannelManager: Channel '#{channel.name}' error: #{error}" }
|
|
50
47
|
end
|
|
51
48
|
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
# Request channel to be reattached by sending an attach protocol message
|
|
50
|
+
# @param [Hash] options
|
|
51
|
+
# @option options [Ably::Models::ErrorInfo] :reason
|
|
52
|
+
def request_reattach(options = {})
|
|
53
|
+
reason = options[:reason]
|
|
54
|
+
send_attach_protocol_message
|
|
55
|
+
logger.debug { "Explicit channel reattach request sent to Ably due to #{reason}" }
|
|
56
|
+
channel.set_channel_error_reason(reason) if reason
|
|
57
|
+
channel.transition_state_machine! :attaching, reason: reason unless channel.attaching?
|
|
55
58
|
end
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
def duplicate_attached_received(protocol_message)
|
|
61
|
+
if protocol_message.error
|
|
62
|
+
channel.set_channel_error_reason protocol_message.error
|
|
63
|
+
log_channel_error protocol_message.error
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if protocol_message.has_channel_resumed_flag?
|
|
67
|
+
logger.debug { "ChannelManager: Additional resumed ATTACHED message received for #{channel.state} channel '#{channel.name}'" }
|
|
68
|
+
else
|
|
69
|
+
channel.emit :update, Ably::Models::ChannelStateChange.new(
|
|
70
|
+
current: channel.state,
|
|
71
|
+
previous: channel.state,
|
|
72
|
+
event: Ably::Realtime::Channel::EVENT(:update),
|
|
73
|
+
reason: protocol_message.error,
|
|
74
|
+
resumed: false,
|
|
75
|
+
)
|
|
76
|
+
update_presence_sync_state_following_attached protocol_message
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
channel.set_attached_serial protocol_message.channel_serial
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Handle DETACED messages, see #RTL13 for server-initated detaches
|
|
83
|
+
def detached_received(reason)
|
|
84
|
+
case channel.state.to_sym
|
|
85
|
+
when :detaching
|
|
86
|
+
channel.transition_state_machine :detached, reason: reason
|
|
87
|
+
when :attached, :suspended
|
|
88
|
+
channel.transition_state_machine :attaching, reason: reason
|
|
89
|
+
else
|
|
90
|
+
logger.debug { "ChannelManager: DETACHED ProtocolMessage received, but no action to take as not DETACHING, ATTACHED OR SUSPENDED" }
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# When continuity on the connection is interrupted or channel becomes suspended (implying loss of continuity)
|
|
95
|
+
# then all messages published but awaiting an ACK from Ably should be failed with a NACK
|
|
96
|
+
# @param [Hash] options
|
|
97
|
+
# @option options [Boolean] :immediately
|
|
98
|
+
def fail_messages_awaiting_ack(error, options = {})
|
|
99
|
+
immediately = options[:immediately] || false
|
|
100
|
+
|
|
101
|
+
fail_proc = Proc.new do
|
|
102
|
+
error = Ably::Exceptions::MessageDeliveryFailed.new("Continuity of connection was lost so published messages awaiting ACK have failed") unless error
|
|
63
103
|
fail_messages_in_queue connection.__pending_message_ack_queue__, error
|
|
64
|
-
fail_messages_in_queue connection.__outgoing_message_queue__, error
|
|
65
104
|
end
|
|
105
|
+
|
|
106
|
+
# Allow a short time for other queued operations to complete before failing all messages
|
|
107
|
+
if immediately
|
|
108
|
+
fail_proc.call
|
|
109
|
+
else
|
|
110
|
+
EventMachine.add_timer(0.1) { fail_proc.call }
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# When a channel becomes detached, suspended or failed,
|
|
115
|
+
# all queued messages should be failed immediately as we don't queue in
|
|
116
|
+
# any of those states
|
|
117
|
+
def fail_queued_messages(error)
|
|
118
|
+
error = Ably::Exceptions::MessageDeliveryFailed.new("Queued messages on channel '#{channel.name}' in state '#{channel.state}' will never be delivered") unless error
|
|
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
|
|
66
124
|
end
|
|
67
125
|
|
|
68
126
|
def fail_messages_in_queue(queue, error)
|
|
69
127
|
queue.delete_if do |protocol_message|
|
|
70
|
-
if protocol_message.
|
|
71
|
-
|
|
72
|
-
|
|
128
|
+
if protocol_message.action.match_any?(:presence, :message)
|
|
129
|
+
if protocol_message.channel == channel.name
|
|
130
|
+
nack_messages protocol_message, error
|
|
131
|
+
true
|
|
132
|
+
end
|
|
73
133
|
end
|
|
74
134
|
end
|
|
75
135
|
end
|
|
76
136
|
|
|
77
137
|
def nack_messages(protocol_message, error)
|
|
78
138
|
(protocol_message.messages + protocol_message.presence).each do |message|
|
|
79
|
-
|
|
80
|
-
message.fail error
|
|
139
|
+
nack_message message, error, protocol_message
|
|
81
140
|
end
|
|
82
|
-
logger.debug "Calling NACK failure callbacks for #{protocol_message.class.name} - #{protocol_message.to_json}"
|
|
141
|
+
logger.debug { "Calling NACK failure callbacks for #{protocol_message.class.name} - #{protocol_message.to_json}" }
|
|
83
142
|
protocol_message.fail error
|
|
84
143
|
end
|
|
85
144
|
|
|
145
|
+
def nack_message(message, error, protocol_message = nil)
|
|
146
|
+
logger.debug { "Calling NACK failure callbacks for #{message.class.name} - #{message.to_json} #{"protocol message: #{protocol_message}" if protocol_message}" }
|
|
147
|
+
message.fail error
|
|
148
|
+
end
|
|
149
|
+
|
|
86
150
|
def drop_pending_queue_from_ack(ack_protocol_message)
|
|
87
151
|
message_serial_up_to = ack_protocol_message.message_serial + ack_protocol_message.count - 1
|
|
88
152
|
connection.__pending_message_ack_queue__.drop_while do |protocol_message|
|
|
@@ -93,7 +157,23 @@ module Ably::Realtime
|
|
|
93
157
|
end
|
|
94
158
|
end
|
|
95
159
|
|
|
160
|
+
# If the connection is still connected and the channel still suspended after
|
|
161
|
+
# channel_retry_timeout has passed, then attempt to reattach automatically, see #RTL13b
|
|
162
|
+
def start_attach_from_suspended_timer
|
|
163
|
+
cancel_attach_from_suspended_timer
|
|
164
|
+
if connection.connected?
|
|
165
|
+
channel.unsafe_once { |event| cancel_attach_from_suspended_timer unless event == :update }
|
|
166
|
+
connection.unsafe_once { |event| cancel_attach_from_suspended_timer unless event == :update }
|
|
167
|
+
|
|
168
|
+
@attach_from_suspended_timer = EventMachine::Timer.new(channel_retry_timeout) do
|
|
169
|
+
channel.transition_state_machine! :attaching
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
96
174
|
private
|
|
175
|
+
attr_reader :pending_state_change_timer
|
|
176
|
+
|
|
97
177
|
def channel
|
|
98
178
|
@channel
|
|
99
179
|
end
|
|
@@ -104,63 +184,72 @@ module Ably::Realtime
|
|
|
104
184
|
|
|
105
185
|
def_delegators :channel, :can_transition_to?
|
|
106
186
|
|
|
187
|
+
def cancel_attach_from_suspended_timer
|
|
188
|
+
@attach_from_suspended_timer.cancel if @attach_from_suspended_timer
|
|
189
|
+
@attach_from_suspended_timer = nil
|
|
190
|
+
end
|
|
191
|
+
|
|
107
192
|
# If the connection has not previously connected, connect now
|
|
108
193
|
def connect_if_connection_initialized
|
|
109
194
|
connection.connect if connection.initialized?
|
|
110
195
|
end
|
|
111
196
|
|
|
112
|
-
def
|
|
113
|
-
|
|
197
|
+
def realtime_request_timeout
|
|
198
|
+
connection.defaults.fetch(:realtime_request_timeout)
|
|
114
199
|
end
|
|
115
200
|
|
|
116
|
-
def
|
|
117
|
-
|
|
201
|
+
def channel_retry_timeout
|
|
202
|
+
connection.defaults.fetch(:channel_retry_timeout)
|
|
118
203
|
end
|
|
119
204
|
|
|
120
|
-
def
|
|
121
|
-
|
|
122
|
-
action: state.to_i,
|
|
123
|
-
channel: channel.name
|
|
124
|
-
)
|
|
205
|
+
def send_attach_protocol_message
|
|
206
|
+
send_state_change_protocol_message Ably::Models::ProtocolMessage::ACTION.Attach, :suspended # move to suspended
|
|
125
207
|
end
|
|
126
208
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
# It is up to Ably to ensure that duplicate messages are not retransmitted on the channel
|
|
130
|
-
# base on the serial numbers
|
|
131
|
-
#
|
|
132
|
-
# TODO: Move this into the Connection class, it does not belong in a Channel class
|
|
133
|
-
#
|
|
134
|
-
# @api private
|
|
135
|
-
def resend_pending_message_ack_queue
|
|
136
|
-
connection.__pending_message_ack_queue__.delete_if do |protocol_message|
|
|
137
|
-
if protocol_message.channel == channel.name
|
|
138
|
-
connection.__outgoing_message_queue__ << protocol_message
|
|
139
|
-
connection.__outgoing_protocol_msgbus__.publish :protocol_message
|
|
140
|
-
true
|
|
141
|
-
end
|
|
142
|
-
end
|
|
209
|
+
def send_detach_protocol_message(previous_state)
|
|
210
|
+
send_state_change_protocol_message Ably::Models::ProtocolMessage::ACTION.Detach, previous_state # return to previous state if failed
|
|
143
211
|
end
|
|
144
212
|
|
|
145
|
-
def
|
|
146
|
-
|
|
147
|
-
|
|
213
|
+
def send_state_change_protocol_message(new_state, state_if_failed)
|
|
214
|
+
state_at_time_of_request = channel.state
|
|
215
|
+
@pending_state_change_timer = EventMachine::Timer.new(realtime_request_timeout) do
|
|
216
|
+
if channel.state == state_at_time_of_request
|
|
217
|
+
error = Ably::Models::ErrorInfo.new(code: 90007, message: "Channel #{new_state} operation failed (timed out)")
|
|
218
|
+
channel.transition_state_machine state_if_failed, reason: error
|
|
219
|
+
end
|
|
148
220
|
end
|
|
149
221
|
|
|
150
|
-
|
|
151
|
-
if
|
|
152
|
-
|
|
153
|
-
end
|
|
222
|
+
channel.once_state_changed do
|
|
223
|
+
@pending_state_change_timer.cancel if @pending_state_change_timer
|
|
224
|
+
@pending_state_change_timer = nil
|
|
154
225
|
end
|
|
155
226
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
227
|
+
resend_if_disconnected_and_connected = Proc.new do
|
|
228
|
+
connection.unsafe_once(:disconnected) do
|
|
229
|
+
next unless pending_state_change_timer
|
|
230
|
+
connection.unsafe_once(:connected) do
|
|
231
|
+
next unless pending_state_change_timer
|
|
232
|
+
connection.send_protocol_message(
|
|
233
|
+
action: new_state.to_i,
|
|
234
|
+
channel: channel.name
|
|
235
|
+
)
|
|
236
|
+
resend_if_disconnected_and_connected.call
|
|
237
|
+
end
|
|
159
238
|
end
|
|
160
239
|
end
|
|
240
|
+
resend_if_disconnected_and_connected.call
|
|
161
241
|
|
|
162
|
-
connection.
|
|
163
|
-
|
|
242
|
+
connection.send_protocol_message(
|
|
243
|
+
action: new_state.to_i,
|
|
244
|
+
channel: channel.name
|
|
245
|
+
)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def update_presence_sync_state_following_attached(attached_protocol_message)
|
|
249
|
+
if attached_protocol_message.has_presence_flag?
|
|
250
|
+
channel.presence.manager.sync_expected
|
|
251
|
+
else
|
|
252
|
+
channel.presence.manager.sync_not_expected
|
|
164
253
|
end
|
|
165
254
|
end
|
|
166
255
|
|