ably-rest 0.7.1 → 0.7.3
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 +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,74 @@
|
|
|
1
|
+
module Ably::Models
|
|
2
|
+
# Authentication token issued by Ably in response to an token request
|
|
3
|
+
class Token
|
|
4
|
+
include Ably::Modules::ModelCommon
|
|
5
|
+
|
|
6
|
+
DEFAULTS = {
|
|
7
|
+
capability: { '*' => ['*'] },
|
|
8
|
+
ttl: 60 * 60 # 1 hour
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
# Buffer in seconds given to the use of a token prior to it being considered unusable
|
|
12
|
+
# For example, if buffer is 10s, the token can no longer be used for new requests 9s before it expires
|
|
13
|
+
TOKEN_EXPIRY_BUFFER = 5
|
|
14
|
+
|
|
15
|
+
def initialize(attributes)
|
|
16
|
+
@hash_object = IdiomaticRubyWrapper(attributes.clone.freeze)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @!attribute [r] id
|
|
20
|
+
# @return [String] Unique token ID used to authenticate requests
|
|
21
|
+
def id
|
|
22
|
+
hash.fetch(:id)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @!attribute [r] key_id
|
|
26
|
+
# @return [String] Key ID used to create this token
|
|
27
|
+
def key_id
|
|
28
|
+
hash.fetch(:key)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @!attribute [r] issued_at
|
|
32
|
+
# @return [Time] Time the token was issued
|
|
33
|
+
def issued_at
|
|
34
|
+
as_time_from_epoch(hash.fetch(:issued_at), granularity: :s)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @!attribute [r] expires_at
|
|
38
|
+
# @return [Time] Time the token expires
|
|
39
|
+
def expires_at
|
|
40
|
+
as_time_from_epoch(hash.fetch(:expires), granularity: :s)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @!attribute [r] capability
|
|
44
|
+
# @return [Hash] Capabilities assigned to this token
|
|
45
|
+
def capability
|
|
46
|
+
hash.fetch(:capability)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @!attribute [r] client_id
|
|
50
|
+
# @return [String] Optional client ID assigned to this token
|
|
51
|
+
def client_id
|
|
52
|
+
hash[:client_id]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @!attribute [r] nonce
|
|
56
|
+
# @return [String] unique nonce used to generate Token and ensure token generation cannot be replayed
|
|
57
|
+
def nonce
|
|
58
|
+
hash.fetch(:nonce)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns true if token is expired or about to expire
|
|
62
|
+
#
|
|
63
|
+
# @return [Boolean]
|
|
64
|
+
def expired?
|
|
65
|
+
expires_at < Time.now + TOKEN_EXPIRY_BUFFER
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @!attribute [r] hash
|
|
69
|
+
# @return [Hash] Access the token Hash object ruby'fied to use symbolized keys
|
|
70
|
+
def hash
|
|
71
|
+
@hash_object
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Ably is the base namespace for the Ably {Ably::Realtime Realtime} & {Ably::Rest Rest} client libraries.
|
|
2
|
+
#
|
|
3
|
+
# Please refer to the {file:README.md Readme} on getting started.
|
|
4
|
+
#
|
|
5
|
+
# @see file:README.md README
|
|
6
|
+
module Ably
|
|
7
|
+
# Fallback hosts to use when a connection to rest/realtime.ably.io is not possible due to
|
|
8
|
+
# network failures either at the client, between the client and Ably, within an Ably data center, or at the IO domain registrar
|
|
9
|
+
#
|
|
10
|
+
FALLBACK_HOSTS = %w(A.ably-realtime.com B.ably-realtime.com C.ably-realtime.com D.ably-realtime.com E.ably-realtime.com)
|
|
11
|
+
INTERNET_CHECK = {
|
|
12
|
+
url: '//internet-up.ably-realtime.com/is-the-internet-up.txt',
|
|
13
|
+
ok_text: 'yes'
|
|
14
|
+
}
|
|
15
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require 'eventmachine'
|
|
2
|
+
|
|
3
|
+
module Ably::Modules
|
|
4
|
+
# Provides methods to convert synchronous operations into async operations through the use of
|
|
5
|
+
# {EventMachine#defer http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine#defer-class_method}.
|
|
6
|
+
# The async_wrap method can only be called from within an EventMachine reactor, and must be thread safe.
|
|
7
|
+
#
|
|
8
|
+
# @note using this AsyncWrapper should only be used for methods that are used less frequently and typically
|
|
9
|
+
# not run with levels of concurrency due to the limited number of threads available to EventMachine by default.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# class BlockingOperation
|
|
13
|
+
# include Aby::Modules::AsyncWrapper
|
|
14
|
+
#
|
|
15
|
+
# def operation(&success_callback)
|
|
16
|
+
# async_wrap(success_callback) do
|
|
17
|
+
# sleep 1
|
|
18
|
+
# 'slept'
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# blocking_object = BlockingOperation.new
|
|
24
|
+
# deferrable = blocking_object.operation do |result|
|
|
25
|
+
# puts "Done with result: #{result}"
|
|
26
|
+
# end
|
|
27
|
+
# puts "Starting"
|
|
28
|
+
#
|
|
29
|
+
# # => 'Starting'
|
|
30
|
+
# # => 'Done with result: slept'
|
|
31
|
+
#
|
|
32
|
+
module AsyncWrapper
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# Will yield the provided block in a new thread and return an {EventMachine::Deferrable http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Deferrable}
|
|
36
|
+
#
|
|
37
|
+
# @yield [Object] operation block that is run in a thread
|
|
38
|
+
# @return [EventMachine::Deferrable]
|
|
39
|
+
#
|
|
40
|
+
def async_wrap(success_callback = nil)
|
|
41
|
+
raise ArgumentError, 'Block required' unless block_given?
|
|
42
|
+
|
|
43
|
+
EventMachine::DefaultDeferrable.new.tap do |deferrable|
|
|
44
|
+
deferrable.callback &success_callback if success_callback
|
|
45
|
+
|
|
46
|
+
operation_with_exception_handling = proc do
|
|
47
|
+
begin
|
|
48
|
+
yield
|
|
49
|
+
rescue StandardError => e
|
|
50
|
+
deferrable.fail e
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
complete_callback = proc do |result|
|
|
55
|
+
deferrable.succeed result
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
EventMachine.defer operation_with_exception_handling, complete_callback
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module Ably::Modules
|
|
2
|
+
# ChannelsCollection module provides common functionality to the Rest and Realtime Channels objects
|
|
3
|
+
# such as #get, #[], #fetch, and #release
|
|
4
|
+
module ChannelsCollection
|
|
5
|
+
include Enumerable
|
|
6
|
+
|
|
7
|
+
def initialize(client, channel_klass)
|
|
8
|
+
@client = client
|
|
9
|
+
@channel_klass = channel_klass
|
|
10
|
+
@channels = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Return a Channel for the given name
|
|
14
|
+
#
|
|
15
|
+
# @param name [String] The name of the channel
|
|
16
|
+
# @param channel_options [Hash] Channel options including the encryption options
|
|
17
|
+
#
|
|
18
|
+
# @return [Channel]
|
|
19
|
+
#
|
|
20
|
+
def get(name, channel_options = {})
|
|
21
|
+
channels[name] ||= channel_klass.new(client, name, channel_options)
|
|
22
|
+
end
|
|
23
|
+
alias_method :[], :get
|
|
24
|
+
|
|
25
|
+
# Return a Channel for the given name if it exists, else the block will be called.
|
|
26
|
+
# 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
|
|
27
|
+
#
|
|
28
|
+
# @param name [String] The name of the channel
|
|
29
|
+
#
|
|
30
|
+
# @yield [options] (optional) if a missing_block is passed to this method and no channel exists matching the name, this block is called
|
|
31
|
+
# @yieldparam [String] name of the missing channel
|
|
32
|
+
#
|
|
33
|
+
# @return [Channel]
|
|
34
|
+
#
|
|
35
|
+
def fetch(name, &missing_block)
|
|
36
|
+
channels.fetch(name, &missing_block)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Destroy the Channel and releases the associated resources.
|
|
40
|
+
#
|
|
41
|
+
# Releasing a Channel is not typically necessary as a channel consumes no resources other than the memory footprint of the
|
|
42
|
+
# Channel object. Explicitly release channels to free up resources if required
|
|
43
|
+
#
|
|
44
|
+
# @param name [String] The name of the channel
|
|
45
|
+
#
|
|
46
|
+
# @return [void]
|
|
47
|
+
#
|
|
48
|
+
def release(name)
|
|
49
|
+
channels.delete(name)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @!attribute [r] length
|
|
53
|
+
# @return [Integer] number of channels created
|
|
54
|
+
def length
|
|
55
|
+
channels.length
|
|
56
|
+
end
|
|
57
|
+
alias_method :count, :length
|
|
58
|
+
alias_method :size, :length
|
|
59
|
+
|
|
60
|
+
# Method to allow {ChannelsCollection} to be {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
|
|
61
|
+
def each(&block)
|
|
62
|
+
return to_enum(:each) unless block_given?
|
|
63
|
+
channels.values.each(&block)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
attr_reader :client, :channel_klass, :channels
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
module Ably::Modules
|
|
2
|
+
# Conversions module provides common timestamp and variable naming conversions to Ably classes.
|
|
3
|
+
# All methods are private
|
|
4
|
+
module Conversions
|
|
5
|
+
extend self
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
def as_since_epoch(time, options = {})
|
|
9
|
+
granularity = options.fetch(:granularity, :ms)
|
|
10
|
+
|
|
11
|
+
case time
|
|
12
|
+
when Time
|
|
13
|
+
time.to_f * multiplier_from_granularity(granularity)
|
|
14
|
+
when Numeric
|
|
15
|
+
time
|
|
16
|
+
else
|
|
17
|
+
raise ArgumentError, 'time argument must be a Numeric or Time object'
|
|
18
|
+
end.to_i
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def as_time_from_epoch(time, options = {})
|
|
22
|
+
granularity = options.fetch(:granularity, :ms)
|
|
23
|
+
|
|
24
|
+
case time
|
|
25
|
+
when Numeric
|
|
26
|
+
Time.at(time / multiplier_from_granularity(granularity))
|
|
27
|
+
when Time
|
|
28
|
+
time
|
|
29
|
+
else
|
|
30
|
+
raise ArgumentError, 'time argument must be a Numeric or Time object'
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def multiplier_from_granularity(granularity)
|
|
35
|
+
case granularity
|
|
36
|
+
when :ms # milliseconds
|
|
37
|
+
1_000.0
|
|
38
|
+
when :s # seconds
|
|
39
|
+
1.0
|
|
40
|
+
else
|
|
41
|
+
raise ArgumentError, 'invalid granularity'
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Convert key to mixedCase from mixed_case
|
|
46
|
+
def convert_to_mixed_case(key, options = {})
|
|
47
|
+
force_camel = options.fetch(:force_camel, false)
|
|
48
|
+
|
|
49
|
+
key.to_s.
|
|
50
|
+
split('_').
|
|
51
|
+
each_with_index.map do |str, index|
|
|
52
|
+
if index > 0 || force_camel
|
|
53
|
+
str.capitalize
|
|
54
|
+
else
|
|
55
|
+
str
|
|
56
|
+
end
|
|
57
|
+
end.
|
|
58
|
+
join
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Convert a Hash into a mixed case Hash objet
|
|
62
|
+
# i.e. { client_id: 1 } becomes { 'clientId' => 1 }
|
|
63
|
+
def convert_to_mixed_case_hash(hash, options = {})
|
|
64
|
+
raise ArgumentError, 'Hash expected' unless hash.kind_of?(Hash)
|
|
65
|
+
hash.each_with_object({}) do |pair, new_hash|
|
|
66
|
+
key, val = pair
|
|
67
|
+
new_hash[convert_to_mixed_case(key, options)] = val
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Convert key to :snake_case from snakeCase
|
|
72
|
+
def convert_to_snake_case_symbol(key)
|
|
73
|
+
key.to_s.gsub(/::/, '/').
|
|
74
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
|
75
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
|
76
|
+
gsub(/([a-zA-Z])(\d)/,'\1_\2').
|
|
77
|
+
tr("-", "_").
|
|
78
|
+
downcase.
|
|
79
|
+
to_sym
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def convert_to_lower_case(key)
|
|
83
|
+
key.to_s.gsub('_', '')
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Ensures that the string value is converted to UTF-8 encoding
|
|
87
|
+
# Unless option allow_nil: true, an {ArgumentError} is raised if the string_value is not a string
|
|
88
|
+
#
|
|
89
|
+
# @return <void>
|
|
90
|
+
#
|
|
91
|
+
def ensure_utf_8(field_name, string_value, options = {})
|
|
92
|
+
unless options[:allow_nil] && string_value.nil?
|
|
93
|
+
raise ArgumentError, "#{field_name} must be a String" unless string_value.kind_of?(String)
|
|
94
|
+
end
|
|
95
|
+
string_value.encode!(Encoding::UTF_8) if string_value
|
|
96
|
+
rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e
|
|
97
|
+
raise ArgumentError, "#{field_name} could not be converted to UTF-8: #{e.message}"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
require 'ably/exceptions'
|
|
3
|
+
|
|
4
|
+
module Ably::Modules
|
|
5
|
+
# Provides methods to allow this model's `data` property to be encoded and decoded based on the `encoding` property.
|
|
6
|
+
#
|
|
7
|
+
# This module expects the following:
|
|
8
|
+
# - A #hash method that returns the underlying hash object
|
|
9
|
+
# - A #set_hash_object(hash) method that updates the underlying hash object
|
|
10
|
+
# - A #raw_hash_object attribute that returns the original hash used to create this object
|
|
11
|
+
#
|
|
12
|
+
module Encodeable
|
|
13
|
+
# Encode a message using the channel options and register encoders for the client
|
|
14
|
+
# @param channel [Ably::Realtime::Channel]
|
|
15
|
+
# @return [void]
|
|
16
|
+
# @api private
|
|
17
|
+
def encode(channel)
|
|
18
|
+
apply_encoders :encode, channel
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Decode a message using the channel options and registered encoders for the client
|
|
22
|
+
# @param channel [Ably::Realtime::Channel]
|
|
23
|
+
# @return [void]
|
|
24
|
+
# @api private
|
|
25
|
+
def decode(channel)
|
|
26
|
+
apply_encoders :decode, channel
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The original encoding of this message when it was received as a raw message from the Ably service
|
|
30
|
+
# @return [String,nil]
|
|
31
|
+
# @api private
|
|
32
|
+
def original_encoding
|
|
33
|
+
raw_hash_object['encoding']
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
def decode_binary_data_before_to_json(message)
|
|
38
|
+
if message[:data].kind_of?(String) && message[:data].encoding == ::Encoding::ASCII_8BIT
|
|
39
|
+
message[:data] = ::Base64.encode64(message[:data])
|
|
40
|
+
message[:encoding] = [message[:encoding], 'base64'].compact.join('/')
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def apply_encoders(method, channel)
|
|
45
|
+
max_encoding_length = 512
|
|
46
|
+
message_hash = hash.dup
|
|
47
|
+
|
|
48
|
+
begin
|
|
49
|
+
if message_hash[:encoding].to_s.length > max_encoding_length
|
|
50
|
+
raise Ably::Exceptions::EncoderError("Encoding error, encoding value is too long: '#{message_hash[:encoding]}'", nil, 92100)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
previous_encoding = message_hash[:encoding]
|
|
54
|
+
channel.client.encoders.each do |encoder|
|
|
55
|
+
encoder.send method, message_hash, channel.options
|
|
56
|
+
end
|
|
57
|
+
end until previous_encoding == message_hash[:encoding]
|
|
58
|
+
|
|
59
|
+
set_hash_object message_hash
|
|
60
|
+
rescue Ably::Exceptions::CipherError => cipher_error
|
|
61
|
+
if channel.respond_to?(:trigger)
|
|
62
|
+
channel.client.logger.error "Encoder error #{cipher_error.code} trying to #{method} message: #{cipher_error.message}"
|
|
63
|
+
channel.trigger :error, cipher_error
|
|
64
|
+
else
|
|
65
|
+
raise cipher_error
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
require 'ably/modules/conversions'
|
|
2
|
+
|
|
3
|
+
module Ably::Modules
|
|
4
|
+
# Enum brings Enum like functionality used in other languages to Ruby
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# class House
|
|
8
|
+
# extend Ably::Moduels::Enum
|
|
9
|
+
# CONSTRUCTION = ruby_enum('CONSTRUCTION',
|
|
10
|
+
# :brick,
|
|
11
|
+
# :steel,
|
|
12
|
+
# :wood
|
|
13
|
+
# )
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# House::CONSTRUCTION(:brick).to_i # => 0
|
|
17
|
+
# House::CONSTRUCTION('Wood').to_i # => 2
|
|
18
|
+
# House::CONSTRUCTION.Wood == :wood # => true
|
|
19
|
+
#
|
|
20
|
+
module Enum
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
class Base; end
|
|
24
|
+
|
|
25
|
+
# ruby_enum returns an Enum-like class that should be assigned to a constant in your class
|
|
26
|
+
# The first `enum_name` argument must match the constant name so that the coercion method is available
|
|
27
|
+
#
|
|
28
|
+
# @example
|
|
29
|
+
# class House
|
|
30
|
+
# extend Ably::Moduels::Enum
|
|
31
|
+
# CONSTRUCTION = ruby_enum('CONSTRUCTION', :brick)
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# # ensures the following coercion method is available
|
|
35
|
+
# House::CONSTRUCTION(:brick) # => CONSTRUCTION.Brick
|
|
36
|
+
#
|
|
37
|
+
def ruby_enum(enum_name, *values)
|
|
38
|
+
enum_class = Class.new(Enum::Base) do
|
|
39
|
+
include Conversions
|
|
40
|
+
extend Conversions
|
|
41
|
+
|
|
42
|
+
@enum_name = enum_name
|
|
43
|
+
@by_index = {}
|
|
44
|
+
@by_symbol = {}
|
|
45
|
+
|
|
46
|
+
class << self
|
|
47
|
+
include Enumerable
|
|
48
|
+
|
|
49
|
+
def get(identifier)
|
|
50
|
+
case identifier
|
|
51
|
+
when Symbol
|
|
52
|
+
by_symbol.fetch(identifier)
|
|
53
|
+
when String
|
|
54
|
+
by_symbol.fetch(convert_to_snake_case_symbol(identifier))
|
|
55
|
+
when Numeric
|
|
56
|
+
by_index.fetch(identifier)
|
|
57
|
+
when ancestors.first
|
|
58
|
+
identifier
|
|
59
|
+
else
|
|
60
|
+
if identifier.class.ancestors.include?(Enum::Base)
|
|
61
|
+
by_symbol.fetch(identifier.to_sym)
|
|
62
|
+
else
|
|
63
|
+
raise KeyError, "Cannot find Enum matching identifier '#{identifier}' argument as it is an unacceptable type: #{identifier.class}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def [](*args)
|
|
69
|
+
get(*args)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def to_s
|
|
73
|
+
name
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def size
|
|
77
|
+
by_symbol.keys.length
|
|
78
|
+
end
|
|
79
|
+
alias_method :length, :size
|
|
80
|
+
|
|
81
|
+
# Method ensuring this {Enum} is {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
|
|
82
|
+
def each(&block)
|
|
83
|
+
return to_enum(:each) unless block_given?
|
|
84
|
+
by_symbol.values.each(&block)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The name provided in the constructor for this Enum
|
|
88
|
+
def name
|
|
89
|
+
@enum_name
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
attr_reader :by_index, :by_symbol
|
|
94
|
+
|
|
95
|
+
# Define constants for each of the Enum values
|
|
96
|
+
# e.g. define_constants(:dog) creates Enum::Dog
|
|
97
|
+
def define_values(values)
|
|
98
|
+
raise RuntimeError, "#{name} Enum cannot be modified" if by_index.frozen?
|
|
99
|
+
|
|
100
|
+
# Allow another Enum to be used as a set of values
|
|
101
|
+
if values.length == 1 && klass = values.first
|
|
102
|
+
if klass.kind_of?(Class) && klass.ancestors.include?(Enum::Base)
|
|
103
|
+
values = values.first.map(&:to_sym)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
values.map do |value|
|
|
108
|
+
# Convert any key => index_value pairs into array pairs
|
|
109
|
+
Array(value)
|
|
110
|
+
end.flatten(1).each_with_index do |name, index|
|
|
111
|
+
name, index = name if name.kind_of?(Array) # name is key => index_value pair
|
|
112
|
+
raise ArgumentError, "Index value '#{index}' is invalid" unless index.kind_of?(Numeric)
|
|
113
|
+
|
|
114
|
+
camel_name = convert_to_mixed_case(name, force_camel: true)
|
|
115
|
+
name_symbol = convert_to_snake_case_symbol(name)
|
|
116
|
+
enum = new(camel_name, name_symbol, index.to_i)
|
|
117
|
+
|
|
118
|
+
by_index[index.to_i] = enum
|
|
119
|
+
by_symbol[name_symbol] = enum
|
|
120
|
+
|
|
121
|
+
define_singleton_method camel_name do
|
|
122
|
+
enum
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
by_index.freeze
|
|
127
|
+
by_symbol.freeze
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def initialize(name, symbol, index)
|
|
132
|
+
@name = name
|
|
133
|
+
@index = index
|
|
134
|
+
@symbol = symbol
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def to_s
|
|
138
|
+
"#{self.class}.#{name}"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def to_i
|
|
142
|
+
index
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def to_sym
|
|
146
|
+
symbol
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def to_json(*args)
|
|
150
|
+
%{"#{symbol}"}
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Allow comparison of Enum objects based on:
|
|
154
|
+
#
|
|
155
|
+
# * Other equivalent Enum objects
|
|
156
|
+
# * Symbol
|
|
157
|
+
# * String
|
|
158
|
+
# * Integer index of Enum
|
|
159
|
+
#
|
|
160
|
+
def ==(other)
|
|
161
|
+
case other
|
|
162
|
+
when Symbol
|
|
163
|
+
self.to_sym == convert_to_snake_case_symbol(other)
|
|
164
|
+
when String
|
|
165
|
+
self.to_sym == convert_to_snake_case_symbol(other)
|
|
166
|
+
when Numeric
|
|
167
|
+
self.to_i == other.to_i
|
|
168
|
+
when self.class
|
|
169
|
+
self.to_i == other.to_i
|
|
170
|
+
else
|
|
171
|
+
false
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
attr_reader :name, :index, :symbol
|
|
177
|
+
|
|
178
|
+
define_values values
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Convert any comparable object into this Enum
|
|
182
|
+
# @example
|
|
183
|
+
# class Example
|
|
184
|
+
# DOGS = ruby_enum('DOGS', :terrier, :labrador, :great_dane)
|
|
185
|
+
# end
|
|
186
|
+
#
|
|
187
|
+
# Example.DOGS(:great_dane) # => <DOGS.GreatDane>
|
|
188
|
+
# Example.DOGS(0) # => <DOGS.Terrier>
|
|
189
|
+
# Example.new.DOGS(0) # => <DOGS.Terrier>
|
|
190
|
+
#
|
|
191
|
+
define_singleton_method enum_name do |val|
|
|
192
|
+
enum_class.get(val)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
define_method enum_name do |val|
|
|
196
|
+
enum_class.get(val)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
enum_class
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|