ably 0.7.2 → 0.7.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/LICENSE.txt +1 -1
  2. data/README.md +107 -24
  3. data/SPEC.md +531 -398
  4. data/lib/ably/auth.rb +23 -15
  5. data/lib/ably/exceptions.rb +9 -0
  6. data/lib/ably/models/message.rb +17 -9
  7. data/lib/ably/models/paginated_resource.rb +12 -8
  8. data/lib/ably/models/presence_message.rb +18 -10
  9. data/lib/ably/models/protocol_message.rb +15 -4
  10. data/lib/ably/modules/async_wrapper.rb +4 -3
  11. data/lib/ably/modules/event_emitter.rb +31 -2
  12. data/lib/ably/modules/message_emitter.rb +77 -0
  13. data/lib/ably/modules/safe_deferrable.rb +71 -0
  14. data/lib/ably/modules/safe_yield.rb +41 -0
  15. data/lib/ably/modules/state_emitter.rb +28 -8
  16. data/lib/ably/realtime.rb +0 -5
  17. data/lib/ably/realtime/channel.rb +24 -29
  18. data/lib/ably/realtime/channel/channel_manager.rb +54 -11
  19. data/lib/ably/realtime/channel/channel_state_machine.rb +21 -6
  20. data/lib/ably/realtime/client.rb +7 -2
  21. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +29 -26
  22. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +4 -4
  23. data/lib/ably/realtime/connection.rb +41 -9
  24. data/lib/ably/realtime/connection/connection_manager.rb +72 -24
  25. data/lib/ably/realtime/connection/connection_state_machine.rb +26 -4
  26. data/lib/ably/realtime/connection/websocket_transport.rb +19 -6
  27. data/lib/ably/realtime/presence.rb +74 -208
  28. data/lib/ably/realtime/presence/members_map.rb +264 -0
  29. data/lib/ably/realtime/presence/presence_manager.rb +59 -0
  30. data/lib/ably/realtime/presence/presence_state_machine.rb +64 -0
  31. data/lib/ably/rest/channel.rb +1 -1
  32. data/lib/ably/rest/client.rb +6 -2
  33. data/lib/ably/rest/presence.rb +1 -1
  34. data/lib/ably/util/pub_sub.rb +3 -1
  35. data/lib/ably/util/safe_deferrable.rb +18 -0
  36. data/lib/ably/version.rb +1 -1
  37. data/spec/acceptance/realtime/channel_history_spec.rb +2 -2
  38. data/spec/acceptance/realtime/channel_spec.rb +28 -6
  39. data/spec/acceptance/realtime/connection_failures_spec.rb +116 -46
  40. data/spec/acceptance/realtime/connection_spec.rb +55 -10
  41. data/spec/acceptance/realtime/message_spec.rb +32 -0
  42. data/spec/acceptance/realtime/presence_spec.rb +456 -96
  43. data/spec/acceptance/realtime/stats_spec.rb +2 -2
  44. data/spec/acceptance/realtime/time_spec.rb +2 -2
  45. data/spec/acceptance/rest/auth_spec.rb +75 -7
  46. data/spec/shared/client_initializer_behaviour.rb +8 -0
  47. data/spec/shared/safe_deferrable_behaviour.rb +71 -0
  48. data/spec/support/api_helper.rb +1 -1
  49. data/spec/support/event_machine_helper.rb +1 -1
  50. data/spec/support/test_app.rb +13 -7
  51. data/spec/unit/models/message_spec.rb +15 -14
  52. data/spec/unit/models/paginated_resource_spec.rb +4 -4
  53. data/spec/unit/models/presence_message_spec.rb +17 -17
  54. data/spec/unit/models/stat_spec.rb +4 -4
  55. data/spec/unit/modules/async_wrapper_spec.rb +28 -9
  56. data/spec/unit/modules/event_emitter_spec.rb +50 -0
  57. data/spec/unit/modules/state_emitter_spec.rb +76 -2
  58. data/spec/unit/realtime/channel_spec.rb +51 -20
  59. data/spec/unit/realtime/channels_spec.rb +3 -3
  60. data/spec/unit/realtime/connection_spec.rb +30 -0
  61. data/spec/unit/realtime/presence_spec.rb +52 -26
  62. data/spec/unit/realtime/safe_deferrable_spec.rb +12 -0
  63. metadata +85 -39
  64. checksums.yaml +0 -7
  65. data/.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'