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,92 @@
|
|
|
1
|
+
module Ably::Realtime
|
|
2
|
+
class Channel
|
|
3
|
+
# ChannelManager is responsible for all actions relating to channel state: attaching, detaching or failure
|
|
4
|
+
# Channel state changes are performed by this class and executed from {ChannelStateMachine}
|
|
5
|
+
#
|
|
6
|
+
# This is a private class and should never be used directly by developers as the API is likely to change in future.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
class ChannelManager
|
|
10
|
+
extend Forwardable
|
|
11
|
+
|
|
12
|
+
def initialize(channel, connection)
|
|
13
|
+
@channel = channel
|
|
14
|
+
@connection = connection
|
|
15
|
+
|
|
16
|
+
connection.on(:closed) do
|
|
17
|
+
channel.transition_state_machine :detaching if can_transition_to?(:detaching)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
connection.on(:failed) do |error|
|
|
21
|
+
channel.transition_state_machine :failed, error if can_transition_to?(:failed)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Commence attachment
|
|
26
|
+
def attach
|
|
27
|
+
if can_transition_to?(:attached)
|
|
28
|
+
connect_if_connection_initialized
|
|
29
|
+
send_attach_protocol_message
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Commence attachment
|
|
34
|
+
def detach(error = nil)
|
|
35
|
+
if connection.closed? || connection.connecting?
|
|
36
|
+
channel.transition_state_machine :detached, error
|
|
37
|
+
elsif can_transition_to?(:detached)
|
|
38
|
+
send_detach_protocol_message
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Commence presence SYNC if applicable
|
|
43
|
+
def sync(attached_protocol_message)
|
|
44
|
+
if attached_protocol_message.has_presence_flag?
|
|
45
|
+
channel.presence.sync_started
|
|
46
|
+
else
|
|
47
|
+
channel.presence.sync_completed
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# An error has occurred on the channel
|
|
52
|
+
def emit_error(error)
|
|
53
|
+
logger.error "ChannelManager: Channel '#{channel.name}' error: #{error}"
|
|
54
|
+
channel.trigger :error, error
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Detach a channel as a result of an error
|
|
58
|
+
def suspend(error)
|
|
59
|
+
channel.transition_state_machine! :detaching, error
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
attr_reader :channel, :connection
|
|
65
|
+
def_delegators :channel, :can_transition_to?
|
|
66
|
+
|
|
67
|
+
# If the connection has not previously connected, connect now
|
|
68
|
+
def connect_if_connection_initialized
|
|
69
|
+
connection.connect if connection.initialized?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def send_attach_protocol_message
|
|
73
|
+
send_state_change_protocol_message Ably::Models::ProtocolMessage::ACTION.Attach
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def send_detach_protocol_message
|
|
77
|
+
send_state_change_protocol_message Ably::Models::ProtocolMessage::ACTION.Detach
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def send_state_change_protocol_message(state)
|
|
81
|
+
connection.send_protocol_message(
|
|
82
|
+
action: state.to_i,
|
|
83
|
+
channel: channel.name
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def logger
|
|
88
|
+
connection.logger
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require 'ably/modules/state_machine'
|
|
2
|
+
|
|
3
|
+
module Ably::Realtime
|
|
4
|
+
class Channel
|
|
5
|
+
# Internal class to manage channel state for {Ably::Realtime::Channel}
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
#
|
|
9
|
+
class ChannelStateMachine
|
|
10
|
+
include Ably::Modules::StateMachine
|
|
11
|
+
|
|
12
|
+
# States supported by this StateMachine match #{Channel::STATE}s
|
|
13
|
+
# :initialized
|
|
14
|
+
# :attaching
|
|
15
|
+
# :attached
|
|
16
|
+
# :detaching
|
|
17
|
+
# :detached
|
|
18
|
+
# :failed
|
|
19
|
+
Channel::STATE.each_with_index do |state_enum, index|
|
|
20
|
+
state state_enum.to_sym, initial: index == 0
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
transition :from => :initialized, :to => [:attaching]
|
|
24
|
+
transition :from => :attaching, :to => [:attached, :detaching, :failed]
|
|
25
|
+
transition :from => :attached, :to => [:detaching, :failed]
|
|
26
|
+
transition :from => :detaching, :to => [:detached, :attaching, :failed]
|
|
27
|
+
transition :from => :failed, :to => [:attaching]
|
|
28
|
+
|
|
29
|
+
after_transition do |channel, transition|
|
|
30
|
+
channel.synchronize_state_with_statemachine
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
after_transition(to: [:attaching]) do |channel|
|
|
34
|
+
channel.manager.attach
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
before_transition(to: [:attached]) do |channel, current_transition|
|
|
38
|
+
channel.manager.sync current_transition.metadata
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
after_transition(to: [:detaching]) do |channel, current_transition|
|
|
42
|
+
channel.manager.detach current_transition.metadata
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
after_transition(to: [:detached]) do |channel, current_transition|
|
|
46
|
+
channel.manager.emit_error current_transition.metadata if current_transition.metadata
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
after_transition(to: [:failed]) do |channel, current_transition|
|
|
50
|
+
channel.manager.emit_error current_transition.metadata
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Transitions responsible for updating channel#error_reason
|
|
54
|
+
before_transition(to: [:attached, :detached, :failed]) do |channel, current_transition|
|
|
55
|
+
reason = current_transition.metadata if is_error_type?(current_transition.metadata)
|
|
56
|
+
channel.set_failed_channel_error_reason reason
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
def channel
|
|
61
|
+
object
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def logger
|
|
65
|
+
channel.logger
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Ably
|
|
2
|
+
module Realtime
|
|
3
|
+
# Class that maintains a map of Channels ensuring Channels are reused
|
|
4
|
+
class Channels
|
|
5
|
+
include Ably::Modules::ChannelsCollection
|
|
6
|
+
|
|
7
|
+
# @return [Ably::Realtime::Channels]
|
|
8
|
+
def initialize(client)
|
|
9
|
+
super client, Ably::Realtime::Channel
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# @!method get(name, channel_options = {})
|
|
13
|
+
# Return a {Ably::Realtime::Channel} for the given name
|
|
14
|
+
#
|
|
15
|
+
# @param name [String] The name of the channel
|
|
16
|
+
# @param channel_options [Hash] Channel options, currently reserved for Encryption options
|
|
17
|
+
# @return [Ably::Realtime::Channel}
|
|
18
|
+
#
|
|
19
|
+
def get(*args)
|
|
20
|
+
super
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @!method fetch(name, &missing_block)
|
|
24
|
+
# Return a {Ably::Realtime::Channel} for the given name if it exists, else the block will be called.
|
|
25
|
+
# 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
|
|
26
|
+
#
|
|
27
|
+
# @param name [String] The name of the channel
|
|
28
|
+
# @yield [options] (optional) if a missing_block is passed to this method and no channel exists matching the name, this block is called
|
|
29
|
+
# @yieldparam [String] name of the missing channel
|
|
30
|
+
# @return [Ably::Realtime::Channel]
|
|
31
|
+
#
|
|
32
|
+
def fetch(*args)
|
|
33
|
+
super
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Detaches the {Ably::Realtime::Channel Realtime Channel} and releases all associated resources.
|
|
37
|
+
#
|
|
38
|
+
# Releasing a Realtime Channel is not typically necessary as a channel, once detached, consumes no resources other than
|
|
39
|
+
# the memory footprint of the {Ably::Realtime::Channel Realtime Channel object}. Release channels to free up resources if required
|
|
40
|
+
#
|
|
41
|
+
# @return [void]
|
|
42
|
+
#
|
|
43
|
+
def release(channel)
|
|
44
|
+
get(channel).detach do
|
|
45
|
+
@channels.delete(channel)
|
|
46
|
+
end if @channels.has_key?(channel)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
module Ably
|
|
2
|
+
module Realtime
|
|
3
|
+
# Client for the Ably Realtime API
|
|
4
|
+
#
|
|
5
|
+
# @!attribute [r] client_id
|
|
6
|
+
# (see Ably::Rest::Client#client_id)
|
|
7
|
+
# @!attribute [r] auth_options
|
|
8
|
+
# (see Ably::Rest::Client#auth_options)
|
|
9
|
+
# @!attribute [r] environment
|
|
10
|
+
# (see Ably::Rest::Client#environment)
|
|
11
|
+
# @!attribute [r] channels
|
|
12
|
+
# @return [Aby::Realtime::Channels] The collection of {Ably::Realtime::Channel}s that have been created
|
|
13
|
+
# @!attribute [r] encoders
|
|
14
|
+
# (see Ably::Rest::Client#encoders)
|
|
15
|
+
# @!attribute [r] protocol
|
|
16
|
+
# (see Ably::Rest::Client#protocol)
|
|
17
|
+
# @!attribute [r] protocol_binary?
|
|
18
|
+
# (see Ably::Rest::Client#protocol_binary?)
|
|
19
|
+
#
|
|
20
|
+
class Client
|
|
21
|
+
include Ably::Modules::AsyncWrapper
|
|
22
|
+
extend Forwardable
|
|
23
|
+
|
|
24
|
+
DOMAIN = 'realtime.ably.io'
|
|
25
|
+
|
|
26
|
+
# The collection of {Ably::Realtime::Channel}s that have been created
|
|
27
|
+
# @return [Aby::Realtime::Channels]
|
|
28
|
+
attr_reader :channels
|
|
29
|
+
|
|
30
|
+
# (see Ably::Rest::Client#auth)
|
|
31
|
+
attr_reader :auth
|
|
32
|
+
|
|
33
|
+
# The {Ably::Rest::Client REST client} instantiated with the same credentials and configuration that is used for all REST operations such as authentication
|
|
34
|
+
# @return [Ably::Rest::Client]
|
|
35
|
+
attr_reader :rest_client
|
|
36
|
+
|
|
37
|
+
# When false the client suppresses messages originating from this connection being echoed back on the same connection. Defaults to true
|
|
38
|
+
# @return [Boolean]
|
|
39
|
+
attr_reader :echo_messages
|
|
40
|
+
|
|
41
|
+
# The custom realtime websocket host that is being used if it was provided with the option `:ws_host` when the {Client} was created
|
|
42
|
+
# @return [String,Nil]
|
|
43
|
+
attr_reader :custom_realtime_host
|
|
44
|
+
|
|
45
|
+
# When true, as soon as the client library is instantiated it will connect to Ably. If this attribute is false, a connection must be opened explicitly
|
|
46
|
+
# @return [Boolean]
|
|
47
|
+
attr_reader :connect_automatically
|
|
48
|
+
|
|
49
|
+
# When a recover option is specified a connection inherits the state of a previous connection that may have existed under a different instance of the Realtime library, please refer to the API documentation for further information on connection state recovery
|
|
50
|
+
# @return [String,Nil]
|
|
51
|
+
attr_reader :recover
|
|
52
|
+
|
|
53
|
+
def_delegators :auth, :client_id, :auth_options
|
|
54
|
+
def_delegators :@rest_client, :encoders
|
|
55
|
+
def_delegators :@rest_client, :environment, :use_tls?, :protocol, :protocol_binary?, :custom_host
|
|
56
|
+
def_delegators :@rest_client, :log_level
|
|
57
|
+
|
|
58
|
+
# Creates a {Ably::Realtime::Client Realtime Client} and configures the {Ably::Auth} object for the connection.
|
|
59
|
+
#
|
|
60
|
+
# @param (see Ably::Rest::Client#initialize)
|
|
61
|
+
# @option options (see Ably::Rest::Client#initialize)
|
|
62
|
+
# @option options [Boolean] :queue_messages If false, this disables the default behaviour whereby the library queues messages on a connection in the disconnected or connecting states
|
|
63
|
+
# @option options [Boolean] :echo_messages If false, prevents messages originating from this connection being echoed back on the same connection
|
|
64
|
+
# @option options [String] :recover When a recover option is specified a connection inherits the state of a previous connection that may have existed under a different instance of the Realtime library, please refer to the API documentation for further information on connection state recovery
|
|
65
|
+
# @option options [Boolean] :connect_automatically By default as soon as the client library is instantiated it will connect to Ably. You can optionally set this to false and explicitly connect.
|
|
66
|
+
#
|
|
67
|
+
# @yield (see Ably::Rest::Client#initialize)
|
|
68
|
+
# @yieldparam (see Ably::Rest::Client#initialize)
|
|
69
|
+
# @yieldreturn (see Ably::Rest::Client#initialize)
|
|
70
|
+
#
|
|
71
|
+
# @return [Ably::Realtime::Client]
|
|
72
|
+
#
|
|
73
|
+
# @example
|
|
74
|
+
# # create a new client authenticating with basic auth
|
|
75
|
+
# client = Ably::Realtime::Client.new('key.id:secret')
|
|
76
|
+
#
|
|
77
|
+
# # create a new client and configure a client ID used for presence
|
|
78
|
+
# client = Ably::Realtime::Client.new(api_key: 'key.id:secret', client_id: 'john')
|
|
79
|
+
#
|
|
80
|
+
def initialize(options, &token_request_block)
|
|
81
|
+
@rest_client = Ably::Rest::Client.new(options, &token_request_block)
|
|
82
|
+
@auth = @rest_client.auth
|
|
83
|
+
@channels = Ably::Realtime::Channels.new(self)
|
|
84
|
+
@echo_messages = @rest_client.options.fetch(:echo_messages, true) == false ? false : true
|
|
85
|
+
@custom_realtime_host = @rest_client.options[:realtime_host] || @rest_client.options[:ws_host]
|
|
86
|
+
@connect_automatically = @rest_client.options.fetch(:connect_automatically, true) == false ? false : true
|
|
87
|
+
@recover = @rest_client.options[:recover]
|
|
88
|
+
|
|
89
|
+
raise ArgumentError, "Recovery key is invalid" if @recover && !@recover.match(Connection::RECOVER_REGEX)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Return a {Ably::Realtime::Channel Realtime Channel} for the given name
|
|
93
|
+
#
|
|
94
|
+
# @param (see Ably::Realtime::Channels#get)
|
|
95
|
+
# @return (see Ably::Realtime::Channels#get)
|
|
96
|
+
#
|
|
97
|
+
def channel(name, channel_options = {})
|
|
98
|
+
channels.get(name, channel_options)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Retrieve the Ably service time
|
|
102
|
+
#
|
|
103
|
+
# @yield [Time] The time as reported by the Ably service
|
|
104
|
+
# @return [EventMachine::Deferrable]
|
|
105
|
+
#
|
|
106
|
+
def time(&success_callback)
|
|
107
|
+
async_wrap(success_callback) do
|
|
108
|
+
rest_client.time
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Retrieve the stats for the application
|
|
113
|
+
#
|
|
114
|
+
# @param (see Ably::Rest::Client#stats)
|
|
115
|
+
# @option options (see Ably::Rest::Client#stats)
|
|
116
|
+
#
|
|
117
|
+
# @yield [Ably::Models::PaginatedResource<Ably::Models::Stat>] An Array of Stats
|
|
118
|
+
#
|
|
119
|
+
# @return [EventMachine::Deferrable]
|
|
120
|
+
#
|
|
121
|
+
def stats(options = {}, &success_callback)
|
|
122
|
+
async_wrap(success_callback) do
|
|
123
|
+
rest_client.stats(options)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# (see Ably::Realtime::Connection#close)
|
|
128
|
+
def close(&block)
|
|
129
|
+
connection.close(&block)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @!attribute [r] endpoint
|
|
133
|
+
# @return [URI::Generic] Default Ably Realtime endpoint used for all requests
|
|
134
|
+
def endpoint
|
|
135
|
+
endpoint_for_host(custom_realtime_host || [environment, DOMAIN].compact.join('-'))
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# @!attribute [r] connection
|
|
139
|
+
# @return [Aby::Realtime::Connection] The underlying connection for this client
|
|
140
|
+
def connection
|
|
141
|
+
@connection ||= Connection.new(self)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# (see Ably::Rest::Client#register_encoder)
|
|
145
|
+
def register_encoder(encoder)
|
|
146
|
+
rest_client.register_encoder encoder
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# (see Ably::Rest::Client#logger)
|
|
150
|
+
def logger
|
|
151
|
+
@logger ||= Ably::Logger.new(self, log_level, rest_client.logger.custom_logger)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Disable connection recovery, typically used after a connection has been recovered
|
|
155
|
+
# @return [void]
|
|
156
|
+
# @api private
|
|
157
|
+
def disable_automatic_connection_recovery
|
|
158
|
+
@recover = nil
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# @!attribute [r] fallback_endpoint
|
|
162
|
+
# @return [URI::Generic] Fallback endpoint used to connect to the realtime Ably service. Note, after each connection attempt, a new random {Ably::FALLBACK_HOSTS fallback host} is used
|
|
163
|
+
# @api private
|
|
164
|
+
def fallback_endpoint
|
|
165
|
+
unless @fallback_endpoints
|
|
166
|
+
@fallback_endpoints = Ably::FALLBACK_HOSTS.shuffle.map { |fallback_host| endpoint_for_host(fallback_host) }
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
fallback_endpoint_index = connection.manager.retry_count_for_state(:disconnected) + connection.manager.retry_count_for_state(:suspended)
|
|
170
|
+
|
|
171
|
+
@fallback_endpoints[fallback_endpoint_index % @fallback_endpoints.count]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
private
|
|
175
|
+
def endpoint_for_host(host)
|
|
176
|
+
URI::Generic.build(
|
|
177
|
+
scheme: use_tls? ? 'wss' : 'ws',
|
|
178
|
+
host: host
|
|
179
|
+
)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
module Ably::Realtime
|
|
2
|
+
class Client
|
|
3
|
+
# IncomingMessageDispatcher is a (private) class that is used to dispatch {Ably::Models::ProtocolMessage} that are
|
|
4
|
+
# received from Ably via the {Ably::Realtime::Connection}
|
|
5
|
+
class IncomingMessageDispatcher
|
|
6
|
+
ACTION = Ably::Models::ProtocolMessage::ACTION
|
|
7
|
+
|
|
8
|
+
def initialize(client, connection)
|
|
9
|
+
@client = client
|
|
10
|
+
@connection = connection
|
|
11
|
+
|
|
12
|
+
subscribe_to_incoming_protocol_messages
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
attr_reader :client, :connection
|
|
17
|
+
|
|
18
|
+
def channels
|
|
19
|
+
client.channels
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def get_channel(channel_name)
|
|
23
|
+
channels.fetch(channel_name) do
|
|
24
|
+
logger.warn "Received channel message for non-existent channel"
|
|
25
|
+
Ably::Realtime::Models::NilChannel.new
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def logger
|
|
30
|
+
client.logger
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def dispatch_protocol_message(*args)
|
|
34
|
+
protocol_message = args.first
|
|
35
|
+
|
|
36
|
+
unless protocol_message.kind_of?(Ably::Models::ProtocolMessage)
|
|
37
|
+
raise ArgumentError, "Expected a ProtocolMessage. Received #{protocol_message}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
unless [:nack, :error].include?(protocol_message.action)
|
|
41
|
+
logger.debug "#{protocol_message.action} received: #{protocol_message}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
update_connection_recovery_info protocol_message
|
|
45
|
+
|
|
46
|
+
case protocol_message.action
|
|
47
|
+
when ACTION.Heartbeat
|
|
48
|
+
when ACTION.Ack
|
|
49
|
+
ack_pending_queue_for_message_serial(protocol_message) if protocol_message.has_message_serial?
|
|
50
|
+
|
|
51
|
+
when ACTION.Nack
|
|
52
|
+
logger.warn "NACK received: #{protocol_message}"
|
|
53
|
+
nack_pending_queue_for_message_serial(protocol_message) if protocol_message.has_message_serial?
|
|
54
|
+
|
|
55
|
+
when ACTION.Connect
|
|
56
|
+
when ACTION.Connected
|
|
57
|
+
connection.transition_state_machine :connected, protocol_message.error
|
|
58
|
+
|
|
59
|
+
when ACTION.Disconnect, ACTION.Disconnected
|
|
60
|
+
connection.transition_state_machine :disconnected, protocol_message.error
|
|
61
|
+
|
|
62
|
+
when ACTION.Close
|
|
63
|
+
when ACTION.Closed
|
|
64
|
+
connection.transition_state_machine :closed
|
|
65
|
+
|
|
66
|
+
when ACTION.Error
|
|
67
|
+
if protocol_message.channel && !protocol_message.has_message_serial?
|
|
68
|
+
dispatch_channel_error protocol_message
|
|
69
|
+
else
|
|
70
|
+
process_connection_error protocol_message
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
when ACTION.Attach
|
|
74
|
+
when ACTION.Attached
|
|
75
|
+
get_channel(protocol_message.channel).transition_state_machine :attached, protocol_message
|
|
76
|
+
|
|
77
|
+
when ACTION.Detach
|
|
78
|
+
when ACTION.Detached
|
|
79
|
+
get_channel(protocol_message.channel).transition_state_machine :detached
|
|
80
|
+
|
|
81
|
+
when ACTION.Sync
|
|
82
|
+
presence = get_channel(protocol_message.channel).presence
|
|
83
|
+
protocol_message.presence.each do |presence_message|
|
|
84
|
+
presence.__incoming_msgbus__.publish :sync, presence_message
|
|
85
|
+
end
|
|
86
|
+
presence.update_sync_serial protocol_message.channel_serial
|
|
87
|
+
|
|
88
|
+
when ACTION.Presence
|
|
89
|
+
presence = get_channel(protocol_message.channel).presence
|
|
90
|
+
protocol_message.presence.each do |presence_message|
|
|
91
|
+
presence.__incoming_msgbus__.publish :presence, presence_message
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
when ACTION.Message
|
|
95
|
+
channel = get_channel(protocol_message.channel)
|
|
96
|
+
protocol_message.messages.each do |message|
|
|
97
|
+
channel.__incoming_msgbus__.publish :message, message
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
else
|
|
101
|
+
raise ArgumentError, "Protocol Message Action #{protocol_message.action} is unsupported by this MessageDispatcher"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def dispatch_channel_error(protocol_message)
|
|
106
|
+
logger.warn "Channel Error message received: #{protocol_message.error}"
|
|
107
|
+
if !protocol_message.has_message_serial?
|
|
108
|
+
get_channel(protocol_message.channel).transition_state_machine :failed, protocol_message.error
|
|
109
|
+
else
|
|
110
|
+
logger.fatal "Cannot process ProtocolMessage as not yet implemented: #{protocol_message}"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def process_connection_error(protocol_message)
|
|
115
|
+
connection.manager.error_received_from_server protocol_message.error
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def update_connection_recovery_info(protocol_message)
|
|
119
|
+
if protocol_message.connection_key && (protocol_message.connection_key != connection.key)
|
|
120
|
+
logger.debug "New connection ID set to #{protocol_message.connection_id} with connection key #{protocol_message.connection_key}"
|
|
121
|
+
detach_attached_channels protocol_message.error if protocol_message.error
|
|
122
|
+
connection.configure_new protocol_message.connection_id, protocol_message.connection_key, protocol_message.connection_serial
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
if protocol_message.has_connection_serial?
|
|
126
|
+
connection.update_connection_serial protocol_message.connection_serial
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def detach_attached_channels(error)
|
|
131
|
+
channels.select do |channel|
|
|
132
|
+
channel.attached? || channel.attaching?
|
|
133
|
+
end.each do |channel|
|
|
134
|
+
logger.warn "Detaching channel '#{channel.name}': #{error}"
|
|
135
|
+
channel.manager.suspend error
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def ack_pending_queue_for_message_serial(ack_protocol_message)
|
|
140
|
+
drop_pending_queue_from_ack(ack_protocol_message) do |protocol_message|
|
|
141
|
+
ack_messages protocol_message.messages
|
|
142
|
+
ack_messages protocol_message.presence
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def nack_pending_queue_for_message_serial(nack_protocol_message)
|
|
147
|
+
drop_pending_queue_from_ack(nack_protocol_message) do |protocol_message|
|
|
148
|
+
nack_messages protocol_message.messages, nack_protocol_message
|
|
149
|
+
nack_messages protocol_message.presence, nack_protocol_message
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def ack_messages(messages)
|
|
154
|
+
messages.each do |message|
|
|
155
|
+
logger.debug "Calling ACK success callbacks for #{message.class.name} - #{message.to_json}"
|
|
156
|
+
message.succeed message
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def nack_messages(messages, protocol_message)
|
|
161
|
+
messages.each do |message|
|
|
162
|
+
logger.debug "Calling NACK failure callbacks for #{message.class.name} - #{message.to_json}, protocol message: #{protocol_message}"
|
|
163
|
+
message.fail message, protocol_message.error
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def drop_pending_queue_from_ack(ack_protocol_message)
|
|
168
|
+
message_serial_up_to = ack_protocol_message.message_serial + ack_protocol_message.count - 1
|
|
169
|
+
connection.__pending_message_queue__.drop_while do |protocol_message|
|
|
170
|
+
if protocol_message.message_serial <= message_serial_up_to
|
|
171
|
+
yield protocol_message
|
|
172
|
+
true
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def subscribe_to_incoming_protocol_messages
|
|
178
|
+
connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |*args|
|
|
179
|
+
dispatch_protocol_message *args
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|