ably 0.8.15 → 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/.travis.yml +6 -4
- data/CHANGELOG.md +6 -2
- data/README.md +5 -1
- data/SPEC.md +1473 -852
- data/ably.gemspec +11 -8
- data/lib/ably/auth.rb +90 -53
- data/lib/ably/exceptions.rb +37 -8
- data/lib/ably/logger.rb +10 -1
- data/lib/ably/models/auth_details.rb +42 -0
- data/lib/ably/models/channel_state_change.rb +18 -4
- data/lib/ably/models/connection_details.rb +6 -3
- data/lib/ably/models/connection_state_change.rb +4 -3
- data/lib/ably/models/error_info.rb +1 -1
- data/lib/ably/models/message.rb +17 -1
- data/lib/ably/models/message_encoders/base.rb +103 -82
- data/lib/ably/models/message_encoders/base64.rb +1 -1
- data/lib/ably/models/presence_message.rb +16 -1
- data/lib/ably/models/protocol_message.rb +20 -3
- data/lib/ably/models/token_details.rb +11 -1
- data/lib/ably/models/token_request.rb +16 -6
- data/lib/ably/modules/async_wrapper.rb +7 -3
- data/lib/ably/modules/encodeable.rb +51 -12
- data/lib/ably/modules/enum.rb +17 -7
- data/lib/ably/modules/event_emitter.rb +29 -14
- data/lib/ably/modules/model_common.rb +13 -21
- data/lib/ably/modules/state_emitter.rb +7 -4
- data/lib/ably/modules/state_machine.rb +2 -4
- data/lib/ably/modules/uses_state_machine.rb +7 -3
- data/lib/ably/realtime.rb +2 -0
- data/lib/ably/realtime/auth.rb +102 -42
- data/lib/ably/realtime/channel.rb +68 -26
- data/lib/ably/realtime/channel/channel_manager.rb +154 -65
- data/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
- data/lib/ably/realtime/client.rb +18 -3
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
- data/lib/ably/realtime/connection.rb +108 -49
- data/lib/ably/realtime/connection/connection_manager.rb +167 -61
- data/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
- data/lib/ably/realtime/connection/websocket_transport.rb +19 -10
- data/lib/ably/realtime/presence.rb +70 -45
- data/lib/ably/realtime/presence/members_map.rb +201 -36
- data/lib/ably/realtime/presence/presence_manager.rb +30 -6
- data/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
- data/lib/ably/rest.rb +2 -2
- data/lib/ably/rest/channel.rb +5 -5
- data/lib/ably/rest/client.rb +31 -27
- data/lib/ably/rest/middleware/exceptions.rb +1 -3
- data/lib/ably/rest/middleware/logger.rb +2 -2
- data/lib/ably/rest/presence.rb +2 -2
- data/lib/ably/util/pub_sub.rb +1 -1
- data/lib/ably/util/safe_deferrable.rb +26 -0
- data/lib/ably/version.rb +2 -2
- data/spec/acceptance/realtime/auth_spec.rb +470 -111
- data/spec/acceptance/realtime/channel_history_spec.rb +5 -3
- data/spec/acceptance/realtime/channel_spec.rb +1017 -168
- data/spec/acceptance/realtime/client_spec.rb +6 -6
- data/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
- data/spec/acceptance/realtime/connection_spec.rb +424 -105
- data/spec/acceptance/realtime/message_spec.rb +52 -23
- data/spec/acceptance/realtime/presence_history_spec.rb +5 -3
- data/spec/acceptance/realtime/presence_spec.rb +1110 -96
- data/spec/acceptance/rest/auth_spec.rb +222 -59
- data/spec/acceptance/rest/base_spec.rb +1 -1
- data/spec/acceptance/rest/channel_spec.rb +1 -2
- data/spec/acceptance/rest/client_spec.rb +104 -48
- data/spec/acceptance/rest/message_spec.rb +42 -15
- data/spec/acceptance/rest/presence_spec.rb +4 -11
- data/spec/rspec_config.rb +2 -1
- data/spec/shared/client_initializer_behaviour.rb +2 -2
- data/spec/shared/safe_deferrable_behaviour.rb +6 -2
- data/spec/spec_helper.rb +4 -2
- data/spec/support/debug_failure_helper.rb +20 -4
- data/spec/support/event_machine_helper.rb +32 -1
- data/spec/unit/auth_spec.rb +4 -11
- data/spec/unit/logger_spec.rb +28 -2
- data/spec/unit/models/auth_details_spec.rb +49 -0
- data/spec/unit/models/channel_state_change_spec.rb +23 -3
- data/spec/unit/models/connection_details_spec.rb +12 -1
- data/spec/unit/models/connection_state_change_spec.rb +15 -4
- data/spec/unit/models/message_encoders/base64_spec.rb +2 -1
- data/spec/unit/models/message_spec.rb +153 -0
- data/spec/unit/models/presence_message_spec.rb +192 -0
- data/spec/unit/models/protocol_message_spec.rb +64 -6
- data/spec/unit/models/token_details_spec.rb +75 -0
- data/spec/unit/models/token_request_spec.rb +74 -0
- data/spec/unit/modules/async_wrapper_spec.rb +2 -1
- data/spec/unit/modules/enum_spec.rb +69 -0
- data/spec/unit/modules/event_emitter_spec.rb +149 -22
- data/spec/unit/modules/state_emitter_spec.rb +9 -3
- data/spec/unit/realtime/client_spec.rb +1 -1
- data/spec/unit/realtime/connection_spec.rb +8 -5
- data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
- data/spec/unit/realtime/presence_spec.rb +4 -3
- data/spec/unit/rest/client_spec.rb +1 -1
- data/spec/unit/util/crypto_spec.rb +3 -3
- metadata +22 -19
@@ -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
|
data/lib/ably/realtime.rb
CHANGED
@@ -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
|
data/lib/ably/realtime/auth.rb
CHANGED
@@ -44,18 +44,20 @@ 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
|
#
|
57
|
-
# @param (see Ably::Auth#
|
58
|
-
# @option (see Ably::Auth#
|
59
|
+
# @param (see Ably::Auth#authorize)
|
60
|
+
# @option (see Ably::Auth#authorize)
|
59
61
|
#
|
60
62
|
# @return [Ably::Util::SafeDeferrable]
|
61
63
|
# @yield [Ably::Models::TokenDetails]
|
@@ -63,33 +65,90 @@ module Ably
|
|
63
65
|
# @example
|
64
66
|
# # will issue a simple token request using basic auth
|
65
67
|
# client = Ably::Rest::Client.new(key: 'key.id:secret')
|
66
|
-
# client.auth.
|
68
|
+
# client.auth.authorize do |token_details|
|
67
69
|
# token_details #=> Ably::Models::TokenDetails
|
68
70
|
# end
|
69
71
|
#
|
70
|
-
def
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
72
|
+
def authorize(token_params = nil, auth_options = nil, &success_callback)
|
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
|
-
#
|
81
|
-
|
82
|
-
|
121
|
+
# @deprecated Use {#authorize} instead
|
122
|
+
def authorise(*args, &block)
|
123
|
+
logger.warn { "Auth#authorise is deprecated and will be removed in 1.0. Please use Auth#authorize instead" }
|
124
|
+
authorize(*args, &block)
|
125
|
+
end
|
126
|
+
|
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
|
+
#
|
131
|
+
# @param (see Ably::Auth#authorize)
|
132
|
+
# @option (see Ably::Auth#authorize)
|
83
133
|
# @return [Ably::Models::TokenDetails]
|
84
134
|
#
|
85
|
-
def
|
86
|
-
|
135
|
+
def authorize_sync(token_params = nil, auth_options = nil)
|
136
|
+
@authorization_in_flight = true
|
137
|
+
auth_sync.authorize(token_params, auth_options)
|
138
|
+
ensure
|
139
|
+
@authorization_in_flight = false
|
87
140
|
end
|
88
141
|
|
89
|
-
#
|
90
|
-
|
91
|
-
|
92
|
-
|
142
|
+
# @api private
|
143
|
+
def authorization_in_flight?
|
144
|
+
@authorization_in_flight
|
145
|
+
end
|
146
|
+
|
147
|
+
# @deprecated Use {#authorize_sync} instead
|
148
|
+
def authorise_sync(*args)
|
149
|
+
logger.warn { "Auth#authorise_sync is deprecated and will be removed in 1.0. Please use Auth#authorize_sync instead" }
|
150
|
+
authorize_sync(*args)
|
151
|
+
end
|
93
152
|
|
94
153
|
# Request a {Ably::Models::TokenDetails} which can be used to make authenticated token based requests
|
95
154
|
#
|
@@ -113,8 +172,8 @@ module Ably
|
|
113
172
|
end
|
114
173
|
|
115
174
|
# Synchronous version of {#request_token}. See {Ably::Auth#request_token} for method definition
|
116
|
-
# @param (see Ably::Auth#
|
117
|
-
# @option (see Ably::Auth#
|
175
|
+
# @param (see Ably::Auth#authorize)
|
176
|
+
# @option (see Ably::Auth#authorize)
|
118
177
|
# @return [Ably::Models::TokenDetails]
|
119
178
|
#
|
120
179
|
def request_token_sync(token_params = {}, auth_options = {})
|
@@ -140,8 +199,8 @@ module Ably
|
|
140
199
|
end
|
141
200
|
|
142
201
|
# Synchronous version of {#create_token_request}. See {Ably::Auth#create_token_request} for method definition
|
143
|
-
# @param (see Ably::Auth#
|
144
|
-
# @option (see Ably::Auth#
|
202
|
+
# @param (see Ably::Auth#authorize)
|
203
|
+
# @option (see Ably::Auth#authorize)
|
145
204
|
# @return [Ably::Models::TokenRequest]
|
146
205
|
#
|
147
206
|
def create_token_request_sync(token_params = {}, auth_options = {})
|
@@ -149,7 +208,7 @@ module Ably
|
|
149
208
|
end
|
150
209
|
|
151
210
|
# Auth header string used in HTTP requests to Ably
|
152
|
-
# Will
|
211
|
+
# Will reauthorize implicitly if required and capable
|
153
212
|
#
|
154
213
|
# @return [Ably::Util::SafeDeferrable]
|
155
214
|
# @yield [String] HTTP authentication value used in HTTP_AUTHORIZATION header
|
@@ -168,13 +227,22 @@ module Ably
|
|
168
227
|
end
|
169
228
|
|
170
229
|
# Auth params used in URI endpoint for Realtime connections
|
171
|
-
# Will
|
230
|
+
# Will reauthorize implicitly if required and capable
|
172
231
|
#
|
173
232
|
# @return [Ably::Util::SafeDeferrable]
|
174
233
|
# @yield [Hash] Auth params for a new Realtime connection
|
175
234
|
#
|
176
235
|
def auth_params(&success_callback)
|
177
|
-
|
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
|
178
246
|
auth_params_sync
|
179
247
|
end
|
180
248
|
end
|
@@ -197,22 +265,14 @@ module Ably
|
|
197
265
|
@client
|
198
266
|
end
|
199
267
|
|
200
|
-
#
|
201
|
-
#
|
202
|
-
def
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
logger.debug "Realtime::Auth - current transport disconnected"
|
209
|
-
client.connection.transport.disconnect
|
210
|
-
else
|
211
|
-
EventMachine.add_timer(0.1, &block)
|
212
|
-
end
|
213
|
-
end
|
214
|
-
block.call
|
215
|
-
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
|
+
)
|
216
276
|
end
|
217
277
|
end
|
218
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
|
-
EventMachine.next_tick { deferrable.succeed }
|
212
|
-
end
|
230
|
+
return Ably::Util::SafeDeferrable.new_and_succeed_immediately(logger)
|
213
231
|
end
|
214
232
|
|
215
|
-
|
233
|
+
if failed? || connection.closing? || connection.failed?
|
234
|
+
return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, exception_for_state_change_to(:detaching))
|
235
|
+
end
|
236
|
+
|
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,22 +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
|
-
message.decode
|
337
|
+
message.decode(client.encoders, options) do |encode_error, error_message|
|
338
|
+
client.logger.error error_message
|
339
|
+
end
|
303
340
|
emit_message message.name, message
|
304
341
|
end
|
305
342
|
|
306
|
-
|
343
|
+
unsafe_on(STATE.Attached) do
|
307
344
|
process_queue
|
308
345
|
end
|
309
346
|
end
|
@@ -316,15 +353,18 @@ module Ably
|
|
316
353
|
create_message(raw_msg).tap do |message|
|
317
354
|
next if message.client_id.nil?
|
318
355
|
if message.client_id == '*'
|
319
|
-
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')
|
320
360
|
end
|
321
361
|
unless client.auth.can_assume_client_id?(message.client_id)
|
322
|
-
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}'")
|
323
363
|
end
|
324
364
|
end
|
325
365
|
end
|
326
366
|
|
327
|
-
|
367
|
+
__queue__.push(*messages)
|
328
368
|
|
329
369
|
if attached?
|
330
370
|
process_queue
|
@@ -366,14 +406,14 @@ module Ably
|
|
366
406
|
end
|
367
407
|
|
368
408
|
def messages_in_queue?
|
369
|
-
!
|
409
|
+
!__queue__.empty?
|
370
410
|
end
|
371
411
|
|
372
412
|
# Move messages from Channel Queue into Outgoing Connection Queue
|
373
413
|
def process_queue
|
374
414
|
condition = -> { attached? && messages_in_queue? }
|
375
415
|
non_blocking_loop_while(condition) do
|
376
|
-
send_messages_within_protocol_message
|
416
|
+
send_messages_within_protocol_message __queue__.shift(MAX_PROTOCOL_MESSAGE_BATCH_SIZE)
|
377
417
|
end
|
378
418
|
end
|
379
419
|
|
@@ -387,7 +427,9 @@ module Ably
|
|
387
427
|
|
388
428
|
def create_message(message)
|
389
429
|
Ably::Models::Message(message.dup).tap do |msg|
|
390
|
-
msg.encode
|
430
|
+
msg.encode(client.encoders, options) do |encode_error, error_message|
|
431
|
+
client.logger.error error_message
|
432
|
+
end
|
391
433
|
end
|
392
434
|
end
|
393
435
|
|