ably 0.6.2 → 0.7.0

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.ruby-version.old +1 -0
  4. data/.travis.yml +0 -2
  5. data/Rakefile +22 -4
  6. data/SPEC.md +1676 -0
  7. data/ably.gemspec +1 -1
  8. data/lib/ably.rb +0 -8
  9. data/lib/ably/auth.rb +54 -46
  10. data/lib/ably/exceptions.rb +19 -5
  11. data/lib/ably/logger.rb +1 -1
  12. data/lib/ably/models/error_info.rb +1 -1
  13. data/lib/ably/models/idiomatic_ruby_wrapper.rb +11 -9
  14. data/lib/ably/models/message.rb +15 -12
  15. data/lib/ably/models/message_encoders/base.rb +6 -5
  16. data/lib/ably/models/message_encoders/base64.rb +1 -0
  17. data/lib/ably/models/message_encoders/cipher.rb +6 -3
  18. data/lib/ably/models/message_encoders/json.rb +1 -0
  19. data/lib/ably/models/message_encoders/utf8.rb +2 -9
  20. data/lib/ably/models/nil_logger.rb +20 -0
  21. data/lib/ably/models/paginated_resource.rb +5 -2
  22. data/lib/ably/models/presence_message.rb +21 -12
  23. data/lib/ably/models/protocol_message.rb +22 -6
  24. data/lib/ably/modules/ably.rb +11 -0
  25. data/lib/ably/modules/async_wrapper.rb +2 -0
  26. data/lib/ably/modules/conversions.rb +23 -3
  27. data/lib/ably/modules/encodeable.rb +2 -1
  28. data/lib/ably/modules/enum.rb +2 -0
  29. data/lib/ably/modules/event_emitter.rb +7 -1
  30. data/lib/ably/modules/event_machine_helpers.rb +2 -0
  31. data/lib/ably/modules/http_helpers.rb +2 -0
  32. data/lib/ably/modules/model_common.rb +12 -2
  33. data/lib/ably/modules/state_emitter.rb +76 -0
  34. data/lib/ably/modules/state_machine.rb +53 -0
  35. data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  36. data/lib/ably/modules/uses_state_machine.rb +74 -0
  37. data/lib/ably/realtime.rb +4 -2
  38. data/lib/ably/realtime/channel.rb +51 -58
  39. data/lib/ably/realtime/channel/channel_manager.rb +91 -0
  40. data/lib/ably/realtime/channel/channel_state_machine.rb +68 -0
  41. data/lib/ably/realtime/client.rb +70 -26
  42. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +31 -13
  43. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  44. data/lib/ably/realtime/connection.rb +135 -92
  45. data/lib/ably/realtime/connection/connection_manager.rb +216 -33
  46. data/lib/ably/realtime/connection/connection_state_machine.rb +30 -73
  47. data/lib/ably/realtime/models/nil_channel.rb +10 -1
  48. data/lib/ably/realtime/presence.rb +336 -92
  49. data/lib/ably/rest.rb +2 -2
  50. data/lib/ably/rest/channel.rb +13 -4
  51. data/lib/ably/rest/client.rb +138 -38
  52. data/lib/ably/rest/middleware/logger.rb +24 -3
  53. data/lib/ably/rest/presence.rb +12 -7
  54. data/lib/ably/version.rb +1 -1
  55. data/spec/acceptance/realtime/channel_history_spec.rb +101 -85
  56. data/spec/acceptance/realtime/channel_spec.rb +461 -120
  57. data/spec/acceptance/realtime/client_spec.rb +119 -0
  58. data/spec/acceptance/realtime/connection_failures_spec.rb +499 -0
  59. data/spec/acceptance/realtime/connection_spec.rb +571 -97
  60. data/spec/acceptance/realtime/message_spec.rb +347 -333
  61. data/spec/acceptance/realtime/presence_history_spec.rb +35 -40
  62. data/spec/acceptance/realtime/presence_spec.rb +769 -239
  63. data/spec/acceptance/realtime/stats_spec.rb +14 -22
  64. data/spec/acceptance/realtime/time_spec.rb +16 -20
  65. data/spec/acceptance/rest/auth_spec.rb +425 -364
  66. data/spec/acceptance/rest/base_spec.rb +108 -176
  67. data/spec/acceptance/rest/channel_spec.rb +89 -89
  68. data/spec/acceptance/rest/channels_spec.rb +30 -32
  69. data/spec/acceptance/rest/client_spec.rb +273 -0
  70. data/spec/acceptance/rest/encoders_spec.rb +185 -0
  71. data/spec/acceptance/rest/message_spec.rb +186 -163
  72. data/spec/acceptance/rest/presence_spec.rb +150 -111
  73. data/spec/acceptance/rest/stats_spec.rb +45 -40
  74. data/spec/acceptance/rest/time_spec.rb +8 -10
  75. data/spec/rspec_config.rb +10 -1
  76. data/spec/shared/client_initializer_behaviour.rb +212 -0
  77. data/spec/{support/model_helper.rb → shared/model_behaviour.rb} +6 -6
  78. data/spec/{support/protocol_msgbus_helper.rb → shared/protocol_msgbus_behaviour.rb} +1 -1
  79. data/spec/spec_helper.rb +9 -0
  80. data/spec/support/api_helper.rb +11 -0
  81. data/spec/support/event_machine_helper.rb +101 -3
  82. data/spec/support/markdown_spec_formatter.rb +90 -0
  83. data/spec/support/private_api_formatter.rb +36 -0
  84. data/spec/support/protocol_helper.rb +32 -0
  85. data/spec/support/random_helper.rb +15 -0
  86. data/spec/support/test_app.rb +4 -0
  87. data/spec/unit/auth_spec.rb +68 -0
  88. data/spec/unit/logger_spec.rb +77 -66
  89. data/spec/unit/models/error_info_spec.rb +1 -1
  90. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +2 -3
  91. data/spec/unit/models/message_encoders/base64_spec.rb +2 -2
  92. data/spec/unit/models/message_encoders/cipher_spec.rb +2 -2
  93. data/spec/unit/models/message_encoders/utf8_spec.rb +2 -46
  94. data/spec/unit/models/message_spec.rb +160 -15
  95. data/spec/unit/models/paginated_resource_spec.rb +29 -27
  96. data/spec/unit/models/presence_message_spec.rb +163 -20
  97. data/spec/unit/models/protocol_message_spec.rb +43 -8
  98. data/spec/unit/modules/async_wrapper_spec.rb +2 -3
  99. data/spec/unit/modules/conversions_spec.rb +1 -1
  100. data/spec/unit/modules/enum_spec.rb +2 -3
  101. data/spec/unit/modules/event_emitter_spec.rb +62 -5
  102. data/spec/unit/modules/state_emitter_spec.rb +283 -0
  103. data/spec/unit/realtime/channel_spec.rb +107 -2
  104. data/spec/unit/realtime/channels_spec.rb +1 -0
  105. data/spec/unit/realtime/client_spec.rb +8 -48
  106. data/spec/unit/realtime/connection_spec.rb +3 -3
  107. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +2 -2
  108. data/spec/unit/realtime/presence_spec.rb +13 -4
  109. data/spec/unit/realtime/realtime_spec.rb +0 -11
  110. data/spec/unit/realtime/websocket_transport_spec.rb +2 -2
  111. data/spec/unit/rest/channel_spec.rb +109 -0
  112. data/spec/unit/rest/channels_spec.rb +4 -3
  113. data/spec/unit/rest/client_spec.rb +30 -125
  114. data/spec/unit/rest/rest_spec.rb +10 -0
  115. data/spec/unit/util/crypto_spec.rb +10 -5
  116. data/spec/unit/util/pub_sub_spec.rb +5 -5
  117. metadata +44 -12
  118. data/spec/integration/modules/state_emitter_spec.rb +0 -80
  119. data/spec/integration/rest/auth.rb +0 -9
@@ -0,0 +1,53 @@
1
+ require 'statesman'
2
+ require 'ably/modules/statesman_monkey_patch'
3
+
4
+ module Ably::Modules
5
+ # Module providing Statesman StateMachine functionality
6
+ #
7
+ # Expects method #logger to be defined
8
+ #
9
+ # @api private
10
+ module StateMachine
11
+ def self.included(klass)
12
+ klass.class_eval do
13
+ include Statesman::Machine
14
+ end
15
+ klass.extend Ably::Modules::StatesmanMonkeyPatch
16
+ end
17
+
18
+ # Alternative to Statesman's #transition_to that:
19
+ # * log state change failures to {Logger}
20
+ # * raise an exception on the {Ably::Realtime::Channel}
21
+ #
22
+ # @return [void]
23
+ def transition_state(state, *args)
24
+ unless result = transition_to(state, *args)
25
+ exception = exception_for_state_change_to(state)
26
+ object.trigger :error, exception
27
+ logger.fatal "#{self.class}: #{exception.message}"
28
+ end
29
+ result
30
+ end
31
+
32
+ # @return [Statesman History Object]
33
+ def previous_transition
34
+ history[-2]
35
+ end
36
+
37
+ # @return [Symbol]
38
+ def previous_state
39
+ previous_transition.to_state if previous_transition
40
+ end
41
+
42
+ # @return [Ably::Exceptions::StateChangeError]
43
+ def exception_for_state_change_to(state)
44
+ error_message = "#{self.class}: Unable to transition from #{current_state} => #{state}"
45
+ Ably::Exceptions::StateChangeError.new(error_message, nil, 80020)
46
+ end
47
+
48
+ private
49
+ def self.is_error_type?(error)
50
+ error.kind_of?(Ably::Models::ErrorInfo) || error.kind_of?(StandardError)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,33 @@
1
+ module Ably::Modules
2
+ # @api private
3
+ module StatesmanMonkeyPatch
4
+ # Override Statesman's #before_transition to support :from arrays
5
+ # This can be removed once https://github.com/gocardless/statesman/issues/95 is solved
6
+ def before_transition(options = nil, &block)
7
+ arrayify_transition(options) do |options_without_from_array|
8
+ super *options_without_from_array, &block
9
+ end
10
+ end
11
+
12
+ # Override Statesman's #after_transition to support :from arrays
13
+ # This can be removed once https://github.com/gocardless/statesman/issues/95 is solved
14
+ def after_transition(options = nil, &block)
15
+ arrayify_transition(options) do |options_without_from_array|
16
+ super *options_without_from_array, &block
17
+ end
18
+ end
19
+
20
+ private
21
+ def arrayify_transition(options, &block)
22
+ if options.nil?
23
+ yield []
24
+ elsif options.fetch(:from, nil).kind_of?(Array)
25
+ options[:from].each do |from_state|
26
+ yield [options.merge(from: from_state)]
27
+ end
28
+ else
29
+ yield [options]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,74 @@
1
+ module Ably::Modules
2
+ # Mixing module that assists with {https://github.com/gocardless/statesman Statemans State Machine} state transitions
3
+ # and maintaining state of this object's #state.
4
+ #
5
+ # Expects:
6
+ # - @state_machine is set to the StateMachine
7
+ # - StateEmitter is included in the object
8
+ #
9
+ module UsesStateMachine
10
+ extend Forwardable
11
+
12
+ # Call #transition_to on the StateMachine
13
+ #
14
+ # @return [Boolean] true if new_state can be transitioned to by state machine
15
+ # @api private
16
+ def transition_state_machine(new_state, emit_object = nil)
17
+ state_machine.transition_state(new_state, emit_object)
18
+ end
19
+
20
+ # Call #transition_to! on the StateMachine
21
+ # An exception wil be raised if new_state cannot be transitioned to by state machine
22
+ #
23
+ # @return [void]
24
+ # @api private
25
+ def transition_state_machine!(new_state, emit_object = nil)
26
+ state_machine.transition_to!(new_state, emit_object)
27
+ end
28
+
29
+ # Provides an internal method for this object's state to match the StateMachine's current state.
30
+ # The current object's state will be changed to the StateMachine state and will emit an event
31
+ # @api private
32
+ def synchronize_state_with_statemachine(*args)
33
+ log_state_machine_state_change
34
+ change_state state_machine.current_state, state_machine.last_transition.metadata
35
+ end
36
+
37
+ # @!attribute [r] previous_state
38
+ # @return [STATE,nil] The previous state for this connection
39
+ # @api private
40
+ def previous_state
41
+ if state_machine.previous_state
42
+ STATE(state_machine.previous_state)
43
+ end
44
+ end
45
+
46
+ # @!attribute [r] state_history
47
+ # @return [Array<Hash>] All previous states including the current state in date ascending order with Hash properties :state, :metadata, :transitioned_at
48
+ # @api private
49
+ def state_history
50
+ state_machine.history.map do |transition|
51
+ {
52
+ state: STATE(transition.to_state),
53
+ metadata: transition.metadata,
54
+ transitioned_at: transition.created_at
55
+ }
56
+ end
57
+ end
58
+
59
+ def_delegators :state_machine, :can_transition_to?
60
+
61
+ private
62
+ attr_reader :state_machine
63
+
64
+ def_delegators :state_machine, :exception_for_state_change_to
65
+
66
+ def log_state_machine_state_change
67
+ if state_machine.previous_state
68
+ logger.debug "#{self.class.name}: Transitioned from #{state_machine.previous_state} => #{state_machine.current_state}"
69
+ else
70
+ logger.debug "#{self.class.name}: Transitioned to #{state_machine.current_state}"
71
+ end
72
+ end
73
+ end
74
+ end
@@ -5,6 +5,8 @@ require 'ably/modules/event_emitter'
5
5
 
6
6
  require 'ably/realtime/channel'
7
7
  require 'ably/realtime/channels'
8
+ require 'ably/realtime/channel/channel_manager'
9
+ require 'ably/realtime/channel/channel_state_machine'
8
10
  require 'ably/realtime/client'
9
11
  require 'ably/realtime/connection'
10
12
  require 'ably/realtime/connection/connection_manager'
@@ -55,8 +57,8 @@ module Ably
55
57
  # # create a new client authenticating with basic auth and a client_id
56
58
  # client = Ably::Realtime.new(api_key: 'key.id:secret', client_id: 'john')
57
59
  #
58
- def self.new(options, &auth_block)
59
- Ably::Realtime::Client.new(options, &auth_block)
60
+ def self.new(options, &token_request_block)
61
+ Ably::Realtime::Client.new(options, &token_request_block)
60
62
  end
61
63
  end
62
64
  end
@@ -23,14 +23,10 @@ module Ably
23
23
  # Channel::STATE.Detached
24
24
  # Channel::STATE.Failed
25
25
  #
26
+ # Channels emit errors - use `on(:error)` to subscribe to errors
27
+ #
26
28
  # @!attribute [r] state
27
29
  # @return {Ably::Realtime::Connection::STATE} channel state
28
- # @!attribute [r] client
29
- # @return {Ably::Realtime::Client} Ably client associated with this channel
30
- # @!attribute [r] name
31
- # @return {String} channel name
32
- # @!attribute [r] options
33
- # @return {Hash} channel options configured for this channel, see {#initialize} for channel_options
34
30
  #
35
31
  class Channel
36
32
  include Ably::Modules::Conversions
@@ -48,11 +44,31 @@ module Ably
48
44
  :failed
49
45
  )
50
46
  include Ably::Modules::StateEmitter
47
+ include Ably::Modules::UsesStateMachine
51
48
 
52
49
  # Max number of messages to bundle in a single ProtocolMessage
53
50
  MAX_PROTOCOL_MESSAGE_BATCH_SIZE = 50
54
51
 
55
- attr_reader :client, :name, :options
52
+ # {Ably::Realtime::Client} associated with this channel
53
+ # @return [Ably::Realtime::Client]
54
+ attr_reader :client
55
+
56
+ # Channel name
57
+ # @return [String]
58
+ attr_reader :name
59
+
60
+ # Channel options configured for this channel, see {#initialize} for channel_options
61
+ # @return [Hash]
62
+ attr_reader :options
63
+
64
+ # When a channel failure occurs this attribute contains the Ably Exception
65
+ # @return [Ably::Models::ErrorInfo,Ably::Exceptions::BaseAblyException]
66
+ attr_reader :error_reason
67
+
68
+ # The Channel manager responsible for attaching, detaching and handling failures for this channel
69
+ # @return [Ably::Realtime::Channel::ChannelManager]
70
+ # @api private
71
+ attr_reader :manager
56
72
 
57
73
  # Initialize a new Channel object
58
74
  #
@@ -63,12 +79,17 @@ module Ably
63
79
  # @option channel_options [Hash] :cipher_params A hash of options to configure the encryption. *:key* is required, all other options are optional. See {Ably::Util::Crypto#initialize} for a list of `cipher_params` options
64
80
  #
65
81
  def initialize(client, name, channel_options = {})
82
+ ensure_utf_8 :name, name
83
+
66
84
  @client = client
67
85
  @name = name
68
86
  @options = channel_options.clone.freeze
69
87
  @subscriptions = Hash.new { |hash, key| hash[key] = [] }
70
88
  @queue = []
71
- @state = STATE.Initialized
89
+
90
+ @state_machine = ChannelStateMachine.new(self)
91
+ @state = STATE(state_machine.current_state)
92
+ @manager = ChannelManager.new(self, client.connection)
72
93
 
73
94
  setup_event_handlers
74
95
  end
@@ -91,9 +112,11 @@ module Ably
91
112
  # puts "#{message.name} was not received, error #{error.message}"
92
113
  # end
93
114
  #
94
- def publish(name, data, &callback)
115
+ def publish(name, data, &success_block)
116
+ ensure_utf_8 :name, name
117
+
95
118
  create_message(name, data).tap do |message|
96
- message.callback(&callback) if block_given?
119
+ message.callback(&success_block) if block_given?
97
120
  queue_message message
98
121
  end
99
122
  end
@@ -105,9 +128,9 @@ module Ably
105
128
  #
106
129
  # @return [void]
107
130
  #
108
- def subscribe(name = :all, &blk)
131
+ def subscribe(name = :all, &callback)
109
132
  attach unless attached? || attaching?
110
- subscriptions[message_name_key(name)] << blk
133
+ subscriptions[message_name_key(name)] << callback
111
134
  end
112
135
 
113
136
  # Unsubscribe the matching block for messages matching providing event name, or all messages if event name not provided.
@@ -117,14 +140,14 @@ module Ably
117
140
  #
118
141
  # @return [void]
119
142
  #
120
- def unsubscribe(name = :all, &blk)
143
+ def unsubscribe(name = :all, &callback)
121
144
  if message_name_key(name) == :all
122
145
  subscriptions.keys
123
146
  else
124
147
  Array(message_name_key(name))
125
148
  end.each do |key|
126
149
  subscriptions[key].delete_if do |block|
127
- !block_given? || blk == block
150
+ !block_given? || callback == block
128
151
  end
129
152
  end
130
153
  end
@@ -134,18 +157,11 @@ module Ably
134
157
  # to need to call attach explicitly.
135
158
  #
136
159
  # @yield [Ably::Realtime::Channel] Block is called as soon as this channel is in the Attached state
137
- # @return [void]
160
+ # @return [EventMachine::Deferrable] Deferrable that supports both success (callback) and failure (errback) callback
138
161
  #
139
- def attach(&block)
140
- if attached?
141
- block.call self if block_given?
142
- else
143
- once(STATE.Attached) { block.call self } if block_given?
144
- if !attaching?
145
- change_state STATE.Attaching
146
- send_attach_protocol_message
147
- end
148
- end
162
+ def attach(&success_block)
163
+ transition_state_machine :attaching if can_transition_to?(:attaching)
164
+ deferrable_for_state_change_to(STATE.Attached, &success_block)
149
165
  end
150
166
 
151
167
  # Detach this channel, and call the block if provided when in a Detached or Failed state
@@ -153,16 +169,10 @@ module Ably
153
169
  # @yield [Ably::Realtime::Channel] Block is called as soon as this channel is in the Detached or Failed state
154
170
  # @return [void]
155
171
  #
156
- def detach(&block)
157
- if detached? || failed?
158
- block.call self if block_given?
159
- else
160
- once(STATE.Detached, STATE.Failed) { block.call self } if block_given?
161
- if !detaching?
162
- change_state STATE.Detaching
163
- send_detach_protocol_message
164
- end
165
- end
172
+ def detach(&success_block)
173
+ raise exception_for_state_change_to(:detaching) if failed? || initialized?
174
+ transition_state_machine :detaching if can_transition_to?(:detaching)
175
+ deferrable_for_state_change_to(STATE.Detached, &success_block)
166
176
  end
167
177
 
168
178
  # Presence object for this Channel. This controls this client's
@@ -172,6 +182,7 @@ module Ably
172
182
  # @return {Ably::Realtime::Presence}
173
183
  #
174
184
  def presence
185
+ attach if initialized?
175
186
  @presence ||= Presence.new(self)
176
187
  end
177
188
 
@@ -198,6 +209,11 @@ module Ably
198
209
  )
199
210
  end
200
211
 
212
+ # @api private
213
+ def set_failed_channel_error_reason(error)
214
+ @error_reason = error
215
+ end
216
+
201
217
  private
202
218
  attr_reader :queue, :subscriptions
203
219
 
@@ -212,14 +228,6 @@ module Ably
212
228
  on(STATE.Attached) do
213
229
  process_queue
214
230
  end
215
-
216
- connection.on(Connection::STATE.Closed) do
217
- change_state STATE.Detached
218
- end
219
-
220
- connection.on(Connection::STATE.Failed) do
221
- change_state STATE.Failed unless detached? || initialized?
222
- end
223
231
  end
224
232
 
225
233
  # Queue message and process queue if channel is attached.
@@ -254,21 +262,6 @@ module Ably
254
262
  )
255
263
  end
256
264
 
257
- def send_attach_protocol_message
258
- send_state_change_protocol_message Ably::Models::ProtocolMessage::ACTION.Attach
259
- end
260
-
261
- def send_detach_protocol_message
262
- send_state_change_protocol_message Ably::Models::ProtocolMessage::ACTION.Detach
263
- end
264
-
265
- def send_state_change_protocol_message(state)
266
- client.connection.send_protocol_message(
267
- action: state.to_i,
268
- channel: name
269
- )
270
- end
271
-
272
265
  def create_message(name, data)
273
266
  message = { name: name }
274
267
  message.merge!(data: data) unless data.nil?
@@ -0,0 +1,91 @@
1
+ module Ably::Realtime
2
+ class Channel
3
+ # ChannelManager is responsible for all actions relating to channel state: attaching, detaching or failure
4
+ # Channel state changes are performed by this class and executed from {ChannelStateMachine}
5
+ #
6
+ # This is a private class and should never be used directly by developers as the API is likely to change in future.
7
+ #
8
+ # @api private
9
+ class ChannelManager
10
+ extend Forwardable
11
+
12
+ def initialize(channel, connection)
13
+ @channel = channel
14
+ @connection = connection
15
+
16
+ connection.on(:closed) do
17
+ channel.transition_state_machine :detaching if can_transition_to?(:detaching)
18
+ end
19
+
20
+ connection.on(:failed) do |error|
21
+ channel.transition_state_machine :failed, error if can_transition_to?(:failed)
22
+ end
23
+
24
+ channel.on(:attached, :detached) do
25
+ channel.set_failed_channel_error_reason nil
26
+ end
27
+ end
28
+
29
+ # Commence attachment
30
+ def attach
31
+ if can_transition_to?(:attached)
32
+ connect_if_connection_initialized
33
+ send_attach_protocol_message
34
+ end
35
+ end
36
+
37
+ # Commence attachment
38
+ def detach
39
+ if connection.closed?
40
+ channel.transition_state_machine :detached
41
+ elsif can_transition_to?(:detached)
42
+ send_detach_protocol_message
43
+ end
44
+ end
45
+
46
+ # Commence presence SYNC if applicable
47
+ def sync(attached_protocol_message)
48
+ if attached_protocol_message.has_presence_flag?
49
+ channel.presence.sync_started
50
+ else
51
+ channel.presence.sync_completed
52
+ end
53
+ end
54
+
55
+ # Channel has failed
56
+ def failed(error)
57
+ logger.error "Channel #{channel.name} error: #{error}"
58
+ channel.trigger :error, error
59
+ end
60
+
61
+ private
62
+
63
+ attr_reader :channel, :connection
64
+ def_delegators :channel, :can_transition_to?
65
+
66
+ # If the connection has not previously connected, connect now
67
+ def connect_if_connection_initialized
68
+ connection.connect if connection.initialized?
69
+ end
70
+
71
+ def send_attach_protocol_message
72
+ send_state_change_protocol_message Ably::Models::ProtocolMessage::ACTION.Attach
73
+ end
74
+
75
+ def send_detach_protocol_message
76
+ send_state_change_protocol_message Ably::Models::ProtocolMessage::ACTION.Detach
77
+ end
78
+
79
+ def send_state_change_protocol_message(state)
80
+ connection.send_protocol_message(
81
+ action: state.to_i,
82
+ channel: channel.name
83
+ )
84
+ end
85
+
86
+ def logger
87
+ connection.logger
88
+ end
89
+ end
90
+ end
91
+ end