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,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
|