ably-pubsub-core 0.0.1.pre

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 (105) hide show
  1. checksums.yaml +7 -0
  2. data/lib/ably/agent.rb +8 -0
  3. data/lib/ably/auth.rb +791 -0
  4. data/lib/ably/exceptions.rb +169 -0
  5. data/lib/ably/logger.rb +119 -0
  6. data/lib/ably/models/auth_details.rb +45 -0
  7. data/lib/ably/models/channel_details.rb +69 -0
  8. data/lib/ably/models/channel_metrics.rb +107 -0
  9. data/lib/ably/models/channel_occupancy.rb +50 -0
  10. data/lib/ably/models/channel_options.rb +120 -0
  11. data/lib/ably/models/channel_state_change.rb +84 -0
  12. data/lib/ably/models/channel_status.rb +63 -0
  13. data/lib/ably/models/cipher_params.rb +137 -0
  14. data/lib/ably/models/connection_details.rb +149 -0
  15. data/lib/ably/models/connection_state_change.rb +83 -0
  16. data/lib/ably/models/delta_extras.rb +28 -0
  17. data/lib/ably/models/device_details.rb +126 -0
  18. data/lib/ably/models/device_push_details.rb +94 -0
  19. data/lib/ably/models/error_info.rb +104 -0
  20. data/lib/ably/models/http_paginated_response.rb +112 -0
  21. data/lib/ably/models/idiomatic_ruby_wrapper.rb +235 -0
  22. data/lib/ably/models/message.rb +223 -0
  23. data/lib/ably/models/message_encoders/base.rb +135 -0
  24. data/lib/ably/models/message_encoders/base64.rb +40 -0
  25. data/lib/ably/models/message_encoders/cipher.rb +84 -0
  26. data/lib/ably/models/message_encoders/json.rb +34 -0
  27. data/lib/ably/models/message_encoders/utf8.rb +26 -0
  28. data/lib/ably/models/nil_logger.rb +20 -0
  29. data/lib/ably/models/paginated_result.rb +201 -0
  30. data/lib/ably/models/presence_message.rb +244 -0
  31. data/lib/ably/models/protocol_message.rb +288 -0
  32. data/lib/ably/models/push_channel_subscription.rb +114 -0
  33. data/lib/ably/models/stats.rb +242 -0
  34. data/lib/ably/models/stats_types.rb +107 -0
  35. data/lib/ably/models/token_details.rb +148 -0
  36. data/lib/ably/models/token_request.rb +180 -0
  37. data/lib/ably/modules/ably.rb +26 -0
  38. data/lib/ably/modules/async_wrapper.rb +69 -0
  39. data/lib/ably/modules/channels_collection.rb +105 -0
  40. data/lib/ably/modules/conversions.rb +153 -0
  41. data/lib/ably/modules/encodeable.rb +111 -0
  42. data/lib/ably/modules/enum.rb +230 -0
  43. data/lib/ably/modules/event_emitter.rb +200 -0
  44. data/lib/ably/modules/event_machine_helpers.rb +26 -0
  45. data/lib/ably/modules/exception_codes.rb +128 -0
  46. data/lib/ably/modules/http_helpers.rb +41 -0
  47. data/lib/ably/modules/message_emitter.rb +78 -0
  48. data/lib/ably/modules/message_pack.rb +14 -0
  49. data/lib/ably/modules/model_common.rb +84 -0
  50. data/lib/ably/modules/safe_deferrable.rb +83 -0
  51. data/lib/ably/modules/safe_yield.rb +41 -0
  52. data/lib/ably/modules/state_emitter.rb +181 -0
  53. data/lib/ably/modules/state_machine.rb +59 -0
  54. data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  55. data/lib/ably/modules/uses_state_machine.rb +111 -0
  56. data/lib/ably/realtime/auth.rb +283 -0
  57. data/lib/ably/realtime/channel/channel_manager.rb +272 -0
  58. data/lib/ably/realtime/channel/channel_properties.rb +40 -0
  59. data/lib/ably/realtime/channel/channel_state_machine.rb +104 -0
  60. data/lib/ably/realtime/channel/publisher.rb +82 -0
  61. data/lib/ably/realtime/channel/push_channel.rb +69 -0
  62. data/lib/ably/realtime/channel.rb +440 -0
  63. data/lib/ably/realtime/channels.rb +71 -0
  64. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +225 -0
  65. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +87 -0
  66. data/lib/ably/realtime/client.rb +363 -0
  67. data/lib/ably/realtime/connection/connection_manager.rb +594 -0
  68. data/lib/ably/realtime/connection/connection_state_machine.rb +144 -0
  69. data/lib/ably/realtime/connection/websocket_transport.rb +287 -0
  70. data/lib/ably/realtime/connection.rb +738 -0
  71. data/lib/ably/realtime/models/nil_channel.rb +30 -0
  72. data/lib/ably/realtime/presence/members_map.rb +395 -0
  73. data/lib/ably/realtime/presence/presence_manager.rb +78 -0
  74. data/lib/ably/realtime/presence/presence_state_machine.rb +57 -0
  75. data/lib/ably/realtime/presence.rb +502 -0
  76. data/lib/ably/realtime/push/admin.rb +75 -0
  77. data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
  78. data/lib/ably/realtime/push/device_registrations.rb +105 -0
  79. data/lib/ably/realtime/push.rb +25 -0
  80. data/lib/ably/realtime/recovery_key_context.rb +36 -0
  81. data/lib/ably/realtime.rb +60 -0
  82. data/lib/ably/rest/channel/push_channel.rb +59 -0
  83. data/lib/ably/rest/channel.rb +194 -0
  84. data/lib/ably/rest/channels.rb +47 -0
  85. data/lib/ably/rest/client.rb +727 -0
  86. data/lib/ably/rest/middleware/encoder.rb +49 -0
  87. data/lib/ably/rest/middleware/exceptions.rb +53 -0
  88. data/lib/ably/rest/middleware/external_exceptions.rb +24 -0
  89. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +22 -0
  90. data/lib/ably/rest/middleware/logger.rb +58 -0
  91. data/lib/ably/rest/middleware/parse_json.rb +27 -0
  92. data/lib/ably/rest/middleware/parse_message_pack.rb +43 -0
  93. data/lib/ably/rest/presence.rb +113 -0
  94. data/lib/ably/rest/push/admin.rb +58 -0
  95. data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
  96. data/lib/ably/rest/push/device_registrations.rb +114 -0
  97. data/lib/ably/rest/push.rb +25 -0
  98. data/lib/ably/rest.rb +40 -0
  99. data/lib/ably/util/ably_extensions.rb +29 -0
  100. data/lib/ably/util/crypto.rb +134 -0
  101. data/lib/ably/util/pub_sub.rb +45 -0
  102. data/lib/ably/util/safe_deferrable.rb +44 -0
  103. data/lib/ably/version.rb +7 -0
  104. data/lib/ably.rb +16 -0
  105. metadata +497 -0
@@ -0,0 +1,502 @@
1
+ module Ably::Realtime
2
+ # Enables the presence set to be entered and subscribed to, and the historic presence set to be retrieved for a channel.
3
+ #
4
+ class Presence
5
+ include Ably::Modules::Conversions
6
+ include Ably::Modules::EventEmitter
7
+ include Ably::Modules::AsyncWrapper
8
+ include Ably::Modules::MessageEmitter
9
+ include Ably::Modules::SafeYield
10
+ extend Ably::Modules::Enum
11
+
12
+ STATE = ruby_enum('STATE',
13
+ :initialized,
14
+ :entering,
15
+ :entered,
16
+ :leaving,
17
+ :left
18
+ )
19
+ include Ably::Modules::StateEmitter
20
+ include Ably::Modules::UsesStateMachine
21
+
22
+ # {Ably::Realtime::Channel} this Presence object is associated with
23
+ # @return [Ably::Realtime::Channel]
24
+ attr_reader :channel
25
+
26
+ # The client_id for the member present on this channel
27
+ # @return [String]
28
+ attr_reader :client_id
29
+
30
+ # The data for the member present on this channel
31
+ # @return [String]
32
+ attr_reader :data
33
+
34
+ # {MembersMap} containing an up to date list of members on this channel
35
+ # @return [MembersMap]
36
+ # @api private
37
+ attr_reader :members
38
+
39
+ # The Presence manager responsible for actions relating to state changes such as entering a channel
40
+ # @return [Ably::Realtime::Presence::PresenceManager]
41
+ # @api private
42
+ attr_reader :manager
43
+
44
+ def initialize(channel)
45
+ @channel = channel
46
+ @client_id = client.client_id
47
+
48
+ @state_machine = PresenceStateMachine.new(self)
49
+ @state = STATE(state_machine.current_state)
50
+ @members = MembersMap.new(self)
51
+ @manager = PresenceManager.new(self)
52
+ end
53
+
54
+ # Enter this client into this channel. This client will be added to the presence set
55
+ # and presence subscribers will see an enter message for this client.
56
+ #
57
+ # @param [String,Hash,nil] data optional data (eg a status message) for this member
58
+ #
59
+ # @yield [Ably::Realtime::Presence] On success, will call the block with this {Ably::Realtime::Presence} object
60
+ # @return [Ably::Util::SafeDeferrable] Deferrable that supports both success (callback) and failure (errback) callbacks
61
+ #
62
+ def enter(data = nil, &success_block)
63
+ deferrable = create_deferrable
64
+
65
+ ensure_supported_payload data
66
+ @data = data
67
+
68
+ return deferrable_succeed(deferrable, &success_block) if state == STATE.Entered
69
+
70
+ requirements_failed_deferrable = ensure_presence_publishable_on_connection_deferrable
71
+ return requirements_failed_deferrable if requirements_failed_deferrable
72
+
73
+ ensure_channel_attached(deferrable) do
74
+ if entering?
75
+ once_or_if(STATE.Entered, else: lambda { |args| deferrable_fail deferrable, *args }) do
76
+ deferrable_succeed deferrable, &success_block
77
+ end
78
+ else
79
+ current_state = state
80
+ change_state STATE.Entering
81
+ send_protocol_message_and_transition_state_to(
82
+ Ably::Models::PresenceMessage::ACTION.Enter,
83
+ deferrable: deferrable,
84
+ target_state: STATE.Entered,
85
+ data: data,
86
+ client_id: client_id,
87
+ failed_state: current_state, # return to current state if enter fails
88
+ &success_block
89
+ )
90
+ end
91
+ end
92
+ end
93
+
94
+ # Enters the presence set of the channel for a given clientId. Enables a single client to update presence on behalf
95
+ # of any number of clients using a single connection. The library must have been instantiated with an API key
96
+ # or a token bound to a wildcard clientId. An optional callback may be provided to notify of the success or failure of the operation.
97
+ #
98
+ # @spec RTP4, RTP14, RTP15
99
+ #
100
+ # @param [String] client_id id of the client
101
+ # @param [String,Hash,nil] data The payload associated with the presence member. A JSON object of arbitrary key-value pairs that may contain metadata, and/or ancillary payloads.
102
+ #
103
+ # @yield [Ably::Realtime::Presence] On success, will call the block with this {Ably::Realtime::Presence} object
104
+ # @return [Ably::Util::SafeDeferrable] Deferrable that supports both success (callback) and failure (errback) callbacks
105
+ #
106
+ def enter_client(client_id, data = nil, &success_block)
107
+ ensure_supported_client_id client_id
108
+ ensure_supported_payload data
109
+
110
+ send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Enter, client_id, data, &success_block)
111
+ end
112
+
113
+ # @api private
114
+ def enter_client_with_id(id, client_id, data = nil, &success_block)
115
+ ensure_supported_client_id client_id
116
+ ensure_supported_payload data
117
+
118
+ send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Enter, client_id, data, id, &success_block)
119
+ end
120
+
121
+ # Leave this client from this channel. This client will be removed from the presence
122
+ # set and presence subscribers will see a leave message for this client.
123
+ #
124
+ # @param (see Presence#enter)
125
+ #
126
+ # @yield (see Presence#enter)
127
+ # @return (see Presence#enter)
128
+ #
129
+ def leave(data = nil, &success_block)
130
+ deferrable = create_deferrable
131
+
132
+ ensure_supported_payload data
133
+
134
+ @data = data
135
+
136
+ return deferrable_succeed(deferrable, &success_block) if state == STATE.Left
137
+
138
+ requirements_failed_deferrable = ensure_presence_publishable_on_connection_deferrable
139
+ return requirements_failed_deferrable if requirements_failed_deferrable
140
+
141
+ ensure_channel_attached(deferrable) do
142
+ if leaving?
143
+ once_or_if(STATE.Left, else: lambda { |error|deferrable_fail deferrable, *args }) do
144
+ deferrable_succeed deferrable, &success_block
145
+ end
146
+ else
147
+ current_state = state
148
+ change_state STATE.Leaving
149
+ send_protocol_message_and_transition_state_to(
150
+ Ably::Models::PresenceMessage::ACTION.Leave,
151
+ deferrable: deferrable,
152
+ target_state: STATE.Left,
153
+ data: data,
154
+ client_id: client_id,
155
+ failed_state: current_state, # return to current state if leave fails
156
+ &success_block
157
+ )
158
+ end
159
+ end
160
+ end
161
+
162
+ # Leaves the presence set of the channel for a given clientId. Enables a single client to update presence on behalf
163
+ # of any number of clients using a single connection. The library must have been instantiated with an API key
164
+ # or a token bound to a wildcard clientId. An optional callback may be provided to notify of the success or failure of the operation.
165
+ #
166
+ # @spec RTP15
167
+ #
168
+ # @param (see {Ably::Realtime::Presence#enter_client})
169
+ #
170
+ # @yield (see {Ably::Realtime::Presence#enter_client})
171
+ # @return (see {Ably::Realtime::Presence#enter_client})
172
+ #
173
+ def leave_client(client_id, data = nil, &success_block)
174
+ ensure_supported_client_id client_id
175
+ ensure_supported_payload data
176
+
177
+ send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Leave, client_id, data, &success_block)
178
+ end
179
+
180
+ # Updates the data payload for a presence member. If called before entering the presence set, this is treated as
181
+ # an {Ably::Realtime::Presence::STATE.Entered} event. An optional callback may be provided to notify of the success or failure of the operation.
182
+ #
183
+ # @spec RTP9
184
+ #
185
+ # @param (see {Ably::Realtime::Presence#enter})
186
+ #
187
+ # @yield (see {Ably::Realtime::Presence#enter})
188
+ # @return (see {Ably::Realtime::Presence#enter})
189
+ #
190
+ def update(data = nil, &success_block)
191
+ deferrable = create_deferrable
192
+
193
+ ensure_supported_payload data
194
+
195
+ @data = data
196
+
197
+ requirements_failed_deferrable = ensure_presence_publishable_on_connection_deferrable
198
+ return requirements_failed_deferrable if requirements_failed_deferrable
199
+
200
+ ensure_channel_attached(deferrable) do
201
+ send_protocol_message_and_transition_state_to(
202
+ Ably::Models::PresenceMessage::ACTION.Update,
203
+ deferrable: deferrable,
204
+ target_state: STATE.Entered,
205
+ client_id: client_id,
206
+ data: data,
207
+ &success_block
208
+ )
209
+ end
210
+ end
211
+
212
+ # Updates the data payload for a presence member using a given clientId. Enables a single client to update presence
213
+ # on behalf of any number of clients using a single connection. The library must have been instantiated with an API
214
+ # key or a token bound to a wildcard clientId. An optional callback may be provided to notify of the success
215
+ # or failure of the operation.
216
+ #
217
+ # @spec RTP15
218
+ #
219
+ # @param (see Presence#enter_client)
220
+ #
221
+ # @yield (see Presence#enter_client)
222
+ # @return (see Presence#enter_client)
223
+ #
224
+ def update_client(client_id, data = nil, &success_block)
225
+ ensure_supported_client_id client_id
226
+ ensure_supported_payload data
227
+
228
+ send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Update, client_id, data, &success_block)
229
+ end
230
+
231
+ # Retrieves the current members present on the channel and the metadata for each member, such as their
232
+ # {Ably::Models::ProtocolMessage::ACTION} and ID. Returns an array of {Ably::Models::PresenceMessage} objects.
233
+ #
234
+ # @spec RTP11, RTP11c1, RTP11c2, RTP11c3
235
+ #
236
+ # @param (see {Ably::Realtime::Presence::MembersMap#get})
237
+ # @option options (see {Ably::Realtime::Presence::MembersMap#get})
238
+ # @yield (see {Ably::Realtime::Presence::MembersMap#get})
239
+ #
240
+ # @return (see {Ably::Realtime::Presence::MembersMap#get})
241
+ #
242
+ def get(options = {}, &block)
243
+ deferrable = create_deferrable
244
+
245
+ # #RTP11d Don't return PresenceMap when wait for sync is true
246
+ # if the map is stale
247
+ wait_for_sync = options.fetch(:wait_for_sync, true)
248
+ if wait_for_sync && channel.suspended?
249
+ EventMachine.next_tick do
250
+ deferrable.fail Ably::Exceptions::InvalidState.new(
251
+ 'Presence state is out of sync as channel is SUSPENDED. Presence#get on a SUSPENDED channel is only supported with option wait_for_sync: false',
252
+ nil,
253
+ Ably::Exceptions::Codes::PRESENCE_STATE_IS_OUT_OF_SYNC
254
+ )
255
+ end
256
+ return deferrable
257
+ end
258
+
259
+ ensure_channel_attached(deferrable, allow_suspended: true) do
260
+ members.get(options).tap do |members_map_deferrable|
261
+ members_map_deferrable.callback do |members|
262
+ safe_yield(block, members) if block_given?
263
+ deferrable.succeed(members)
264
+ end
265
+ members_map_deferrable.errback do |*args|
266
+ deferrable.fail(*args)
267
+ end
268
+ end
269
+ end
270
+ end
271
+
272
+ # Registers a listener that is called each time a {Ably::Models::PresenceMessage} is received on the channel,
273
+ # such as a new member entering the presence set. A callback may optionally be passed in to this call to be notified
274
+ # of success or failure of the channel {Ably::Realtime::Channel#attach} operation.
275
+ #
276
+ # @spec RTP6a, RTP6b
277
+ #
278
+ # @param actions [Ably::Models::PresenceMessage::ACTION] Optional, the state change action to subscribe to. Defaults to all presence actions
279
+ # @yield [Ably::Models::PresenceMessage] For each presence state change event, the block is called
280
+ #
281
+ # @return [void]
282
+ #
283
+ def subscribe(*actions, &callback)
284
+ implicit_attach
285
+ super
286
+ end
287
+
288
+ # Unsubscribe the matching block for presence events on the associated Channel.
289
+ # If a block is not provided, all subscriptions will be unsubscribed {Ably::Models::PresenceMessage} for the channel.
290
+ #
291
+ # @spec RTP7a, RTP7b, RTE5
292
+ #
293
+ # @param actions [Ably::Models::PresenceMessage::ACTION] Optional, the state change action to subscribe to. Defaults to all presence actions
294
+ #
295
+ # @return [void]
296
+ #
297
+ def unsubscribe(*actions, &callback)
298
+ super
299
+ end
300
+
301
+ # Retrieves a {Ably::Models::PaginatedResult} object, containing an array of historical
302
+ # {Ably::Models::PresenceMessage} objects for the channel. If the channel is configured to persist messages,
303
+ # then presence messages can be retrieved from history for up to 72 hours in the past. If not, presence messages
304
+ # can only be retrieved from history for up to two minutes in the past.
305
+ #
306
+ # @spec RTP12c, RTP12a
307
+ #
308
+ # @param (see {Ably::Rest::Presence#history})
309
+ # @option options (see {Ably::Rest::Presence#history})
310
+ #
311
+ # @yield [Ably::Models::PaginatedResult<Ably::Models::PresenceMessage>] First {Ably::Models::PaginatedResult page} of {Ably::Models::PresenceMessage} objects accessible with {Ably::Models::PaginatedResult#items #items}.
312
+ #
313
+ # @return [Ably::Util::SafeDeferrable]
314
+ #
315
+ def history(options = {}, &callback)
316
+ async_wrap(callback) do
317
+ rest_presence.history(options.merge(async_blocking_operations: true))
318
+ end
319
+ end
320
+
321
+ # @!attribute [r] __incoming_msgbus__
322
+ # @return [Ably::Util::PubSub] Client library internal channel incoming protocol message bus
323
+ # @api private
324
+ def __incoming_msgbus__
325
+ @__incoming_msgbus__ ||= Ably::Util::PubSub.new(
326
+ coerce_into: lambda { |event| Ably::Models::ProtocolMessage::ACTION(event) }
327
+ )
328
+ end
329
+
330
+ # Used by {Ably::Modules::StateEmitter} to debug action changes
331
+ # @api private
332
+ def logger
333
+ client.logger
334
+ end
335
+
336
+ # Indicates whether the presence set synchronization between Ably and the clients on the channel has been completed.
337
+ # Set to true when the sync is complete.
338
+ #
339
+ # @spec RTP13
340
+ #
341
+ # return [Boolean]
342
+ #
343
+ def sync_complete?
344
+ members.sync_complete?
345
+ end
346
+
347
+ private
348
+ # @return [Ably::Models::PresenceMessage] presence message is returned allowing callbacks to be added
349
+ def send_presence_protocol_message(presence_action, client_id, data, id = nil)
350
+ presence_message = create_presence_message(presence_action, client_id, data, id)
351
+ unless presence_message.client_id
352
+ raise Ably::Exceptions::Standard.new('Unable to enter create presence message without a client_id', 400, Ably::Exceptions::Codes::UNABLE_TO_ENTER_PRESENCE_CHANNEL_NO_CLIENTID)
353
+ end
354
+
355
+ protocol_message = {
356
+ action: Ably::Models::ProtocolMessage::ACTION.Presence,
357
+ channel: channel.name,
358
+ presence: [presence_message]
359
+ }
360
+
361
+ client.connection.send_protocol_message protocol_message
362
+
363
+ presence_message
364
+ end
365
+
366
+ def create_presence_message(action, client_id, data, id = nil)
367
+ model = {
368
+ action: Ably::Models::PresenceMessage.ACTION(action).to_i,
369
+ clientId: client_id,
370
+ data: data,
371
+ }
372
+ model[:id] = id unless id.nil?
373
+
374
+ Ably::Models::PresenceMessage.new(model, logger: logger).tap do |presence_message|
375
+ presence_message.encode(client.encoders, channel.options) do |encode_error, error_message|
376
+ client.logger.error error_message
377
+ end
378
+ end
379
+ end
380
+
381
+ def ensure_presence_publishable_on_connection_deferrable
382
+ if !connection.can_publish_messages?
383
+ error = Ably::Exceptions::MessageQueueingDisabled.new("Presence event cannot be published as they cannot be queued when the connection is #{connection.state}")
384
+ Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
385
+ end
386
+ end
387
+
388
+ def ensure_channel_attached(deferrable = nil, options = {})
389
+ if channel.attached?
390
+ yield
391
+ elsif options[:allow_suspended] && channel.suspended?
392
+ yield
393
+ else
394
+ attach_channel_then(deferrable) { yield }
395
+ end
396
+ deferrable
397
+ end
398
+
399
+ def ensure_supported_client_id(check_client_id)
400
+ unless check_client_id
401
+ raise Ably::Exceptions::IncompatibleClientId.new('Unable to enter/update/leave presence channel without a client_id')
402
+ end
403
+ if check_client_id == '*'
404
+ raise Ably::Exceptions::IncompatibleClientId.new('Unable to enter/update/leave presence channel with the reserved wildcard client_id')
405
+ end
406
+ unless check_client_id.kind_of?(String)
407
+ raise Ably::Exceptions::IncompatibleClientId.new('Unable to enter/update/leave with a non String client_id value')
408
+ end
409
+ unless client.auth.can_assume_client_id?(check_client_id)
410
+ raise Ably::Exceptions::IncompatibleClientId.new("Cannot enter with provided client_id '#{check_client_id}' as it is incompatible with the current configured client_id '#{client.client_id}'")
411
+ end
412
+ end
413
+
414
+ def send_protocol_message_and_transition_state_to(action, options = {}, &success_block)
415
+ deferrable = options.fetch(:deferrable) { raise ArgumentError, 'option :deferrable is required' }
416
+ client_id = options.fetch(:client_id) { raise ArgumentError, 'option :client_id is required' }
417
+ target_state = options.fetch(:target_state, nil)
418
+ failed_state = options.fetch(:failed_state, nil)
419
+
420
+ send_presence_protocol_message(action, client_id, options[:data]).tap do |protocol_message|
421
+ protocol_message.callback do |message|
422
+ change_state target_state, message if target_state
423
+ deferrable_succeed deferrable, &success_block
424
+ end
425
+
426
+ protocol_message.errback do |error|
427
+ change_state failed_state, error if failed_state
428
+ deferrable_fail deferrable, error
429
+ end
430
+ end
431
+ end
432
+
433
+ def deferrable_succeed(deferrable, *args, &block)
434
+ safe_yield(block, self, *args) if block_given?
435
+ EventMachine.next_tick { deferrable.succeed self, *args } # allow callback to be added to the returned Deferrable before calling succeed
436
+ deferrable
437
+ end
438
+
439
+ def deferrable_fail(deferrable, *args, &block)
440
+ safe_yield(block, *args) if block_given?
441
+ EventMachine.next_tick { deferrable.fail(*args) } # allow errback to be added to the returned Deferrable
442
+ deferrable
443
+ end
444
+
445
+ def send_presence_action_for_client(action, client_id, data, id = nil, &success_block)
446
+ requirements_failed_deferrable = ensure_presence_publishable_on_connection_deferrable
447
+ return requirements_failed_deferrable if requirements_failed_deferrable
448
+
449
+ deferrable = create_deferrable
450
+ ensure_channel_attached(deferrable) do
451
+ send_presence_protocol_message(action, client_id, data, id).tap do |protocol_message|
452
+ protocol_message.callback { |message| deferrable_succeed deferrable, &success_block }
453
+ protocol_message.errback { |error| deferrable_fail deferrable, error }
454
+ end
455
+ end
456
+ end
457
+
458
+ def attach_channel_then(deferrable)
459
+ if channel.detached? || channel.failed?
460
+ deferrable.fail Ably::Exceptions::InvalidState.new("Operation is not allowed when channel is in #{channel.state}", 400, Ably::Exceptions::Codes::UNABLE_TO_ENTER_PRESENCE_CHANNEL_INVALID_CHANNEL_STATE)
461
+ else
462
+ channel.unsafe_once(:attached, :detached, :failed) do |channel_state_change|
463
+ if channel_state_change.current == :attached
464
+ yield
465
+ else
466
+ deferrable.fail Ably::Exceptions::InvalidState.new("Operation failed as channel transitioned to #{channel_state_change.current}", 400, Ably::Exceptions::Codes::UNABLE_TO_ENTER_PRESENCE_CHANNEL_INVALID_CHANNEL_STATE)
467
+ end
468
+ end
469
+ channel.attach
470
+ end
471
+ end
472
+
473
+ def implicit_attach
474
+ channel.attach if channel.initialized?
475
+ end
476
+
477
+ def client
478
+ channel.client
479
+ end
480
+
481
+ def connection
482
+ client.connection
483
+ end
484
+
485
+ def rest_presence
486
+ client.rest_client.channel(channel.name).presence
487
+ end
488
+
489
+ # Force subscriptions to match valid PresenceMessage actions
490
+ def message_emitter_subscriptions_coerce_message_key(name)
491
+ Ably::Models::PresenceMessage.ACTION(name)
492
+ end
493
+
494
+ def create_deferrable
495
+ Ably::Util::SafeDeferrable.new(logger)
496
+ end
497
+ end
498
+ end
499
+
500
+ require 'ably/realtime/presence/presence_manager'
501
+ require 'ably/realtime/presence/members_map'
502
+ require 'ably/realtime/presence/presence_state_machine'
@@ -0,0 +1,75 @@
1
+ require 'ably/realtime/push/device_registrations'
2
+ require 'ably/realtime/push/channel_subscriptions'
3
+
4
+ module Ably::Realtime
5
+ class Push
6
+ # Class providing push notification administrative functionality
7
+ # for registering devices and attaching to channels etc.
8
+ #
9
+ class Admin
10
+ include Ably::Modules::AsyncWrapper
11
+ include Ably::Modules::Conversions
12
+
13
+ # @api private
14
+ attr_reader :client
15
+
16
+ # @api private
17
+ attr_reader :push
18
+
19
+ def initialize(push)
20
+ @push = push
21
+ @client = push.client
22
+ end
23
+
24
+ # Sends a push notification directly to a device, or a group of devices sharing the same clientId.
25
+ #
26
+ # (see Ably::Rest::Push#publish)
27
+ #
28
+ # @spec RSH1a
29
+ #
30
+ # @yield Block is invoked upon successful publish of the message
31
+ #
32
+ # @return [Ably::Util::SafeDeferrable]
33
+ #
34
+ def publish(recipient, data, &callback)
35
+ raise ArgumentError, "Expecting a Hash object for recipient, got #{recipient.class}" unless recipient.kind_of?(Hash)
36
+ raise ArgumentError, "Recipient data is empty. You must provide recipient details" if recipient.empty?
37
+ raise ArgumentError, "Expecting a Hash object for data, got #{data.class}" unless data.kind_of?(Hash)
38
+ raise ArgumentError, "Push data field is empty. You must provide attributes for the push notification" if data.empty?
39
+
40
+ async_wrap(callback) do
41
+ rest_push_admin.publish(recipient, data)
42
+ end
43
+ end
44
+
45
+ # A {Ably::Realtime::Push::DeviceRegistrations} object.
46
+ #
47
+ # @spec RSH1b
48
+ #
49
+ # @return [Ably::Realtime::Push::DeviceRegistrations]
50
+ #
51
+ def device_registrations
52
+ @device_registrations ||= DeviceRegistrations.new(self)
53
+ end
54
+
55
+ # A {Ably::Realtime::Push::ChannelSubscriptions} object.
56
+ #
57
+ # @spec RSH1c
58
+ #
59
+ # @return [Ably::Realtime::Push::ChannelSubscriptions]
60
+ #
61
+ def channel_subscriptions
62
+ @channel_subscriptions ||= ChannelSubscriptions.new(self)
63
+ end
64
+
65
+ private
66
+ def rest_push_admin
67
+ client.rest_client.push.admin
68
+ end
69
+
70
+ def logger
71
+ client.logger
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,108 @@
1
+ module Ably::Realtime
2
+ class Push
3
+ # Manage push notification channel subscriptions for devices or clients
4
+ class ChannelSubscriptions
5
+ include Ably::Modules::Conversions
6
+ include Ably::Modules::AsyncWrapper
7
+
8
+ # @api private
9
+ attr_reader :client
10
+
11
+ # @api private
12
+ attr_reader :admin
13
+
14
+ def initialize(admin)
15
+ @admin = admin
16
+ @client = admin.client
17
+ end
18
+
19
+ # (see Ably::Rest::Push::ChannelSubscriptions#list)
20
+ #
21
+ # @yield Block is invoked when request succeeds
22
+ # @return [Ably::Util::SafeDeferrable]
23
+ #
24
+ def list(params, &callback)
25
+ raise ArgumentError, "params must be a Hash" unless params.kind_of?(Hash)
26
+
27
+ if (IdiomaticRubyWrapper(params).keys & [:channel, :client_id, :device_id]).length == 0
28
+ raise ArgumentError, "at least one channel, client_id or device_id filter param must be provided"
29
+ end
30
+
31
+ async_wrap(callback) do
32
+ rest_channel_subscriptions.list(params.merge(async_blocking_operations: true))
33
+ end
34
+ end
35
+
36
+ # (see Ably::Rest::Push::ChannelSubscriptions#list_channels)
37
+ #
38
+ # @yield Block is invoked when request succeeds
39
+ # @return [Ably::Util::SafeDeferrable]
40
+ #
41
+ def list_channels(params = {}, &callback)
42
+ params = {} if params.nil?
43
+ raise ArgumentError, "params must be a Hash" unless params.kind_of?(Hash)
44
+
45
+ async_wrap(callback) do
46
+ rest_channel_subscriptions.list_channels(params.merge(async_blocking_operations: true))
47
+ end
48
+ end
49
+
50
+ # (see Ably::Rest::Push::ChannelSubscriptions#save)
51
+ #
52
+ # @yield Block is invoked when request succeeds
53
+ # @return [Ably::Util::SafeDeferrable]
54
+ #
55
+ def save(push_channel_subscription, &callback)
56
+ push_channel_subscription_object = PushChannelSubscription(push_channel_subscription)
57
+ raise ArgumentError, "Channel is required yet is empty" if push_channel_subscription_object.channel.to_s.empty?
58
+
59
+ async_wrap(callback) do
60
+ rest_channel_subscriptions.save(push_channel_subscription)
61
+ end
62
+ end
63
+
64
+ # (see Ably::Rest::Push::ChannelSubscriptions#remove)
65
+ #
66
+ # @yield Block is invoked when request succeeds
67
+ # @return [Ably::Util::SafeDeferrable]
68
+ #
69
+ def remove(push_channel_subscription, &callback)
70
+ push_channel_subscription_object = PushChannelSubscription(push_channel_subscription)
71
+ raise ArgumentError, "Channel is required yet is empty" if push_channel_subscription_object.channel.to_s.empty?
72
+ if push_channel_subscription_object.client_id.to_s.empty? && push_channel_subscription_object.device_id.to_s.empty?
73
+ raise ArgumentError, "Either client_id or device_id must be present"
74
+ end
75
+
76
+ async_wrap(callback) do
77
+ rest_channel_subscriptions.remove(push_channel_subscription)
78
+ end
79
+ end
80
+
81
+ # (see Ably::Rest::Push::ChannelSubscriptions#remove_where)
82
+ #
83
+ # @yield Block is invoked when request succeeds
84
+ # @return [Ably::Util::SafeDeferrable]
85
+ #
86
+ def remove_where(params, &callback)
87
+ raise ArgumentError, "params must be a Hash" unless params.kind_of?(Hash)
88
+
89
+ if (IdiomaticRubyWrapper(params).keys & [:channel, :client_id, :device_id]).length == 0
90
+ raise ArgumentError, "at least one channel, client_id or device_id filter param must be provided"
91
+ end
92
+
93
+ async_wrap(callback) do
94
+ rest_channel_subscriptions.remove_where(params)
95
+ end
96
+ end
97
+
98
+ private
99
+ def rest_channel_subscriptions
100
+ client.rest_client.push.admin.channel_subscriptions
101
+ end
102
+
103
+ def logger
104
+ client.logger
105
+ end
106
+ end
107
+ end
108
+ end