ably 0.1.5 → 0.1.6

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