ably-rest 0.7.3 → 0.7.5

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 (69) hide show
  1. checksums.yaml +8 -8
  2. data/.travis.yml +1 -0
  3. data/SPEC.md +480 -472
  4. data/lib/ably-rest.rb +1 -1
  5. data/lib/submodules/ably-ruby/LICENSE.txt +1 -1
  6. data/lib/submodules/ably-ruby/README.md +107 -24
  7. data/lib/submodules/ably-ruby/SPEC.md +531 -398
  8. data/lib/submodules/ably-ruby/lib/ably/auth.rb +24 -16
  9. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +9 -0
  10. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +17 -9
  11. data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +12 -8
  12. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +18 -10
  13. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +15 -4
  14. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +4 -3
  15. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +31 -2
  16. data/lib/submodules/ably-ruby/lib/ably/modules/message_emitter.rb +77 -0
  17. data/lib/submodules/ably-ruby/lib/ably/modules/safe_deferrable.rb +71 -0
  18. data/lib/submodules/ably-ruby/lib/ably/modules/safe_yield.rb +41 -0
  19. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +28 -8
  20. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +0 -5
  21. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +24 -29
  22. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +54 -11
  23. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +21 -6
  24. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +7 -2
  25. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +29 -26
  26. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +4 -4
  27. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +41 -9
  28. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +72 -24
  29. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +26 -4
  30. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +19 -6
  31. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +74 -208
  32. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +264 -0
  33. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_manager.rb +59 -0
  34. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_state_machine.rb +64 -0
  35. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +1 -1
  36. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +6 -2
  37. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
  38. data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +3 -1
  39. data/lib/submodules/ably-ruby/lib/ably/util/safe_deferrable.rb +18 -0
  40. data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
  41. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +2 -2
  42. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +28 -6
  43. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +116 -46
  44. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +55 -10
  45. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +32 -0
  46. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +456 -96
  47. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +2 -2
  48. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +2 -2
  49. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +96 -7
  50. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +8 -0
  51. data/lib/submodules/ably-ruby/spec/shared/safe_deferrable_behaviour.rb +71 -0
  52. data/lib/submodules/ably-ruby/spec/support/api_helper.rb +1 -1
  53. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +1 -1
  54. data/lib/submodules/ably-ruby/spec/support/test_app.rb +13 -7
  55. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +15 -14
  56. data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +4 -4
  57. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +17 -17
  58. data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +4 -4
  59. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +28 -9
  60. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +50 -0
  61. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +76 -2
  62. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +51 -20
  63. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +3 -3
  64. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +30 -0
  65. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +52 -26
  66. data/lib/submodules/ably-ruby/spec/unit/realtime/safe_deferrable_spec.rb +12 -0
  67. data/spec/spec_helper.rb +5 -0
  68. metadata +12 -4
  69. data/lib/submodules/ably-ruby/.ruby-version.old +0 -1
@@ -40,8 +40,16 @@ module Ably::Realtime
40
40
  connection.manager.reconnect_transport
41
41
  end
42
42
 
43
+ before_transition(to: [:connected]) do |connection, current_transition|
44
+ connection.manager.connected current_transition.metadata
45
+ end
46
+
43
47
  after_transition(to: [:connected]) do |connection, current_transition|
44
- connection.manager.connected_with_error current_transition.metadata if current_transition.metadata
48
+ protocol_message = current_transition.metadata
49
+ if is_error_type?(protocol_message.error)
50
+ connection.logger.warn "ConnectionManager: Connected with error - #{protocol_message.error.message}"
51
+ connection.trigger :error, protocol_message.error
52
+ end
45
53
  end
46
54
 
47
55
  after_transition(to: [:disconnected, :suspended], from: [:connecting]) do |connection, current_transition|
@@ -73,9 +81,23 @@ module Ably::Realtime
73
81
  end
74
82
 
75
83
  # Transitions responsible for updating connection#error_reason
76
- before_transition(to: [:connected, :closed, :disconnected, :suspended, :failed]) do |connection, current_transition|
77
- reason = current_transition.metadata if is_error_type?(current_transition.metadata)
78
- connection.set_failed_connection_error_reason reason
84
+ before_transition(to: [:disconnected, :suspended, :failed]) do |connection, current_transition|
85
+ connection.set_failed_connection_error_reason current_transition.metadata
86
+ end
87
+
88
+ before_transition(to: [:connected, :closed]) do |connection, current_transition|
89
+ error = if current_transition.metadata.kind_of?(Ably::Models::ProtocolMessage)
90
+ current_transition.metadata.error
91
+ else
92
+ current_transition.metadata
93
+ end
94
+
95
+ if is_error_type?(error)
96
+ connection.set_failed_connection_error_reason error
97
+ else
98
+ # Connected & Closed are "healthy" final states so reset the error reason
99
+ connection.clear_error_reason
100
+ end
79
101
  end
80
102
 
81
103
  private
@@ -33,7 +33,7 @@ module Ably::Realtime
33
33
  change_state STATE.Disconnecting
34
34
  create_timer(2) do
35
35
  # if connection is not disconnected within 2s, set state as disconnected
36
- change_state STATE.Disconnected
36
+ change_state STATE.Disconnected unless disconnected?
37
37
  end
38
38
  end
39
39
 
@@ -63,7 +63,7 @@ module Ably::Realtime
63
63
  # Called whenever a connection (either a server or client connection) is closed
64
64
  # Required {http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Connection EventMachine::Connection} interface
65
65
  def unbind
66
- change_state STATE.Disconnected
66
+ change_state STATE.Disconnected, reason_closed || 'Websocket connection closed unexpectedly'
67
67
  end
68
68
 
69
69
  # URL end point including initialization configuration
@@ -98,7 +98,7 @@ module Ably::Realtime
98
98
  end
99
99
 
100
100
  private
101
- attr_reader :connection, :driver
101
+ attr_reader :connection, :driver, :reason_closed
102
102
 
103
103
  # Send object down the WebSocket driver connection as a serialized string/byte array based on protocol
104
104
  # @param [Object] object to serialize and send to the WebSocket driver
@@ -143,15 +143,28 @@ module Ably::Realtime
143
143
 
144
144
  driver.on("message") do |event|
145
145
  event_data = parse_event_data(event.data).freeze
146
- protocol_message = Ably::Models::ProtocolMessage.new(event_data)
147
- logger.debug "WebsocketTransport: Prot msg recv <=: #{protocol_message.action} #{event_data}"
146
+ protocol_message = Ably::Models::ProtocolMessage.new(event_data, logger: logger)
147
+ action_name = Ably::Models::ProtocolMessage::ACTION[event_data['action']] rescue event_data['action']
148
+ logger.debug "WebsocketTransport: Prot msg recv <=: #{action_name} - #{event_data}"
148
149
 
149
150
  if protocol_message.invalid?
150
- logger.fatal "WebsocketTransport: Invalid Protocol Message received: #{event_data}\nNo action taken"
151
+ error = Ably::Exceptions::ProtocolError.new("Invalid Protocol Message received: #{event_data}\nMessage has been discarded", 400, 80013)
152
+ connection.trigger :error, error
153
+ logger.fatal "WebsocketTransport: #{error.message}"
151
154
  else
152
155
  __incoming_protocol_msgbus__.publish :protocol_message, protocol_message
153
156
  end
154
157
  end
158
+
159
+ driver.on("error") do |error|
160
+ logger.error "WebsocketTransport: Protocol Error on transports - #{error.message}"
161
+ end
162
+
163
+ @reason_closed = nil
164
+ driver.on("closed") do |event|
165
+ @reason_closed = "#{event.code}: #{event.reason}"
166
+ logger.warn "WebsocketTransport: Driver reported transport as closed - #{reason_closed}"
167
+ end
155
168
  end
156
169
 
157
170
  def client
@@ -3,6 +3,8 @@ module Ably::Realtime
3
3
  class Presence
4
4
  include Ably::Modules::EventEmitter
5
5
  include Ably::Modules::AsyncWrapper
6
+ include Ably::Modules::MessageEmitter
7
+ include Ably::Modules::SafeYield
6
8
  extend Ably::Modules::Enum
7
9
 
8
10
  STATE = ruby_enum('STATE',
@@ -14,6 +16,7 @@ module Ably::Realtime
14
16
  :failed
15
17
  )
16
18
  include Ably::Modules::StateEmitter
19
+ include Ably::Modules::UsesStateMachine
17
20
 
18
21
  # {Ably::Realtime::Channel} this Presence object is associated with
19
22
  # @return [Ably::Realtime::Channel]
@@ -32,14 +35,24 @@ module Ably::Realtime
32
35
  # @return [String]
33
36
  attr_reader :data
34
37
 
38
+ # {MembersMap} containing an up to date list of members on this channel
39
+ # @return [MembersMap]
40
+ # @api private
41
+ attr_reader :members
42
+
43
+ # The Presence manager responsible for actions relating to state changes such as entering a channel
44
+ # @return [Ably::Realtime::Presence::PresenceManager]
45
+ # @api private
46
+ attr_reader :manager
47
+
35
48
  def initialize(channel)
36
49
  @channel = channel
37
- @state = STATE.Initialized
38
- @members = Hash.new
39
- @subscriptions = Hash.new { |hash, key| hash[key] = [] }
40
50
  @client_id = client.client_id
41
51
 
42
- setup_event_handlers
52
+ @state_machine = PresenceStateMachine.new(self)
53
+ @state = STATE(state_machine.current_state)
54
+ @members = MembersMap.new(self)
55
+ @manager = PresenceManager.new(self)
43
56
  end
44
57
 
45
58
  # Enter this client into this channel. This client will be added to the presence set
@@ -53,12 +66,12 @@ module Ably::Realtime
53
66
  # library must have been instanced either with a key, or with a token bound to the wildcard clientId.
54
67
  #
55
68
  # @yield [Ably::Realtime::Presence] On success, will call the block with this {Ably::Realtime::Presence} object
56
- # @return [EventMachine::Deferrable] Deferrable that supports both success (callback) and failure (errback) callbacks
69
+ # @return [Ably::Util::SafeDeferrable] Deferrable that supports both success (callback) and failure (errback) callbacks
57
70
  #
58
71
  def enter(options = {}, &success_block)
59
72
  @client_id = options.fetch(:client_id, client_id)
60
73
  @data = options.fetch(:data, nil)
61
- deferrable = EventMachine::DefaultDeferrable.new
74
+ deferrable = create_deferrable
62
75
 
63
76
  raise Ably::Exceptions::Standard.new('Unable to enter presence channel without a client_id', 400, 91000) unless client_id
64
77
  return deferrable_succeed(deferrable, &success_block) if state == STATE.Entered
@@ -96,7 +109,7 @@ module Ably::Realtime
96
109
  # @option options [String] :data optional data (eg a status message) for this member
97
110
  #
98
111
  # @yield [Ably::Realtime::Presence] On success, will call the block with this {Ably::Realtime::Presence} object
99
- # @return [EventMachine::Deferrable] Deferrable that supports both success (callback) and failure (errback) callbacks
112
+ # @return [Ably::Util::SafeDeferrable] Deferrable that supports both success (callback) and failure (errback) callbacks
100
113
  #
101
114
  def enter_client(client_id, options = {}, &success_block)
102
115
  raise ArgumentError, 'options must be a Hash' unless options.kind_of?(Hash)
@@ -116,7 +129,7 @@ module Ably::Realtime
116
129
  #
117
130
  def leave(options = {}, &success_block)
118
131
  @data = options.fetch(:data, data) # nil value defaults leave data to existing value
119
- deferrable = EventMachine::DefaultDeferrable.new
132
+ deferrable = create_deferrable
120
133
 
121
134
  raise Ably::Exceptions::Standard.new('Unable to leave presence channel that is not entered', 400, 91002) unless able_to_leave?
122
135
  return deferrable_succeed(deferrable, &success_block) if state == STATE.Left
@@ -169,7 +182,7 @@ module Ably::Realtime
169
182
  #
170
183
  def update(options = {}, &success_block)
171
184
  @data = options.fetch(:data, nil)
172
- deferrable = EventMachine::DefaultDeferrable.new
185
+ deferrable = create_deferrable
173
186
 
174
187
  ensure_channel_attached(deferrable) do
175
188
  send_protocol_message_and_transition_state_to(
@@ -204,39 +217,22 @@ module Ably::Realtime
204
217
 
205
218
  # Get the presence state for this Channel.
206
219
  #
207
- # @param [Hash,String] options an options Hash to filter members
208
- # @option options [String] :client_id optional client_id for the member
209
- # @option options [String] :connection_id optional connection_id for the member
210
- # @option options [String] :wait_for_sync defaults to true, if false the get method returns the current list of members and does not wait for the presence sync to complete
211
- #
212
- # @yield [Array<Ably::Models::PresenceMessage>] array of members or the member
213
- #
214
- # @return [EventMachine::Deferrable] Deferrable that supports both success (callback) and failure (errback) callbacks
220
+ # @param (see Ably::Realtime::Presence::MembersMap#get)
221
+ # @option options (see Ably::Realtime::Presence::MembersMap#get)
222
+ # @yield (see Ably::Realtime::Presence::MembersMap#get)
223
+ # @return (see Ably::Realtime::Presence::MembersMap#get)
215
224
  #
216
- def get(options = {})
217
- wait_for_sync = options.fetch(:wait_for_sync, true)
218
- deferrable = EventMachine::DefaultDeferrable.new
225
+ def get(options = {}, &block)
226
+ deferrable = create_deferrable
219
227
 
220
228
  ensure_channel_attached(deferrable) do
221
- result_block = proc do
222
- members.map { |key, presence| presence }.tap do |filtered_members|
223
- filtered_members.keep_if { |presence| presence.connection_id == options[:connection_id] } if options[:connection_id]
224
- filtered_members.keep_if { |presence| presence.client_id == options[:client_id] } if options[:client_id]
225
- end.tap do |current_members|
226
- yield current_members if block_given?
227
- deferrable.succeed current_members
229
+ members.get(options).tap do |members_map_deferrable|
230
+ members_map_deferrable.callback do |*args|
231
+ safe_yield block, *args if block_given?
232
+ deferrable.succeed *args
228
233
  end
229
- end
230
-
231
- if !wait_for_sync || sync_complete?
232
- result_block.call
233
- else
234
- sync_pubsub.once(:done) do
235
- result_block.call
236
- end
237
-
238
- sync_pubsub.once(:failed) do |error|
239
- deferrable.fail error
234
+ members_map_deferrable.errback do |*args|
235
+ deferrable.fail *args
240
236
  end
241
237
  end
242
238
  end
@@ -245,34 +241,26 @@ module Ably::Realtime
245
241
  # Subscribe to presence events on the associated Channel.
246
242
  # This implicitly attaches the Channel if it is not already attached.
247
243
  #
248
- # @param action [Ably::Models::PresenceMessage::ACTION] Optional, the state change action to subscribe to. Defaults to all presence actions
244
+ # @param actions [Ably::Models::PresenceMessage::ACTION] Optional, the state change action to subscribe to. Defaults to all presence actions
249
245
  # @yield [Ably::Models::PresenceMessage] For each presence state change event, the block is called
250
246
  #
251
247
  # @return [void]
252
248
  #
253
- def subscribe(action = :all, &callback)
249
+ def subscribe(*actions, &callback)
254
250
  ensure_channel_attached do
255
- subscriptions[message_action_key(action)] << callback
251
+ super
256
252
  end
257
253
  end
258
254
 
259
255
  # Unsubscribe the matching block for presence events on the associated Channel.
260
256
  # If a block is not provided, all subscriptions will be unsubscribed
261
257
  #
262
- # @param action [Ably::Models::PresenceMessage::ACTION] Optional, the state change action to subscribe to. Defaults to all presence actions
258
+ # @param actions [Ably::Models::PresenceMessage::ACTION] Optional, the state change action to subscribe to. Defaults to all presence actions
263
259
  #
264
260
  # @return [void]
265
261
  #
266
- def unsubscribe(action = :all, &callback)
267
- if message_action_key(action) == :all
268
- subscriptions.keys
269
- else
270
- Array(message_action_key(action))
271
- end.each do |key|
272
- subscriptions[key].delete_if do |block|
273
- !block_given? || callback == block
274
- end
275
- end
262
+ def unsubscribe(*actions, &callback)
263
+ super
276
264
  end
277
265
 
278
266
  # Return the presence messages history for the channel
@@ -282,7 +270,7 @@ module Ably::Realtime
282
270
  #
283
271
  # @yield [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] An Array of {Ably::Models::PresenceMessage} objects that supports paging (#next_page, #first_page)
284
272
  #
285
- # @return [EventMachine::Deferrable]
273
+ # @return [Ably::Util::SafeDeferrable]
286
274
  #
287
275
  def history(options = {}, &callback)
288
276
  async_wrap(callback) do
@@ -290,62 +278,8 @@ module Ably::Realtime
290
278
  end
291
279
  end
292
280
 
293
- # When attaching to a channel that has members present, the client and server
294
- # initiate a sync automatically so that the client has a complete list of members.
295
- #
296
- # Whilst this sync is happening, this method returns false
297
- #
298
- # @return [Boolean]
299
- def sync_complete?
300
- sync_complete
301
- end
302
-
303
- # Expect SYNC ProtocolMessages with a list of current members on this channel from the server
304
- #
305
- # @return [void]
306
- #
307
- # @api private
308
- def sync_started
309
- @sync_complete = false
310
-
311
- sync_pubsub.once(:sync_complete) do
312
- sync_changes_backlog.each do |presence_message|
313
- apply_member_presence_changes presence_message
314
- end
315
- sync_completed
316
- sync_pubsub.trigger :done
317
- end
318
-
319
- channel.once_or_if [:detached, :failed] do |error|
320
- sync_completed
321
- sync_pubsub.trigger :failed, error
322
- end
323
- end
324
-
325
- # The server has indicated that no members are present on this channel and no SYNC is expected,
326
- # or that the SYNC has now completed
327
- #
328
- # @return [void]
329
- #
330
- # @api private
331
- def sync_completed
332
- @sync_complete = true
333
- @sync_changes_backlog = []
334
- end
335
-
336
- # Update the SYNC serial from the ProtocolMessage so that SYNC can be resumed.
337
- # If the serial is nil, or the part after the first : is empty, then the SYNC is complete
338
- #
339
- # @return [void]
340
- #
341
- # @api private
342
- def update_sync_serial(serial)
343
- @sync_serial = serial
344
- sync_pubsub.trigger :sync_complete if sync_serial_cursor_at_end?
345
- end
346
-
347
281
  # @!attribute [r] __incoming_msgbus__
348
- # @return [Ably::Util::PubSub] Client library internal channel incoming message bus
282
+ # @return [Ably::Util::PubSub] Client library internal channel incoming protocol message bus
349
283
  # @api private
350
284
  def __incoming_msgbus__
351
285
  @__incoming_msgbus__ ||= Ably::Util::PubSub.new(
@@ -353,54 +287,29 @@ module Ably::Realtime
353
287
  )
354
288
  end
355
289
 
356
- private
357
- attr_reader :members, :subscriptions, :sync_serial, :sync_complete
358
-
359
-
360
- # A simple PubSub class used to publish synchronisation state changes
361
- def sync_pubsub
362
- @sync_pubsub ||= Ably::Util::PubSub.new
290
+ # Configure the connection ID for this presence channel.
291
+ # Typically configured only once when a user first enters a presence channel.
292
+ # @api private
293
+ def set_connection_id(new_connection_id)
294
+ @connection_id = new_connection_id
363
295
  end
364
296
 
365
- # During a SYNC of presence members, all enter, update and leave events are queued for processing once the SYNC is complete
366
- def sync_changes_backlog
367
- @sync_changes_backlog ||= []
297
+ # Used by {Ably::Modules::StateEmitter} to debug action changes
298
+ # @api private
299
+ def logger
300
+ client.logger
368
301
  end
369
302
 
370
- # When channel serial in ProtocolMessage SYNC is nil or
371
- # an empty cursor appears after the ':' such as 'cf30e75054887:psl_7g:client:189'
372
- # then there are no more SYNC messages to come
373
- def sync_serial_cursor_at_end?
374
- sync_serial.nil? || sync_serial.to_s.match(/^[\w-]+:?$/)
303
+ # Returns true when the initial member SYNC following channel attach is completed
304
+ def sync_complete?
305
+ members.sync_complete?
375
306
  end
376
307
 
308
+ private
377
309
  def able_to_leave?
378
310
  entering? || entered?
379
311
  end
380
312
 
381
- def setup_event_handlers
382
- __incoming_msgbus__.subscribe(:presence, :sync) do |presence_message|
383
- presence_message.decode self.channel
384
- update_members_from_presence_message presence_message
385
- end
386
-
387
- channel.on(Channel::STATE.Detaching) do
388
- change_state STATE.Leaving
389
- end
390
-
391
- channel.on(Channel::STATE.Detached) do
392
- change_state STATE.Left
393
- end
394
-
395
- channel.on(Channel::STATE.Failed) do
396
- change_state STATE.Failed unless left? || initialized?
397
- end
398
-
399
- on(STATE.Entered) do |message|
400
- @connection_id = message.connection_id
401
- end
402
- end
403
-
404
313
  # @return [Ably::Models::PresenceMessage] presence message is returned allowing callbacks to be added
405
314
  def send_presence_protocol_message(presence_action, client_id, options = {})
406
315
  presence_message = create_presence_message(presence_action, client_id, options)
@@ -426,54 +335,11 @@ module Ably::Realtime
426
335
  }
427
336
  model.merge!(data: options.fetch(:data)) if options.has_key?(:data)
428
337
 
429
- Ably::Models::PresenceMessage.new(model, nil).tap do |presence_message|
338
+ Ably::Models::PresenceMessage.new(model, logger: logger).tap do |presence_message|
430
339
  presence_message.encode self.channel
431
340
  end
432
341
  end
433
342
 
434
- def update_members_from_presence_message(presence_message)
435
- unless presence_message.connection_id
436
- Ably::Exceptions::ProtocolError.new("Protocol error, presence message is missing connectionId", 400, 80013)
437
- end
438
-
439
- if sync_complete?
440
- apply_member_presence_changes presence_message
441
- else
442
- if presence_message.action == Ably::Models::PresenceMessage::ACTION.Present
443
- add_presence_member presence_message
444
- publish_presence_member_state_change presence_message
445
- else
446
- sync_changes_backlog << presence_message
447
- end
448
- end
449
- end
450
-
451
- def apply_member_presence_changes(presence_message)
452
- case presence_message.action
453
- when Ably::Models::PresenceMessage::ACTION.Enter, Ably::Models::PresenceMessage::ACTION.Update
454
- add_presence_member presence_message
455
- when Ably::Models::PresenceMessage::ACTION.Leave
456
- remove_presence_member presence_message
457
- else
458
- Ably::Exceptions::ProtocolError.new("Protocol error, unknown presence action #{presence_message.action}", 400, 80013)
459
- end
460
-
461
- publish_presence_member_state_change presence_message
462
- end
463
-
464
- def add_presence_member(presence_message)
465
- members[presence_message.member_key] = presence_message
466
- end
467
-
468
- def remove_presence_member(presence_message)
469
- members.delete presence_message.member_key
470
- end
471
-
472
- def publish_presence_member_state_change(presence_message)
473
- subscriptions[:all].each { |cb| cb.call(presence_message) }
474
- subscriptions[presence_message.action].each { |cb| cb.call(presence_message) }
475
- end
476
-
477
343
  def ensure_channel_attached(deferrable = nil)
478
344
  if channel.attached?
479
345
  yield
@@ -508,34 +374,34 @@ module Ably::Realtime
508
374
  end
509
375
  end
510
376
 
511
- def deferrable_succeed(deferrable, *args)
512
- yield self, *args if block_given?
377
+ def deferrable_succeed(deferrable, *args, &block)
378
+ safe_yield block, self, *args if block_given?
513
379
  EventMachine.next_tick { deferrable.succeed self, *args } # allow callback to be added to the returned Deferrable before calling succeed
514
380
  deferrable
515
381
  end
516
382
 
517
- def deferrable_fail(deferrable, *args)
518
- yield self, *args if block_given?
383
+ def deferrable_fail(deferrable, *args, &block)
384
+ safe_yield block, self, *args if block_given?
519
385
  EventMachine.next_tick { deferrable.fail self, *args } # allow errback to be added to the returned Deferrable
520
386
  deferrable
521
- end
387
+ end
522
388
 
523
389
  def send_presence_action_for_client(action, client_id, options = {}, &success_block)
524
- deferrable = EventMachine::DefaultDeferrable.new
390
+ deferrable = create_deferrable
525
391
 
526
392
  ensure_channel_attached(deferrable) do
527
393
  send_presence_protocol_message(action, client_id, options).tap do |protocol_message|
528
394
  protocol_message.callback { |message| deferrable_succeed deferrable, &success_block }
529
- protocol_message.errback { |message| deferrable_fail deferrable }
395
+ protocol_message.errback { |message, error| deferrable_fail deferrable, error }
530
396
  end
531
397
  end
532
398
  end
533
399
 
534
400
  def attach_channel_then
535
401
  if channel.detached? || channel.failed?
536
- raise Ably::Exceptions::Standard.new('Unable to enter presence channel in detached or failed action', 400, 91001)
402
+ raise Ably::Exceptions::IncompatibleStateForOperation.new("Operation is not allowed when channel is in #{channel.state}", 400, 91001)
537
403
  else
538
- channel.once(Channel::STATE.Attached) { yield }
404
+ channel.unsafe_once(Channel::STATE.Attached) { yield }
539
405
  channel.attach
540
406
  end
541
407
  end
@@ -548,17 +414,17 @@ module Ably::Realtime
548
414
  client.rest_client.channel(channel.name).presence
549
415
  end
550
416
 
551
- # Used by {Ably::Modules::StateEmitter} to debug action changes
552
- def logger
553
- client.logger
417
+ # Force subscriptions to match valid PresenceMessage actions
418
+ def message_emitter_subscriptions_coerce_message_key(name)
419
+ Ably::Models::PresenceMessage.ACTION(name)
554
420
  end
555
421
 
556
- def message_action_key(action)
557
- if action == :all
558
- :all
559
- else
560
- Ably::Models::PresenceMessage.ACTION(action)
561
- end
422
+ def create_deferrable
423
+ Ably::Util::SafeDeferrable.new(logger)
562
424
  end
563
425
  end
564
426
  end
427
+
428
+ require 'ably/realtime/presence/presence_manager'
429
+ require 'ably/realtime/presence/members_map'
430
+ require 'ably/realtime/presence/presence_state_machine'