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.
- checksums.yaml +7 -0
- data/lib/ably/agent.rb +8 -0
- data/lib/ably/auth.rb +791 -0
- data/lib/ably/exceptions.rb +169 -0
- data/lib/ably/logger.rb +119 -0
- data/lib/ably/models/auth_details.rb +45 -0
- data/lib/ably/models/channel_details.rb +69 -0
- data/lib/ably/models/channel_metrics.rb +107 -0
- data/lib/ably/models/channel_occupancy.rb +50 -0
- data/lib/ably/models/channel_options.rb +120 -0
- data/lib/ably/models/channel_state_change.rb +84 -0
- data/lib/ably/models/channel_status.rb +63 -0
- data/lib/ably/models/cipher_params.rb +137 -0
- data/lib/ably/models/connection_details.rb +149 -0
- data/lib/ably/models/connection_state_change.rb +83 -0
- data/lib/ably/models/delta_extras.rb +28 -0
- data/lib/ably/models/device_details.rb +126 -0
- data/lib/ably/models/device_push_details.rb +94 -0
- data/lib/ably/models/error_info.rb +104 -0
- data/lib/ably/models/http_paginated_response.rb +112 -0
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +235 -0
- data/lib/ably/models/message.rb +223 -0
- data/lib/ably/models/message_encoders/base.rb +135 -0
- data/lib/ably/models/message_encoders/base64.rb +40 -0
- data/lib/ably/models/message_encoders/cipher.rb +84 -0
- data/lib/ably/models/message_encoders/json.rb +34 -0
- data/lib/ably/models/message_encoders/utf8.rb +26 -0
- data/lib/ably/models/nil_logger.rb +20 -0
- data/lib/ably/models/paginated_result.rb +201 -0
- data/lib/ably/models/presence_message.rb +244 -0
- data/lib/ably/models/protocol_message.rb +288 -0
- data/lib/ably/models/push_channel_subscription.rb +114 -0
- data/lib/ably/models/stats.rb +242 -0
- data/lib/ably/models/stats_types.rb +107 -0
- data/lib/ably/models/token_details.rb +148 -0
- data/lib/ably/models/token_request.rb +180 -0
- data/lib/ably/modules/ably.rb +26 -0
- data/lib/ably/modules/async_wrapper.rb +69 -0
- data/lib/ably/modules/channels_collection.rb +105 -0
- data/lib/ably/modules/conversions.rb +153 -0
- data/lib/ably/modules/encodeable.rb +111 -0
- data/lib/ably/modules/enum.rb +230 -0
- data/lib/ably/modules/event_emitter.rb +200 -0
- data/lib/ably/modules/event_machine_helpers.rb +26 -0
- data/lib/ably/modules/exception_codes.rb +128 -0
- data/lib/ably/modules/http_helpers.rb +41 -0
- data/lib/ably/modules/message_emitter.rb +78 -0
- data/lib/ably/modules/message_pack.rb +14 -0
- data/lib/ably/modules/model_common.rb +84 -0
- data/lib/ably/modules/safe_deferrable.rb +83 -0
- data/lib/ably/modules/safe_yield.rb +41 -0
- data/lib/ably/modules/state_emitter.rb +181 -0
- data/lib/ably/modules/state_machine.rb +59 -0
- data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/ably/modules/uses_state_machine.rb +111 -0
- data/lib/ably/realtime/auth.rb +283 -0
- data/lib/ably/realtime/channel/channel_manager.rb +272 -0
- data/lib/ably/realtime/channel/channel_properties.rb +40 -0
- data/lib/ably/realtime/channel/channel_state_machine.rb +104 -0
- data/lib/ably/realtime/channel/publisher.rb +82 -0
- data/lib/ably/realtime/channel/push_channel.rb +69 -0
- data/lib/ably/realtime/channel.rb +440 -0
- data/lib/ably/realtime/channels.rb +71 -0
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +225 -0
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +87 -0
- data/lib/ably/realtime/client.rb +363 -0
- data/lib/ably/realtime/connection/connection_manager.rb +594 -0
- data/lib/ably/realtime/connection/connection_state_machine.rb +144 -0
- data/lib/ably/realtime/connection/websocket_transport.rb +287 -0
- data/lib/ably/realtime/connection.rb +738 -0
- data/lib/ably/realtime/models/nil_channel.rb +30 -0
- data/lib/ably/realtime/presence/members_map.rb +395 -0
- data/lib/ably/realtime/presence/presence_manager.rb +78 -0
- data/lib/ably/realtime/presence/presence_state_machine.rb +57 -0
- data/lib/ably/realtime/presence.rb +502 -0
- data/lib/ably/realtime/push/admin.rb +75 -0
- data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
- data/lib/ably/realtime/push/device_registrations.rb +105 -0
- data/lib/ably/realtime/push.rb +25 -0
- data/lib/ably/realtime/recovery_key_context.rb +36 -0
- data/lib/ably/realtime.rb +60 -0
- data/lib/ably/rest/channel/push_channel.rb +59 -0
- data/lib/ably/rest/channel.rb +194 -0
- data/lib/ably/rest/channels.rb +47 -0
- data/lib/ably/rest/client.rb +727 -0
- data/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/ably/rest/middleware/exceptions.rb +53 -0
- data/lib/ably/rest/middleware/external_exceptions.rb +24 -0
- data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +22 -0
- data/lib/ably/rest/middleware/logger.rb +58 -0
- data/lib/ably/rest/middleware/parse_json.rb +27 -0
- data/lib/ably/rest/middleware/parse_message_pack.rb +43 -0
- data/lib/ably/rest/presence.rb +113 -0
- data/lib/ably/rest/push/admin.rb +58 -0
- data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
- data/lib/ably/rest/push/device_registrations.rb +114 -0
- data/lib/ably/rest/push.rb +25 -0
- data/lib/ably/rest.rb +40 -0
- data/lib/ably/util/ably_extensions.rb +29 -0
- data/lib/ably/util/crypto.rb +134 -0
- data/lib/ably/util/pub_sub.rb +45 -0
- data/lib/ably/util/safe_deferrable.rb +44 -0
- data/lib/ably/version.rb +7 -0
- data/lib/ably.rb +16 -0
- 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
|