ably 1.0.7 → 1.1.4.rc
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.editorconfig +14 -0
- data/.travis.yml +10 -8
- data/CHANGELOG.md +58 -4
- data/LICENSE +1 -3
- data/README.md +9 -5
- data/Rakefile +32 -0
- data/SPEC.md +920 -565
- data/ably.gemspec +16 -11
- data/lib/ably/auth.rb +28 -2
- data/lib/ably/exceptions.rb +10 -4
- data/lib/ably/logger.rb +7 -1
- data/lib/ably/models/channel_state_change.rb +1 -1
- data/lib/ably/models/connection_state_change.rb +1 -1
- data/lib/ably/models/device_details.rb +87 -0
- data/lib/ably/models/device_push_details.rb +86 -0
- data/lib/ably/models/error_info.rb +23 -2
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -4
- data/lib/ably/models/protocol_message.rb +32 -2
- data/lib/ably/models/push_channel_subscription.rb +89 -0
- data/lib/ably/modules/conversions.rb +1 -1
- data/lib/ably/modules/encodeable.rb +1 -1
- data/lib/ably/modules/exception_codes.rb +128 -0
- data/lib/ably/modules/model_common.rb +15 -2
- data/lib/ably/modules/state_machine.rb +2 -2
- data/lib/ably/realtime.rb +1 -0
- data/lib/ably/realtime/auth.rb +1 -1
- data/lib/ably/realtime/channel.rb +24 -102
- data/lib/ably/realtime/channel/channel_manager.rb +2 -6
- data/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
- data/lib/ably/realtime/channel/publisher.rb +74 -0
- data/lib/ably/realtime/channel/push_channel.rb +62 -0
- data/lib/ably/realtime/client.rb +91 -3
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +6 -2
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
- data/lib/ably/realtime/connection.rb +34 -20
- data/lib/ably/realtime/connection/connection_manager.rb +25 -9
- data/lib/ably/realtime/connection/websocket_transport.rb +1 -1
- data/lib/ably/realtime/presence.rb +4 -4
- data/lib/ably/realtime/presence/members_map.rb +3 -3
- data/lib/ably/realtime/push.rb +40 -0
- data/lib/ably/realtime/push/admin.rb +61 -0
- data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
- data/lib/ably/realtime/push/device_registrations.rb +105 -0
- data/lib/ably/rest.rb +1 -0
- data/lib/ably/rest/channel.rb +53 -17
- data/lib/ably/rest/channel/push_channel.rb +62 -0
- data/lib/ably/rest/client.rb +161 -35
- data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -1
- data/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
- data/lib/ably/rest/presence.rb +1 -0
- data/lib/ably/rest/push.rb +42 -0
- data/lib/ably/rest/push/admin.rb +54 -0
- data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
- data/lib/ably/rest/push/device_registrations.rb +103 -0
- data/lib/ably/version.rb +7 -2
- data/spec/acceptance/realtime/auth_spec.rb +22 -21
- data/spec/acceptance/realtime/channel_history_spec.rb +26 -20
- data/spec/acceptance/realtime/channel_spec.rb +177 -59
- data/spec/acceptance/realtime/client_spec.rb +153 -0
- data/spec/acceptance/realtime/connection_failures_spec.rb +72 -6
- data/spec/acceptance/realtime/connection_spec.rb +129 -18
- data/spec/acceptance/realtime/message_spec.rb +36 -34
- data/spec/acceptance/realtime/presence_spec.rb +201 -167
- data/spec/acceptance/realtime/push_admin_spec.rb +736 -0
- data/spec/acceptance/realtime/push_spec.rb +27 -0
- data/spec/acceptance/rest/auth_spec.rb +4 -3
- data/spec/acceptance/rest/base_spec.rb +2 -2
- data/spec/acceptance/rest/channel_spec.rb +79 -4
- data/spec/acceptance/rest/channels_spec.rb +6 -0
- data/spec/acceptance/rest/client_spec.rb +129 -10
- data/spec/acceptance/rest/message_spec.rb +158 -6
- data/spec/acceptance/rest/push_admin_spec.rb +952 -0
- data/spec/acceptance/rest/push_spec.rb +25 -0
- data/spec/acceptance/rest/time_spec.rb +1 -1
- data/spec/run_parallel_tests +33 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/support/debug_failure_helper.rb +9 -5
- data/spec/support/test_app.rb +2 -2
- data/spec/unit/logger_spec.rb +10 -3
- data/spec/unit/models/device_details_spec.rb +102 -0
- data/spec/unit/models/device_push_details_spec.rb +101 -0
- data/spec/unit/models/error_info_spec.rb +51 -3
- data/spec/unit/models/message_spec.rb +17 -2
- data/spec/unit/models/presence_message_spec.rb +1 -1
- data/spec/unit/models/push_channel_subscription_spec.rb +86 -0
- data/spec/unit/modules/enum_spec.rb +1 -1
- data/spec/unit/realtime/client_spec.rb +13 -1
- data/spec/unit/realtime/connection_spec.rb +1 -1
- data/spec/unit/realtime/push_channel_spec.rb +36 -0
- data/spec/unit/rest/channel_spec.rb +8 -1
- data/spec/unit/rest/client_spec.rb +30 -0
- data/spec/unit/rest/push_channel_spec.rb +36 -0
- metadata +94 -31
@@ -43,25 +43,38 @@ module Ably::Realtime
|
|
43
43
|
end
|
44
44
|
|
45
45
|
unless client.auth.authentication_security_requirements_met?
|
46
|
-
connection.transition_state_machine :failed, reason: Ably::Exceptions::InsecureRequest.new('Cannot use Basic Auth over non-TLS connections', 401,
|
46
|
+
connection.transition_state_machine :failed, reason: Ably::Exceptions::InsecureRequest.new('Cannot use Basic Auth over non-TLS connections', 401, Ably::Exceptions::Codes::INVALID_USE_OF_BASIC_AUTH_OVER_NONTLS_TRANSPORT)
|
47
47
|
return
|
48
48
|
end
|
49
49
|
|
50
50
|
logger.debug { 'ConnectionManager: Opening a websocket transport connection' }
|
51
51
|
|
52
|
+
# The socket attempt can fail at the same time as a timer firing so ensure
|
53
|
+
# only one outcome is processed from this setup attempt
|
54
|
+
setup_attempt_status = {}
|
55
|
+
setup_failed = lambda do
|
56
|
+
return true if setup_attempt_status[:failed]
|
57
|
+
setup_attempt_status[:failed] = true
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
52
61
|
connection.create_websocket_transport.tap do |socket_deferrable|
|
53
62
|
socket_deferrable.callback do |websocket_transport|
|
54
63
|
subscribe_to_transport_events websocket_transport
|
55
64
|
yield websocket_transport if block_given?
|
56
65
|
end
|
57
66
|
socket_deferrable.errback do |error|
|
67
|
+
next if setup_failed.call
|
58
68
|
connection_opening_failed error
|
59
69
|
end
|
60
70
|
end
|
61
71
|
|
72
|
+
# The connection request timeout must be marginally higher than the REST request timeout to ensure
|
73
|
+
# any HTTP auth request failure due to timeout triggers before the connection timer kicks in
|
62
74
|
logger.debug { "ConnectionManager: Setting up automatic connection timeout timer for #{realtime_request_timeout}s" }
|
63
75
|
create_timeout_timer_whilst_in_state(:connecting, realtime_request_timeout) do
|
64
|
-
|
76
|
+
next if setup_failed.call
|
77
|
+
connection_opening_failed Ably::Exceptions::ConnectionTimeout.new("Connection to Ably timed out after #{realtime_request_timeout}s", nil, Ably::Exceptions::Codes::CONNECTION_TIMED_OUT)
|
65
78
|
end
|
66
79
|
end
|
67
80
|
|
@@ -72,7 +85,7 @@ module Ably::Realtime
|
|
72
85
|
if error.kind_of?(Ably::Exceptions::BaseAblyException)
|
73
86
|
# Authentication errors that indicate the authentication failure is terminal should move to the failed state
|
74
87
|
if ([401, 403].include?(error.status) && !RESOLVABLE_ERROR_CODES.fetch(:token_expired).include?(error.code)) ||
|
75
|
-
(error.code == Ably::Exceptions::INVALID_CLIENT_ID)
|
88
|
+
(error.code == Ably::Exceptions::Codes::INVALID_CLIENT_ID)
|
76
89
|
connection.transition_state_machine :failed, reason: error
|
77
90
|
return
|
78
91
|
end
|
@@ -80,7 +93,12 @@ module Ably::Realtime
|
|
80
93
|
|
81
94
|
logger.warn { "ConnectionManager: Connection to #{connection.current_host}:#{connection.port} failed; #{error.message}" }
|
82
95
|
next_state = get_next_retry_state_info
|
83
|
-
|
96
|
+
|
97
|
+
if connection.state == next_state.fetch(:state)
|
98
|
+
logger.error { "ConnectionManager: Skipping next retry state after connection opening failed as already in state #{next_state}\n#{caller[0..20].join("\n")}" }
|
99
|
+
else
|
100
|
+
connection.transition_state_machine next_state.fetch(:state), retry_in: next_state.fetch(:pause), reason: Ably::Exceptions::ConnectionError.new("Connection failed: #{error.message}", nil, Ably::Exceptions::Codes::CONNECTION_FAILED, error)
|
101
|
+
end
|
84
102
|
end
|
85
103
|
|
86
104
|
# Called whenever a new connection is made
|
@@ -100,13 +118,11 @@ module Ably::Realtime
|
|
100
118
|
resend_pending_message_ack_queue
|
101
119
|
else
|
102
120
|
logger.debug { "ConnectionManager: Connection was not resumed, old connection ID #{connection.id} has been updated with new connection ID #{protocol_message.connection_id} and key #{protocol_message.connection_key}" }
|
103
|
-
connection.reset_client_serial
|
104
121
|
nack_messages_on_all_channels protocol_message.error
|
105
122
|
force_reattach_on_channels protocol_message.error
|
106
123
|
end
|
107
124
|
else
|
108
125
|
logger.debug { "ConnectionManager: New connection created with ID #{protocol_message.connection_id} and key #{protocol_message.connection_key}" }
|
109
|
-
connection.reset_client_serial
|
110
126
|
end
|
111
127
|
|
112
128
|
reattach_suspended_channels protocol_message.error
|
@@ -319,7 +335,7 @@ module Ably::Realtime
|
|
319
335
|
@liveness_timer = EventMachine::Timer.new(connection.heartbeat_interval + 0.1) do
|
320
336
|
if connection.connected? && (connection.time_since_connection_confirmed_alive? >= connection.heartbeat_interval)
|
321
337
|
msg = "No activity seen from realtime in #{connection.heartbeat_interval}; assuming connection has dropped";
|
322
|
-
error = Ably::Exceptions::ConnectionTimeout.new(msg,
|
338
|
+
error = Ably::Exceptions::ConnectionTimeout.new(msg, Ably::Exceptions::Codes::DISCONNECTED, 408)
|
323
339
|
connection.transition_state_machine! :disconnected, reason: error
|
324
340
|
end
|
325
341
|
end
|
@@ -492,9 +508,9 @@ module Ably::Realtime
|
|
492
508
|
connection.transition_state_machine :closed
|
493
509
|
elsif !connection.closed? && !connection.disconnected? && !connection.failed? && !connection.suspended?
|
494
510
|
exception = if reason
|
495
|
-
Ably::Exceptions::TransportClosed.new(reason, nil,
|
511
|
+
Ably::Exceptions::TransportClosed.new(reason, nil, Ably::Exceptions::Codes::DISCONNECTED)
|
496
512
|
else
|
497
|
-
Ably::Exceptions::TransportClosed.new('Transport disconnected unexpectedly', nil,
|
513
|
+
Ably::Exceptions::TransportClosed.new('Transport disconnected unexpectedly', nil, Ably::Exceptions::Codes::DISCONNECTED)
|
498
514
|
end
|
499
515
|
next_state = get_next_retry_state_info
|
500
516
|
connection.transition_state_machine next_state.fetch(:state), retry_in: next_state.fetch(:pause), reason: exception
|
@@ -157,7 +157,7 @@ module Ably::Realtime
|
|
157
157
|
logger.debug { "WebsocketTransport: Prot msg recv <=: #{action_name} - #{event_data}" }
|
158
158
|
|
159
159
|
if protocol_message.invalid?
|
160
|
-
error = Ably::Exceptions::ProtocolError.new("Invalid Protocol Message received: #{event_data}\nConnection moving to the failed state as the protocol is invalid and unsupported", 400,
|
160
|
+
error = Ably::Exceptions::ProtocolError.new("Invalid Protocol Message received: #{event_data}\nConnection moving to the failed state as the protocol is invalid and unsupported", 400, Ably::Exceptions::Codes::PROTOCOL_ERROR)
|
161
161
|
logger.fatal { "WebsocketTransport: #{error.message}" }
|
162
162
|
failed_protocol_message = Ably::Models::ProtocolMessage.new(
|
163
163
|
action: Ably::Models::ProtocolMessage::ACTION.Error,
|
@@ -233,7 +233,7 @@ module Ably::Realtime
|
|
233
233
|
deferrable.fail Ably::Exceptions::InvalidState.new(
|
234
234
|
'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',
|
235
235
|
nil,
|
236
|
-
|
236
|
+
Ably::Exceptions::Codes::PRESENCE_STATE_IS_OUT_OF_SYNC
|
237
237
|
)
|
238
238
|
end
|
239
239
|
return deferrable
|
@@ -330,7 +330,7 @@ module Ably::Realtime
|
|
330
330
|
def send_presence_protocol_message(presence_action, client_id, data)
|
331
331
|
presence_message = create_presence_message(presence_action, client_id, data)
|
332
332
|
unless presence_message.client_id
|
333
|
-
raise Ably::Exceptions::Standard.new('Unable to enter create presence message without a client_id', 400,
|
333
|
+
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)
|
334
334
|
end
|
335
335
|
|
336
336
|
protocol_message = {
|
@@ -437,13 +437,13 @@ module Ably::Realtime
|
|
437
437
|
|
438
438
|
def attach_channel_then(deferrable)
|
439
439
|
if channel.detached? || channel.failed?
|
440
|
-
deferrable.fail Ably::Exceptions::InvalidState.new("Operation is not allowed when channel is in #{channel.state}", 400,
|
440
|
+
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)
|
441
441
|
else
|
442
442
|
channel.unsafe_once(:attached, :detached, :failed) do |channel_state_change|
|
443
443
|
if channel_state_change.current == :attached
|
444
444
|
yield
|
445
445
|
else
|
446
|
-
deferrable.fail Ably::Exceptions::InvalidState.new("Operation failed as channel transitioned to #{channel_state_change.current}", 400,
|
446
|
+
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)
|
447
447
|
end
|
448
448
|
end
|
449
449
|
channel.attach
|
@@ -275,7 +275,7 @@ module Ably::Realtime
|
|
275
275
|
presence_message_client_id = presence_message.client_id || client.auth.client_id
|
276
276
|
re_enter_error = Ably::Models::ErrorInfo.new(
|
277
277
|
message: "unable to automatically re-enter presence channel for client_id '#{presence_message_client_id}'. Source error code #{error.code} and message '#{error.message}'",
|
278
|
-
code:
|
278
|
+
code: Ably::Exceptions::Codes::UNABLE_TO_AUTOMATICALLY_REENTER_PRESENCE_CHANNEL
|
279
279
|
)
|
280
280
|
channel.emit :update, Ably::Models::ChannelStateChange.new(
|
281
281
|
current: channel.state,
|
@@ -312,14 +312,14 @@ module Ably::Realtime
|
|
312
312
|
when Ably::Models::PresenceMessage::ACTION.Leave
|
313
313
|
remove_presence_member presence_message
|
314
314
|
else
|
315
|
-
Ably::Exceptions::ProtocolError.new("Protocol error, unknown presence action #{presence_message.action}", 400,
|
315
|
+
Ably::Exceptions::ProtocolError.new("Protocol error, unknown presence action #{presence_message.action}", 400, Ably::Exceptions::Codes::PROTOCOL_ERROR)
|
316
316
|
end
|
317
317
|
end
|
318
318
|
|
319
319
|
def ensure_presence_message_is_valid(presence_message)
|
320
320
|
return true if presence_message.connection_id
|
321
321
|
|
322
|
-
error = Ably::Exceptions::ProtocolError.new("Protocol error, presence message is missing connectionId", 400,
|
322
|
+
error = Ably::Exceptions::ProtocolError.new("Protocol error, presence message is missing connectionId", 400, Ably::Exceptions::Codes::PROTOCOL_ERROR)
|
323
323
|
logger.error { "PresenceMap: On channel '#{channel.name}' error: #{error}" }
|
324
324
|
end
|
325
325
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'ably/realtime/push/admin'
|
2
|
+
|
3
|
+
module Ably
|
4
|
+
module Realtime
|
5
|
+
# Class providing push notification functionality
|
6
|
+
class Push
|
7
|
+
# @private
|
8
|
+
attr_reader :client
|
9
|
+
|
10
|
+
def initialize(client)
|
11
|
+
@client = client
|
12
|
+
end
|
13
|
+
|
14
|
+
# Admin features for push notifications like managing devices and channel subscriptions
|
15
|
+
# @return [Ably::Realtime::Push::Admin]
|
16
|
+
def admin
|
17
|
+
@admin ||= Admin.new(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Activate this device for push notifications by registering with the push transport such as GCM/APNS
|
21
|
+
#
|
22
|
+
# @note This is unsupported in the Ruby library
|
23
|
+
def activate(*arg)
|
24
|
+
raise_unsupported
|
25
|
+
end
|
26
|
+
|
27
|
+
# Deactivate this device for push notifications by removing the registration with the push transport such as GCM/APNS
|
28
|
+
#
|
29
|
+
# @note This is unsupported in the Ruby library
|
30
|
+
def deactivate(*arg)
|
31
|
+
raise_unsupported
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def raise_unsupported
|
36
|
+
raise Ably::Exceptions::PushNotificationsNotSupported, 'This device does not support receiving or subscribing to push notifications. All PushChannel methods are unavailable'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,61 @@
|
|
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
|
+
class Admin
|
9
|
+
include Ably::Modules::AsyncWrapper
|
10
|
+
include Ably::Modules::Conversions
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
attr_reader :client
|
14
|
+
|
15
|
+
# @api private
|
16
|
+
attr_reader :push
|
17
|
+
|
18
|
+
def initialize(push)
|
19
|
+
@push = push
|
20
|
+
@client = push.client
|
21
|
+
end
|
22
|
+
|
23
|
+
# (see Ably::Rest::Push#publish)
|
24
|
+
#
|
25
|
+
# @yield Block is invoked upon successful publish of the message
|
26
|
+
# @return [Ably::Util::SafeDeferrable]
|
27
|
+
#
|
28
|
+
def publish(recipient, data, &callback)
|
29
|
+
raise ArgumentError, "Expecting a Hash object for recipient, got #{recipient.class}" unless recipient.kind_of?(Hash)
|
30
|
+
raise ArgumentError, "Recipient data is empty. You must provide recipient details" if recipient.empty?
|
31
|
+
raise ArgumentError, "Expecting a Hash object for data, got #{data.class}" unless data.kind_of?(Hash)
|
32
|
+
raise ArgumentError, "Push data field is empty. You must provide attributes for the push notification" if data.empty?
|
33
|
+
|
34
|
+
async_wrap(callback) do
|
35
|
+
rest_push_admin.publish(recipient, data)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Manage device registrations
|
40
|
+
# @return [Ably::Realtime::Push::DeviceRegistrations]
|
41
|
+
def device_registrations
|
42
|
+
@device_registrations ||= DeviceRegistrations.new(self)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Manage channel subscriptions for devices or clients
|
46
|
+
# @return [Ably::Realtime::Push::ChannelSubscriptions]
|
47
|
+
def channel_subscriptions
|
48
|
+
@channel_subscriptions ||= ChannelSubscriptions.new(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def rest_push_admin
|
53
|
+
client.rest_client.push.admin
|
54
|
+
end
|
55
|
+
|
56
|
+
def logger
|
57
|
+
client.logger
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
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
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Ably::Realtime
|
2
|
+
class Push
|
3
|
+
# Manage device registrations for push notifications
|
4
|
+
class DeviceRegistrations
|
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::DeviceRegistrations#get)
|
20
|
+
#
|
21
|
+
# @yield Block is invoked when request succeeds
|
22
|
+
# @return [Ably::Util::SafeDeferrable]
|
23
|
+
#
|
24
|
+
def get(device_id, &callback)
|
25
|
+
device_id = device_id.id if device_id.kind_of?(Ably::Models::DeviceDetails)
|
26
|
+
raise ArgumentError, "device_id must be a string or DeviceDetails object" unless device_id.kind_of?(String)
|
27
|
+
|
28
|
+
async_wrap(callback) do
|
29
|
+
rest_device_registrations.get(device_id)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# (see Ably::Rest::Push::DeviceRegistrations#list)
|
34
|
+
#
|
35
|
+
# @yield Block is invoked when request succeeds
|
36
|
+
# @return [Ably::Util::SafeDeferrable]
|
37
|
+
#
|
38
|
+
def list(params = {}, &callback)
|
39
|
+
params = {} if params.nil?
|
40
|
+
raise ArgumentError, "params must be a Hash" unless params.kind_of?(Hash)
|
41
|
+
raise ArgumentError, "device_id filter cannot be specified alongside a client_id filter. Use one or the other" if params[:client_id] && params[:device_id]
|
42
|
+
|
43
|
+
async_wrap(callback) do
|
44
|
+
rest_device_registrations.list(params.merge(async_blocking_operations: true))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# (see Ably::Rest::Push::DeviceRegistrations#save)
|
49
|
+
#
|
50
|
+
# @yield Block is invoked when request succeeds
|
51
|
+
# @return [Ably::Util::SafeDeferrable]
|
52
|
+
#
|
53
|
+
def save(device, &callback)
|
54
|
+
device_details = DeviceDetails(device)
|
55
|
+
raise ArgumentError, "Device ID is required yet is empty" if device_details.id.nil? || device_details == ''
|
56
|
+
|
57
|
+
async_wrap(callback) do
|
58
|
+
rest_device_registrations.save(device_details)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# (see Ably::Rest::Push::DeviceRegistrations#remove)
|
63
|
+
#
|
64
|
+
# @yield Block is invoked when request succeeds
|
65
|
+
# @return [Ably::Util::SafeDeferrable]
|
66
|
+
#
|
67
|
+
def remove(device_id, &callback)
|
68
|
+
device_id = device_id.id if device_id.kind_of?(Ably::Models::DeviceDetails)
|
69
|
+
raise ArgumentError, "device_id must be a string or DeviceDetails object" unless device_id.kind_of?(String)
|
70
|
+
|
71
|
+
async_wrap(callback) do
|
72
|
+
rest_device_registrations.remove(device_id)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# (see Ably::Rest::Push::DeviceRegistrations#remove_where)
|
77
|
+
#
|
78
|
+
# @yield Block is invoked when request succeeds
|
79
|
+
# @return [Ably::Util::SafeDeferrable]
|
80
|
+
#
|
81
|
+
def remove_where(params = {}, &callback)
|
82
|
+
filter = if params.kind_of?(Ably::Models::DeviceDetails)
|
83
|
+
{ 'deviceId' => params.id }
|
84
|
+
else
|
85
|
+
raise ArgumentError, "params must be a Hash" unless params.kind_of?(Hash)
|
86
|
+
raise ArgumentError, "device_id filter cannot be specified alongside a client_id filter. Use one or the other" if params[:client_id] && params[:device_id]
|
87
|
+
IdiomaticRubyWrapper(params).as_json
|
88
|
+
end
|
89
|
+
|
90
|
+
async_wrap(callback) do
|
91
|
+
rest_device_registrations.remove_where(filter)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
def rest_device_registrations
|
97
|
+
client.rest_client.push.admin.device_registrations
|
98
|
+
end
|
99
|
+
|
100
|
+
def logger
|
101
|
+
client.logger
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|