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.
Files changed (148) hide show
  1. checksums.yaml +13 -5
  2. data/.gitmodules +1 -1
  3. data/.rspec +1 -0
  4. data/.travis.yml +7 -3
  5. data/SPEC.md +495 -419
  6. data/ably-rest.gemspec +19 -5
  7. data/lib/ably-rest.rb +9 -1
  8. data/lib/submodules/ably-ruby/.gitignore +6 -0
  9. data/lib/submodules/ably-ruby/.rspec +1 -0
  10. data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
  11. data/lib/submodules/ably-ruby/.travis.yml +10 -0
  12. data/lib/submodules/ably-ruby/Gemfile +4 -0
  13. data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
  14. data/lib/submodules/ably-ruby/README.md +122 -0
  15. data/lib/submodules/ably-ruby/Rakefile +34 -0
  16. data/lib/submodules/ably-ruby/SPEC.md +1794 -0
  17. data/lib/submodules/ably-ruby/ably.gemspec +36 -0
  18. data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
  19. data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
  20. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
  21. data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
  22. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
  23. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
  24. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
  25. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
  26. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
  27. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
  28. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
  29. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
  30. data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
  31. data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
  32. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
  33. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
  34. data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
  35. data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
  36. data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
  37. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
  38. data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
  39. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
  40. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
  41. data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
  42. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
  43. data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
  44. data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
  45. data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
  46. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
  47. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
  48. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
  49. data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  50. data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
  51. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
  52. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
  53. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
  54. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
  55. data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
  56. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
  57. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
  58. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
  59. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
  60. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
  61. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
  62. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
  63. data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
  64. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
  65. data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
  66. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
  67. data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
  68. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
  69. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
  70. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
  71. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
  72. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  73. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
  74. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
  75. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
  76. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
  77. data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
  78. data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
  79. data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
  80. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
  81. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
  82. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
  83. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
  84. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
  85. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
  86. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
  87. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
  88. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
  89. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
  90. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
  91. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
  92. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
  93. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
  94. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
  95. data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
  96. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
  97. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
  98. data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
  99. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
  100. data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
  101. data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
  102. data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
  103. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
  104. data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
  105. data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
  106. data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
  107. data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
  108. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
  109. data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
  110. data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
  111. data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
  112. data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
  113. data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
  114. data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
  115. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
  116. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
  117. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
  118. data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
  119. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
  120. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
  121. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
  122. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
  123. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
  124. data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
  125. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
  126. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
  127. data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
  128. data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
  129. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
  130. data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
  131. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
  132. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
  133. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
  134. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
  135. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
  136. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
  137. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
  138. data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
  139. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
  140. data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
  141. data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
  142. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
  143. data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
  144. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
  145. data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
  146. data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
  147. data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
  148. 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