ably 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -1
  3. data/ably.gemspec +4 -3
  4. data/lib/ably.rb +6 -2
  5. data/lib/ably/auth.rb +24 -16
  6. data/lib/ably/exceptions.rb +16 -5
  7. data/lib/ably/{realtime/models → models}/error_info.rb +9 -11
  8. data/lib/ably/models/idiomatic_ruby_wrapper.rb +57 -26
  9. data/lib/ably/{realtime/models → models}/message.rb +45 -38
  10. data/lib/ably/{realtime/models → models}/nil_channel.rb +4 -4
  11. data/lib/ably/{rest/models/paged_resource.rb → models/paginated_resource.rb} +21 -10
  12. data/lib/ably/models/presence_message.rb +126 -0
  13. data/lib/ably/{realtime/models → models}/protocol_message.rb +76 -38
  14. data/lib/ably/models/token.rb +74 -0
  15. data/lib/ably/modules/channels_collection.rb +49 -0
  16. data/lib/ably/modules/conversions.rb +2 -0
  17. data/lib/ably/modules/event_emitter.rb +43 -8
  18. data/lib/ably/modules/event_machine_helpers.rb +1 -0
  19. data/lib/ably/modules/http_helpers.rb +9 -2
  20. data/lib/ably/modules/message_pack.rb +14 -0
  21. data/lib/ably/modules/model_common.rb +29 -0
  22. data/lib/ably/modules/{state.rb → state_emitter.rb} +8 -7
  23. data/lib/ably/realtime.rb +37 -7
  24. data/lib/ably/realtime/channel.rb +154 -31
  25. data/lib/ably/realtime/channels.rb +47 -0
  26. data/lib/ably/realtime/client.rb +39 -33
  27. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +50 -21
  28. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +9 -11
  29. data/lib/ably/realtime/connection.rb +148 -79
  30. data/lib/ably/realtime/connection/connection_state_machine.rb +111 -0
  31. data/lib/ably/realtime/connection/websocket_transport.rb +161 -0
  32. data/lib/ably/realtime/presence.rb +270 -0
  33. data/lib/ably/rest.rb +14 -3
  34. data/lib/ably/rest/channel.rb +3 -3
  35. data/lib/ably/rest/channels.rb +26 -12
  36. data/lib/ably/rest/client.rb +42 -25
  37. data/lib/ably/rest/middleware/exceptions.rb +21 -23
  38. data/lib/ably/rest/middleware/external_exceptions.rb +8 -10
  39. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  40. data/lib/ably/rest/middleware/parse_json.rb +9 -2
  41. data/lib/ably/rest/middleware/parse_message_pack.rb +6 -2
  42. data/lib/ably/rest/presence.rb +4 -4
  43. data/lib/ably/version.rb +1 -1
  44. data/spec/acceptance/realtime/channel_history_spec.rb +125 -0
  45. data/spec/acceptance/realtime/channel_spec.rb +135 -63
  46. data/spec/acceptance/realtime/connection_spec.rb +86 -0
  47. data/spec/acceptance/realtime/message_spec.rb +116 -94
  48. data/spec/acceptance/realtime/presence_history_spec.rb +0 -0
  49. data/spec/acceptance/realtime/presence_spec.rb +277 -0
  50. data/spec/acceptance/rest/auth_spec.rb +351 -347
  51. data/spec/acceptance/rest/base_spec.rb +43 -26
  52. data/spec/acceptance/rest/channel_spec.rb +88 -83
  53. data/spec/acceptance/rest/channels_spec.rb +32 -28
  54. data/spec/acceptance/rest/presence_spec.rb +83 -63
  55. data/spec/acceptance/rest/stats_spec.rb +38 -37
  56. data/spec/acceptance/rest/time_spec.rb +10 -6
  57. data/spec/integration/modules/{state_spec.rb → state_emitter_spec.rb} +16 -2
  58. data/spec/spec_helper.rb +14 -0
  59. data/spec/support/api_helper.rb +4 -0
  60. data/spec/support/model_helper.rb +28 -9
  61. data/spec/support/protocol_msgbus_helper.rb +8 -1
  62. data/spec/support/test_app.rb +24 -14
  63. data/spec/unit/{realtime → models}/error_info_spec.rb +4 -4
  64. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +46 -9
  65. data/spec/unit/models/message_spec.rb +229 -0
  66. data/spec/unit/{rest/paged_resource_spec.rb → models/paginated_resource_spec.rb} +19 -11
  67. data/spec/unit/models/presence_message_spec.rb +230 -0
  68. data/spec/unit/models/protocol_message_spec.rb +280 -0
  69. data/spec/unit/{token_spec.rb → models/token_spec.rb} +18 -22
  70. data/spec/unit/modules/conversions_spec.rb +1 -1
  71. data/spec/unit/modules/event_emitter_spec.rb +36 -4
  72. data/spec/unit/realtime/channel_spec.rb +76 -2
  73. data/spec/unit/realtime/channels_spec.rb +50 -0
  74. data/spec/unit/realtime/client_spec.rb +31 -1
  75. data/spec/unit/realtime/connection_spec.rb +8 -15
  76. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +6 -6
  77. data/spec/unit/realtime/presence_spec.rb +100 -0
  78. data/spec/unit/rest/channels_spec.rb +48 -0
  79. metadata +72 -38
  80. data/lib/ably/realtime/models/shared.rb +0 -17
  81. data/lib/ably/rest/models/message.rb +0 -64
  82. data/lib/ably/rest/models/presence_message.rb +0 -21
  83. data/lib/ably/token.rb +0 -80
  84. data/spec/unit/realtime/message_spec.rb +0 -117
  85. data/spec/unit/realtime/protocol_message_spec.rb +0 -172
  86. data/spec/unit/rest/message_spec.rb +0 -75
@@ -0,0 +1,47 @@
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
+ def get(*args)
19
+ super
20
+ end
21
+
22
+ # @!method fetch(name, &missing_block)
23
+ # Return a {Ably::Realtime::Channel} for the given name if it exists, else the block will be called.
24
+ # 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
25
+ #
26
+ # @param name [String] The name of the channel
27
+ # @yield [options] (optional) if a missing_block is passed to this method and no channel exists matching the name, this block is called
28
+ # @yieldparam [String] name of the missing channel
29
+ # @return [Ably::Realtime::Channel]
30
+ def fetch(*args)
31
+ super
32
+ end
33
+
34
+ # Detaches the {Ably::Realtime::Channel Realtime Channel} and releases all associated resources.
35
+ #
36
+ # Releasing a Realtime Channel is not typically necessary as a channel, once detached, consumes no resources other than
37
+ # the memory footprint of the {Ably::Realtime::Channel Realtime Channel object}. Release channels to free up resources if required
38
+ #
39
+ # @return [void]
40
+ def release(channel)
41
+ get(channel).detach do
42
+ @channels.delete(channel)
43
+ end if @channels.has_key?(channel)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -8,18 +8,24 @@ module Ably
8
8
  # (see Ably::Rest::Client#client_id)
9
9
  # @!attribute [r] auth_options
10
10
  # (see Ably::Rest::Client#auth_options)
11
- # @!attribute [r] tls
12
- # (see Ably::Rest::Client#tls)
13
11
  # @!attribute [r] environment
14
12
  # (see Ably::Rest::Client#environment)
13
+ # @!attribute [r] channels
14
+ # @return [Aby::Realtime::Channels] The collection of {Ably::Realtime::Channel}s that have been created
15
+ # @!attribute [r] rest_client
16
+ # @return [Ably::Rest::Client] The {Ably::Rest::Client REST client} instantiated with the same credentials and configuration that is used for all REST operations such as authentication
17
+ # @!attribute [r] echo_messages
18
+ # @return [Boolean] If false, suppresses messages originating from this connection being echoed back on the same connection. Defaults to true
15
19
  class Client
16
20
  extend Forwardable
17
21
 
18
22
  DOMAIN = 'realtime.ably.io'
19
23
 
20
- attr_reader :channels, :auth
24
+ attr_reader :channels, :auth, :rest_client, :echo_messages
21
25
  def_delegators :auth, :client_id, :auth_options
22
- def_delegators :@rest_client, :tls, :environment, :use_tls?, :logger, :log_level
26
+ def_delegators :@rest_client, :environment, :use_tls?, :protocol
27
+ def_delegators :@rest_client, :logger, :log_level
28
+ def_delegators :@rest_client, :time, :stats
23
29
 
24
30
  # Creates a {Ably::Realtime::Client Realtime Client} and configures the {Ably::Auth} object for the connection.
25
31
  #
@@ -42,28 +48,39 @@ module Ably
42
48
  # # create a new client and configure a client ID used for presence
43
49
  # client = Ably::Realtime::Client.new(api_key: 'key.id:secret', client_id: 'john')
44
50
  #
45
- def initialize(options)
46
- @rest_client = Ably::Rest::Client.new(options)
51
+ def initialize(options, &auth_block)
52
+ @rest_client = Ably::Rest::Client.new(options, &auth_block)
47
53
  @auth = @rest_client.auth
48
- @message_serial = 0
54
+ @channels = Ably::Realtime::Channels.new(self)
55
+ @echo_messages = @rest_client.options.fetch(:echo_messages, true) == false ? false : true
49
56
  end
50
57
 
51
- def token
52
- @token ||= rest_client.request_token
58
+ # Return a {Ably::Realtime::Channel Realtime Channel} for the given name
59
+ #
60
+ # @param (see Ably::Realtime::Channels#get)
61
+ #
62
+ # @return (see Ably::Realtime::Channels#get)
63
+ def channel(name, channel_options = {})
64
+ channels.get(name, channel_options)
53
65
  end
54
66
 
55
- # Return a Realtime Channel for the given name
56
- #
57
- # @param name [String] The name of the channel
58
- # @return [Ably::Realtime::Channel]
59
- def channel(name)
60
- @channels ||= {}
61
- @channels[name] ||= Ably::Realtime::Channel.new(self, name)
67
+ # (see Ably::Rest::Client#time)
68
+ def time
69
+ rest_client.time
62
70
  end
63
71
 
64
- # Default Ably Realtime endpoint used for all requests
65
- #
66
- # @return [URI::Generic]
72
+ # (see Ably::Rest::Client#stats)
73
+ def stats(params = {})
74
+ rest_client.stats(params)
75
+ end
76
+
77
+ # (see Ably::Realtime::Connection#close)
78
+ def close(&block)
79
+ connection.close(&block)
80
+ end
81
+
82
+ # @!attribute [r] endpoint
83
+ # @return [URI::Generic] Default Ably Realtime endpoint used for all requests
67
84
  def endpoint
68
85
  URI::Generic.build(
69
86
  scheme: use_tls? ? "wss" : "ws",
@@ -71,22 +88,11 @@ module Ably
71
88
  )
72
89
  end
73
90
 
91
+ # @!attribute [r] connection
92
+ # @return [Aby::Realtime::Connection] The underlying connection for this client
74
93
  def connection
75
- @connection ||= begin
76
- host = endpoint.host
77
- port = use_tls? ? 443 : 80
78
-
79
- EventMachine.connect(host, port, Connection, self).tap do |connection|
80
- connection.on(:connected) do
81
- IncomingMessageDispatcher.new(self)
82
- OutgoingMessageDispatcher.new(self)
83
- end
84
- end
85
- end
94
+ @connection ||= Connection.new(self)
86
95
  end
87
-
88
- private
89
- attr_reader :rest_client
90
96
  end
91
97
  end
92
98
  end
@@ -1,21 +1,18 @@
1
1
  module Ably::Realtime
2
2
  class Client
3
- # IncomingMessageDispatcher is a (private) class that is used to dispatch {Ably::Realtime::Models::ProtocolMessage} that are
3
+ # IncomingMessageDispatcher is a (private) class that is used to dispatch {Ably::Models::ProtocolMessage} that are
4
4
  # received from Ably via the {Ably::Realtime::Connection}
5
5
  class IncomingMessageDispatcher
6
- ACTION = Models::ProtocolMessage::ACTION
6
+ ACTION = Ably::Models::ProtocolMessage::ACTION
7
7
 
8
- def initialize(client)
9
- @client = client
8
+ def initialize(client, connection)
9
+ @client = client
10
+ @connection = connection
10
11
  subscribe_to_incoming_protocol_messages
11
12
  end
12
13
 
13
14
  private
14
- attr_reader :client
15
-
16
- def connection
17
- client.connection
18
- end
15
+ attr_reader :client, :connection
19
16
 
20
17
  def channels
21
18
  client.channels
@@ -24,7 +21,7 @@ module Ably::Realtime
24
21
  def get_channel(channel_name)
25
22
  channels.fetch(channel_name) do
26
23
  logger.warn "Received channel message for non-existent channel"
27
- Models::NilChannel.new
24
+ Ably::Models::NilChannel.new
28
25
  end
29
26
  end
30
27
 
@@ -35,7 +32,7 @@ module Ably::Realtime
35
32
  def dispatch_protocol_message(*args)
36
33
  protocol_message = args.first
37
34
 
38
- unless protocol_message.kind_of?(Models::ProtocolMessage)
35
+ unless protocol_message.kind_of?(Ably::Models::ProtocolMessage)
39
36
  raise ArgumentError, "Expected a ProtocolMessage. Received #{protocol_message}"
40
37
  end
41
38
 
@@ -43,6 +40,8 @@ module Ably::Realtime
43
40
  logger.debug "#{protocol_message.action} received: #{protocol_message}"
44
41
  end
45
42
 
43
+ update_connection_id protocol_message
44
+
46
45
  case protocol_message.action
47
46
  when ACTION.Heartbeat
48
47
  when ACTION.Ack
@@ -52,12 +51,21 @@ module Ably::Realtime
52
51
  logger.warn "NACK received: #{protocol_message}"
53
52
  nack_pending_queue_for_message_serial(protocol_message) if protocol_message.has_message_serial?
54
53
 
55
- when ACTION.Connect, ACTION.Connected
54
+ when ACTION.Connect
55
+ when ACTION.Connected
56
+ connection.transition_state_machine :connected
57
+
56
58
  when ACTION.Disconnect, ACTION.Disconnected
59
+
57
60
  when ACTION.Close
58
61
  when ACTION.Closed
62
+ connection.transition_state_machine :closed
63
+
59
64
  when ACTION.Error
60
65
  logger.error "Error received: #{protocol_message.error}"
66
+ if protocol_message.channel && !protocol_message.has_message_serial?
67
+ get_channel(protocol_message.channel).change_state Ably::Realtime::Channel::STATE.Failed, protocol_message.error
68
+ end
61
69
 
62
70
  when ACTION.Attach
63
71
  when ACTION.Attached
@@ -68,9 +76,13 @@ module Ably::Realtime
68
76
  get_channel(protocol_message.channel).change_state Ably::Realtime::Channel::STATE.Detached
69
77
 
70
78
  when ACTION.Presence
79
+ protocol_message.presence.each do |presence|
80
+ get_channel(protocol_message.channel).presence.__incoming_msgbus__.publish :presence, presence
81
+ end
82
+
71
83
  when ACTION.Message
72
84
  protocol_message.messages.each do |message|
73
- get_channel(protocol_message.channel).__incoming_protocol_msgbus__.publish :message, message
85
+ get_channel(protocol_message.channel).__incoming_msgbus__.publish :message, message
74
86
  end
75
87
 
76
88
  else
@@ -78,21 +90,38 @@ module Ably::Realtime
78
90
  end
79
91
  end
80
92
 
93
+ def update_connection_id(protocol_message)
94
+ if protocol_message.connection_id && (protocol_message.connection_id != connection.id)
95
+ logger.debug "New connection ID set to #{protocol_message.connection_id}"
96
+ connection.update_connection_id protocol_message.connection_id
97
+ end
98
+ end
99
+
81
100
  def ack_pending_queue_for_message_serial(ack_protocol_message)
82
101
  drop_pending_queue_from_ack(ack_protocol_message) do |protocol_message|
83
- protocol_message.messages.each do |message|
84
- logger.debug "Calling ACK success callbacks for #{message.to_json}"
85
- message.succeed message
86
- end
102
+ ack_messages protocol_message.messages
103
+ ack_messages protocol_message.presence
87
104
  end
88
105
  end
89
106
 
90
107
  def nack_pending_queue_for_message_serial(nack_protocol_message)
91
108
  drop_pending_queue_from_ack(nack_protocol_message) do |protocol_message|
92
- protocol_message.messages.each do |message|
93
- logger.debug "Calling NACK failure callbacks for #{message.to_json}"
94
- message.fail message, nack_protocol_message.error
95
- end
109
+ nack_messages protocol_message.messages, nack_protocol_message
110
+ nack_messages protocol_message.presence, nack_protocol_message
111
+ end
112
+ end
113
+
114
+ def ack_messages(messages)
115
+ messages.each do |message|
116
+ logger.debug "Calling ACK success callbacks for #{message.class.name} - #{message.to_json}"
117
+ message.succeed message
118
+ end
119
+ end
120
+
121
+ def nack_messages(messages, protocol_message)
122
+ messages.each do |message|
123
+ logger.debug "Calling NACK failure callbacks for #{message.class.name} - #{message.to_json}, protocol message: #{protocol_message}"
124
+ message.fail message, protocol_message.error
96
125
  end
97
126
  end
98
127
 
@@ -1,25 +1,22 @@
1
1
  module Ably::Realtime
2
2
  class Client
3
3
  # OutgoingMessageDispatcher is a (private) class that is used to deliver
4
- # outgoing {Ably::Realtime::Models::ProtocolMessage}s using the {Ably::Realtime::Connection}
4
+ # outgoing {Ably::Models::ProtocolMessage}s using the {Ably::Realtime::Connection}
5
5
  # when the connection state is capable of delivering messages
6
6
  class OutgoingMessageDispatcher
7
7
  include Ably::Modules::EventMachineHelpers
8
8
 
9
- ACTION = Models::ProtocolMessage::ACTION
9
+ ACTION = Ably::Models::ProtocolMessage::ACTION
10
10
 
11
- def initialize(client)
12
- @client = client
11
+ def initialize(client, connection)
12
+ @client = client
13
+ @connection = connection
13
14
  subscribe_to_outgoing_protocol_message_queue
14
15
  setup_event_handlers
15
16
  end
16
17
 
17
18
  private
18
- attr_reader :client
19
-
20
- def connection
21
- client.connection
22
- end
19
+ attr_reader :client, :connection
23
20
 
24
21
  def can_send_messages?
25
22
  connection.connected?
@@ -39,11 +36,12 @@ module Ably::Realtime
39
36
 
40
37
  def deliver_queued_protocol_messages
41
38
  condition = -> { can_send_messages? && messages_in_outgoing_queue? }
39
+
42
40
  non_blocking_loop_while(condition) do
43
41
  protocol_message = outgoing_queue.shift
44
42
  pending_queue << protocol_message if protocol_message.ack_required?
45
- connection.send_text(protocol_message.to_json)
46
- client.logger.debug("Prot msg sent =>: #{protocol_message.action} #{protocol_message}")
43
+ connection.transport.send_object protocol_message
44
+ client.logger.debug "Prot msg sent =>: #{protocol_message.action} #{protocol_message}"
47
45
  end
48
46
  end
49
47
 
@@ -25,18 +25,16 @@ module Ably
25
25
  #
26
26
  # @!attribute [r] state
27
27
  # @return {Ably::Realtime::Connection::STATE} connection state
28
- # @!attribute [r] __outgoing_message_queue__
29
- # @return [Array] An internal queue used to manage unsent outgoing messages. You should never interface with this array directly.
30
- # @!attribute [r] __pending_message_queue__
31
- # @return [Array] An internal queue used to manage sent messages. You should never interface with this array directly.
32
- #
33
- class Connection < EventMachine::Connection
34
- include Ably::Modules::Conversions
28
+ # @!attribute [r] id
29
+ # @return {String} the assigned connection ID
30
+ # @!attribute [r] error_reason
31
+ # @return {Ably::Models::ErrorInfo} error information associated with a connection failure
32
+ class Connection
35
33
  include Ably::Modules::EventEmitter
36
34
  extend Ably::Modules::Enum
37
35
 
36
+ # Valid Connection states
38
37
  STATE = ruby_enum('STATE',
39
- :initializing,
40
38
  :initialized,
41
39
  :connecting,
42
40
  :connected,
@@ -45,83 +43,178 @@ module Ably
45
43
  :closed,
46
44
  :failed
47
45
  )
48
- include Ably::Modules::State
46
+ include Ably::Modules::StateEmitter
47
+
48
+ attr_reader :id, :error_reason, :client
49
+
50
+ # @api private
51
+ # Underlying socket transport used for this connection, for internal use by the client library
52
+ # @return {Ably::Realtime::Connection::WebsocketTransport}
53
+ attr_reader :transport
54
+
55
+ # @api private
56
+ # An internal queue used to manage unsent outgoing messages. You should never interface with this array directly
57
+ # @return [Array]
58
+ attr_reader :__outgoing_message_queue__
49
59
 
50
- attr_reader :__outgoing_message_queue__, :__pending_message_queue__
60
+ # @api private
61
+ # An internal queue used to manage sent messages. You should never interface with this array directly
62
+ # @return [Array]
63
+ attr_reader :__pending_message_queue__
51
64
 
65
+ # @api private
66
+ # Timers used to manage connection state, for internal use by the client library
67
+ # @return [Hash]
68
+ attr_reader :timers
69
+
70
+ # @api public
52
71
  def initialize(client)
53
72
  @client = client
54
- @message_serial = 0
73
+
74
+ @serial = -1
55
75
  @__outgoing_message_queue__ = []
56
76
  @__pending_message_queue__ = []
57
- @state = STATE.Initializing
58
- end
59
77
 
60
- # Required for test /unit/realtime/connection_spec.rb
61
- alias_method :orig_send, :send
78
+ @timers = Hash.new { |hash, key| hash[key] = [] }
79
+ @timers[:initializer] << EventMachine::Timer.new(0.001) { connect }
62
80
 
63
- # Add protocol message to the outgoing message queue and notify the dispatcher that a message is
64
- # ready to be sent
65
- def send_protocol_message(protocol_message)
66
- add_message_serial_if_ack_required_to(protocol_message) do
67
- protocol_message = Models::ProtocolMessage.new(protocol_message)
68
- __outgoing_message_queue__ << protocol_message
69
- logger.debug("Prot msg queued =>: #{protocol_message.action} #{protocol_message}")
70
- __outgoing_protocol_msgbus__.publish :message, protocol_message
81
+ Client::IncomingMessageDispatcher.new client, self
82
+ Client::OutgoingMessageDispatcher.new client, self
83
+
84
+ EventMachine.next_tick do
85
+ trigger STATE.Initialized
71
86
  end
87
+
88
+ @state_machine = ConnectionStateMachine.new(self)
89
+ @state = STATE(state_machine.current_state)
72
90
  end
73
91
 
74
- # EventMachine::Connection interface
75
- def post_init
76
- change_state STATE.Initialized
92
+ # Causes the connection to close, entering the closed state, from any state except
93
+ # the failed state. Once closed, the library will not attempt to re-establish the
94
+ # connection without a call to {Connection#connect}.
95
+ #
96
+ # @yield [Ably::Realtime::Connection] block is called as soon as this connection is in the Closed state
97
+ #
98
+ # @return <void>
99
+ def close(&block)
100
+ if closed?
101
+ block.call self
102
+ else
103
+ EventMachine.next_tick do
104
+ state_machine.transition_to(:closed)
105
+ end
106
+ once(STATE.Closed) { block.call self } if block_given?
107
+ end
108
+ end
77
109
 
78
- setup_driver
110
+ # Causes the library to re-attempt connection, if it was previously explicitly
111
+ # closed by the user, or was closed as a result of an unrecoverable error.
112
+ #
113
+ # @yield [Ably::Realtime::Connection] block is called as soon as this connection is in the Connected state
114
+ #
115
+ # @return <void>
116
+ def connect(&block)
117
+ if connected?
118
+ block.call self
119
+ else
120
+ state_machine.transition_to(:connecting)
121
+ once(STATE.Connected) { block.call self } if block_given?
122
+ end
79
123
  end
80
124
 
81
- def connection_completed
82
- change_state STATE.Connecting
125
+ # Reconfigure the current connection ID
126
+ # @return <void>
127
+ # @api private
128
+ def update_connection_id(connection_id)
129
+ @id = connection_id
130
+ end
83
131
 
84
- start_tls if client.use_tls?
85
- driver.start
132
+ # Send #transition_to to connection state machine
133
+ # @return [Boolean] true if new_state can be transitioned_to by state machine
134
+ # @api private
135
+ def transition_state_machine(new_state)
136
+ state_machine.transition_to(new_state)
86
137
  end
87
138
 
88
- def receive_data(data)
89
- driver.parse(data)
139
+ # @!attribute [r] __outgoing_protocol_msgbus__
140
+ # @return [Ably::Util::PubSub] Client library internal outgoing message bus
141
+ # @api private
142
+ def __outgoing_protocol_msgbus__
143
+ @__outgoing_protocol_msgbus__ ||= create_pub_sub_message_bus
90
144
  end
91
145
 
92
- def unbind
93
- change_state STATE.Disconnected
146
+ # @!attribute [r] __incoming_protocol_msgbus__
147
+ # @return [Ably::Util::PubSub] Client library internal incoming message bus
148
+ # @api private
149
+ def __incoming_protocol_msgbus__
150
+ @__incoming_protocol_msgbus__ ||= create_pub_sub_message_bus
94
151
  end
95
152
 
96
- # WebSocket::Driver interface
97
- def url
98
- URI(client.endpoint).tap do |endpoint|
99
- endpoint.query = URI.encode_www_form(client.auth.auth_params.merge(timestamp: as_since_epoch(Time.now), binary: false))
100
- end.to_s
153
+ # @!attribute [r] logger
154
+ # @return [Logger] The Logger configured for this client when the client was instantiated.
155
+ # Configure the log_level with the `:log_level` option, refer to {Ably::Realtime::Client#initialize}
156
+ def logger
157
+ client.logger
101
158
  end
102
159
 
103
- def write(data)
104
- send_data(data)
160
+ # Add protocol message to the outgoing message queue and notify the dispatcher that a message is
161
+ # ready to be sent
162
+ #
163
+ # @param [Ably::Models::ProtocolMessage] protocol_message
164
+ # @return <void>
165
+ # @api private
166
+ def send_protocol_message(protocol_message)
167
+ add_message_serial_if_ack_required_to(protocol_message) do
168
+ Models::ProtocolMessage.new(protocol_message).tap do |protocol_message|
169
+ add_message_to_outgoing_queue protocol_message
170
+ notify_message_dispatcher_of_new_message protocol_message
171
+ logger.debug("Prot msg queued =>: #{protocol_message.action} #{protocol_message}")
172
+ end
173
+ end
105
174
  end
106
175
 
107
- def send_text(text)
108
- driver.text(text)
176
+ def add_message_to_outgoing_queue(protocol_message)
177
+ __outgoing_message_queue__ << protocol_message
109
178
  end
110
179
 
111
- # Client library internal outgoing message bus
112
- def __outgoing_protocol_msgbus__
113
- @__outgoing_protocol_msgbus__ ||= pub_sub_message_bus
180
+ def notify_message_dispatcher_of_new_message(protocol_message)
181
+ __outgoing_protocol_msgbus__.publish :message, protocol_message
114
182
  end
115
183
 
116
- # Client library internal incoming message bus
117
- def __incoming_protocol_msgbus__
118
- @__incoming_protocol_msgbus__ ||= pub_sub_message_bus
184
+ # Creates and sets up a new {WebSocketTransport} available on attribute #transport
185
+ # @yield [Ably::Realtime::Connection::WebsocketTransport] block is called with new websocket transport
186
+ # @api private
187
+ def setup_transport(&block)
188
+ if transport && !transport.ready_for_release?
189
+ raise RuntimeError, "Existing WebsocketTransport is connected, and must be closed first"
190
+ end
191
+
192
+ @transport = EventMachine.connect(connection_host, connection_port, WebsocketTransport, self) do |websocket|
193
+ yield websocket
194
+ end
195
+ end
196
+
197
+ # Reconnect the {Ably::Realtime::Connection::WebsocketTransport} following a disconnection
198
+ # @api private
199
+ def reconnect_transport
200
+ raise RuntimeError, "WebsocketTransport is not set up" if !transport
201
+ raise RuntimeError, "WebsocketTransport is not disconnected so cannot be reconnected" if !transport.disconnected?
202
+
203
+ transport.reconnect(connection_host, connection_port)
119
204
  end
120
205
 
121
206
  private
122
- attr_reader :client, :driver, :message_serial
207
+ attr_reader :manager, :serial, :state_machine
208
+
209
+ def connection_host
210
+ client.endpoint.host
211
+ end
123
212
 
124
- def pub_sub_message_bus
213
+ def connection_port
214
+ client.use_tls? ? 443 : 80
215
+ end
216
+
217
+ def create_pub_sub_message_bus
125
218
  Ably::Util::PubSub.new(
126
219
  coerce_into: Proc.new { |event| Models::ProtocolMessage::ACTION(event) }
127
220
  )
@@ -136,37 +229,13 @@ module Ably
136
229
  end
137
230
 
138
231
  def add_message_serial_to(protocol_message)
139
- @message_serial += 1
140
- protocol_message[:msgSerial] = @message_serial
232
+ @serial += 1
233
+ protocol_message[:msgSerial] = serial
141
234
  yield
142
235
  rescue StandardError => e
143
- @message_serial -= 1
236
+ @serial -= 1
144
237
  raise e
145
238
  end
146
-
147
- def setup_driver
148
- @driver = WebSocket::Driver.client(self)
149
-
150
- driver.on("open") do
151
- logger.debug("WebSocket connection opened to #{url}")
152
- change_state STATE.Connected
153
- end
154
-
155
- driver.on("message") do |event|
156
- begin
157
- message = Models::ProtocolMessage.new(JSON.parse(event.data).freeze)
158
- logger.debug("Prot msg recv <=: #{message.action} #{event.data}")
159
- __incoming_protocol_msgbus__.publish :message, message
160
- rescue KeyError
161
- client.logger.error("Unsupported Protocol Message received, unrecognised 'action': #{event.data}\nNo action taken")
162
- end
163
- end
164
- end
165
-
166
- # Used by {Ably::Modules::State} to debug state changes
167
- def logger
168
- client.logger
169
- end
170
239
  end
171
240
  end
172
241
  end