ably-rest 1.0.6 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +14 -14
- data/lib/submodules/ably-ruby/.editorconfig +14 -0
- data/lib/submodules/ably-ruby/.travis.yml +4 -4
- data/lib/submodules/ably-ruby/CHANGELOG.md +43 -2
- data/lib/submodules/ably-ruby/README.md +3 -2
- data/lib/submodules/ably-ruby/Rakefile +32 -0
- data/lib/submodules/ably-ruby/SPEC.md +1277 -835
- data/lib/submodules/ably-ruby/ably.gemspec +9 -4
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +30 -4
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +8 -2
- data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/device_details.rb +87 -0
- data/lib/submodules/ably-ruby/lib/ably/models/device_push_details.rb +86 -0
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +23 -2
- data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -4
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +32 -2
- data/lib/submodules/ably-ruby/lib/ably/models/push_channel_subscription.rb +89 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/exception_codes.rb +128 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +15 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +24 -102
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +2 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/publisher.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/push_channel.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +87 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +6 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +8 -5
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +7 -7
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +4 -4
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +3 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/push.rb +40 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/push/admin.rb +61 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/push/device_registrations.rb +105 -0
- data/lib/submodules/ably-ruby/lib/ably/rest.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +33 -5
- data/lib/submodules/ably-ruby/lib/ably/rest/channel/push_channel.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +138 -28
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push.rb +42 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push/admin.rb +54 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push/channel_subscriptions.rb +121 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push/device_registrations.rb +103 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +7 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +233 -8
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +166 -51
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +149 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +4 -4
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +19 -17
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +5 -5
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_admin_spec.rb +696 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_spec.rb +27 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +41 -3
- data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +129 -10
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +175 -4
- data/lib/submodules/ably-ruby/spec/acceptance/rest/push_admin_spec.rb +896 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/push_spec.rb +25 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/run_parallel_tests +33 -0
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +10 -3
- data/lib/submodules/ably-ruby/spec/unit/models/device_details_spec.rb +102 -0
- data/lib/submodules/ably-ruby/spec/unit/models/device_push_details_spec.rb +101 -0
- data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +51 -3
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +17 -2
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/models/push_channel_subscription_spec.rb +86 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +12 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/push_channel_spec.rb +36 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +8 -1
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +30 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/push_channel_spec.rb +36 -0
- metadata +29 -4
@@ -0,0 +1,62 @@
|
|
1
|
+
module Ably::Rest
|
2
|
+
class Channel
|
3
|
+
# A push channel used for push notifications
|
4
|
+
# Each PushChannel maps to exactly one Rest Channel
|
5
|
+
#
|
6
|
+
# @!attribute [r] channel
|
7
|
+
# @return [Ably::Rest::Channel] Underlying channel object
|
8
|
+
#
|
9
|
+
class PushChannel
|
10
|
+
attr_reader :channel
|
11
|
+
|
12
|
+
def initialize(channel)
|
13
|
+
raise ArgumentError, "Unsupported channel type '#{channel.class}'" unless channel.kind_of?(Ably::Rest::Channel)
|
14
|
+
@channel = channel
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"<PushChannel: name=#{channel.name}>"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Subscribe local device for push notifications on this channel
|
22
|
+
#
|
23
|
+
# @note This is unsupported in the Ruby library
|
24
|
+
def subscribe_device(*args)
|
25
|
+
raise_unsupported
|
26
|
+
end
|
27
|
+
|
28
|
+
# Subscribe all devices registered to this client's authenticated client_id for push notifications on this channel
|
29
|
+
#
|
30
|
+
# @note This is unsupported in the Ruby library
|
31
|
+
def subscribe_client_id(*args)
|
32
|
+
raise_unsupported
|
33
|
+
end
|
34
|
+
|
35
|
+
# Unsubscribe local device for push notifications on this channel
|
36
|
+
#
|
37
|
+
# @note This is unsupported in the Ruby library
|
38
|
+
def unsubscribe_device(*args)
|
39
|
+
raise_unsupported
|
40
|
+
end
|
41
|
+
|
42
|
+
# Unsubscribe all devices registered to this client's authenticated client_id for push notifications on this channel
|
43
|
+
#
|
44
|
+
# @note This is unsupported in the Ruby library
|
45
|
+
def unsubscribe_client_id(*args)
|
46
|
+
raise_unsupported
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get list of subscriptions on this channel for this device or authenticate client_id
|
50
|
+
#
|
51
|
+
# @note This is unsupported in the Ruby library
|
52
|
+
def get_subscriptions(*args)
|
53
|
+
raise_unsupported
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def raise_unsupported
|
58
|
+
raise Ably::Exceptions::PushNotificationsNotSupported, 'This device does not support receiving or subscribing to push notifications. All PushChannel methods are unavailable'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -30,6 +30,8 @@ module Ably
|
|
30
30
|
max_retry_count: 3
|
31
31
|
}.freeze
|
32
32
|
|
33
|
+
FALLBACK_RETRY_TIMEOUT = 10 * 60
|
34
|
+
|
33
35
|
def_delegators :auth, :client_id, :auth_options
|
34
36
|
|
35
37
|
# Custom environment to use such as 'sandbox' when testing the client library against an alternate Ably environment
|
@@ -83,10 +85,23 @@ module Ably
|
|
83
85
|
# if empty or nil then fallback host functionality is disabled
|
84
86
|
attr_reader :fallback_hosts
|
85
87
|
|
86
|
-
#
|
88
|
+
# Whether the {Client} has to add a random identifier to the path of a request
|
87
89
|
# @return [Boolean]
|
88
90
|
attr_reader :add_request_ids
|
89
91
|
|
92
|
+
# Retries are logged by default to warn and error. When true, retries are logged at info level
|
93
|
+
# @return [Boolean]
|
94
|
+
# @api private
|
95
|
+
attr_reader :log_retries_as_info
|
96
|
+
|
97
|
+
# True when idempotent publishing is enabled for all messages published via REST.
|
98
|
+
# When this feature is enabled, the client library will add a unique ID to every published message (without an ID)
|
99
|
+
# ensuring any failed published attempts (due to failures such as HTTP requests failing mid-flight) that are
|
100
|
+
# automatically retried will not result in duplicate messages being published to the Ably platform.
|
101
|
+
# Note: This is a beta unsupported feature!
|
102
|
+
# @return [Boolean]
|
103
|
+
attr_reader :idempotent_rest_publishing
|
104
|
+
|
90
105
|
# Creates a {Ably::Rest::Client Rest Client} and configures the {Ably::Auth} object for the connection.
|
91
106
|
#
|
92
107
|
# @param [Hash,String] options an options Hash used to configure the client and the authentication, or String with an API key or Token ID
|
@@ -117,6 +132,10 @@ module Ably
|
|
117
132
|
#
|
118
133
|
# @option options [Boolean] :fallback_hosts_use_default (false) When true, forces the user of fallback hosts even if a non-default production endpoint is being used
|
119
134
|
# @option options [Array<String>] :fallback_hosts When an array of fallback hosts are provided, these fallback hosts are always used if a request fails to the primary endpoint. If an empty array is provided, the fallback host functionality is disabled
|
135
|
+
# @option options [Integer] :fallback_retry_timeout (600 seconds) amount of time in seconds a REST client will continue to use a working fallback host when the primary fallback host has previously failed
|
136
|
+
#
|
137
|
+
# @option options [Boolean] :add_request_ids (false) When true, adds a unique request_id to each request sent to Ably servers. This is handy when reporting issues, because you can refer to a specific request.
|
138
|
+
# @option options [Boolean] :idempotent_rest_publishing (false if ver < 1.2) When true, idempotent publishing is enabled for all messages published via REST
|
120
139
|
#
|
121
140
|
# @return [Ably::Rest::Client]
|
122
141
|
#
|
@@ -139,18 +158,21 @@ module Ably
|
|
139
158
|
end
|
140
159
|
end
|
141
160
|
|
142
|
-
@realtime_client
|
143
|
-
@tls
|
144
|
-
@environment
|
145
|
-
@environment
|
146
|
-
@protocol
|
147
|
-
@debug_http
|
148
|
-
@log_level
|
149
|
-
@custom_logger
|
150
|
-
@custom_host
|
151
|
-
@custom_port
|
152
|
-
@custom_tls_port
|
153
|
-
@add_request_ids
|
161
|
+
@realtime_client = options.delete(:realtime_client)
|
162
|
+
@tls = options.delete(:tls) == false ? false : true
|
163
|
+
@environment = options.delete(:environment) # nil is production
|
164
|
+
@environment = nil if [:production, 'production'].include?(@environment)
|
165
|
+
@protocol = options.delete(:protocol) || :msgpack
|
166
|
+
@debug_http = options.delete(:debug_http)
|
167
|
+
@log_level = options.delete(:log_level) || ::Logger::WARN
|
168
|
+
@custom_logger = options.delete(:logger)
|
169
|
+
@custom_host = options.delete(:rest_host)
|
170
|
+
@custom_port = options.delete(:port)
|
171
|
+
@custom_tls_port = options.delete(:tls_port)
|
172
|
+
@add_request_ids = options.delete(:add_request_ids)
|
173
|
+
@log_retries_as_info = options.delete(:log_retries_as_info)
|
174
|
+
@idempotent_rest_publishing = options.delete(:idempotent_rest_publishing) || Ably.major_minor_version_numeric > 1.1
|
175
|
+
|
154
176
|
|
155
177
|
if options[:fallback_hosts_use_default] && options[:fallback_jhosts]
|
156
178
|
raise ArgumentError, "fallback_hosts_use_default cannot be set to trye when fallback_jhosts is also provided"
|
@@ -166,6 +188,10 @@ module Ably
|
|
166
188
|
Ably::FALLBACK_HOSTS
|
167
189
|
end
|
168
190
|
|
191
|
+
options[:fallback_retry_timeout] ||= FALLBACK_RETRY_TIMEOUT
|
192
|
+
|
193
|
+
# Take option keys prefixed with `http_`, remove the http_ and
|
194
|
+
# check if the option exists in HTTP_DEFAULTS. If so, update http_defaults
|
169
195
|
@http_defaults = HTTP_DEFAULTS.dup
|
170
196
|
options.each do |key, val|
|
171
197
|
if http_key = key[/^http_(.+)/, 1]
|
@@ -190,8 +216,12 @@ module Ably
|
|
190
216
|
raise ArgumentError, 'Protocol is invalid. Must be either :msgpack or :json' unless [:msgpack, :json].include?(@protocol)
|
191
217
|
|
192
218
|
token_params = options.delete(:default_token_params) || {}
|
193
|
-
@options
|
194
|
-
|
219
|
+
@options = options
|
220
|
+
init_auth_options = options.select do |key, _|
|
221
|
+
Auth::AUTH_OPTIONS_KEYS.include?(key.to_s)
|
222
|
+
end
|
223
|
+
|
224
|
+
@auth = Auth.new(self, token_params, init_auth_options)
|
195
225
|
@channels = Ably::Rest::Channels.new(self)
|
196
226
|
@encoders = []
|
197
227
|
|
@@ -273,6 +303,24 @@ module Ably
|
|
273
303
|
raw_request(:post, path, params, options)
|
274
304
|
end
|
275
305
|
|
306
|
+
# Perform an HTTP PUT request to the API using configured authentication
|
307
|
+
#
|
308
|
+
# @return [Faraday::Response]
|
309
|
+
#
|
310
|
+
# @api private
|
311
|
+
def put(path, params, options = {})
|
312
|
+
raw_request(:put, path, params, options)
|
313
|
+
end
|
314
|
+
|
315
|
+
# Perform an HTTP DELETE request to the API using configured authentication
|
316
|
+
#
|
317
|
+
# @return [Faraday::Response]
|
318
|
+
#
|
319
|
+
# @api private
|
320
|
+
def delete(path, params, options = {})
|
321
|
+
raw_request(:delete, path, params, options)
|
322
|
+
end
|
323
|
+
|
276
324
|
# Perform an HTTP request to the Ably API
|
277
325
|
# This is a convenience for customers who wish to use bleeding edge REST API functionality
|
278
326
|
# that is either not documented or is not included in the API for our client libraries.
|
@@ -292,14 +340,14 @@ module Ably
|
|
292
340
|
|
293
341
|
response = case method.to_sym
|
294
342
|
when :get
|
295
|
-
|
343
|
+
reauthorize_on_authorization_failure do
|
296
344
|
send_request(method, path, params, headers: headers)
|
297
345
|
end
|
298
346
|
when :post
|
299
347
|
path_with_params = Addressable::URI.new
|
300
348
|
path_with_params.query_values = params || {}
|
301
349
|
query = path_with_params.query
|
302
|
-
|
350
|
+
reauthorize_on_authorization_failure do
|
303
351
|
send_request(method, "#{path}#{"?#{query}" unless query.nil? || query.empty?}", body, headers: headers)
|
304
352
|
end
|
305
353
|
end
|
@@ -321,6 +369,20 @@ module Ably
|
|
321
369
|
Models::HttpPaginatedResponse.new(response, path, self)
|
322
370
|
end
|
323
371
|
|
372
|
+
# The local device detilas
|
373
|
+
# @return [Ably::Models::LocalDevice]
|
374
|
+
#
|
375
|
+
# @note This is unsupported in the Ruby library
|
376
|
+
def device
|
377
|
+
raise Ably::Exceptions::PushNotificationsNotSupported, 'This device does not support receiving or subscribing to push notifications. The local device object is not unavailable'
|
378
|
+
end
|
379
|
+
|
380
|
+
# Push notification object for publishing and managing push notifications
|
381
|
+
# @return [Ably::Rest::Push]
|
382
|
+
def push
|
383
|
+
@push ||= Push.new(self)
|
384
|
+
end
|
385
|
+
|
324
386
|
# @!attribute [r] endpoint
|
325
387
|
# @return [URI::Generic] Default Ably REST endpoint used for all requests
|
326
388
|
def endpoint
|
@@ -389,7 +451,6 @@ module Ably
|
|
389
451
|
def fallback_connection
|
390
452
|
unless defined?(@fallback_connections) && @fallback_connections
|
391
453
|
@fallback_connections = fallback_hosts.shuffle.map { |host| Faraday.new(endpoint_for_host(host).to_s, connection_options) }
|
392
|
-
@fallback_connections << Faraday.new(endpoint.to_s, connection_options) # Try the original host last if all fallbacks have been used
|
393
454
|
end
|
394
455
|
@fallback_index ||= 0
|
395
456
|
|
@@ -420,13 +481,46 @@ module Ably
|
|
420
481
|
end
|
421
482
|
end
|
422
483
|
|
484
|
+
# If the primary host endpoint fails, and a subsequent fallback host succeeds, the fallback
|
485
|
+
# host that succeeded is used for +ClientOption+ +fallback_retry_timeout+ seconds to avoid
|
486
|
+
# retries to known failing hosts for a short period of time.
|
487
|
+
# See https://github.com/ably/docs/pull/554, spec id #RSC15f
|
488
|
+
#
|
489
|
+
# @return [nil, String] Returns nil (falsey) if the primary host is being used, or the currently used host if a fallback host is currently preferred
|
490
|
+
def using_preferred_fallback_host?
|
491
|
+
if preferred_fallback_connection && (preferred_fallback_connection.fetch(:expires_at) > Time.now)
|
492
|
+
preferred_fallback_connection.fetch(:connection_object).host
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
423
496
|
private
|
497
|
+
|
498
|
+
attr_reader :preferred_fallback_connection
|
499
|
+
|
500
|
+
# See #using_preferred_fallback_host? for context
|
501
|
+
def set_preferred_fallback_connection(connection)
|
502
|
+
@preferred_fallback_connection = if connection == @connection
|
503
|
+
# If the succeeded connection is in fact the primary connection (tried after a failed fallback)
|
504
|
+
# then clear the preferred fallback connection
|
505
|
+
nil
|
506
|
+
else
|
507
|
+
{
|
508
|
+
expires_at: Time.now + options.fetch(:fallback_retry_timeout),
|
509
|
+
connection_object: connection,
|
510
|
+
}
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
def get_preferred_fallback_connection_object
|
515
|
+
preferred_fallback_connection.fetch(:connection_object) if using_preferred_fallback_host?
|
516
|
+
end
|
517
|
+
|
424
518
|
def raw_request(method, path, params = {}, options = {})
|
425
519
|
options = options.clone
|
426
520
|
if options.delete(:disable_automatic_reauthorize) == true
|
427
521
|
send_request(method, path, params, options)
|
428
522
|
else
|
429
|
-
|
523
|
+
reauthorize_on_authorization_failure do
|
430
524
|
send_request(method, path, params, options)
|
431
525
|
end
|
432
526
|
end
|
@@ -442,10 +536,22 @@ module Ably
|
|
442
536
|
retry_sequence_id = nil
|
443
537
|
request_id = SecureRandom.urlsafe_base64(10) if add_request_ids
|
444
538
|
|
539
|
+
preferred_fallback_connection_for_first_request = get_preferred_fallback_connection_object
|
540
|
+
|
445
541
|
begin
|
446
|
-
use_fallback = can_fallback_to_alternate_ably_host? && retry_count > 0
|
542
|
+
use_fallback = can_fallback_to_alternate_ably_host? && (retry_count > 0)
|
543
|
+
|
544
|
+
conn = if preferred_fallback_connection_for_first_request
|
545
|
+
case retry_count
|
546
|
+
when 0
|
547
|
+
preferred_fallback_connection_for_first_request
|
548
|
+
when 1
|
549
|
+
# Ensure the root host is used first if the preferred fallback fails, see #RSC15f
|
550
|
+
connection(use_fallback: false)
|
551
|
+
end
|
552
|
+
end || connection(use_fallback: use_fallback) # default to normal connection selection process if not preferred connection set
|
447
553
|
|
448
|
-
|
554
|
+
conn.send(method, path, params) do |request|
|
449
555
|
if add_request_ids
|
450
556
|
request.params[:request_id] = request_id
|
451
557
|
request.options.context = {} if request.options.context.nil?
|
@@ -461,10 +567,12 @@ module Ably
|
|
461
567
|
end
|
462
568
|
end.tap do
|
463
569
|
if retry_count > 0
|
464
|
-
|
570
|
+
retry_log_severity = log_retries_as_info ? :info : :warn
|
571
|
+
logger.public_send(retry_log_severity) do
|
465
572
|
"Ably::Rest::Client - Request SUCCEEDED after #{retry_count} #{retry_count > 1 ? 'retries' : 'retry' } for" \
|
466
573
|
" #{method} #{path} #{params} (seq ##{retry_sequence_id}, time elapsed #{(Time.now.to_f - requested_at.to_f).round(2)}s)"
|
467
574
|
end
|
575
|
+
set_preferred_fallback_connection conn
|
468
576
|
end
|
469
577
|
end
|
470
578
|
|
@@ -472,30 +580,32 @@ module Ably
|
|
472
580
|
retry_sequence_id ||= SecureRandom.urlsafe_base64(4)
|
473
581
|
time_passed = Time.now - requested_at
|
474
582
|
|
475
|
-
if can_fallback_to_alternate_ably_host? && retry_count < max_retry_count && time_passed <= max_retry_duration
|
583
|
+
if can_fallback_to_alternate_ably_host? && (retry_count < max_retry_count) && (time_passed <= max_retry_duration)
|
476
584
|
retry_count += 1
|
477
|
-
|
585
|
+
retry_log_severity = log_retries_as_info ? :info : :warn
|
586
|
+
logger.public_send(retry_log_severity) { "Ably::Rest::Client - Retry #{retry_count} for #{method} #{path} #{params} as initial attempt failed (seq ##{retry_sequence_id}): #{error}" }
|
478
587
|
retry
|
479
588
|
end
|
480
589
|
|
481
|
-
|
590
|
+
retry_log_severity = log_retries_as_info ? :info : :error
|
591
|
+
logger.public_send(retry_log_severity) do
|
482
592
|
"Ably::Rest::Client - Request FAILED after #{retry_count} #{retry_count > 1 ? 'retries' : 'retry' } for" \
|
483
593
|
" #{method} #{path} #{params} (seq ##{retry_sequence_id}, time elapsed #{(Time.now.to_f - requested_at.to_f).round(2)}s)"
|
484
594
|
end
|
485
595
|
|
486
596
|
case error
|
487
597
|
when Faraday::TimeoutError
|
488
|
-
raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil,
|
598
|
+
raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil, Ably::Exceptions::Codes::CONNECTION_TIMED_OUT, error, { request_id: request_id })
|
489
599
|
when Faraday::ClientError
|
490
600
|
# request_id is also available in the request context
|
491
|
-
raise Ably::Exceptions::ConnectionError.new(error.message, nil,
|
601
|
+
raise Ably::Exceptions::ConnectionError.new(error.message, nil, Ably::Exceptions::Codes::CONNECTION_FAILED, error, { request_id: request_id })
|
492
602
|
else
|
493
603
|
raise error
|
494
604
|
end
|
495
605
|
end
|
496
606
|
end
|
497
607
|
|
498
|
-
def
|
608
|
+
def reauthorize_on_authorization_failure
|
499
609
|
yield
|
500
610
|
rescue Ably::Exceptions::TokenExpired => e
|
501
611
|
if auth.token_renewable?
|
@@ -10,6 +10,14 @@ module Ably
|
|
10
10
|
env.body = parse(env.body) unless env.response_headers['Ably-Middleware-Parsed'] == true
|
11
11
|
env.response_headers['Ably-Middleware-Parsed'] = true
|
12
12
|
end
|
13
|
+
rescue Ably::Exceptions::InvalidResponseBody => e
|
14
|
+
debug_info = {
|
15
|
+
method: env.method,
|
16
|
+
url: env.url,
|
17
|
+
base64_body: base64_body(env.body),
|
18
|
+
response_headers: env.response_headers
|
19
|
+
}
|
20
|
+
raise Ably::Exceptions::InvalidResponseBody, "#{e.message}\nRequest env: #{debug_info}"
|
13
21
|
end
|
14
22
|
|
15
23
|
def parse(body)
|
@@ -18,8 +26,16 @@ module Ably
|
|
18
26
|
else
|
19
27
|
body
|
20
28
|
end
|
29
|
+
rescue MessagePack::UnknownExtTypeError => e
|
30
|
+
raise Ably::Exceptions::InvalidResponseBody, "MessagePack::UnknownExtTypeError body could not be decoded: #{e.message}. Got Base64:\n#{base64_body(body)}"
|
21
31
|
rescue MessagePack::MalformedFormatError => e
|
22
|
-
raise Ably::Exceptions::InvalidResponseBody, "
|
32
|
+
raise Ably::Exceptions::InvalidResponseBody, "MessagePack::MalformedFormatError body could not be decoded: #{e.message}. Got Base64:\n#{base64_body(body)}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def base64_body(body)
|
36
|
+
Base64.encode64(body)
|
37
|
+
rescue => err
|
38
|
+
"[#{err.message}! Could not base64 encode body: '#{body}']"
|
23
39
|
end
|
24
40
|
end
|
25
41
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'ably/rest/push/admin'
|
2
|
+
|
3
|
+
module Ably
|
4
|
+
module Rest
|
5
|
+
# Class providing push notification functionality
|
6
|
+
class Push
|
7
|
+
include Ably::Modules::Conversions
|
8
|
+
|
9
|
+
# @private
|
10
|
+
attr_reader :client
|
11
|
+
|
12
|
+
def initialize(client)
|
13
|
+
@client = client
|
14
|
+
end
|
15
|
+
|
16
|
+
# Admin features for push notifications like managing devices and channel subscriptions
|
17
|
+
# @return [Ably::Rest::Push::Admin]
|
18
|
+
def admin
|
19
|
+
@admin ||= Admin.new(self)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Activate this device for push notifications by registering with the push transport such as GCM/APNS
|
23
|
+
#
|
24
|
+
# @note This is unsupported in the Ruby library
|
25
|
+
def activate(*arg)
|
26
|
+
raise_unsupported
|
27
|
+
end
|
28
|
+
|
29
|
+
# Deactivate this device for push notifications by removing the registration with the push transport such as GCM/APNS
|
30
|
+
#
|
31
|
+
# @note This is unsupported in the Ruby library
|
32
|
+
def deactivate(*arg)
|
33
|
+
raise_unsupported
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def raise_unsupported
|
38
|
+
raise Ably::Exceptions::PushNotificationsNotSupported, 'This device does not support receiving or subscribing to push notifications. All PushChannel methods are unavailable'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'ably/rest/push/device_registrations'
|
2
|
+
require 'ably/rest/push/channel_subscriptions'
|
3
|
+
|
4
|
+
module Ably::Rest
|
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::Conversions
|
10
|
+
|
11
|
+
# @api private
|
12
|
+
attr_reader :client
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
attr_reader :push
|
16
|
+
|
17
|
+
def initialize(push)
|
18
|
+
@push = push
|
19
|
+
@client = push.client
|
20
|
+
end
|
21
|
+
|
22
|
+
# Publish a push message directly to a single recipient
|
23
|
+
#
|
24
|
+
# @param recipient [Hash] A recipient device, client_id or raw APNS/GCM target. Refer to push documentation
|
25
|
+
# @param data [Hash] The notification payload data and fields. Refer to push documentation
|
26
|
+
#
|
27
|
+
# @return [void]
|
28
|
+
#
|
29
|
+
def publish(recipient, data)
|
30
|
+
raise ArgumentError, "Expecting a Hash object for recipient, got #{recipient.class}" unless recipient.kind_of?(Hash)
|
31
|
+
raise ArgumentError, "Recipient data is empty. You must provide recipient details" if recipient.empty?
|
32
|
+
raise ArgumentError, "Expecting a Hash object for data, got #{data.class}" unless data.kind_of?(Hash)
|
33
|
+
raise ArgumentError, "Push data field is empty. You must provide attributes for the push notification" if data.empty?
|
34
|
+
|
35
|
+
publish_data = data.merge(recipient: IdiomaticRubyWrapper(recipient))
|
36
|
+
# Co-erce to camelCase for notitication fields which are always camelCase
|
37
|
+
publish_data[:notification] = IdiomaticRubyWrapper(data[:notification]) if publish_data[:notification].kind_of?(Hash)
|
38
|
+
client.post('/push/publish', publish_data)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Manage device registrations
|
42
|
+
# @return [Ably::Rest::Push::DeviceRegistrations]
|
43
|
+
def device_registrations
|
44
|
+
@device_registrations ||= DeviceRegistrations.new(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Manage channel subscriptions for devices or clients
|
48
|
+
# @return [Ably::Rest::Push::ChannelSubscriptions]
|
49
|
+
def channel_subscriptions
|
50
|
+
@channel_subscriptions ||= ChannelSubscriptions.new(self)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|