ably 0.7.0 → 0.7.1

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 (49) hide show
  1. data/.travis.yml +2 -2
  2. data/Rakefile +2 -0
  3. data/SPEC.md +230 -194
  4. data/ably.gemspec +2 -0
  5. data/lib/ably/auth.rb +7 -5
  6. data/lib/ably/models/idiomatic_ruby_wrapper.rb +5 -7
  7. data/lib/ably/models/paginated_resource.rb +14 -21
  8. data/lib/ably/models/protocol_message.rb +1 -1
  9. data/lib/ably/modules/ably.rb +4 -0
  10. data/lib/ably/modules/async_wrapper.rb +2 -2
  11. data/lib/ably/modules/channels_collection.rb +31 -8
  12. data/lib/ably/modules/conversions.rb +10 -0
  13. data/lib/ably/modules/enum.rb +2 -3
  14. data/lib/ably/modules/state_emitter.rb +8 -8
  15. data/lib/ably/modules/state_machine.rb +7 -3
  16. data/lib/ably/realtime/channel.rb +6 -5
  17. data/lib/ably/realtime/channel/channel_manager.rb +11 -10
  18. data/lib/ably/realtime/channel/channel_state_machine.rb +10 -9
  19. data/lib/ably/realtime/channels.rb +3 -0
  20. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +11 -1
  21. data/lib/ably/realtime/connection.rb +55 -16
  22. data/lib/ably/realtime/connection/connection_manager.rb +25 -8
  23. data/lib/ably/realtime/connection/connection_state_machine.rb +9 -9
  24. data/lib/ably/realtime/connection/websocket_transport.rb +2 -2
  25. data/lib/ably/realtime/presence.rb +16 -17
  26. data/lib/ably/util/crypto.rb +1 -1
  27. data/lib/ably/version.rb +1 -1
  28. data/spec/acceptance/realtime/channel_history_spec.rb +6 -5
  29. data/spec/acceptance/realtime/connection_failures_spec.rb +103 -27
  30. data/spec/acceptance/realtime/connection_spec.rb +81 -17
  31. data/spec/acceptance/realtime/presence_spec.rb +82 -30
  32. data/spec/acceptance/rest/auth_spec.rb +22 -19
  33. data/spec/acceptance/rest/client_spec.rb +4 -4
  34. data/spec/acceptance/rest/presence_spec.rb +12 -6
  35. data/spec/rspec_config.rb +9 -0
  36. data/spec/shared/model_behaviour.rb +1 -1
  37. data/spec/spec_helper.rb +4 -1
  38. data/spec/support/event_machine_helper.rb +26 -37
  39. data/spec/support/markdown_spec_formatter.rb +96 -68
  40. data/spec/support/rest_testapp_before_retry.rb +15 -0
  41. data/spec/support/test_app.rb +4 -0
  42. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +20 -2
  43. data/spec/unit/models/message_spec.rb +1 -1
  44. data/spec/unit/models/paginated_resource_spec.rb +15 -1
  45. data/spec/unit/modules/enum_spec.rb +10 -0
  46. data/spec/unit/realtime/channels_spec.rb +30 -0
  47. data/spec/unit/rest/channels_spec.rb +30 -0
  48. metadata +101 -35
  49. checksums.yaml +0 -7
@@ -15,6 +15,7 @@ module Ably
15
15
  # @param name [String] The name of the channel
16
16
  # @param channel_options [Hash] Channel options, currently reserved for Encryption options
17
17
  # @return [Ably::Realtime::Channel}
18
+ #
18
19
  def get(*args)
19
20
  super
20
21
  end
@@ -27,6 +28,7 @@ module Ably
27
28
  # @yield [options] (optional) if a missing_block is passed to this method and no channel exists matching the name, this block is called
28
29
  # @yieldparam [String] name of the missing channel
29
30
  # @return [Ably::Realtime::Channel]
31
+ #
30
32
  def fetch(*args)
31
33
  super
32
34
  end
@@ -37,6 +39,7 @@ module Ably
37
39
  # the memory footprint of the {Ably::Realtime::Channel Realtime Channel object}. Release channels to free up resources if required
38
40
  #
39
41
  # @return [void]
42
+ #
40
43
  def release(channel)
41
44
  get(channel).detach do
42
45
  @channels.delete(channel)
@@ -54,7 +54,7 @@ module Ably::Realtime
54
54
 
55
55
  when ACTION.Connect
56
56
  when ACTION.Connected
57
- connection.transition_state_machine :connected
57
+ connection.transition_state_machine :connected, protocol_message.error
58
58
 
59
59
  when ACTION.Disconnect, ACTION.Disconnected
60
60
  connection.transition_state_machine :disconnected, protocol_message.error
@@ -118,6 +118,7 @@ module Ably::Realtime
118
118
  def update_connection_recovery_info(protocol_message)
119
119
  if protocol_message.connection_key && (protocol_message.connection_key != connection.key)
120
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
121
122
  connection.update_connection_id_and_key protocol_message.connection_id, protocol_message.connection_key
122
123
  end
123
124
 
@@ -126,6 +127,15 @@ module Ably::Realtime
126
127
  end
127
128
  end
128
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
+
129
139
  def ack_pending_queue_for_message_serial(ack_protocol_message)
130
140
  drop_pending_queue_from_ack(ack_protocol_message) do |protocol_message|
131
141
  ack_messages protocol_message.messages
@@ -156,7 +156,7 @@ module Ably
156
156
  #
157
157
  # @return [void]
158
158
  #
159
- def ping(&block)
159
+ def ping
160
160
  raise RuntimeError, 'Cannot send a ping when connection is not open' if initialized?
161
161
  raise RuntimeError, 'Cannot send a ping when connection is in a closed or failed state' if closed? || failed?
162
162
 
@@ -166,7 +166,7 @@ module Ably
166
166
  if protocol_message.action == Ably::Models::ProtocolMessage::ACTION.Heartbeat
167
167
  __incoming_protocol_msgbus__.unsubscribe(:protocol_message, &wait_for_ping)
168
168
  time_passed = (Time.now.to_f * 1000 - started.to_f * 1000).to_i
169
- block.call time_passed if block_given?
169
+ yield time_passed if block_given?
170
170
  end
171
171
  end
172
172
 
@@ -177,6 +177,25 @@ module Ably
177
177
  end
178
178
  end
179
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(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
+
180
199
  # @!attribute [r] recovery_key
181
200
  # @return [String] recovery key that can be used by another client to recover this connection with the :recover option
182
201
  def recovery_key
@@ -220,16 +239,31 @@ module Ably
220
239
  @__incoming_protocol_msgbus__ ||= create_pub_sub_message_bus
221
240
  end
222
241
 
223
- # @!attribute [r] host
224
- # @return [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
225
- def host
242
+ # Determines the correct host name to use for the next connection attempt and updates current_host
243
+ # @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
244
+ # @api private
245
+ def determine_host
246
+ raise ArgumentError, 'Block required' unless block_given?
247
+
226
248
  if can_use_fallback_hosts?
227
- client.fallback_endpoint.host
249
+ internet_up? do |internet_is_up_result|
250
+ @current_host = if internet_is_up_result
251
+ client.fallback_endpoint.host
252
+ else
253
+ client.endpoint.host
254
+ end
255
+ yield current_host
256
+ end
228
257
  else
229
- client.endpoint.host
258
+ @current_host = client.endpoint.host
259
+ yield current_host
230
260
  end
231
261
  end
232
262
 
263
+ # @return [String] The current host that is configured following a call to method {#determine_host}
264
+ # @api private
265
+ attr_reader :current_host
266
+
233
267
  # @!attribute [r] port
234
268
  # @return [Integer] The default port used for this connection
235
269
  def port
@@ -270,8 +304,10 @@ module Ably
270
304
  end
271
305
 
272
306
  # @api private
273
- def create_websocket_transport(&block)
274
- operation = proc do
307
+ def create_websocket_transport
308
+ raise ArgumentError, 'Block required' unless block_given?
309
+
310
+ blocking_operation = proc do
275
311
  URI(client.endpoint).tap do |endpoint|
276
312
  url_params = client.auth.auth_params.merge(
277
313
  timestamp: as_since_epoch(Time.now),
@@ -295,17 +331,20 @@ module Ably
295
331
  end
296
332
 
297
333
  callback = proc do |url|
298
- begin
299
- @transport = EventMachine.connect(host, port, WebsocketTransport, self, url) do |websocket_transport|
300
- yield websocket_transport if block_given?
334
+ determine_host do |host|
335
+ begin
336
+ logger.debug "Connection: Opening socket connection to #{host}:#{port} and URL '#{url}'"
337
+ @transport = EventMachine.connect(host, port, WebsocketTransport, self, url) do |websocket_transport|
338
+ yield websocket_transport if block_given?
339
+ end
340
+ rescue EventMachine::ConnectionError => error
341
+ manager.connection_opening_failed error
301
342
  end
302
- rescue EventMachine::ConnectionError => error
303
- manager.connection_opening_failed error
304
343
  end
305
344
  end
306
345
 
307
346
  # client.auth.auth_params is a blocking call, so defer this into a thread
308
- EventMachine.defer operation, callback
347
+ EventMachine.defer blocking_operation, callback
309
348
  end
310
349
 
311
350
  # @api private
@@ -350,7 +389,7 @@ module Ably
350
389
  end
351
390
 
352
391
  # Simply wait until the next EventMachine tick to ensure Connection initialization is complete
353
- def when_initialized(&block)
392
+ def when_initialized
354
393
  EventMachine.next_tick { yield }
355
394
  end
356
395
 
@@ -47,7 +47,7 @@ module Ably::Realtime
47
47
  #
48
48
  # @yield [Ably::Realtime::Connection::WebsocketTransport] block is called with new websocket transport
49
49
  # @api private
50
- def setup_transport(&block)
50
+ def setup_transport
51
51
  if transport && !transport.ready_for_release?
52
52
  raise RuntimeError, 'Existing WebsocketTransport is connected, and must be closed first'
53
53
  end
@@ -57,14 +57,14 @@ module Ably::Realtime
57
57
  return
58
58
  end
59
59
 
60
- logger.debug "ConnectionManager: Opening connection to #{connection.host}:#{connection.port}"
60
+ logger.debug 'ConnectionManager: Opening a websocket transport connection'
61
61
 
62
62
  connection.create_websocket_transport do |websocket_transport|
63
63
  subscribe_to_transport_events websocket_transport
64
64
  yield websocket_transport if block_given?
65
65
  end
66
66
 
67
- logger.debug 'ConnectionManager: Setting up automatic connection timeout timer for #{TIMEOUTS.fetch(:open)}s'
67
+ logger.debug "ConnectionManager: Setting up automatic connection timeout timer for #{TIMEOUTS.fetch(:open)}s"
68
68
  create_timeout_timer_whilst_in_state(:connect, TIMEOUTS.fetch(:open)) do
69
69
  connection_opening_failed Ably::Exceptions::ConnectionTimeoutError.new("Connection to Ably timed out after #{TIMEOUTS.fetch(:open)}s", nil, 80014)
70
70
  end
@@ -74,10 +74,18 @@ module Ably::Realtime
74
74
  #
75
75
  # @api private
76
76
  def connection_opening_failed(error)
77
- logger.warn "ConnectionManager: Connection to #{connection.host}:#{connection.port} failed; #{error.message}"
77
+ logger.warn "ConnectionManager: Connection to #{connection.current_host}:#{connection.port} failed; #{error.message}"
78
78
  connection.transition_state_machine next_retry_state, Ably::Exceptions::ConnectionError.new("Connection failed; #{error.message}", nil, 80000)
79
79
  end
80
80
 
81
+ # Called whenever a new connection message is received with an error
82
+ #
83
+ # @api private
84
+ def connected_with_error(error)
85
+ logger.warn "ConnectionManager: Connected with error; #{error.message}"
86
+ connection.trigger :error, error
87
+ end
88
+
81
89
  # Ensures the underlying transport has been disconnected and all event emitter callbacks removed
82
90
  #
83
91
  # @api private
@@ -96,7 +104,7 @@ module Ably::Realtime
96
104
  if !transport || transport.disconnected?
97
105
  setup_transport
98
106
  else
99
- transport.reconnect connection.host, connection.port
107
+ transport.reconnect connection.current_host, connection.port
100
108
  end
101
109
  end
102
110
 
@@ -119,6 +127,15 @@ module Ably::Realtime
119
127
  connection.transition_state_machine :closed
120
128
  end
121
129
 
130
+ # Connection has failed
131
+ #
132
+ # @api private
133
+ def fail(error)
134
+ connection.logger.fatal "ConnectionManager: Connection failed - #{error}"
135
+ connection.manager.destroy_transport
136
+ connection.once(:failed) { connection.trigger :error, error }
137
+ end
138
+
122
139
  # When a connection is disconnected whilst connecting, attempt reconnect and/or set state to :suspended or :failed
123
140
  #
124
141
  # @api private
@@ -192,11 +209,11 @@ module Ably::Realtime
192
209
 
193
210
  # Create a timer that will execute in timeout_in seconds.
194
211
  # If the connection state changes however, cancel the timer
195
- def create_timeout_timer_whilst_in_state(timer_id, timeout_in, &block)
196
- raise 'Block required for timer' unless block_given?
212
+ def create_timeout_timer_whilst_in_state(timer_id, timeout_in)
213
+ raise ArgumentError, 'Block required' unless block_given?
197
214
 
198
215
  timers[timer_id] << EventMachine::Timer.new(timeout_in) do
199
- block.call
216
+ yield
200
217
  end
201
218
  connection.once_state_changed { clear_timers timer_id }
202
219
  end
@@ -40,6 +40,10 @@ module Ably::Realtime
40
40
  connection.manager.reconnect_transport
41
41
  end
42
42
 
43
+ after_transition(to: [:connected]) do |connection, current_transition|
44
+ connection.manager.connected_with_error current_transition.metadata if current_transition.metadata
45
+ end
46
+
43
47
  after_transition(to: [:disconnected, :suspended], from: [:connecting]) do |connection, current_transition|
44
48
  connection.manager.respond_to_transport_disconnected_when_connecting current_transition
45
49
  end
@@ -52,9 +56,8 @@ module Ably::Realtime
52
56
  connection.manager.destroy_transport # never reuse a transport if the connection has failed
53
57
  end
54
58
 
55
- after_transition(to: [:failed]) do |connection, current_transition|
56
- connection.logger.fatal "ConnectionStateMachine: Connection failed - #{current_transition.metadata}"
57
- connection.manager.destroy_transport
59
+ before_transition(to: [:failed]) do |connection, current_transition|
60
+ connection.manager.fail current_transition.metadata
58
61
  end
59
62
 
60
63
  after_transition(to: [:closing], from: [:initialized, :disconnected, :suspended]) do |connection|
@@ -70,12 +73,9 @@ module Ably::Realtime
70
73
  end
71
74
 
72
75
  # Transitions responsible for updating connection#error_reason
73
- before_transition(to: [:disconnected, :suspended, :failed]) do |connection, current_transition|
74
- connection.set_failed_connection_error_reason current_transition.metadata
75
- end
76
-
77
- before_transition(to: [:connected, :closed]) do |connection, current_transition|
78
- connection.set_failed_connection_error_reason nil
76
+ before_transition(to: [:connected, :closed, :disconnected, :suspended, :failed]) do |connection, current_transition|
77
+ reason = current_transition.metadata if is_error_type?(current_transition.metadata)
78
+ connection.set_failed_connection_error_reason reason
79
79
  end
80
80
 
81
81
  private
@@ -128,9 +128,9 @@ module Ably::Realtime
128
128
  end
129
129
  end
130
130
 
131
- def create_timer(period, &block)
131
+ def create_timer(period)
132
132
  @timer = EventMachine::Timer.new(period) do
133
- block.call
133
+ yield
134
134
  end
135
135
  end
136
136
 
@@ -57,7 +57,7 @@ module Ably::Realtime
57
57
  #
58
58
  def enter(options = {}, &success_block)
59
59
  @client_id = options.fetch(:client_id, client_id)
60
- @data = options.fetch(:data, data)
60
+ @data = options.fetch(:data, nil)
61
61
  deferrable = EventMachine::DefaultDeferrable.new
62
62
 
63
63
  raise Ably::Exceptions::Standard.new('Unable to enter presence channel without a client_id', 400, 91000) unless client_id
@@ -115,7 +115,7 @@ module Ably::Realtime
115
115
  # @return (see Presence#enter)
116
116
  #
117
117
  def leave(options = {}, &success_block)
118
- @data = options.fetch(:data) if options.has_key?(:data)
118
+ @data = options.fetch(:data, data) # nil value defaults leave data to existing value
119
119
  deferrable = EventMachine::DefaultDeferrable.new
120
120
 
121
121
  raise Ably::Exceptions::Standard.new('Unable to leave presence channel that is not entered', 400, 91002) unless able_to_leave?
@@ -168,7 +168,7 @@ module Ably::Realtime
168
168
  # @return (see Presence#enter)
169
169
  #
170
170
  def update(options = {}, &success_block)
171
- @data = options.fetch(:data) if options.has_key?(:data)
171
+ @data = options.fetch(:data, nil)
172
172
  deferrable = EventMachine::DefaultDeferrable.new
173
173
 
174
174
  ensure_channel_attached(deferrable) do
@@ -211,9 +211,9 @@ module Ably::Realtime
211
211
  #
212
212
  # @yield [Array<Ably::Models::PresenceMessage>] array of members or the member
213
213
  #
214
- # @return [EventMachine::Deferrable] Deferrable that supports both success (callback) and failure (errback) callback
214
+ # @return [EventMachine::Deferrable] Deferrable that supports both success (callback) and failure (errback) callbacks
215
215
  #
216
- def get(options = {}, &success_block)
216
+ def get(options = {})
217
217
  wait_for_sync = options.fetch(:wait_for_sync, true)
218
218
  deferrable = EventMachine::DefaultDeferrable.new
219
219
 
@@ -222,18 +222,17 @@ module Ably::Realtime
222
222
  members.map { |key, presence| presence }.tap do |filtered_members|
223
223
  filtered_members.keep_if { |presence| presence.connection_id == options[:connection_id] } if options[:connection_id]
224
224
  filtered_members.keep_if { |presence| presence.client_id == options[:client_id] } if options[:client_id]
225
+ end.tap do |current_members|
226
+ yield current_members if block_given?
227
+ deferrable.succeed current_members
225
228
  end
226
229
  end
227
230
 
228
231
  if !wait_for_sync || sync_complete?
229
- result = result_block.call
230
- success_block.call result if block_given?
231
- deferrable.succeed result
232
+ result_block.call
232
233
  else
233
234
  sync_pubsub.once(:done) do
234
- result = result_block.call
235
- success_block.call result if block_given?
236
- deferrable.succeed result
235
+ result_block.call
237
236
  end
238
237
 
239
238
  sync_pubsub.once(:failed) do |error|
@@ -486,7 +485,7 @@ module Ably::Realtime
486
485
 
487
486
  def send_protocol_message_and_transition_state_to(action, options = {}, &success_block)
488
487
  deferrable = options.fetch(:deferrable) { raise ArgumentError, 'option :deferrable is required' }
489
- client_id = options.fetch(:client_id) { raise ArgumentError, 'option :client_id is required' }
488
+ client_id = options.fetch(:client_id) { raise ArgumentError, 'option :client_id is required' }
490
489
  target_state = options.fetch(:target_state, nil)
491
490
  failed_state = options.fetch(:failed_state, nil)
492
491
 
@@ -509,14 +508,14 @@ module Ably::Realtime
509
508
  end
510
509
  end
511
510
 
512
- def deferrable_succeed(deferrable, *args, &block)
513
- block.call self, *args if block_given?
514
- EventMachine.next_tick { deferrable.succeed self, *args } # allow callback to be added to the returned Deferrable
511
+ def deferrable_succeed(deferrable, *args)
512
+ yield self, *args if block_given?
513
+ EventMachine.next_tick { deferrable.succeed self, *args } # allow callback to be added to the returned Deferrable before calling succeed
515
514
  deferrable
516
515
  end
517
516
 
518
- def deferrable_fail(deferrable, *args, &block)
519
- block.call self, *args if block_given?
517
+ def deferrable_fail(deferrable, *args)
518
+ yield self, *args if block_given?
520
519
  EventMachine.next_tick { deferrable.fail self, *args } # allow errback to be added to the returned Deferrable
521
520
  deferrable
522
521
  end
@@ -62,7 +62,7 @@ module Ably::Util
62
62
  # @return [String]
63
63
  #
64
64
  def decrypt(encrypted_payload_with_iv)
65
- raise Ably::Exceptions::EncryptionError, 'iv is missing or not long enough' unless encrypted_payload_with_iv.length >= BLOCK_LENGTH*2
65
+ raise Ably::Exceptions::CipherError, 'iv is missing or not long enough' unless encrypted_payload_with_iv.length >= BLOCK_LENGTH*2
66
66
 
67
67
  iv = encrypted_payload_with_iv.slice(0...BLOCK_LENGTH)
68
68
  encrypted_payload = encrypted_payload_with_iv.slice(BLOCK_LENGTH..-1)
data/lib/ably/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ably
2
- VERSION = '0.7.0'
2
+ VERSION = '0.7.1'
3
3
  end
@@ -60,8 +60,9 @@ describe Ably::Realtime::Channel, '#history', :event_machine do
60
60
  end
61
61
 
62
62
  context 'with lots of messages published with a single client and channel' do
63
- let(:messages_sent) { 40 }
64
- let(:limit) { 20 }
63
+ let(:messages_sent) { 30 }
64
+ let(:rate_per_second) { 4 }
65
+ let(:limit) { 15 }
65
66
 
66
67
  def ensure_message_history_direction_and_paging_is_correct(direction)
67
68
  channel.history(direction: direction, limit: limit) do |history|
@@ -102,10 +103,10 @@ describe Ably::Realtime::Channel, '#history', :event_machine do
102
103
  end
103
104
  end
104
105
 
105
- context 'in multiple ProtocolMessages' do
106
+ context 'in multiple ProtocolMessages', em_timeout: (30 / 4) + 20 do
106
107
  it 'retrieves limited history forwards with pagination' do
107
108
  messages_sent.times do |index|
108
- EventMachine.add_timer(index.to_f / 10) do
109
+ EventMachine.add_timer(index.to_f / rate_per_second) do
109
110
  channel.publish('event', "history#{index}") do
110
111
  next unless index == messages_sent - 1
111
112
  ensure_message_history_direction_and_paging_is_correct :forwards
@@ -116,7 +117,7 @@ describe Ably::Realtime::Channel, '#history', :event_machine do
116
117
 
117
118
  it 'retrieves limited history backwards with pagination' do
118
119
  messages_sent.times.to_a.reverse.each do |index|
119
- EventMachine.add_timer((messages_sent - index).to_f / 10) do
120
+ EventMachine.add_timer((messages_sent - index).to_f / rate_per_second) do
120
121
  channel.publish('event', "history#{index}") do
121
122
  next unless index == 0
122
123
  ensure_message_history_direction_and_paging_is_correct :backwards if index == 0