ably 0.1.5 → 0.1.6

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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -1
  3. data/ably.gemspec +4 -3
  4. data/lib/ably.rb +6 -2
  5. data/lib/ably/auth.rb +24 -16
  6. data/lib/ably/exceptions.rb +16 -5
  7. data/lib/ably/{realtime/models → models}/error_info.rb +9 -11
  8. data/lib/ably/models/idiomatic_ruby_wrapper.rb +57 -26
  9. data/lib/ably/{realtime/models → models}/message.rb +45 -38
  10. data/lib/ably/{realtime/models → models}/nil_channel.rb +4 -4
  11. data/lib/ably/{rest/models/paged_resource.rb → models/paginated_resource.rb} +21 -10
  12. data/lib/ably/models/presence_message.rb +126 -0
  13. data/lib/ably/{realtime/models → models}/protocol_message.rb +76 -38
  14. data/lib/ably/models/token.rb +74 -0
  15. data/lib/ably/modules/channels_collection.rb +49 -0
  16. data/lib/ably/modules/conversions.rb +2 -0
  17. data/lib/ably/modules/event_emitter.rb +43 -8
  18. data/lib/ably/modules/event_machine_helpers.rb +1 -0
  19. data/lib/ably/modules/http_helpers.rb +9 -2
  20. data/lib/ably/modules/message_pack.rb +14 -0
  21. data/lib/ably/modules/model_common.rb +29 -0
  22. data/lib/ably/modules/{state.rb → state_emitter.rb} +8 -7
  23. data/lib/ably/realtime.rb +37 -7
  24. data/lib/ably/realtime/channel.rb +154 -31
  25. data/lib/ably/realtime/channels.rb +47 -0
  26. data/lib/ably/realtime/client.rb +39 -33
  27. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +50 -21
  28. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +9 -11
  29. data/lib/ably/realtime/connection.rb +148 -79
  30. data/lib/ably/realtime/connection/connection_state_machine.rb +111 -0
  31. data/lib/ably/realtime/connection/websocket_transport.rb +161 -0
  32. data/lib/ably/realtime/presence.rb +270 -0
  33. data/lib/ably/rest.rb +14 -3
  34. data/lib/ably/rest/channel.rb +3 -3
  35. data/lib/ably/rest/channels.rb +26 -12
  36. data/lib/ably/rest/client.rb +42 -25
  37. data/lib/ably/rest/middleware/exceptions.rb +21 -23
  38. data/lib/ably/rest/middleware/external_exceptions.rb +8 -10
  39. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  40. data/lib/ably/rest/middleware/parse_json.rb +9 -2
  41. data/lib/ably/rest/middleware/parse_message_pack.rb +6 -2
  42. data/lib/ably/rest/presence.rb +4 -4
  43. data/lib/ably/version.rb +1 -1
  44. data/spec/acceptance/realtime/channel_history_spec.rb +125 -0
  45. data/spec/acceptance/realtime/channel_spec.rb +135 -63
  46. data/spec/acceptance/realtime/connection_spec.rb +86 -0
  47. data/spec/acceptance/realtime/message_spec.rb +116 -94
  48. data/spec/acceptance/realtime/presence_history_spec.rb +0 -0
  49. data/spec/acceptance/realtime/presence_spec.rb +277 -0
  50. data/spec/acceptance/rest/auth_spec.rb +351 -347
  51. data/spec/acceptance/rest/base_spec.rb +43 -26
  52. data/spec/acceptance/rest/channel_spec.rb +88 -83
  53. data/spec/acceptance/rest/channels_spec.rb +32 -28
  54. data/spec/acceptance/rest/presence_spec.rb +83 -63
  55. data/spec/acceptance/rest/stats_spec.rb +38 -37
  56. data/spec/acceptance/rest/time_spec.rb +10 -6
  57. data/spec/integration/modules/{state_spec.rb → state_emitter_spec.rb} +16 -2
  58. data/spec/spec_helper.rb +14 -0
  59. data/spec/support/api_helper.rb +4 -0
  60. data/spec/support/model_helper.rb +28 -9
  61. data/spec/support/protocol_msgbus_helper.rb +8 -1
  62. data/spec/support/test_app.rb +24 -14
  63. data/spec/unit/{realtime → models}/error_info_spec.rb +4 -4
  64. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +46 -9
  65. data/spec/unit/models/message_spec.rb +229 -0
  66. data/spec/unit/{rest/paged_resource_spec.rb → models/paginated_resource_spec.rb} +19 -11
  67. data/spec/unit/models/presence_message_spec.rb +230 -0
  68. data/spec/unit/models/protocol_message_spec.rb +280 -0
  69. data/spec/unit/{token_spec.rb → models/token_spec.rb} +18 -22
  70. data/spec/unit/modules/conversions_spec.rb +1 -1
  71. data/spec/unit/modules/event_emitter_spec.rb +36 -4
  72. data/spec/unit/realtime/channel_spec.rb +76 -2
  73. data/spec/unit/realtime/channels_spec.rb +50 -0
  74. data/spec/unit/realtime/client_spec.rb +31 -1
  75. data/spec/unit/realtime/connection_spec.rb +8 -15
  76. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +6 -6
  77. data/spec/unit/realtime/presence_spec.rb +100 -0
  78. data/spec/unit/rest/channels_spec.rb +48 -0
  79. metadata +72 -38
  80. data/lib/ably/realtime/models/shared.rb +0 -17
  81. data/lib/ably/rest/models/message.rb +0 -64
  82. data/lib/ably/rest/models/presence_message.rb +0 -21
  83. data/lib/ably/token.rb +0 -80
  84. data/spec/unit/realtime/message_spec.rb +0 -117
  85. data/spec/unit/realtime/protocol_message_spec.rb +0 -172
  86. data/spec/unit/rest/message_spec.rb +0 -75
@@ -0,0 +1,111 @@
1
+ require 'statesman'
2
+
3
+ module Ably::Realtime
4
+ class Connection
5
+ module StatesmanMonkeyPatch
6
+ # Override Statesman's #before_transition to support :from arrays
7
+ # This can be removed once https://github.com/gocardless/statesman/issues/95 is solved
8
+ def before_transition(options, &block)
9
+ if options.fetch(:from, nil).kind_of?(Array)
10
+ options[:from].each do |from_state|
11
+ super(options.merge(from: from_state), &block)
12
+ end
13
+ else
14
+ super
15
+ end
16
+ end
17
+ end
18
+
19
+ # Internal class to manage connection state, recovery and state transitions for an {Ably::Realtime::Connection}
20
+ class ConnectionStateMachine
21
+ include Statesman::Machine
22
+ extend StatesmanMonkeyPatch
23
+
24
+ # States supported by this StateMachine match #{Connection::STATE}s
25
+ # :initialized
26
+ # :connecting
27
+ # :connected
28
+ # :disconnected
29
+ # :suspended
30
+ # :closed
31
+ # :failed
32
+ Connection::STATE.each_with_index do |state_enum, index|
33
+ state state_enum.to_sym, initial: index == 0
34
+ end
35
+
36
+ transition :from => :initialized, :to => [:connecting, :closed]
37
+ transition :from => :connecting, :to => [:connected, :failed, :closed]
38
+ transition :from => :connected, :to => [:disconnected, :suspended, :closed, :failed]
39
+ transition :from => :disconnected, :to => [:connecting, :closed]
40
+ transition :from => :suspended, :to => [:connecting, :closed]
41
+ transition :from => :closed, :to => [:connecting]
42
+ transition :from => :failed, :to => [:connecting]
43
+
44
+ before_transition(to: [:connecting], from: [:initialized, :closed, :failed]) do |connection|
45
+ connection.setup_transport do |transport|
46
+ # Transition this StateMachine once the transport is connected or disconnected
47
+ # Invalid state changes are simply ignored and logged
48
+ transport.on(:disconnected) do
49
+ connection.transition_state_machine :disconnected
50
+ end
51
+ end
52
+ end
53
+
54
+ before_transition(to: [:connecting], from: [:disconnected, :suspended]) do |connection|
55
+ connection.reconnect_transport
56
+ end
57
+
58
+ after_transition(to: [:failed]) do |connection|
59
+ connection.transport.disconnect
60
+ end
61
+
62
+ before_transition(to: [:closed], from: [:initialized]) do |connection|
63
+ connection.timers.fetch(:initializer, []).each(&:cancel)
64
+ end
65
+
66
+ before_transition(to: [:closed], from: [:connecting, :connected, :disconnected, :suspended]) do |connection|
67
+ connection.send_protocol_message action: Ably::Models::ProtocolMessage::ACTION.Close
68
+ connection.transport.disconnect
69
+ end
70
+
71
+ after_transition do |connection, transition|
72
+ connection.change_state transition.to_state
73
+ end
74
+
75
+ def initialize(connection)
76
+ @connection = connection
77
+ super(connection)
78
+ end
79
+
80
+ # Override Statesman's #transition_to to simply log state change failures
81
+ def transition_to(*args)
82
+ unless super(*args)
83
+ logger.debug "Unable to transition to #{args[0]} from #{current_state}"
84
+ end
85
+ end
86
+
87
+ private
88
+ attr_reader :connection
89
+
90
+ # TODO: Implement once CLOSED ProtocolMessage is sent back from Ably in response to a CLOSE message
91
+ #
92
+ # FORCE_CONNECTION_CLOSED_TIMEOUT = 5
93
+ #
94
+ # def force_closed_unless_server_acknowledge_closed
95
+ # timeouts[:close_connection] << EventMachine::Timer.new(FORCE_CONNECTION_CLOSED_TIMEOUT) do
96
+ # transition_to :closed
97
+ # end
98
+ # end
99
+ #
100
+ # def clear_force_closed_timeouts
101
+ # timeouts[:close_connection].each do |timeout|
102
+ # timeout.cancel
103
+ # end.clear
104
+ # end
105
+
106
+ def logger
107
+ connection.logger
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,161 @@
1
+ module Ably::Realtime
2
+ class Connection
3
+ # EventMachine WebSocket transport
4
+ # @api private
5
+ class WebsocketTransport < EventMachine::Connection
6
+ include Ably::Modules::EventEmitter
7
+ include Ably::Modules::Conversions
8
+ extend Ably::Modules::Enum
9
+
10
+ # Valid WebSocket connection states
11
+ STATE = ruby_enum('STATE',
12
+ :initialized,
13
+ :connecting,
14
+ :connected,
15
+ :disconnecting,
16
+ :disconnected
17
+ )
18
+ include Ably::Modules::StateEmitter
19
+
20
+ def initialize(connection)
21
+ @connection = connection
22
+ @state = STATE.Initialized
23
+ end
24
+
25
+ # Send object down the WebSocket driver connection as a serialized string/byte array based on protocol
26
+ # @param [Object] object to serialize and send to the WebSocket driver
27
+ # @api public
28
+ def send_object(object)
29
+ case client.protocol
30
+ when :json
31
+ driver.text(object.to_json)
32
+ when :msgpack
33
+ driver.binary(object.to_msgpack.unpack('c*'))
34
+ else
35
+ client.logger.error "Unsupported protocol '#{client.protocol}' for serialization, object cannot be serialized and sent to Ably over this WebSocket"
36
+ end
37
+ end
38
+
39
+ # Disconnect the socket transport connection and write all pending text.
40
+ # If Disconnected state is not automatically triggered, it will be triggered automatically
41
+ # @return <void>
42
+ # @api public
43
+ def disconnect
44
+ close_connection_after_writing
45
+ change_state STATE.Disconnecting
46
+ create_timer(2) do
47
+ # if connection is not disconnected within 2s, set state as disconnected
48
+ change_state STATE.Disconnected
49
+ end
50
+ end
51
+
52
+ # Network connection has been established
53
+ # Required {http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Connection EventMachine::Connection} interface
54
+ def post_init
55
+ clear_timer
56
+ change_state STATE.Connecting
57
+ setup_driver
58
+ end
59
+
60
+ # Remote TCP connection attempt completes successfully
61
+ # Required {http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Connection EventMachine::Connection} interface
62
+ def connection_completed
63
+ change_state STATE.Connected
64
+ start_tls if client.use_tls?
65
+ driver.start
66
+ end
67
+
68
+ # Called by the event loop whenever data has been received by the network connection.
69
+ # Simply pass onto the WebSocket driver to process and determine content boundaries.
70
+ # Required {http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Connection EventMachine::Connection} interface
71
+ def receive_data(data)
72
+ driver.parse(data)
73
+ end
74
+
75
+ # Called whenever a connection (either a server or client connection) is closed
76
+ # Required {http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Connection EventMachine::Connection} interface
77
+ def unbind
78
+ change_state STATE.Disconnected
79
+ end
80
+
81
+ # URL end point including initialization configuration
82
+ # {http://www.rubydoc.info/gems/websocket-driver/0.3.5/WebSocket/Driver WebSocket::Driver} interface
83
+ def url
84
+ URI(client.endpoint).tap do |endpoint|
85
+ endpoint.query = URI.encode_www_form(client.auth.auth_params.merge(
86
+ timestamp: as_since_epoch(Time.now),
87
+ format: client.protocol,
88
+ echo: client.echo_messages
89
+ ))
90
+ end.to_s
91
+ end
92
+
93
+ # {http://www.rubydoc.info/gems/websocket-driver/0.3.5/WebSocket/Driver WebSocket::Driver} interface
94
+ def write(data)
95
+ send_data(data)
96
+ end
97
+
98
+ # True if socket connection is ready to be released
99
+ # i.e. it is not currently connecting or connected
100
+ def ready_for_release?
101
+ !connecting? && !connected?
102
+ end
103
+
104
+ private
105
+ attr_reader :connection, :driver
106
+
107
+ def clear_timer
108
+ if @timer
109
+ @timer.cancel
110
+ @timer = nil
111
+ end
112
+ end
113
+
114
+ def create_timer(period, &block)
115
+ @timer = EventMachine::Timer.new(period) do
116
+ block.call
117
+ end
118
+ end
119
+
120
+ def setup_driver
121
+ @driver = WebSocket::Driver.client(self)
122
+
123
+ driver.on("open") do
124
+ logger.debug "WebSocket connection opened to #{url}, waiting for Connected protocol message"
125
+ end
126
+
127
+ driver.on("message") do |event|
128
+ event_data = parse_event_data(event.data).freeze
129
+ protocol_message = Ably::Models::ProtocolMessage.new(event_data)
130
+ logger.debug "Prot msg recv <=: #{protocol_message.action} #{event_data}"
131
+ if protocol_message.invalid?
132
+ logger.error "Invalid Protocol Message received: #{event_data}\nNo action taken"
133
+ else
134
+ connection.__incoming_protocol_msgbus__.publish :message, protocol_message
135
+ end
136
+ end
137
+ end
138
+
139
+ def client
140
+ connection.client
141
+ end
142
+
143
+ # Used to log transport messages
144
+ def logger
145
+ connection.logger
146
+ end
147
+
148
+ def parse_event_data(data)
149
+ case client.protocol
150
+ when :json
151
+ JSON.parse(data)
152
+ when :msgpack
153
+ MessagePack.unpack(data.pack('c*'))
154
+ else
155
+ client.logger.error "Unsupported Protocol Message format #{client.protocol}"
156
+ data
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,270 @@
1
+ module Ably::Realtime
2
+ # Presence provides access to presence operations and state for the associated Channel
3
+ class Presence
4
+ include Ably::Modules::EventEmitter
5
+ extend Ably::Modules::Enum
6
+
7
+ STATE = ruby_enum('STATE',
8
+ :initialized,
9
+ :entering,
10
+ :entered,
11
+ :leaving,
12
+ :left,
13
+ :failed
14
+ )
15
+ include Ably::Modules::StateEmitter
16
+
17
+ # {Ably::Realtime::Channel} this Presence object is assoicated with
18
+ attr_reader :channel
19
+
20
+ def initialize(channel)
21
+ @channel = channel
22
+ @state = STATE.Initialized
23
+ @members = Hash.new
24
+ @subscriptions = Hash.new { |hash, key| hash[key] = [] }
25
+ @client_id = client.client_id
26
+ @client_data = nil
27
+
28
+ setup_event_handlers
29
+ end
30
+
31
+ # Enter this client into this channel. This client will be added to the presence set
32
+ # and presence subscribers will see an enter message for this client.
33
+ # @param [Hash,String] options an options Hash to specify client data and/or client ID, or a String with the client data
34
+ # @option options [String] :client_data optional data (eg a status message) for this member
35
+ # @option options [String] :client_id the optional id of the client.
36
+ # This option is provided to support connections from server instances that act on behalf of
37
+ # multiple client_ids. In order to be able to enter the channel with this method, the client
38
+ # library must have been instanced either with a key, or with a token bound to the wildcard clientId.
39
+ # @yield [Ably::Realtime::Presence] On success, will call the block with the {Ably::Realtime::Presence}
40
+ # @return [Ably::Realtime::PresenceMessage] Deferrable {Ably::Realtime::PresenceMessage} that supports both success (callback) and failure (errback) callbacks
41
+ #
42
+ def enter(options = {}, &blk)
43
+ @client_id = options.fetch(:client_id, client_id)
44
+ @client_data = options.fetch(:client_data, client_data)
45
+
46
+ raise Ably::Exceptions::Standard.new('Unable to enter presence channel without a client_id', 400, 91000) unless client_id
47
+
48
+ if state == STATE.Entered
49
+ blk.call self if block_given?
50
+ return
51
+ end
52
+
53
+ ensure_channel_attached do
54
+ once(STATE.Entered) { blk.call self } if block_given?
55
+
56
+ if !entering?
57
+ change_state STATE.Entering
58
+ send_presence_protocol_message(Ably::Models::PresenceMessage::ACTION.Enter).tap do |deferrable|
59
+ deferrable.errback { |message, error| change_state STATE.Failed, error }
60
+ deferrable.callback { |message| change_state STATE.Entered }
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ # Leave this client from this channel. This client will be removed from the presence
67
+ # set and presence subscribers will see a leave message for this client.
68
+ # @param (see Presence#enter)
69
+ # @yield (see Presence#enter)
70
+ # @return (see Presence#enter)
71
+ #
72
+ def leave(options = {}, &blk)
73
+ raise Ably::Exceptions::Standard.new('Unable to leave presence channel that is not entered', 400, 91002) unless ably_to_leave?
74
+
75
+ @client_data = options.fetch(:client_data, client_data)
76
+
77
+ if state == STATE.Left
78
+ blk.call self if block_given?
79
+ return
80
+ end
81
+
82
+ ensure_channel_attached do
83
+ once(STATE.Left) { blk.call self } if block_given?
84
+
85
+ if !leaving?
86
+ change_state STATE.Leaving
87
+ send_presence_protocol_message(Ably::Models::PresenceMessage::ACTION.Leave).tap do |deferrable|
88
+ deferrable.errback { |message, error| change_state STATE.Failed, error }
89
+ deferrable.callback { |message| change_state STATE.Left }
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ # Update the presence data for this client. If the client is not already a member of
96
+ # the presence set it will be added, and presence subscribers will see an enter or
97
+ # update message for this client.
98
+ # @param (see Presence#enter)
99
+ # @yield (see Presence#enter)
100
+ # @return (see Presence#enter)
101
+ #
102
+ def update(options = {}, &blk)
103
+ @client_data = options.fetch(:client_data, client_data)
104
+
105
+ ensure_channel_attached do
106
+ send_presence_protocol_message(Ably::Models::PresenceMessage::ACTION.Update).tap do |deferrable|
107
+ deferrable.callback do |message|
108
+ change_state STATE.Entered unless entered?
109
+ blk.call self if block_given?
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ # Get the presence state for this Channel.
116
+ # Optionally get a member's {Ably::Models::PresenceMessage} state by member_id
117
+ # @return [Array<Ably::Models::PresenceMessage>, Ably::Models::PresenceMessage] members on the channel
118
+ def get()
119
+ members.map { |key, presence| presence }
120
+ end
121
+
122
+ # Subscribe to presence events on the associated Channel.
123
+ # This implicitly attaches the Channel if it is not already attached.
124
+ #
125
+ # @param action [Ably::Models::PresenceMessage::ACTION] Optional, the state change action to subscribe to. Defaults to all presence actions
126
+ # @yield [Ably::Models::PresenceMessage] For each presence state change event, the block is called
127
+ #
128
+ def subscribe(action = :all, &blk)
129
+ ensure_channel_attached do
130
+ subscriptions[message_action_key(action)] << blk
131
+ end
132
+ end
133
+
134
+ # Unsubscribe the matching block for presence events on the associated Channel.
135
+ # If a block is not provided, all subscriptions will be unsubscribed
136
+ #
137
+ # @param action [Ably::Models::PresenceMessage::ACTION] Optional, the state change action to subscribe to. Defaults to all presence actions
138
+ #
139
+ def unsubscribe(action = :all, &blk)
140
+ if message_action_key(action) == :all
141
+ subscriptions.keys
142
+ else
143
+ Array(message_action_key(action))
144
+ end.each do |key|
145
+ subscriptions[key].delete_if do |block|
146
+ !block_given? || blk == block
147
+ end
148
+ end
149
+ end
150
+
151
+ # @!attribute [r] __incoming_msgbus__
152
+ # @return [Ably::Util::PubSub] Client library internal channel incoming message bus
153
+ # @api private
154
+ def __incoming_msgbus__
155
+ @__incoming_msgbus__ ||= Ably::Util::PubSub.new(
156
+ coerce_into: Proc.new { |event| Ably::Models::ProtocolMessage::ACTION(event) }
157
+ )
158
+ end
159
+
160
+ private
161
+ attr_reader :members, :subscriptions, :client_id, :client_data
162
+
163
+ def ably_to_leave?
164
+ entering? || entered?
165
+ end
166
+
167
+ def setup_event_handlers
168
+ __incoming_msgbus__.subscribe(:presence) do |presence|
169
+ update_members_from_presence_message presence
170
+ subscriptions[:all].each { |cb| cb.call(presence) }
171
+ subscriptions[presence.action].each { |cb| cb.call(presence) }
172
+ end
173
+
174
+ channel.on(Channel::STATE.Detaching) do
175
+ change_state STATE.Leaving
176
+ end
177
+
178
+ channel.on(Channel::STATE.Detached) do
179
+ change_state STATE.Left
180
+ end
181
+
182
+ channel.on(Channel::STATE.Failed) do
183
+ change_state STATE.Failed unless left? || initialized?
184
+ end
185
+ end
186
+
187
+ # @return [Ably::Models::PresenceMessage] presence message is returned allowing callbacks to be added
188
+ def send_presence_protocol_message(presence_action)
189
+ presence_message = create_presence_message(presence_action)
190
+ unless presence_message.client_id
191
+ raise Ably::Exceptions::Standard.new('Unable to enter create presence message without a client_id', 400, 91000)
192
+ end
193
+
194
+ protocol_message = {
195
+ action: Ably::Models::ProtocolMessage::ACTION.Presence,
196
+ channel: channel.name,
197
+ presence: [presence_message]
198
+ }
199
+
200
+ client.connection.send_protocol_message protocol_message
201
+
202
+ presence_message
203
+ end
204
+
205
+ def create_presence_message(action)
206
+ model = {
207
+ action: Ably::Models::PresenceMessage.ACTION(action).to_i,
208
+ clientId: client_id,
209
+ }
210
+ model.merge!(clientData: client_data) if client_data
211
+
212
+ Ably::Models::PresenceMessage.new(model, nil)
213
+ end
214
+
215
+ def update_members_from_presence_message(presence_message)
216
+ unless presence_message.member_id
217
+ new Ably::Exceptions::ProtocolError.new("Protocol error, presence message is missing memberId", 400, 80013)
218
+ end
219
+
220
+ case presence_message.action
221
+ when Ably::Models::PresenceMessage::ACTION.Enter
222
+ members[presence_message.member_id] = presence_message
223
+
224
+ when Ably::Models::PresenceMessage::ACTION.Update
225
+ members[presence_message.member_id] = presence_message
226
+
227
+ when Ably::Models::PresenceMessage::ACTION.Leave
228
+ members.delete presence_message.member_id
229
+
230
+ else
231
+ new Ably::Exceptions::ProtocolError.new("Protocol error, unknown presence action #{presence.action}", 400, 80013)
232
+ end
233
+ end
234
+
235
+ def ensure_channel_attached
236
+ if channel.attached?
237
+ yield
238
+ else
239
+ attach_channel_then { yield }
240
+ end
241
+ end
242
+
243
+ def attach_channel_then
244
+ if channel.detached? || channel.failed?
245
+ raise Ably::Exceptions::Standard.new('Unable to enter presence channel in detached or failed action', 400, 91001)
246
+ else
247
+ channel.once(Channel::STATE.Attached) { yield }
248
+ channel.attach
249
+ end
250
+ end
251
+
252
+ def client
253
+ channel.client
254
+ end
255
+
256
+ # Used by {Ably::Modules::StateEmitter} to debug state changes
257
+ # Used by {Ably::Modules::StateEmitter} to debug action changes
258
+ def logger
259
+ client.logger
260
+ end
261
+
262
+ def message_action_key(action)
263
+ if action == :all
264
+ :all
265
+ else
266
+ Ably::Models::PresenceMessage.ACTION(action)
267
+ end
268
+ end
269
+ end
270
+ end