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,70 @@
|
|
|
1
|
+
module Ably::Realtime
|
|
2
|
+
class Client
|
|
3
|
+
# OutgoingMessageDispatcher is a (private) class that is used to deliver
|
|
4
|
+
# outgoing {Ably::Models::ProtocolMessage}s using the {Ably::Realtime::Connection}
|
|
5
|
+
# when the connection state is capable of delivering messages
|
|
6
|
+
class OutgoingMessageDispatcher
|
|
7
|
+
include Ably::Modules::EventMachineHelpers
|
|
8
|
+
|
|
9
|
+
ACTION = Ably::Models::ProtocolMessage::ACTION
|
|
10
|
+
|
|
11
|
+
def initialize(client, connection)
|
|
12
|
+
@client = client
|
|
13
|
+
@connection = connection
|
|
14
|
+
|
|
15
|
+
subscribe_to_outgoing_protocol_message_queue
|
|
16
|
+
setup_event_handlers
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
attr_reader :client, :connection
|
|
21
|
+
|
|
22
|
+
def can_send_messages?
|
|
23
|
+
connection.connected? || connection.closing?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def messages_in_outgoing_queue?
|
|
27
|
+
!outgoing_queue.empty?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def outgoing_queue
|
|
31
|
+
connection.__outgoing_message_queue__
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def pending_queue
|
|
35
|
+
connection.__pending_message_queue__
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def current_transport_outgoing_message_bus
|
|
39
|
+
connection.transport.__outgoing_protocol_msgbus__
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def deliver_queued_protocol_messages
|
|
43
|
+
condition = -> { can_send_messages? && messages_in_outgoing_queue? }
|
|
44
|
+
|
|
45
|
+
non_blocking_loop_while(condition) do
|
|
46
|
+
protocol_message = outgoing_queue.shift
|
|
47
|
+
current_transport_outgoing_message_bus.publish :protocol_message, protocol_message
|
|
48
|
+
|
|
49
|
+
if protocol_message.ack_required?
|
|
50
|
+
pending_queue << protocol_message
|
|
51
|
+
else
|
|
52
|
+
protocol_message.succeed protocol_message
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def subscribe_to_outgoing_protocol_message_queue
|
|
58
|
+
connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |*args|
|
|
59
|
+
deliver_queued_protocol_messages
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def setup_event_handlers
|
|
64
|
+
connection.on(:connected) do
|
|
65
|
+
deliver_queued_protocol_messages
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
module Ably
|
|
2
|
+
module Realtime
|
|
3
|
+
# The Connection class represents the connection associated with an Ably Realtime instance.
|
|
4
|
+
# The Connection object exposes the lifecycle and parameters of the realtime connection.
|
|
5
|
+
#
|
|
6
|
+
# Connections will always be in one of the following states:
|
|
7
|
+
#
|
|
8
|
+
# initialized: 0
|
|
9
|
+
# connecting: 1
|
|
10
|
+
# connected: 2
|
|
11
|
+
# disconnected: 3
|
|
12
|
+
# suspended: 4
|
|
13
|
+
# closed: 5
|
|
14
|
+
# failed: 6
|
|
15
|
+
#
|
|
16
|
+
# Note that the states are available as Enum-like constants:
|
|
17
|
+
#
|
|
18
|
+
# Connection::STATE.Initialized
|
|
19
|
+
# Connection::STATE.Connecting
|
|
20
|
+
# Connection::STATE.Connected
|
|
21
|
+
# Connection::STATE.Disconnected
|
|
22
|
+
# Connection::STATE.Suspended
|
|
23
|
+
# Connection::STATE.Closed
|
|
24
|
+
# Connection::STATE.Failed
|
|
25
|
+
#
|
|
26
|
+
# Connection emit errors - use `on(:error)` to subscribe to errors
|
|
27
|
+
#
|
|
28
|
+
# @example
|
|
29
|
+
# client = Ably::Realtime::Client.new('key.id:secret')
|
|
30
|
+
# client.connection.on(:connected) do
|
|
31
|
+
# puts "Connected with connection ID: #{client.connection.id}"
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# @!attribute [r] state
|
|
35
|
+
# @return [Ably::Realtime::Connection::STATE] connection state
|
|
36
|
+
#
|
|
37
|
+
class Connection
|
|
38
|
+
include Ably::Modules::EventEmitter
|
|
39
|
+
include Ably::Modules::Conversions
|
|
40
|
+
extend Ably::Modules::Enum
|
|
41
|
+
|
|
42
|
+
# Valid Connection states
|
|
43
|
+
STATE = ruby_enum('STATE',
|
|
44
|
+
:initialized,
|
|
45
|
+
:connecting,
|
|
46
|
+
:connected,
|
|
47
|
+
:disconnected,
|
|
48
|
+
:suspended,
|
|
49
|
+
:closing,
|
|
50
|
+
:closed,
|
|
51
|
+
:failed
|
|
52
|
+
)
|
|
53
|
+
include Ably::Modules::StateEmitter
|
|
54
|
+
include Ably::Modules::UsesStateMachine
|
|
55
|
+
|
|
56
|
+
# Expected format for a connection recover key
|
|
57
|
+
RECOVER_REGEX = /^(?<recover>[\w-]+):(?<connection_serial>\-?\w+)$/
|
|
58
|
+
|
|
59
|
+
# A unique public identifier for this connection, used to identify this member in presence events and messages
|
|
60
|
+
# @return [String]
|
|
61
|
+
attr_reader :id
|
|
62
|
+
|
|
63
|
+
# A unique private connection key used to recover this connection, assigned by Ably
|
|
64
|
+
# @return [String]
|
|
65
|
+
attr_reader :key
|
|
66
|
+
|
|
67
|
+
# The serial number of the last message to be received on this connection, used to recover or resume a connection
|
|
68
|
+
# @return [Integer]
|
|
69
|
+
attr_reader :serial
|
|
70
|
+
|
|
71
|
+
# When a connection failure occurs this attribute contains the Ably Exception
|
|
72
|
+
# @return [Ably::Models::ErrorInfo,Ably::Exceptions::BaseAblyException]
|
|
73
|
+
attr_reader :error_reason
|
|
74
|
+
|
|
75
|
+
# {Ably::Realtime::Client} associated with this connection
|
|
76
|
+
# @return [Ably::Realtime::Client]
|
|
77
|
+
attr_reader :client
|
|
78
|
+
|
|
79
|
+
# Underlying socket transport used for this connection, for internal use by the client library
|
|
80
|
+
# @return [Ably::Realtime::Connection::WebsocketTransport]
|
|
81
|
+
# @api private
|
|
82
|
+
attr_reader :transport
|
|
83
|
+
|
|
84
|
+
# The Connection manager responsible for creating, maintaining and closing the connection and underlying transport
|
|
85
|
+
# @return [Ably::Realtime::Connection::ConnectionManager]
|
|
86
|
+
# @api private
|
|
87
|
+
attr_reader :manager
|
|
88
|
+
|
|
89
|
+
# An internal queue used to manage unsent outgoing messages. You should never interface with this array directly
|
|
90
|
+
# @return [Array]
|
|
91
|
+
# @api private
|
|
92
|
+
attr_reader :__outgoing_message_queue__
|
|
93
|
+
|
|
94
|
+
# An internal queue used to manage sent messages. You should never interface with this array directly
|
|
95
|
+
# @return [Array]
|
|
96
|
+
# @api private
|
|
97
|
+
attr_reader :__pending_message_queue__
|
|
98
|
+
|
|
99
|
+
# @api public
|
|
100
|
+
def initialize(client)
|
|
101
|
+
@client = client
|
|
102
|
+
@client_serial = -1
|
|
103
|
+
@__outgoing_message_queue__ = []
|
|
104
|
+
@__pending_message_queue__ = []
|
|
105
|
+
|
|
106
|
+
Client::IncomingMessageDispatcher.new client, self
|
|
107
|
+
Client::OutgoingMessageDispatcher.new client, self
|
|
108
|
+
|
|
109
|
+
@state_machine = ConnectionStateMachine.new(self)
|
|
110
|
+
@state = STATE(state_machine.current_state)
|
|
111
|
+
@manager = ConnectionManager.new(self)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Causes the connection to close, entering the closed state, from any state except
|
|
115
|
+
# the failed state. Once closed, the library will not attempt to re-establish the
|
|
116
|
+
# connection without a call to {Connection#connect}.
|
|
117
|
+
#
|
|
118
|
+
# @yield [Ably::Realtime::Connection] block is called as soon as this connection is in the Closed state
|
|
119
|
+
#
|
|
120
|
+
# @return [EventMachine::Deferrable]
|
|
121
|
+
#
|
|
122
|
+
def close(&success_block)
|
|
123
|
+
unless closing? || closed?
|
|
124
|
+
raise exception_for_state_change_to(:closing) unless can_transition_to?(:closing)
|
|
125
|
+
transition_state_machine :closing
|
|
126
|
+
end
|
|
127
|
+
deferrable_for_state_change_to(STATE.Closed, &success_block)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Causes the library to attempt connection. If it was previously explicitly
|
|
131
|
+
# closed by the user, or was closed as a result of an unrecoverable error, a new connection will be opened.
|
|
132
|
+
#
|
|
133
|
+
# @yield [Ably::Realtime::Connection] block is called as soon as this connection is in the Connected state
|
|
134
|
+
#
|
|
135
|
+
# @return [EventMachine::Deferrable]
|
|
136
|
+
#
|
|
137
|
+
def connect(&success_block)
|
|
138
|
+
unless connecting? || connected?
|
|
139
|
+
raise exception_for_state_change_to(:connecting) unless can_transition_to?(:connecting)
|
|
140
|
+
transition_state_machine :connecting
|
|
141
|
+
end
|
|
142
|
+
deferrable_for_state_change_to(STATE.Connected, &success_block)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Sends a ping to Ably and yields the provided block when a heartbeat ping request is echoed from the server.
|
|
146
|
+
# This can be useful for measuring true roundtrip client to Ably server latency for a simple message, or checking that an underlying transport is responding currently.
|
|
147
|
+
# The elapsed milliseconds is passed as an argument to the block and represents the time taken to echo a ping heartbeat once the connection is in the `:connected` state.
|
|
148
|
+
#
|
|
149
|
+
# @yield [Integer] if a block is passed to this method, then this block will be called once the ping heartbeat is received with the time elapsed in milliseconds
|
|
150
|
+
#
|
|
151
|
+
# @example
|
|
152
|
+
# client = Ably::Rest::Client.new(api_key: 'key.id:secret')
|
|
153
|
+
# client.connection.ping do |ms_elapsed|
|
|
154
|
+
# puts "Ping took #{ms_elapsed}ms"
|
|
155
|
+
# end
|
|
156
|
+
#
|
|
157
|
+
# @return [void]
|
|
158
|
+
#
|
|
159
|
+
def ping
|
|
160
|
+
raise RuntimeError, 'Cannot send a ping when connection is not open' if initialized?
|
|
161
|
+
raise RuntimeError, 'Cannot send a ping when connection is in a closed or failed state' if closed? || failed?
|
|
162
|
+
|
|
163
|
+
started = nil
|
|
164
|
+
|
|
165
|
+
wait_for_ping = Proc.new do |protocol_message|
|
|
166
|
+
if protocol_message.action == Ably::Models::ProtocolMessage::ACTION.Heartbeat
|
|
167
|
+
__incoming_protocol_msgbus__.unsubscribe(:protocol_message, &wait_for_ping)
|
|
168
|
+
time_passed = (Time.now.to_f * 1000 - started.to_f * 1000).to_i
|
|
169
|
+
yield time_passed if block_given?
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
once_or_if(STATE.Connected) do
|
|
174
|
+
started = Time.now
|
|
175
|
+
send_protocol_message action: Ably::Models::ProtocolMessage::ACTION.Heartbeat.to_i
|
|
176
|
+
__incoming_protocol_msgbus__.subscribe :protocol_message, &wait_for_ping
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# @yield [Boolean] True if an internet connection check appears to be up following an HTTP request to a reliable CDN
|
|
181
|
+
# @return [EventMachine::Deferrable]
|
|
182
|
+
# @api private
|
|
183
|
+
def internet_up?
|
|
184
|
+
EventMachine::DefaultDeferrable.new.tap do |deferrable|
|
|
185
|
+
EventMachine::HttpRequest.new("http#{'s' if client.use_tls?}:#{Ably::INTERNET_CHECK.fetch(:url)}").get.tap do |http|
|
|
186
|
+
http.errback do
|
|
187
|
+
yield false if block_given?
|
|
188
|
+
deferrable.fail
|
|
189
|
+
end
|
|
190
|
+
http.callback do
|
|
191
|
+
result = http.response_header.status == 200 && http.response.strip == Ably::INTERNET_CHECK.fetch(:ok_text)
|
|
192
|
+
yield result if block_given?
|
|
193
|
+
deferrable.succeed
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# @!attribute [r] recovery_key
|
|
200
|
+
# @return [String] recovery key that can be used by another client to recover this connection with the :recover option
|
|
201
|
+
def recovery_key
|
|
202
|
+
"#{key}:#{serial}" if connection_resumable?
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Following a new connection being made, resumed or recovered, the connection ID, connection key
|
|
206
|
+
# and message serial need to match the details provided by the server.
|
|
207
|
+
#
|
|
208
|
+
# @return [void]
|
|
209
|
+
# @api private
|
|
210
|
+
def configure_new(connection_id, connection_key, connection_serial)
|
|
211
|
+
@id = connection_id
|
|
212
|
+
@key = connection_key
|
|
213
|
+
@client_serial = connection_serial
|
|
214
|
+
|
|
215
|
+
update_connection_serial connection_serial
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Store last received connection serial so that the connection can be resumed from the last known point-in-time
|
|
219
|
+
# @return [void]
|
|
220
|
+
# @api private
|
|
221
|
+
def update_connection_serial(connection_serial)
|
|
222
|
+
@serial = connection_serial
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Disable automatic resume of a connection
|
|
226
|
+
# @return [void]
|
|
227
|
+
# @api private
|
|
228
|
+
def reset_resume_info
|
|
229
|
+
@key = nil
|
|
230
|
+
@serial = nil
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# @!attribute [r] __outgoing_protocol_msgbus__
|
|
234
|
+
# @return [Ably::Util::PubSub] Client library internal outgoing protocol message bus
|
|
235
|
+
# @api private
|
|
236
|
+
def __outgoing_protocol_msgbus__
|
|
237
|
+
@__outgoing_protocol_msgbus__ ||= create_pub_sub_message_bus
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# @!attribute [r] __incoming_protocol_msgbus__
|
|
241
|
+
# @return [Ably::Util::PubSub] Client library internal incoming protocol message bus
|
|
242
|
+
# @api private
|
|
243
|
+
def __incoming_protocol_msgbus__
|
|
244
|
+
@__incoming_protocol_msgbus__ ||= create_pub_sub_message_bus
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Determines the correct host name to use for the next connection attempt and updates current_host
|
|
248
|
+
# @yield [String] The host name used for this connection, for network connection failures a {Ably::FALLBACK_HOSTS fallback host} is used to route around networking or intermittent problems if an Internet connection is available
|
|
249
|
+
# @api private
|
|
250
|
+
def determine_host
|
|
251
|
+
raise ArgumentError, 'Block required' unless block_given?
|
|
252
|
+
|
|
253
|
+
if can_use_fallback_hosts?
|
|
254
|
+
internet_up? do |internet_is_up_result|
|
|
255
|
+
@current_host = if internet_is_up_result
|
|
256
|
+
client.fallback_endpoint.host
|
|
257
|
+
else
|
|
258
|
+
client.endpoint.host
|
|
259
|
+
end
|
|
260
|
+
yield current_host
|
|
261
|
+
end
|
|
262
|
+
else
|
|
263
|
+
@current_host = client.endpoint.host
|
|
264
|
+
yield current_host
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# @return [String] The current host that is configured following a call to method {#determine_host}
|
|
269
|
+
# @api private
|
|
270
|
+
attr_reader :current_host
|
|
271
|
+
|
|
272
|
+
# @!attribute [r] port
|
|
273
|
+
# @return [Integer] The default port used for this connection
|
|
274
|
+
def port
|
|
275
|
+
client.use_tls? ? 443 : 80
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# @!attribute [r] logger
|
|
279
|
+
# @return [Logger] The {Ably::Logger} for this client.
|
|
280
|
+
# Configure the log_level with the `:log_level` option, refer to {Ably::Realtime::Client#initialize}
|
|
281
|
+
def logger
|
|
282
|
+
client.logger
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Add protocol message to the outgoing message queue and notify the dispatcher that a message is
|
|
286
|
+
# ready to be sent
|
|
287
|
+
#
|
|
288
|
+
# @param [Ably::Models::ProtocolMessage] protocol_message
|
|
289
|
+
# @return [void]
|
|
290
|
+
# @api private
|
|
291
|
+
def send_protocol_message(protocol_message)
|
|
292
|
+
add_message_serial_if_ack_required_to(protocol_message) do
|
|
293
|
+
Ably::Models::ProtocolMessage.new(protocol_message).tap do |protocol_message|
|
|
294
|
+
add_message_to_outgoing_queue protocol_message
|
|
295
|
+
notify_message_dispatcher_of_new_message protocol_message
|
|
296
|
+
logger.debug("Connection: Prot msg queued =>: #{protocol_message.action} #{protocol_message}")
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# @api private
|
|
302
|
+
def add_message_to_outgoing_queue(protocol_message)
|
|
303
|
+
__outgoing_message_queue__ << protocol_message
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# @api private
|
|
307
|
+
def notify_message_dispatcher_of_new_message(protocol_message)
|
|
308
|
+
__outgoing_protocol_msgbus__.publish :protocol_message, protocol_message
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# @api private
|
|
312
|
+
def create_websocket_transport
|
|
313
|
+
raise ArgumentError, 'Block required' unless block_given?
|
|
314
|
+
|
|
315
|
+
blocking_operation = proc do
|
|
316
|
+
URI(client.endpoint).tap do |endpoint|
|
|
317
|
+
url_params = client.auth.auth_params.merge(
|
|
318
|
+
timestamp: as_since_epoch(Time.now),
|
|
319
|
+
format: client.protocol,
|
|
320
|
+
echo: client.echo_messages
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
if connection_resumable?
|
|
324
|
+
url_params.merge! resume: key, connection_serial: serial
|
|
325
|
+
logger.debug "Resuming connection key #{key} with serial #{serial}"
|
|
326
|
+
elsif connection_recoverable?
|
|
327
|
+
url_params.merge! recover: connection_recover_parts[:recover], connection_serial: connection_recover_parts[:connection_serial]
|
|
328
|
+
logger.debug "Recovering connection with key #{client.recover}"
|
|
329
|
+
once(:connected, :closed, :failed) do
|
|
330
|
+
client.disable_automatic_connection_recovery
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
endpoint.query = URI.encode_www_form(url_params)
|
|
335
|
+
end.to_s
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
callback = proc do |url|
|
|
339
|
+
determine_host do |host|
|
|
340
|
+
begin
|
|
341
|
+
logger.debug "Connection: Opening socket connection to #{host}:#{port} and URL '#{url}'"
|
|
342
|
+
@transport = EventMachine.connect(host, port, WebsocketTransport, self, url) do |websocket_transport|
|
|
343
|
+
yield websocket_transport if block_given?
|
|
344
|
+
end
|
|
345
|
+
rescue EventMachine::ConnectionError => error
|
|
346
|
+
manager.connection_opening_failed error
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# client.auth.auth_params is a blocking call, so defer this into a thread
|
|
352
|
+
EventMachine.defer blocking_operation, callback
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# @api private
|
|
356
|
+
def release_websocket_transport
|
|
357
|
+
@transport = nil
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# @api private
|
|
361
|
+
def set_failed_connection_error_reason(error)
|
|
362
|
+
@error_reason = error
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# As we are using a state machine, do not allow change_state to be used
|
|
366
|
+
# #transition_state_machine must be used instead
|
|
367
|
+
private :change_state
|
|
368
|
+
|
|
369
|
+
private
|
|
370
|
+
|
|
371
|
+
# The client serial is incremented for every message that is published that requires an ACK.
|
|
372
|
+
# Note that this is different to the connection serial that contains the last known serial number
|
|
373
|
+
# received from the server.
|
|
374
|
+
#
|
|
375
|
+
# A client serial number therefore does not guarantee a message has been received, only sent.
|
|
376
|
+
# A connection serial guarantees the server has received the message and is thus used for connection
|
|
377
|
+
# recovery and resumes.
|
|
378
|
+
# @return [Integer] starting at -1 indicating no messages sent, 0 when the first message is sent
|
|
379
|
+
attr_reader :client_serial
|
|
380
|
+
|
|
381
|
+
def create_pub_sub_message_bus
|
|
382
|
+
Ably::Util::PubSub.new(
|
|
383
|
+
coerce_into: Proc.new do |event|
|
|
384
|
+
raise KeyError, "Expected :protocol_message, :#{event} is disallowed" unless event == :protocol_message
|
|
385
|
+
:protocol_message
|
|
386
|
+
end
|
|
387
|
+
)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def add_message_serial_if_ack_required_to(protocol_message)
|
|
391
|
+
if Ably::Models::ProtocolMessage.ack_required?(protocol_message[:action])
|
|
392
|
+
add_message_serial_to(protocol_message) { yield }
|
|
393
|
+
else
|
|
394
|
+
yield
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def add_message_serial_to(protocol_message)
|
|
399
|
+
@client_serial += 1
|
|
400
|
+
protocol_message[:msgSerial] = client_serial
|
|
401
|
+
yield
|
|
402
|
+
rescue StandardError => e
|
|
403
|
+
@client_serial -= 1
|
|
404
|
+
raise e
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# Simply wait until the next EventMachine tick to ensure Connection initialization is complete
|
|
408
|
+
def when_initialized
|
|
409
|
+
EventMachine.next_tick { yield }
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def connection_resumable?
|
|
413
|
+
!key.nil? && !serial.nil?
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def connection_recoverable?
|
|
417
|
+
connection_recover_parts
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def connection_recover_parts
|
|
421
|
+
client.recover.to_s.match(RECOVER_REGEX)
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def can_use_fallback_hosts?
|
|
425
|
+
if client.environment.nil? && client.custom_realtime_host.nil?
|
|
426
|
+
if connecting? && previous_state
|
|
427
|
+
use_fallback_if_disconnected? || use_fallback_if_suspended?
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def use_fallback_if_disconnected?
|
|
433
|
+
second_reconnect_attempt_for(:disconnected, 1)
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def use_fallback_if_suspended?
|
|
437
|
+
second_reconnect_attempt_for(:suspended, 2) # on first suspended state use default Ably host again
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def second_reconnect_attempt_for(state, first_attempt_count)
|
|
441
|
+
previous_state == state && manager.retry_count_for_state(state) >= first_attempt_count
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
end
|