ably-rest 0.7.1 → 0.7.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/.gitmodules +1 -1
- data/.rspec +1 -0
- data/.travis.yml +7 -3
- data/SPEC.md +495 -419
- data/ably-rest.gemspec +19 -5
- data/lib/ably-rest.rb +9 -1
- data/lib/submodules/ably-ruby/.gitignore +6 -0
- data/lib/submodules/ably-ruby/.rspec +1 -0
- data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
- data/lib/submodules/ably-ruby/.travis.yml +10 -0
- data/lib/submodules/ably-ruby/Gemfile +4 -0
- data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
- data/lib/submodules/ably-ruby/README.md +122 -0
- data/lib/submodules/ably-ruby/Rakefile +34 -0
- data/lib/submodules/ably-ruby/SPEC.md +1794 -0
- data/lib/submodules/ably-ruby/ably.gemspec +36 -0
- data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
- data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
- data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
- data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
- data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
- data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
- data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
- data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
- data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
- data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
- data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
- data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
- data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
- data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
- data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
- data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
- data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
- data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
- data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
- data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
- metadata +182 -27
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'ably/rest/channel'
|
2
|
+
require 'ably/rest/channels'
|
3
|
+
require 'ably/rest/client'
|
4
|
+
require 'ably/rest/presence'
|
5
|
+
|
6
|
+
Dir.glob(File.expand_path("models/*.rb", File.dirname(__FILE__))).each do |file|
|
7
|
+
require file
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'ably/models/message_encoders/base'
|
11
|
+
|
12
|
+
module Ably
|
13
|
+
# Rest provides the top-level class to be instanced for the Ably Rest library
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# client = Ably::Rest.new("xxxxx")
|
17
|
+
# channel = client.channel("test")
|
18
|
+
# channel.publish "greeting", "data"
|
19
|
+
#
|
20
|
+
module Rest
|
21
|
+
# Convenience method providing an alias to {Ably::Rest::Client} constructor.
|
22
|
+
#
|
23
|
+
# @param (see Ably::Rest::Client#initialize)
|
24
|
+
# @option options (see Ably::Rest::Client#initialize)
|
25
|
+
#
|
26
|
+
# @yield (see Ably::Rest::Client#initialize)
|
27
|
+
# @yieldparam (see Ably::Rest::Client#initialize)
|
28
|
+
# @yieldreturn (see Ably::Rest::Client#initialize)
|
29
|
+
#
|
30
|
+
# @return [Ably::Rest::Client]
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# # create a new client authenticating with basic auth
|
34
|
+
# client = Ably::Rest.new('key.id:secret')
|
35
|
+
#
|
36
|
+
# # create a new client authenticating with basic auth and a client_id
|
37
|
+
# client = Ably::Rest.new(api_key: 'key.id:secret', client_id: 'john')
|
38
|
+
#
|
39
|
+
def self.new(options, &token_request_block)
|
40
|
+
Ably::Rest::Client.new(options, &token_request_block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Ably
|
2
|
+
module Rest
|
3
|
+
# The Ably Realtime service organises the traffic within any application into named channels.
|
4
|
+
# Channels are the "unit" of message distribution; clients attach to channels to subscribe to messages, and every message broadcast by the service is associated with a unique channel.
|
5
|
+
#
|
6
|
+
# @!attribute [r] client
|
7
|
+
# @return {Ably::Realtime::Client} Ably client associated with this channel
|
8
|
+
# @!attribute [r] name
|
9
|
+
# @return {String} channel name
|
10
|
+
# @!attribute [r] options
|
11
|
+
# @return {Hash} channel options configured for this channel, see {#initialize} for channel_options
|
12
|
+
class Channel
|
13
|
+
include Ably::Modules::Conversions
|
14
|
+
|
15
|
+
attr_reader :client, :name, :options
|
16
|
+
|
17
|
+
# Initialize a new Channel object
|
18
|
+
#
|
19
|
+
# @param client [Ably::Rest::Client]
|
20
|
+
# @param name [String] The name of the channel
|
21
|
+
# @param channel_options [Hash] Channel options, currently reserved for Encryption options
|
22
|
+
# @option channel_options [Boolean] :encrypted setting this to true for this channel will encrypt & decrypt all messages automatically
|
23
|
+
# @option channel_options [Hash] :cipher_params A hash of options to configure the encryption. *:key* is required, all other options are optional. See {Ably::Util::Crypto#initialize} for a list of `cipher_params` options
|
24
|
+
#
|
25
|
+
def initialize(client, name, channel_options = {})
|
26
|
+
ensure_utf_8 :name, name
|
27
|
+
|
28
|
+
@client = client
|
29
|
+
@name = name
|
30
|
+
@options = channel_options.clone.freeze
|
31
|
+
end
|
32
|
+
|
33
|
+
# Publish a message to the channel
|
34
|
+
#
|
35
|
+
# @param name [String] The event name of the message to publish
|
36
|
+
# @param data [String] The message payload
|
37
|
+
# @return [Boolean] true if the message was published, otherwise false
|
38
|
+
def publish(name, data)
|
39
|
+
ensure_utf_8 :name, name
|
40
|
+
|
41
|
+
payload = {
|
42
|
+
name: name,
|
43
|
+
data: data
|
44
|
+
}
|
45
|
+
|
46
|
+
message = Ably::Models::Message.new(payload, nil).tap do |message|
|
47
|
+
message.encode self
|
48
|
+
end
|
49
|
+
|
50
|
+
response = client.post("#{base_path}/publish", message)
|
51
|
+
|
52
|
+
[201, 204].include?(response.status)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Return the message history of the channel
|
56
|
+
#
|
57
|
+
# @param [Hash] options the options for the message history request
|
58
|
+
# @option options [Integer,Time] :start Time or millisecond since epoch
|
59
|
+
# @option options [Integer,Time] :end Time or millisecond since epoch
|
60
|
+
# @option options [Symbol] :direction `:forwards` or `:backwards`
|
61
|
+
# @option options [Integer] :limit Maximum number of messages to retrieve up to 10,000
|
62
|
+
# @option options [Symbol] :by `:message`, `:bundle` or `:hour`. Defaults to `:message`
|
63
|
+
#
|
64
|
+
# @return [Ably::Models::PaginatedResource<Ably::Models::Message>] An Array of {Ably::Models::Message} objects that supports paging (#next_page, #first_page)
|
65
|
+
def history(options = {})
|
66
|
+
url = "#{base_path}/messages"
|
67
|
+
options = options.dup
|
68
|
+
|
69
|
+
[:start, :end].each { |option| options[option] = as_since_epoch(options[option]) if options.has_key?(option) }
|
70
|
+
|
71
|
+
paginated_options = {
|
72
|
+
coerce_into: 'Ably::Models::Message',
|
73
|
+
async_blocking_operations: options.delete(:async_blocking_operations),
|
74
|
+
}
|
75
|
+
|
76
|
+
response = client.get(url, options)
|
77
|
+
|
78
|
+
Ably::Models::PaginatedResource.new(response, url, client, paginated_options) do |message|
|
79
|
+
message.tap do |message|
|
80
|
+
decode_message message
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return the {Ably::Rest::Presence} object
|
86
|
+
#
|
87
|
+
# @return [Ably::Rest::Presence]
|
88
|
+
def presence
|
89
|
+
@presence ||= Presence.new(client, self)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
def base_path
|
94
|
+
"/channels/#{CGI.escape(name)}"
|
95
|
+
end
|
96
|
+
|
97
|
+
def decode_message(message)
|
98
|
+
message.decode self
|
99
|
+
rescue Ably::Exceptions::CipherError, Ably::Exceptions::EncoderError => e
|
100
|
+
client.logger.error "Decoding Error on channel '#{name}', message event name '#{message.name}'. #{e.class.name}: #{e.message}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Ably
|
2
|
+
module Rest
|
3
|
+
class Channels
|
4
|
+
include Ably::Modules::ChannelsCollection
|
5
|
+
|
6
|
+
# @return [Ably::Rest::Channels]
|
7
|
+
def initialize(client)
|
8
|
+
super client, Ably::Rest::Channel
|
9
|
+
end
|
10
|
+
|
11
|
+
# @!method get(name, channel_options = {})
|
12
|
+
# Return a {Ably::Rest::Channel} for the given name
|
13
|
+
#
|
14
|
+
# @param name [String] The name of the channel
|
15
|
+
# @param channel_options [Hash] Channel options, currently reserved for Encryption options
|
16
|
+
# @return [Ably::Rest::Channel}
|
17
|
+
def get(*args)
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
# @!method fetch(name, &missing_block)
|
22
|
+
# Return a {Ably::Rest::Channel} for the given name if it exists, else the block will be called.
|
23
|
+
# This method is intentionally similar to {http://ruby-doc.org/core-2.1.3/Hash.html#method-i-fetch Hash#fetch} providing a simple way to check if a channel exists or not without creating one
|
24
|
+
#
|
25
|
+
# @param name [String] The name of the channel
|
26
|
+
# @yield [options] (optional) if a missing_block is passed to this method and no channel exists matching the name, this block is called
|
27
|
+
# @yieldparam [String] name of the missing channel
|
28
|
+
# @return [Ably::Rest::Channel]
|
29
|
+
def fetch(*args)
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
# Destroy the {Ably::Rest::Channel} and releases the associated resources.
|
34
|
+
#
|
35
|
+
# Releasing a {Ably::Rest::Channel} is not typically necessary as a channel consumes no resources other than the memory footprint of the
|
36
|
+
# {Ably::Rest::Channel} object. Explicitly release channels to free up resources if required
|
37
|
+
#
|
38
|
+
# @return [void]
|
39
|
+
def release(*args)
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,396 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'json'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
require 'ably/rest/middleware/exceptions'
|
6
|
+
|
7
|
+
module Ably
|
8
|
+
module Rest
|
9
|
+
# Client for the Ably REST API
|
10
|
+
#
|
11
|
+
# @!attribute [r] client_id
|
12
|
+
# @return [String] A client ID, used for identifying this client for presence purposes
|
13
|
+
# @!attribute [r] auth_options
|
14
|
+
# @return [Hash] {Ably::Auth} options configured for this client
|
15
|
+
#
|
16
|
+
class Client
|
17
|
+
include Ably::Modules::Conversions
|
18
|
+
include Ably::Modules::HttpHelpers
|
19
|
+
extend Forwardable
|
20
|
+
|
21
|
+
# Default Ably domain for REST
|
22
|
+
DOMAIN = 'rest.ably.io'
|
23
|
+
|
24
|
+
# Configuration for connection retry attempts
|
25
|
+
CONNECTION_RETRY = {
|
26
|
+
single_request_open_timeout: 4,
|
27
|
+
single_request_timeout: 15,
|
28
|
+
cumulative_request_open_timeout: 10,
|
29
|
+
max_retry_attempts: 3
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
def_delegators :auth, :client_id, :auth_options
|
33
|
+
|
34
|
+
# Custom environment to use such as 'sandbox' when testing the client library against an alternate Ably environment
|
35
|
+
# @return [String]
|
36
|
+
attr_reader :environment
|
37
|
+
|
38
|
+
# The protocol configured for this client, either binary `:msgpack` or text based `:json`
|
39
|
+
# @return [Symbol]
|
40
|
+
attr_reader :protocol
|
41
|
+
|
42
|
+
# {Ably::Auth} authentication object configured for this connection
|
43
|
+
# @return [Ably::Auth]
|
44
|
+
attr_reader :auth
|
45
|
+
|
46
|
+
# The collection of {Ably::Rest::Channel}s that have been created
|
47
|
+
# @return [Aby::Rest::Channels]
|
48
|
+
attr_reader :channels
|
49
|
+
|
50
|
+
# Log level configured for this {Client}
|
51
|
+
# @return [Logger::Severity]
|
52
|
+
attr_reader :log_level
|
53
|
+
|
54
|
+
# The custom host that is being used if it was provided with the option `:rest_host` when the {Client} was created
|
55
|
+
# @return [String,Nil]
|
56
|
+
attr_reader :custom_host
|
57
|
+
|
58
|
+
# The registered encoders that are used to encode and decode message payloads
|
59
|
+
# @return [Array<Ably::Models::MessageEncoder::Base>]
|
60
|
+
# @api private
|
61
|
+
attr_reader :encoders
|
62
|
+
|
63
|
+
# The additional options passed to this Client's #initialize method not available as attributes of this class
|
64
|
+
# @return [Hash]
|
65
|
+
# @api private
|
66
|
+
attr_reader :options
|
67
|
+
|
68
|
+
# Creates a {Ably::Rest::Client Rest Client} and configures the {Ably::Auth} object for the connection.
|
69
|
+
#
|
70
|
+
# @param [Hash,String] options an options Hash used to configure the client and the authentication, or String with an API key
|
71
|
+
# @option options (see Ably::Auth#authorise)
|
72
|
+
# @option options [Boolean] :tls TLS is used by default, providing a value of false disables TLS. Please note Basic Auth is disallowed without TLS as secrets cannot be transmitted over unsecured connections.
|
73
|
+
# @option options [String] :api_key API key comprising the key ID and key secret in a single string
|
74
|
+
# @option options [Boolean] :use_token_auth Will force Basic Auth if set to false, and TOken auth if set to true
|
75
|
+
# @option options [String] :environment Specify 'sandbox' when testing the client library against an alternate Ably environment
|
76
|
+
# @option options [Symbol] :protocol Protocol used to communicate with Ably, :json and :msgpack currently supported. Defaults to :msgpack
|
77
|
+
# @option options [Boolean] :use_binary_protocol Protocol used to communicate with Ably, defaults to true and uses MessagePack protocol. This option will overide :protocol option
|
78
|
+
# @option options [Logger::Severity,Symbol] :log_level Log level for the standard Logger that outputs to STDOUT. Defaults to Logger::ERROR, can be set to :fatal (Logger::FATAL), :error (Logger::ERROR), :warn (Logger::WARN), :info (Logger::INFO), :debug (Logger::DEBUG) or :none
|
79
|
+
# @option options [Logger] :logger A custom logger can be used however it must adhere to the Ruby Logger interface, see http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html
|
80
|
+
#
|
81
|
+
# @yield (see Ably::Auth#authorise)
|
82
|
+
# @yieldparam (see Ably::Auth#authorise)
|
83
|
+
# @yieldreturn (see Ably::Auth#authorise)
|
84
|
+
#
|
85
|
+
# @return [Ably::Rest::Client]
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
# # create a new client authenticating with basic auth
|
89
|
+
# client = Ably::Rest::Client.new('key.id:secret')
|
90
|
+
#
|
91
|
+
# # create a new client and configure a client ID used for presence
|
92
|
+
# client = Ably::Rest::Client.new(api_key: 'key.id:secret', client_id: 'john')
|
93
|
+
#
|
94
|
+
def initialize(options, &token_request_block)
|
95
|
+
raise ArgumentError, 'Options Hash is expected' if options.nil?
|
96
|
+
|
97
|
+
options = options.clone
|
98
|
+
if options.kind_of?(String)
|
99
|
+
options = { api_key: options }
|
100
|
+
end
|
101
|
+
|
102
|
+
@tls = options.delete(:tls) == false ? false : true
|
103
|
+
@environment = options.delete(:environment) # nil is production
|
104
|
+
@protocol = options.delete(:protocol) || :msgpack
|
105
|
+
@debug_http = options.delete(:debug_http)
|
106
|
+
@log_level = options.delete(:log_level) || ::Logger::ERROR
|
107
|
+
@custom_logger = options.delete(:logger)
|
108
|
+
@custom_host = options.delete(:rest_host)
|
109
|
+
|
110
|
+
if @log_level == :none
|
111
|
+
@custom_logger = Ably::Models::NilLogger.new
|
112
|
+
else
|
113
|
+
@log_level = ::Logger.const_get(log_level.to_s.upcase) if log_level.kind_of?(Symbol) || log_level.kind_of?(String)
|
114
|
+
end
|
115
|
+
|
116
|
+
options.delete(:use_binary_protocol).tap do |use_binary_protocol|
|
117
|
+
if use_binary_protocol == true
|
118
|
+
@protocol = :msgpack
|
119
|
+
elsif use_binary_protocol == false
|
120
|
+
@protocol = :json
|
121
|
+
end
|
122
|
+
end
|
123
|
+
raise ArgumentError, 'Protocol is invalid. Must be either :msgpack or :json' unless [:msgpack, :json].include?(@protocol)
|
124
|
+
|
125
|
+
@options = options.freeze
|
126
|
+
@auth = Auth.new(self, options, &token_request_block)
|
127
|
+
@channels = Ably::Rest::Channels.new(self)
|
128
|
+
@encoders = []
|
129
|
+
|
130
|
+
initialize_default_encoders
|
131
|
+
end
|
132
|
+
|
133
|
+
# Return a REST {Ably::Rest::Channel} for the given name
|
134
|
+
#
|
135
|
+
# @param (see Ably::Rest::Channels#get)
|
136
|
+
#
|
137
|
+
# @return (see Ably::Rest::Channels#get)
|
138
|
+
def channel(name, channel_options = {})
|
139
|
+
channels.get(name, channel_options)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Retrieve the Stats for the application
|
143
|
+
#
|
144
|
+
# @param [Hash] options the options for the stats request
|
145
|
+
# @option options [Integer,Time] :start Time or millisecond since epoch
|
146
|
+
# @option options [Integer,Time] :end Time or millisecond since epoch
|
147
|
+
# @option options [Symbol] :direction `:forwards` or `:backwards`
|
148
|
+
# @option options [Integer] :limit Maximum number of stats to retrieve up to 10,000
|
149
|
+
# @option options [Symbol] :by `:minute`, `:hour`, `:day` or `:month`. Defaults to `:minute`
|
150
|
+
#
|
151
|
+
# @return [Ably::Models::PaginatedResource<Ably::Models::Stat>] An Array of Stats
|
152
|
+
#
|
153
|
+
def stats(options = {})
|
154
|
+
options = {
|
155
|
+
:direction => :forwards,
|
156
|
+
:by => :minute
|
157
|
+
}.merge(options)
|
158
|
+
|
159
|
+
[:start, :end].each { |option| options[option] = as_since_epoch(options[option]) if options.has_key?(option) }
|
160
|
+
|
161
|
+
paginated_options = {
|
162
|
+
coerce_into: 'Ably::Models::Stat'
|
163
|
+
}
|
164
|
+
|
165
|
+
url = '/stats'
|
166
|
+
response = get(url, options)
|
167
|
+
|
168
|
+
Ably::Models::PaginatedResource.new(response, url, self, paginated_options)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Retrieve the Ably service time
|
172
|
+
#
|
173
|
+
# @return [Time] The time as reported by the Ably service
|
174
|
+
def time
|
175
|
+
response = get('/time', {}, send_auth_header: false)
|
176
|
+
|
177
|
+
as_time_from_epoch(response.body.first)
|
178
|
+
end
|
179
|
+
|
180
|
+
# @!attribute [r] use_tls?
|
181
|
+
# @return [Boolean] True if client is configured to use TLS for all Ably communication
|
182
|
+
def use_tls?
|
183
|
+
@tls == true
|
184
|
+
end
|
185
|
+
|
186
|
+
# Perform an HTTP GET request to the API using configured authentication
|
187
|
+
#
|
188
|
+
# @return [Faraday::Response]
|
189
|
+
def get(path, params = {}, options = {})
|
190
|
+
request(:get, path, params, options)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Perform an HTTP POST request to the API using configured authentication
|
194
|
+
#
|
195
|
+
# @return [Faraday::Response]
|
196
|
+
def post(path, params, options = {})
|
197
|
+
request(:post, path, params, options)
|
198
|
+
end
|
199
|
+
|
200
|
+
# @!attribute [r] endpoint
|
201
|
+
# @return [URI::Generic] Default Ably REST endpoint used for all requests
|
202
|
+
def endpoint
|
203
|
+
endpoint_for_host(custom_host || [@environment, DOMAIN].compact.join('-'))
|
204
|
+
end
|
205
|
+
|
206
|
+
# @!attribute [r] logger
|
207
|
+
# @return [Logger] The {Ably::Logger} for this client.
|
208
|
+
# Configure the log_level with the `:log_level` option, refer to {Client#initialize}
|
209
|
+
def logger
|
210
|
+
@logger ||= Ably::Logger.new(self, log_level, @custom_logger)
|
211
|
+
end
|
212
|
+
|
213
|
+
# @!attribute [r] mime_type
|
214
|
+
# @return [String] Mime type used for HTTP requests
|
215
|
+
def mime_type
|
216
|
+
case protocol
|
217
|
+
when :json
|
218
|
+
'application/json'
|
219
|
+
else
|
220
|
+
'application/x-msgpack'
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Register a message encoder and decoder that implements Ably::Models::MessageEncoders::Base interface.
|
225
|
+
# Message encoders are used to encode and decode message payloads automatically.
|
226
|
+
# @note Encoders and decoders are processed in the order they are added so the first encoder will be given priority when encoding and decoding
|
227
|
+
#
|
228
|
+
# @param [Ably::Models::MessageEncoders::Base] encoder
|
229
|
+
# @return [void]
|
230
|
+
#
|
231
|
+
# @api private
|
232
|
+
def register_encoder(encoder)
|
233
|
+
encoder_klass = if encoder.kind_of?(String)
|
234
|
+
encoder.split('::').inject(Kernel) do |base, klass_name|
|
235
|
+
base.public_send(:const_get, klass_name)
|
236
|
+
end
|
237
|
+
else
|
238
|
+
encoder
|
239
|
+
end
|
240
|
+
|
241
|
+
raise "Encoder must inherit from `Ably::Models::MessageEncoders::Base`" unless encoder_klass.ancestors.include?(Ably::Models::MessageEncoders::Base)
|
242
|
+
|
243
|
+
encoders << encoder_klass.new(self)
|
244
|
+
end
|
245
|
+
|
246
|
+
# @!attribute [r] protocol_binary?
|
247
|
+
# @return [Boolean] True of the transport #protocol communicates with Ably with a binary protocol
|
248
|
+
def protocol_binary?
|
249
|
+
protocol == :msgpack
|
250
|
+
end
|
251
|
+
|
252
|
+
# Connection used to make HTTP requests
|
253
|
+
#
|
254
|
+
# @param [Hash] options
|
255
|
+
# @option options [Boolean] :use_fallback when true, one of the fallback connections is used randomly, see {Ably::FALLBACK_HOSTS}
|
256
|
+
#
|
257
|
+
# @return [Faraday::Connection]
|
258
|
+
#
|
259
|
+
# @api private
|
260
|
+
def connection(options = {})
|
261
|
+
if options[:use_fallback]
|
262
|
+
fallback_connection
|
263
|
+
else
|
264
|
+
@connection ||= Faraday.new(endpoint.to_s, connection_options)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Fallback connection used to make HTTP requests.
|
269
|
+
# Note, each request uses a random and then subsequent random {Ably::FALLBACK_HOSTS fallback host}
|
270
|
+
#
|
271
|
+
# @return [Faraday::Connection]
|
272
|
+
#
|
273
|
+
# @api private
|
274
|
+
def fallback_connection
|
275
|
+
unless @fallback_connections
|
276
|
+
@fallback_connections = Ably::FALLBACK_HOSTS.shuffle.map { |host| Faraday.new(endpoint_for_host(host).to_s, connection_options) }
|
277
|
+
end
|
278
|
+
@fallback_index ||= 0
|
279
|
+
|
280
|
+
@fallback_connections[@fallback_index % @fallback_connections.count].tap do
|
281
|
+
@fallback_index += 1
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
private
|
286
|
+
def request(method, path, params = {}, options = {})
|
287
|
+
options = options.clone
|
288
|
+
if options.delete(:disable_automatic_reauthorise) == true
|
289
|
+
send_request(method, path, params, options)
|
290
|
+
else
|
291
|
+
reauthorise_on_authorisation_failure do
|
292
|
+
send_request(method, path, params, options)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Sends HTTP request to connection end point
|
298
|
+
# Connection failures will automatically be reattempted until thresholds are met
|
299
|
+
def send_request(method, path, params, options)
|
300
|
+
max_retry_attempts = CONNECTION_RETRY.fetch(:max_retry_attempts)
|
301
|
+
cumulative_timeout = CONNECTION_RETRY.fetch(:cumulative_request_open_timeout)
|
302
|
+
requested_at = Time.now
|
303
|
+
retry_count = 0
|
304
|
+
|
305
|
+
begin
|
306
|
+
use_fallback = can_fallback_to_alternate_ably_host? && retry_count > 0
|
307
|
+
|
308
|
+
connection(use_fallback: use_fallback).send(method, path, params) do |request|
|
309
|
+
unless options[:send_auth_header] == false
|
310
|
+
request.headers[:authorization] = auth.auth_header
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
rescue Faraday::TimeoutError, Faraday::ClientError => error
|
315
|
+
time_passed = Time.now - requested_at
|
316
|
+
if can_fallback_to_alternate_ably_host? && retry_count < max_retry_attempts && time_passed <= cumulative_timeout
|
317
|
+
retry_count += 1
|
318
|
+
retry
|
319
|
+
end
|
320
|
+
|
321
|
+
case error
|
322
|
+
when Faraday::TimeoutError
|
323
|
+
raise Ably::Exceptions::ConnectionTimeoutError.new(error.message, nil, 80014, error)
|
324
|
+
when Faraday::ClientError
|
325
|
+
raise Ably::Exceptions::ConnectionError.new(error.message, nil, 80000, error)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def reauthorise_on_authorisation_failure
|
331
|
+
yield
|
332
|
+
rescue Ably::Exceptions::InvalidRequest => e
|
333
|
+
if e.code == 40140
|
334
|
+
if auth.token_renewable?
|
335
|
+
auth.authorise force: true
|
336
|
+
yield
|
337
|
+
else
|
338
|
+
raise Ably::Exceptions::InvalidToken.new(e.message, e.status, e.code)
|
339
|
+
end
|
340
|
+
else
|
341
|
+
raise e
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def endpoint_for_host(host)
|
346
|
+
URI::Generic.build(
|
347
|
+
scheme: use_tls? ? 'https' : 'http',
|
348
|
+
host: host
|
349
|
+
)
|
350
|
+
end
|
351
|
+
|
352
|
+
# Return a Hash of connection options to initiate the Faraday::Connection with
|
353
|
+
#
|
354
|
+
# @return [Hash]
|
355
|
+
def connection_options
|
356
|
+
@connection_options ||= {
|
357
|
+
builder: middleware,
|
358
|
+
headers: {
|
359
|
+
content_type: mime_type,
|
360
|
+
accept: mime_type,
|
361
|
+
user_agent: user_agent
|
362
|
+
},
|
363
|
+
request: {
|
364
|
+
open_timeout: CONNECTION_RETRY.fetch(:single_request_open_timeout),
|
365
|
+
timeout: CONNECTION_RETRY.fetch(:single_request_timeout)
|
366
|
+
}
|
367
|
+
}
|
368
|
+
end
|
369
|
+
|
370
|
+
# Return a Faraday middleware stack to initiate the Faraday::Connection with
|
371
|
+
#
|
372
|
+
# @see http://mislav.uniqpath.com/2011/07/faraday-advanced-http/
|
373
|
+
def middleware
|
374
|
+
@middleware ||= Faraday::RackBuilder.new do |builder|
|
375
|
+
setup_outgoing_middleware builder
|
376
|
+
|
377
|
+
# Raise exceptions if response code is invalid
|
378
|
+
builder.use Ably::Rest::Middleware::Exceptions
|
379
|
+
|
380
|
+
setup_incoming_middleware builder, logger, fail_if_unsupported_mime_type: true
|
381
|
+
|
382
|
+
# Set Faraday's HTTP adapter
|
383
|
+
builder.adapter Faraday.default_adapter
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
def can_fallback_to_alternate_ably_host?
|
388
|
+
!custom_host && !environment
|
389
|
+
end
|
390
|
+
|
391
|
+
def initialize_default_encoders
|
392
|
+
Ably::Models::MessageEncoders.register_default_encoders self
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|