ably 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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