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