ably-rest 0.8.2 → 0.8.3

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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -43
  3. data/SPEC.md +707 -580
  4. data/lib/submodules/ably-ruby/.travis.yml +1 -0
  5. data/lib/submodules/ably-ruby/CHANGELOG.md +143 -3
  6. data/lib/submodules/ably-ruby/README.md +1 -1
  7. data/lib/submodules/ably-ruby/SPEC.md +842 -520
  8. data/lib/submodules/ably-ruby/ably.gemspec +1 -1
  9. data/lib/submodules/ably-ruby/lib/ably/auth.rb +114 -87
  10. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +40 -14
  11. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +3 -5
  12. data/lib/submodules/ably-ruby/lib/ably/models/paginated_result.rb +3 -12
  13. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +8 -2
  14. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +15 -3
  15. data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +1 -1
  16. data/lib/submodules/ably-ruby/lib/ably/models/token_details.rb +1 -1
  17. data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +7 -1
  18. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +1 -1
  19. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +6 -3
  20. data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +2 -2
  21. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +1 -1
  22. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -2
  23. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +1 -0
  24. data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +191 -0
  25. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +97 -25
  26. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +11 -3
  27. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +22 -6
  28. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +73 -40
  29. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +48 -33
  30. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +17 -3
  31. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +43 -16
  32. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +57 -26
  33. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +3 -1
  34. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -2
  35. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -0
  36. data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
  37. data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +242 -0
  38. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +277 -5
  39. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channels_spec.rb +64 -0
  40. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +26 -5
  41. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +23 -6
  42. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +167 -16
  43. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +9 -8
  44. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +1 -0
  45. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +121 -10
  46. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +13 -1
  47. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +161 -79
  48. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +3 -3
  49. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +142 -15
  50. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +23 -0
  51. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +180 -18
  52. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +8 -8
  53. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +136 -25
  54. data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +60 -4
  55. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +54 -3
  56. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +7 -6
  57. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +1 -9
  58. data/lib/submodules/ably-ruby/spec/unit/models/paginated_result_spec.rb +1 -18
  59. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +1 -1
  60. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +21 -1
  61. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +10 -3
  62. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +27 -8
  63. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +0 -8
  64. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +7 -7
  65. metadata +5 -2
@@ -26,7 +26,7 @@ module Ably::Realtime
26
26
 
27
27
  # Commence attachment
28
28
  def detach(error = nil)
29
- if connection.closed? || connection.connecting?
29
+ if connection.closed? || connection.connecting? || connection.suspended?
30
30
  channel.transition_state_machine :detached, error
31
31
  elsif can_transition_to?(:detached)
32
32
  send_detach_protocol_message
@@ -59,7 +59,7 @@ module Ably::Realtime
59
59
  def fail_messages_awaiting_ack(error)
60
60
  # Allow a short time for other queued operations to complete before failing all messages
61
61
  EventMachine.add_timer(0.1) do
62
- error = Ably::Exceptions::MessageDeliveryError.new('Channel is no longer in a state suitable to deliver this message to the server') unless error
62
+ error = Ably::Exceptions::MessageDeliveryFailed.new("Channel cannot publish messages whilst state is '#{channel.state}'") unless error
63
63
  fail_messages_in_queue connection.__pending_message_ack_queue__, error
64
64
  fail_messages_in_queue connection.__outgoing_message_queue__, error
65
65
  end
@@ -123,8 +123,16 @@ module Ably::Realtime
123
123
  channel.transition_state_machine :detaching if can_transition_to?(:detaching)
124
124
  end
125
125
 
126
+ connection.unsafe_on(:suspended) do |error|
127
+ if can_transition_to?(:detaching)
128
+ channel.transition_state_machine :detaching, Ably::Exceptions::ConnectionSuspended.new('Connection suspended', nil, 80002, error)
129
+ end
130
+ end
131
+
126
132
  connection.unsafe_on(:failed) do |error|
127
- channel.transition_state_machine :failed, error if can_transition_to?(:failed)
133
+ if can_transition_to?(:failed)
134
+ channel.transition_state_machine :failed, Ably::Exceptions::ConnectionFailed.new('Connection failed', nil, 80002, error)
135
+ end
128
136
  end
129
137
  end
130
138
 
@@ -34,10 +34,14 @@ module Ably
34
34
  # @return [Ably::Rest::Client]
35
35
  attr_reader :rest_client
36
36
 
37
- # When false the client suppresses messages originating from this connection being echoed back on the same connection. Defaults to true
37
+ # When false the client suppresses messages originating from this connection being echoed back on the same connection. Defaults to true
38
38
  # @return [Boolean]
39
39
  attr_reader :echo_messages
40
40
 
41
+ # If false, this disables the default behaviour whereby the library queues messages on a connection in the disconnected or connecting states. Defaults to true
42
+ # @return [Boolean]
43
+ attr_reader :queue_messages
44
+
41
45
  # The custom realtime websocket host that is being used if it was provided with the option `:ws_host` when the {Client} was created
42
46
  # @return [String,Nil]
43
47
  attr_reader :custom_realtime_host
@@ -52,7 +56,8 @@ module Ably
52
56
 
53
57
  def_delegators :auth, :client_id, :auth_options
54
58
  def_delegators :@rest_client, :encoders
55
- def_delegators :@rest_client, :environment, :use_tls?, :protocol, :protocol_binary?, :custom_host
59
+ def_delegators :@rest_client, :use_tls?, :protocol, :protocol_binary?
60
+ def_delegators :@rest_client, :environment, :custom_host, :custom_port, :custom_tls_port
56
61
  def_delegators :@rest_client, :log_level
57
62
 
58
63
  # Creates a {Ably::Realtime::Client Realtime Client} and configures the {Ably::Auth} object for the connection.
@@ -75,9 +80,10 @@ module Ably
75
80
  #
76
81
  def initialize(options)
77
82
  @rest_client = Ably::Rest::Client.new(options)
78
- @auth = @rest_client.auth
83
+ @auth = Ably::Realtime::Auth.new(self)
79
84
  @channels = Ably::Realtime::Channels.new(self)
80
85
  @echo_messages = @rest_client.options.fetch(:echo_messages, true) == false ? false : true
86
+ @queue_messages = @rest_client.options.fetch(:queue_messages, true) == false ? false : true
81
87
  @custom_realtime_host = @rest_client.options[:realtime_host] || @rest_client.options[:ws_host]
82
88
  @auto_connect = @rest_client.options.fetch(:auto_connect, true) == false ? false : true
83
89
  @recover = @rest_client.options[:recover]
@@ -174,12 +180,22 @@ module Ably
174
180
 
175
181
  private
176
182
  def endpoint_for_host(host)
177
- URI::Generic.build(
183
+ port = if use_tls?
184
+ custom_tls_port
185
+ else
186
+ custom_port
187
+ end
188
+
189
+ raise ArgumentError, "Custom port must be an Integer or nil" if port && !port.kind_of?(Integer)
190
+
191
+ options = {
178
192
  scheme: use_tls? ? 'wss' : 'ws',
179
193
  host: host
180
- )
194
+ }
195
+ options.merge!(port: port) if port
196
+
197
+ URI::Generic.build(options)
181
198
  end
182
199
  end
183
200
  end
184
201
  end
185
-
@@ -184,16 +184,23 @@ module Ably
184
184
  # @return [EventMachine::Deferrable]
185
185
  # @api private
186
186
  def internet_up?
187
+ url = "http#{'s' if client.use_tls?}:#{Ably::INTERNET_CHECK.fetch(:url)}"
187
188
  EventMachine::DefaultDeferrable.new.tap do |deferrable|
188
- EventMachine::HttpRequest.new("http#{'s' if client.use_tls?}:#{Ably::INTERNET_CHECK.fetch(:url)}").get.tap do |http|
189
+ EventMachine::HttpRequest.new(url).get.tap do |http|
189
190
  http.errback do
190
191
  yield false if block_given?
191
- deferrable.fail
192
+ deferrable.fail "Unable to connect to #{url}"
192
193
  end
193
194
  http.callback do
194
- result = http.response_header.status == 200 && http.response.strip == Ably::INTERNET_CHECK.fetch(:ok_text)
195
- yield result if block_given?
196
- deferrable.succeed
195
+ EventMachine.next_tick do
196
+ result = http.response_header.status == 200 && http.response.strip == Ably::INTERNET_CHECK.fetch(:ok_text)
197
+ yield result if block_given?
198
+ if result
199
+ deferrable.succeed
200
+ else
201
+ deferrable.fail "Unexpected response from #{url} (#{http.response_header.status})"
202
+ end
203
+ end
197
204
  end
198
205
  end
199
206
  end
@@ -275,7 +282,7 @@ module Ably
275
282
  # @!attribute [r] port
276
283
  # @return [Integer] The default port used for this connection
277
284
  def port
278
- client.use_tls? ? 443 : 80
285
+ client.use_tls? ? client.custom_tls_port || 443 : client.custom_port || 80
279
286
  end
280
287
 
281
288
  # @!attribute [r] logger
@@ -311,48 +318,51 @@ module Ably
311
318
  __outgoing_protocol_msgbus__.publish :protocol_message, protocol_message
312
319
  end
313
320
 
321
+ # @return [EventMachine::Deferrable]
314
322
  # @api private
315
323
  def create_websocket_transport
316
- raise ArgumentError, 'Block required' unless block_given?
324
+ EventMachine::DefaultDeferrable.new.tap do |websocket_deferrable|
325
+ # Getting auth params can be blocking so uses a Deferrable
326
+ client.auth.auth_params.tap do |auth_deferrable|
327
+ auth_deferrable.callback do |auth_params|
328
+ url_params = auth_params.merge(
329
+ timestamp: as_since_epoch(Time.now),
330
+ format: client.protocol,
331
+ echo: client.echo_messages
332
+ )
333
+
334
+ if connection_resumable?
335
+ url_params.merge! resume: key, connection_serial: serial
336
+ logger.debug "Resuming connection key #{key} with serial #{serial}"
337
+ elsif connection_recoverable?
338
+ url_params.merge! recover: connection_recover_parts[:recover], connection_serial: connection_recover_parts[:connection_serial]
339
+ logger.debug "Recovering connection with key #{client.recover}"
340
+ once(:connected, :closed, :failed) do
341
+ client.disable_automatic_connection_recovery
342
+ end
343
+ end
317
344
 
318
- blocking_operation = proc do
319
- URI(client.endpoint).tap do |endpoint|
320
- url_params = client.auth.auth_params.merge(
321
- timestamp: as_since_epoch(Time.now),
322
- format: client.protocol,
323
- echo: client.echo_messages
324
- )
325
-
326
- if connection_resumable?
327
- url_params.merge! resume: key, connection_serial: serial
328
- logger.debug "Resuming connection key #{key} with serial #{serial}"
329
- elsif connection_recoverable?
330
- url_params.merge! recover: connection_recover_parts[:recover], connection_serial: connection_recover_parts[:connection_serial]
331
- logger.debug "Recovering connection with key #{client.recover}"
332
- once(:connected, :closed, :failed) do
333
- client.disable_automatic_connection_recovery
345
+ url = URI(client.endpoint).tap do |endpoint|
346
+ endpoint.query = URI.encode_www_form(url_params)
347
+ end.to_s
348
+
349
+ determine_host do |host|
350
+ begin
351
+ logger.debug "Connection: Opening socket connection to #{host}:#{port} and URL '#{url}'"
352
+ @transport = EventMachine.connect(host, port, WebsocketTransport, self, url) do |websocket_transport|
353
+ websocket_deferrable.succeed websocket_transport
354
+ end
355
+ rescue EventMachine::ConnectionError => error
356
+ websocket_deferrable.fail error
357
+ end
334
358
  end
335
359
  end
336
360
 
337
- endpoint.query = URI.encode_www_form(url_params)
338
- end.to_s
339
- end
340
-
341
- callback = proc do |url|
342
- determine_host do |host|
343
- begin
344
- logger.debug "Connection: Opening socket connection to #{host}:#{port} and URL '#{url}'"
345
- @transport = EventMachine.connect(host, port, WebsocketTransport, self, url) do |websocket_transport|
346
- yield websocket_transport if block_given?
347
- end
348
- rescue EventMachine::ConnectionError => error
349
- manager.connection_opening_failed error
361
+ auth_deferrable.errback do |error|
362
+ websocket_deferrable.fail error
350
363
  end
351
364
  end
352
365
  end
353
-
354
- # client.auth.auth_params is a blocking call, so defer this into a thread
355
- EventMachine.defer blocking_operation, callback
356
366
  end
357
367
 
358
368
  # @api private
@@ -388,6 +398,13 @@ module Ably
388
398
  resume_callbacks.delete(callback)
389
399
  end
390
400
 
401
+ # Returns false if messages cannot be published as a result of message queueing being disabled
402
+ # @api private
403
+ def can_publish_messages?
404
+ connected? ||
405
+ ( (initialized? || connecting? || disconnected?) && client.queue_messages )
406
+ end
407
+
391
408
  # As we are using a state machine, do not allow change_state to be used
392
409
  # #transition_state_machine must be used instead
393
410
  private :change_state
@@ -451,8 +468,24 @@ module Ably
451
468
  client.recover.to_s.match(RECOVER_REGEX)
452
469
  end
453
470
 
471
+ def production?
472
+ client.environment.nil? || client.environment == :production
473
+ end
474
+
475
+ def custom_port?
476
+ if client.use_tls?
477
+ !!client.custom_tls_port
478
+ else
479
+ !!client.custom_port
480
+ end
481
+ end
482
+
483
+ def custom_host?
484
+ !!client.custom_realtime_host
485
+ end
486
+
454
487
  def can_use_fallback_hosts?
455
- if client.environment.nil? && client.custom_realtime_host.nil?
488
+ if production? && !custom_port? && !custom_host?
456
489
  if connecting? && previous_state
457
490
  use_fallback_if_disconnected? || use_fallback_if_suspended?
458
491
  end
@@ -53,20 +53,25 @@ module Ably::Realtime
53
53
  end
54
54
 
55
55
  unless client.auth.authentication_security_requirements_met?
56
- connection.transition_state_machine :failed, Ably::Exceptions::InsecureRequestError.new('Cannot use Basic Auth over non-TLS connections', 401, 40103)
56
+ connection.transition_state_machine :failed, Ably::Exceptions::InsecureRequest.new('Cannot use Basic Auth over non-TLS connections', 401, 40103)
57
57
  return
58
58
  end
59
59
 
60
60
  logger.debug 'ConnectionManager: Opening a websocket transport connection'
61
61
 
62
- connection.create_websocket_transport do |websocket_transport|
63
- subscribe_to_transport_events websocket_transport
64
- yield websocket_transport if block_given?
62
+ connection.create_websocket_transport.tap do |socket_deferrable|
63
+ socket_deferrable.callback do |websocket_transport|
64
+ subscribe_to_transport_events websocket_transport
65
+ yield websocket_transport if block_given?
66
+ end
67
+ socket_deferrable.errback do |error|
68
+ connection_opening_failed error
69
+ end
65
70
  end
66
71
 
67
72
  logger.debug "ConnectionManager: Setting up automatic connection timeout timer for #{TIMEOUTS.fetch(:open)}s"
68
73
  create_timeout_timer_whilst_in_state(:connect, TIMEOUTS.fetch(:open)) do
69
- connection_opening_failed Ably::Exceptions::ConnectionTimeoutError.new("Connection to Ably timed out after #{TIMEOUTS.fetch(:open)}s", nil, 80014)
74
+ connection_opening_failed Ably::Exceptions::ConnectionTimeout.new("Connection to Ably timed out after #{TIMEOUTS.fetch(:open)}s", nil, 80014)
70
75
  end
71
76
  end
72
77
 
@@ -161,10 +166,10 @@ module Ably::Realtime
161
166
  end
162
167
 
163
168
  unless connection_retry_from_suspended_state?
164
- return if connection_retry_for(:disconnected, ignore_states: [:connecting])
169
+ return if connection_retry_for(:disconnected)
165
170
  end
166
171
 
167
- return if connection_retry_for(:suspended, ignore_states: [:connecting])
172
+ return if connection_retry_for(:suspended)
168
173
 
169
174
  # Fallback if no other criteria met
170
175
  connection.transition_state_machine :failed, current_transition.metadata
@@ -264,10 +269,10 @@ module Ably::Realtime
264
269
  #
265
270
  # @return [Boolean] True if a connection attempt has been set up, false if no further connection attempts can be made for this state
266
271
  #
267
- def connection_retry_for(from_state, options = {})
272
+ def connection_retry_for(from_state)
268
273
  retry_params = CONNECT_RETRY_CONFIG.fetch(from_state)
269
274
 
270
- if time_spent_attempting_state(from_state, options) < retry_params.fetch(:max_time_in_state)
275
+ if can_reattempt_connect_for_state?(from_state)
271
276
  if retries_for_state(from_state, ignore_states: [:connecting]).empty?
272
277
  logger.debug "ConnectionManager: Will attempt reconnect immediately as no previous reconnect attempts made in this state"
273
278
  EventMachine.next_tick { connection.connect }
@@ -281,6 +286,14 @@ module Ably::Realtime
281
286
  end
282
287
  end
283
288
 
289
+ # True if the client library has not exceeded the configured max_time_in_state for the current State
290
+ # For example, if the state is disconnected, and has been in a cycle of disconnected > connect > disconnected
291
+ # so long as the time in this cycle of states is less than max_time_in_state, this will return true
292
+ def can_reattempt_connect_for_state?(state)
293
+ retry_params = CONNECT_RETRY_CONFIG.fetch(state)
294
+ time_spent_attempting_state(state, ignore_states: [:connecting]) < retry_params.fetch(:max_time_in_state)
295
+ end
296
+
284
297
  # Returns a float representing the amount of time passed since the first consecutive attempt of this state
285
298
  #
286
299
  # @param (see #retries_for_state)
@@ -335,9 +348,13 @@ module Ably::Realtime
335
348
  connection.transition_state_machine :closed
336
349
  elsif !connection.closed? && !connection.disconnected?
337
350
  exception = if reason
338
- Ably::Exceptions::ConnectionClosedError.new(reason)
351
+ Ably::Exceptions::ConnectionClosed.new(reason)
352
+ end
353
+ if connection_retry_from_suspended_state? || !can_reattempt_connect_for_state?(:disconnected)
354
+ connection.transition_state_machine :suspended, exception
355
+ else
356
+ connection.transition_state_machine :disconnected, exception
339
357
  end
340
- connection.transition_state_machine :disconnected, exception
341
358
  end
342
359
  end
343
360
  end
@@ -350,35 +367,33 @@ module Ably::Realtime
350
367
  end
351
368
 
352
369
  @renewing_token = true
353
- logger.warn "ConnectionManager: Token has expired and is renewable, renewing token now"
370
+ logger.info "ConnectionManager: Token has expired and is renewable, renewing token now"
354
371
 
355
- operation = proc do
356
- begin
357
- client.auth.authorise
358
- rescue StandardError => auth_error
359
- connection.transition_state_machine :failed, auth_error
360
- nil
361
- end
362
- end
372
+ client.auth.authorise.tap do |authorise_deferrable|
373
+ authorise_deferrable.callback do |token_details|
374
+ logger.info 'ConnectionManager: Token renewed succesfully following expiration'
363
375
 
364
- callback = proc do |token|
365
- state_changed_callback = proc do
366
- @renewing_token = false
367
- connection.off &state_changed_callback
368
- end
376
+ state_changed_callback = proc do
377
+ @renewing_token = false
378
+ connection.off &state_changed_callback
379
+ end
369
380
 
370
- connection.unsafe_once :connected, :closed, :failed, &state_changed_callback
381
+ connection.unsafe_once :connected, :closed, :failed, &state_changed_callback
371
382
 
372
- if token && !token.expired?
373
- connection.connect
374
- else
375
- connection.transition_state_machine :failed, error unless connection.failed?
383
+ if token_details && !token_details.expired?
384
+ connection.connect
385
+ else
386
+ connection.transition_state_machine :failed, error unless connection.failed?
387
+ end
376
388
  end
377
- end
378
389
 
379
- EventMachine.defer operation, callback
390
+ authorise_deferrable.errback do |auth_error|
391
+ logger.error "ConnectionManager: Error authorising following token expiry: #{auth_error}"
392
+ connection.transition_state_machine :failed, auth_error
393
+ end
394
+ end
380
395
  else
381
- logger.error "ConnectionManager: Token has expired and is not renewable"
396
+ logger.error "ConnectionManager: Token has expired and is not renewable - #{error}"
382
397
  connection.transition_state_machine :failed, error
383
398
  end
384
399
  end
@@ -82,6 +82,7 @@ module Ably::Realtime
82
82
 
83
83
  return deferrable_succeed(deferrable, &success_block) if state == STATE.Entered
84
84
 
85
+ ensure_presence_publishable_on_connection
85
86
  ensure_channel_attached(deferrable) do
86
87
  if entering?
87
88
  once_or_if(STATE.Entered, else: proc { |args| deferrable_fail deferrable, *args }) do
@@ -145,6 +146,7 @@ module Ably::Realtime
145
146
 
146
147
  return deferrable_succeed(deferrable, &success_block) if state == STATE.Left
147
148
 
149
+ ensure_presence_publishable_on_connection
148
150
  ensure_channel_attached(deferrable) do
149
151
  if leaving?
150
152
  once_or_if(STATE.Left, else: proc { |error|deferrable_fail deferrable, *args }) do
@@ -201,6 +203,7 @@ module Ably::Realtime
201
203
 
202
204
  @data = data
203
205
 
206
+ ensure_presence_publishable_on_connection
204
207
  ensure_channel_attached(deferrable) do
205
208
  send_protocol_message_and_transition_state_to(
206
209
  Ably::Models::PresenceMessage::ACTION.Update,
@@ -369,6 +372,12 @@ module Ably::Realtime
369
372
  end
370
373
  end
371
374
 
375
+ def ensure_presence_publishable_on_connection
376
+ if !connection.can_publish_messages?
377
+ raise Ably::Exceptions::MessageQueueingDisabled.new("Message cannot be published. Client is configured to disallow queueing of messages and connection is currently #{connection.state}")
378
+ end
379
+ end
380
+
372
381
  def ensure_channel_attached(deferrable = nil)
373
382
  if channel.attached?
374
383
  yield
@@ -413,11 +422,12 @@ module Ably::Realtime
413
422
  safe_yield block, self, *args if block_given?
414
423
  EventMachine.next_tick { deferrable.fail self, *args } # allow errback to be added to the returned Deferrable
415
424
  deferrable
416
- end
425
+ end
417
426
 
418
427
  def send_presence_action_for_client(action, client_id, options = {}, &success_block)
419
- deferrable = create_deferrable
428
+ ensure_presence_publishable_on_connection
420
429
 
430
+ deferrable = create_deferrable
421
431
  ensure_channel_attached(deferrable) do
422
432
  send_presence_protocol_message(action, client_id, options).tap do |protocol_message|
423
433
  protocol_message.callback { |message| deferrable_succeed deferrable, &success_block }
@@ -428,7 +438,7 @@ module Ably::Realtime
428
438
 
429
439
  def attach_channel_then
430
440
  if channel.detached? || channel.failed?
431
- raise Ably::Exceptions::IncompatibleStateForOperation.new("Operation is not allowed when channel is in #{channel.state}", 400, 91001)
441
+ raise Ably::Exceptions::InvalidStateChange.new("Operation is not allowed when channel is in #{channel.state}", 400, 91001)
432
442
  else
433
443
  channel.unsafe_once(Channel::STATE.Attached) { yield }
434
444
  channel.attach
@@ -439,6 +449,10 @@ module Ably::Realtime
439
449
  channel.client
440
450
  end
441
451
 
452
+ def connection
453
+ client.connection
454
+ end
455
+
442
456
  def rest_presence
443
457
  client.rest_client.channel(channel.name).presence
444
458
  end