ably 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +9 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +8 -1
  6. data/Rakefile +10 -0
  7. data/ably.gemspec +18 -18
  8. data/lib/ably.rb +6 -5
  9. data/lib/ably/auth.rb +11 -14
  10. data/lib/ably/exceptions.rb +18 -15
  11. data/lib/ably/logger.rb +102 -0
  12. data/lib/ably/models/error_info.rb +1 -1
  13. data/lib/ably/models/message.rb +19 -5
  14. data/lib/ably/models/message_encoders/base.rb +107 -0
  15. data/lib/ably/models/message_encoders/base64.rb +39 -0
  16. data/lib/ably/models/message_encoders/cipher.rb +80 -0
  17. data/lib/ably/models/message_encoders/json.rb +33 -0
  18. data/lib/ably/models/message_encoders/utf8.rb +33 -0
  19. data/lib/ably/models/paginated_resource.rb +23 -6
  20. data/lib/ably/models/presence_message.rb +19 -7
  21. data/lib/ably/models/protocol_message.rb +5 -4
  22. data/lib/ably/models/token.rb +2 -2
  23. data/lib/ably/modules/channels_collection.rb +0 -3
  24. data/lib/ably/modules/conversions.rb +3 -3
  25. data/lib/ably/modules/encodeable.rb +68 -0
  26. data/lib/ably/modules/event_emitter.rb +10 -4
  27. data/lib/ably/modules/event_machine_helpers.rb +6 -4
  28. data/lib/ably/modules/http_helpers.rb +7 -2
  29. data/lib/ably/modules/model_common.rb +2 -0
  30. data/lib/ably/modules/state_emitter.rb +10 -1
  31. data/lib/ably/realtime.rb +19 -12
  32. data/lib/ably/realtime/channel.rb +26 -13
  33. data/lib/ably/realtime/client.rb +31 -7
  34. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -3
  35. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +13 -4
  36. data/lib/ably/realtime/connection.rb +152 -46
  37. data/lib/ably/realtime/connection/connection_manager.rb +168 -0
  38. data/lib/ably/realtime/connection/connection_state_machine.rb +56 -33
  39. data/lib/ably/realtime/connection/websocket_transport.rb +56 -29
  40. data/lib/ably/{models → realtime/models}/nil_channel.rb +1 -1
  41. data/lib/ably/realtime/presence.rb +38 -13
  42. data/lib/ably/rest.rb +7 -5
  43. data/lib/ably/rest/channel.rb +24 -3
  44. data/lib/ably/rest/client.rb +56 -17
  45. data/lib/ably/rest/middleware/encoder.rb +49 -0
  46. data/lib/ably/rest/middleware/exceptions.rb +3 -2
  47. data/lib/ably/rest/middleware/logger.rb +37 -0
  48. data/lib/ably/rest/presence.rb +10 -2
  49. data/lib/ably/util/crypto.rb +57 -29
  50. data/lib/ably/util/pub_sub.rb +11 -0
  51. data/lib/ably/version.rb +1 -1
  52. data/spec/acceptance/realtime/channel_spec.rb +65 -7
  53. data/spec/acceptance/realtime/connection_spec.rb +123 -27
  54. data/spec/acceptance/realtime/message_spec.rb +319 -34
  55. data/spec/acceptance/realtime/presence_history_spec.rb +58 -0
  56. data/spec/acceptance/realtime/presence_spec.rb +160 -18
  57. data/spec/acceptance/rest/auth_spec.rb +93 -49
  58. data/spec/acceptance/rest/base_spec.rb +10 -10
  59. data/spec/acceptance/rest/channel_spec.rb +35 -19
  60. data/spec/acceptance/rest/channels_spec.rb +8 -8
  61. data/spec/acceptance/rest/message_spec.rb +224 -0
  62. data/spec/acceptance/rest/presence_spec.rb +159 -23
  63. data/spec/acceptance/rest/stats_spec.rb +5 -5
  64. data/spec/acceptance/rest/time_spec.rb +4 -4
  65. data/spec/integration/rest/auth.rb +1 -1
  66. data/spec/resources/crypto-data-128.json +56 -0
  67. data/spec/resources/crypto-data-256.json +56 -0
  68. data/spec/rspec_config.rb +39 -0
  69. data/spec/spec_helper.rb +4 -42
  70. data/spec/support/api_helper.rb +1 -1
  71. data/spec/support/event_machine_helper.rb +0 -5
  72. data/spec/support/protocol_msgbus_helper.rb +3 -3
  73. data/spec/support/test_app.rb +3 -3
  74. data/spec/unit/logger_spec.rb +135 -0
  75. data/spec/unit/models/message_encoders/base64_spec.rb +181 -0
  76. data/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
  77. data/spec/unit/models/message_encoders/json_spec.rb +135 -0
  78. data/spec/unit/models/message_encoders/utf8_spec.rb +100 -0
  79. data/spec/unit/models/message_spec.rb +16 -1
  80. data/spec/unit/models/paginated_resource_spec.rb +46 -0
  81. data/spec/unit/models/presence_message_spec.rb +18 -5
  82. data/spec/unit/models/token_spec.rb +1 -1
  83. data/spec/unit/modules/event_emitter_spec.rb +24 -10
  84. data/spec/unit/realtime/channel_spec.rb +3 -3
  85. data/spec/unit/realtime/channels_spec.rb +1 -1
  86. data/spec/unit/realtime/client_spec.rb +44 -2
  87. data/spec/unit/realtime/connection_spec.rb +2 -2
  88. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +4 -4
  89. data/spec/unit/realtime/presence_spec.rb +1 -1
  90. data/spec/unit/realtime/realtime_spec.rb +3 -3
  91. data/spec/unit/realtime/websocket_transport_spec.rb +24 -0
  92. data/spec/unit/rest/channels_spec.rb +1 -1
  93. data/spec/unit/rest/client_spec.rb +45 -10
  94. data/spec/unit/util/crypto_spec.rb +82 -0
  95. data/spec/unit/{modules → util}/pub_sub_spec.rb +13 -1
  96. metadata +43 -12
  97. data/spec/acceptance/crypto.rb +0 -63
@@ -4,7 +4,7 @@ module Ably::Models
4
4
  include Ably::Modules::ModelCommon
5
5
 
6
6
  DEFAULTS = {
7
- capability: { "*" => ["*"] },
7
+ capability: { '*' => ['*'] },
8
8
  ttl: 60 * 60 # 1 hour
9
9
  }
10
10
 
@@ -49,7 +49,7 @@ module Ably::Models
49
49
  # @!attribute [r] client_id
50
50
  # @return [String] Optional client ID assigned to this token
51
51
  def client_id
52
- hash.fetch(:client_id)
52
+ hash[:client_id]
53
53
  end
54
54
 
55
55
  # @!attribute [r] nonce
@@ -2,9 +2,6 @@ module Ably::Modules
2
2
  # ChannelsCollection module provides common functionality to the Rest and Realtime Channels objects
3
3
  # such as #get, #[], #fetch, and #release
4
4
  module ChannelsCollection
5
- # Initialize a new Channels object
6
- #
7
- # {self} provides simple accessor methods to access a Channel object
8
5
  def initialize(client, channel_klass)
9
6
  @client = client
10
7
  @channel_klass = channel_klass
@@ -12,7 +12,7 @@ module Ably::Modules
12
12
  when Numeric
13
13
  time
14
14
  else
15
- raise ArgumentError, "time argument must be a Numeric or Time object"
15
+ raise ArgumentError, 'time argument must be a Numeric or Time object'
16
16
  end.to_i
17
17
  end
18
18
 
@@ -23,7 +23,7 @@ module Ably::Modules
23
23
  when Time
24
24
  time
25
25
  else
26
- raise ArgumentError, "time argument must be a Numeric or Time object"
26
+ raise ArgumentError, 'time argument must be a Numeric or Time object'
27
27
  end
28
28
  end
29
29
 
@@ -34,7 +34,7 @@ module Ably::Modules
34
34
  when :s # seconds
35
35
  1.0
36
36
  else
37
- raise ArgumentError, "invalid granularity"
37
+ raise ArgumentError, 'invalid granularity'
38
38
  end
39
39
  end
40
40
 
@@ -0,0 +1,68 @@
1
+ require 'base64'
2
+
3
+ module Ably::Modules
4
+ # Provides methods to allow this model's `data` property to be encoded and decoded based on the `encoding` property.
5
+ #
6
+ # This module expects the following:
7
+ # - A #hash method that returns the underlying hash object
8
+ # - A #set_hash_object(hash) method that updates the underlying hash object
9
+ # - A #raw_hash_object attribute that returns the original hash used to create this object
10
+ #
11
+ module Encodeable
12
+ # Encode a message using the channel options and register encoders for the client
13
+ # @param channel [Ably::Realtime::Channel]
14
+ # @return [void]
15
+ # @api private
16
+ def encode(channel)
17
+ apply_encoders :encode, channel
18
+ end
19
+
20
+ # Decode a message using the channel options and registered encoders for the client
21
+ # @param channel [Ably::Realtime::Channel]
22
+ # @return [void]
23
+ # @api private
24
+ def decode(channel)
25
+ apply_encoders :decode, channel
26
+ end
27
+
28
+ # The original encoding of this message when it was received as a raw message from the Ably service
29
+ # @return [String,nil]
30
+ # @api private
31
+ def original_encoding
32
+ raw_hash_object['encoding']
33
+ end
34
+
35
+ private
36
+ def decode_binary_data_before_to_json(message)
37
+ if message[:data].kind_of?(String) && message[:data].encoding == ::Encoding::ASCII_8BIT
38
+ message[:data] = ::Base64.encode64(message[:data])
39
+ message[:encoding] = [message[:encoding], 'base64'].compact.join('/')
40
+ end
41
+ end
42
+
43
+ def apply_encoders(method, channel)
44
+ max_encoding_length = 512
45
+ message_hash = hash.dup
46
+
47
+ begin
48
+ if message_hash[:encoding].to_s.length > max_encoding_length
49
+ raise Ably::Exceptions::EncoderError("Encoding error, encoding value is too long: '#{message_hash[:encoding]}'", nil, 92100)
50
+ end
51
+
52
+ previous_encoding = message_hash[:encoding]
53
+ channel.client.encoders.each do |encoder|
54
+ encoder.send method, message_hash, channel.options
55
+ end
56
+ end until previous_encoding == message_hash[:encoding]
57
+
58
+ set_hash_object message_hash
59
+ rescue Ably::Exceptions::CipherError => cipher_error
60
+ channel.client.logger.error "Encoder error #{cipher_error.code} trying to #{method} message: #{cipher_error.message}"
61
+ if channel.respond_to?(:trigger)
62
+ channel.trigger :error, cipher_error
63
+ else
64
+ raise cipher_error
65
+ end
66
+ end
67
+ end
68
+ end
@@ -42,7 +42,7 @@ module Ably
42
42
  #
43
43
  # @param [Array<String>] event_names event name
44
44
  #
45
- # @return <void>
45
+ # @return [void]
46
46
  def on(*event_names, &block)
47
47
  event_names.each do |event_name|
48
48
  callbacks[callbacks_event_coerced(event_name)] << proc_for_block(block)
@@ -53,7 +53,7 @@ module Ably
53
53
  #
54
54
  # @param [Array<String>] event_names event name
55
55
  #
56
- # @return <void>
56
+ # @return [void]
57
57
  def once(*event_names, &block)
58
58
  event_names.each do |event_name|
59
59
  callbacks[callbacks_event_coerced(event_name)] << proc_for_block(block, delete_once_run: true)
@@ -72,9 +72,15 @@ module Ably
72
72
  #
73
73
  # @param [Array<String>] event_names event name
74
74
  #
75
- # @return <void>
75
+ # @return [void]
76
76
  def off(*event_names, &block)
77
- event_names.each do |event_name|
77
+ keys = if event_names.empty?
78
+ callbacks.keys
79
+ else
80
+ event_names
81
+ end
82
+
83
+ keys.each do |event_name|
78
84
  if block_given?
79
85
  callbacks[callbacks_event_coerced(event_name)].delete_if { |proc_hash| proc_hash[:block] == block }
80
86
  else
@@ -10,11 +10,13 @@ module Ably::Modules
10
10
  # non_blocking_loop_while(less_than_3) do
11
11
  # x += 1
12
12
  # end
13
- def non_blocking_loop_while(lambda, &execution_block)
14
- if lambda.call
15
- yield
13
+ def non_blocking_loop_while(lambda_condition, &execution_block)
14
+ if lambda_condition.call
16
15
  EventMachine.next_tick do
17
- non_blocking_loop_while(lambda, &execution_block)
16
+ if lambda_condition.call # ensure condition is still met following #next_tick
17
+ yield
18
+ non_blocking_loop_while(lambda_condition, &execution_block)
19
+ end
18
20
  end
19
21
  end
20
22
  end
@@ -1,7 +1,9 @@
1
1
  require 'base64'
2
2
 
3
+ require 'ably/rest/middleware/encoder'
3
4
  require 'ably/rest/middleware/external_exceptions'
4
5
  require 'ably/rest/middleware/fail_if_unsupported_mime_type'
6
+ require 'ably/rest/middleware/logger'
5
7
  require 'ably/rest/middleware/parse_json'
6
8
  require 'ably/rest/middleware/parse_message_pack'
7
9
 
@@ -19,14 +21,17 @@ module Ably::Modules
19
21
 
20
22
  def setup_outgoing_middleware(builder)
21
23
  # Convert request params to "www-form-urlencoded"
22
- builder.use Faraday::Request::UrlEncoded
24
+ builder.use Ably::Rest::Middleware::Encoder
23
25
  end
24
26
 
25
- def setup_incoming_middleware(builder, options = {})
27
+ def setup_incoming_middleware(builder, logger, options = {})
28
+ builder.use Ably::Rest::Middleware::Logger, logger
29
+
26
30
  # Parse JSON / MsgPack response bodies. ParseJson must be first (default) parsing middleware
27
31
  if options[:fail_if_unsupported_mime_type] == true
28
32
  builder.use Ably::Rest::Middleware::FailIfUnsupportedMimeType
29
33
  end
34
+
30
35
  builder.use Ably::Rest::Middleware::ParseJson
31
36
  builder.use Ably::Rest::Middleware::ParseMessagePack
32
37
  end
@@ -1,3 +1,5 @@
1
+ require 'base64'
2
+
1
3
  module Ably::Modules
2
4
  # Common model functionality shared across many {Ably::Models}
3
5
  module ModelCommon
@@ -3,6 +3,9 @@ module Ably::Modules
3
3
  # the instance variable @state is used exclusively, the {Enum} STATE is defined prior to inclusion of this
4
4
  # module, and the class is an {EventEmitter}. It then emits state changes.
5
5
  #
6
+ # It also ensures the EventEmitter is configured to retrict permitted events to the
7
+ # the available STATEs and :error.
8
+ #
6
9
  # @example
7
10
  # class Connection
8
11
  # include Ably::Modules::EventEmitter
@@ -54,7 +57,13 @@ module Ably::Modules
54
57
 
55
58
  private
56
59
  def self.included(klass)
57
- klass.configure_event_emitter coerce_into: Proc.new { |event| klass::STATE(event) }
60
+ klass.configure_event_emitter coerce_into: Proc.new { |event|
61
+ if event == :error
62
+ :error
63
+ else
64
+ klass::STATE(event)
65
+ end
66
+ }
58
67
 
59
68
  klass::STATE.each do |state_predicate|
60
69
  klass.instance_eval do
data/lib/ably/realtime.rb CHANGED
@@ -1,22 +1,29 @@
1
- require "eventmachine"
2
- require "websocket/driver"
1
+ require 'eventmachine'
2
+ require 'websocket/driver'
3
3
 
4
- require "ably/modules/event_emitter"
4
+ require 'ably/modules/event_emitter'
5
5
 
6
- require "ably/realtime/channel"
7
- require "ably/realtime/channels"
8
- require "ably/realtime/client"
9
- require "ably/realtime/connection"
10
- require "ably/realtime/connection/connection_state_machine"
11
- require "ably/realtime/connection/websocket_transport"
12
- require "ably/realtime/presence"
6
+ require 'ably/realtime/channel'
7
+ require 'ably/realtime/channels'
8
+ require 'ably/realtime/client'
9
+ require 'ably/realtime/connection'
10
+ require 'ably/realtime/connection/connection_manager'
11
+ require 'ably/realtime/connection/connection_state_machine'
12
+ require 'ably/realtime/connection/websocket_transport'
13
+ require 'ably/realtime/presence'
13
14
 
14
15
  Dir.glob(File.expand_path("models/*.rb", File.dirname(__FILE__))).each do |file|
15
16
  require file
16
17
  end
17
18
 
18
- require "ably/realtime/client/incoming_message_dispatcher"
19
- require "ably/realtime/client/outgoing_message_dispatcher"
19
+ Dir.glob(File.expand_path("realtime/models/*.rb", File.dirname(__FILE__))).each do |file|
20
+ require file
21
+ end
22
+
23
+ require 'ably/models/message_encoders/base'
24
+
25
+ require 'ably/realtime/client/incoming_message_dispatcher'
26
+ require 'ably/realtime/client/outgoing_message_dispatcher'
20
27
 
21
28
  module Ably
22
29
  # Realtime provides the top-level class to be instanced for the Ably Realtime library
@@ -25,6 +25,12 @@ module Ably
25
25
  #
26
26
  # @!attribute [r] state
27
27
  # @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
28
34
  #
29
35
  class Channel
30
36
  include Ably::Modules::Conversions
@@ -49,9 +55,12 @@ module Ably
49
55
 
50
56
  # Initialize a new Channel object
51
57
  #
52
- # @param client [Ably::Rest::Client]
53
- # @param name [String] The name of the channel
54
- # @param channel_options [Hash] Channel options, currently reserved for future Encryption options
58
+ # @param client [Ably::Rest::Client]
59
+ # @param name [String] The name of the channel
60
+ # @param channel_options [Hash] Channel options, currently reserved for Encryption options
61
+ # @option channel_options [Boolean] :encrypted setting this to true for this channel will encrypt & decrypt all messages automatically
62
+ # @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
63
+ #
55
64
  def initialize(client, name, channel_options = {})
56
65
  @client = client
57
66
  @name = name
@@ -120,6 +129,7 @@ module Ably
120
129
  # to need to call attach explicitly.
121
130
  #
122
131
  # @yield [Ably::Realtime::Channel] Block is called as soon as this channel is in the Attached state
132
+ # @return [void]
123
133
  #
124
134
  def attach(&block)
125
135
  if attached?
@@ -136,6 +146,7 @@ module Ably
136
146
  # Detach this channel, and call the block if provided when in a Detached or Failed state
137
147
  #
138
148
  # @yield [Ably::Realtime::Channel] Block is called as soon as this channel is in the Detached or Failed state
149
+ # @return [void]
139
150
  #
140
151
  def detach(&block)
141
152
  if detached? || failed?
@@ -172,7 +183,7 @@ module Ably
172
183
  # @api private
173
184
  def __incoming_msgbus__
174
185
  @__incoming_msgbus__ ||= Ably::Util::PubSub.new(
175
- coerce_into: Proc.new { |event| Models::ProtocolMessage::ACTION(event) }
186
+ coerce_into: Proc.new { |event| Ably::Models::ProtocolMessage::ACTION(event) }
176
187
  )
177
188
  end
178
189
 
@@ -181,6 +192,8 @@ module Ably
181
192
 
182
193
  def setup_event_handlers
183
194
  __incoming_msgbus__.subscribe(:message) do |message|
195
+ message.decode self
196
+
184
197
  subscriptions[:all].each { |cb| cb.call(message) }
185
198
  subscriptions[message.name].each { |cb| cb.call(message) }
186
199
  end
@@ -224,18 +237,18 @@ module Ably
224
237
 
225
238
  def send_messages_within_protocol_message(messages)
226
239
  client.connection.send_protocol_message(
227
- action: Models::ProtocolMessage::ACTION.Message.to_i,
240
+ action: Ably::Models::ProtocolMessage::ACTION.Message.to_i,
228
241
  channel: name,
229
242
  messages: messages
230
243
  )
231
244
  end
232
245
 
233
246
  def send_attach_protocol_message
234
- send_state_change_protocol_message Models::ProtocolMessage::ACTION.Attach
247
+ send_state_change_protocol_message Ably::Models::ProtocolMessage::ACTION.Attach
235
248
  end
236
249
 
237
250
  def send_detach_protocol_message
238
- send_state_change_protocol_message Models::ProtocolMessage::ACTION.Detach
251
+ send_state_change_protocol_message Ably::Models::ProtocolMessage::ACTION.Detach
239
252
  end
240
253
 
241
254
  def send_state_change_protocol_message(state)
@@ -246,13 +259,13 @@ module Ably
246
259
  end
247
260
 
248
261
  def create_message(name, data)
249
- model = {
250
- name: name,
251
- data: data
252
- }
253
- model.merge!(clientId: client.client_id) if client.client_id
262
+ message = { name: name }
263
+ message.merge!(data: data) unless data.nil?
264
+ message.merge!(clientId: client.client_id) if client.client_id
254
265
 
255
- Models::Message.new(model, nil)
266
+ Ably::Models::Message.new(message, nil).tap do |message|
267
+ message.encode self
268
+ end
256
269
  end
257
270
 
258
271
  def rest_channel
@@ -16,6 +16,12 @@ module Ably
16
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
17
  # @!attribute [r] echo_messages
18
18
  # @return [Boolean] If false, suppresses messages originating from this connection being echoed back on the same connection. Defaults to true
19
+ # @!attribute [r] encoders
20
+ # (see Ably::Rest::Client#encoders)
21
+ # @!attribute [r] protocol
22
+ # (see Ably::Rest::Client#protocol)
23
+ # @!attribute [r] protocol_binary?
24
+ # (see Ably::Rest::Client#protocol_binary?)
19
25
  class Client
20
26
  extend Forwardable
21
27
 
@@ -23,8 +29,9 @@ module Ably
23
29
 
24
30
  attr_reader :channels, :auth, :rest_client, :echo_messages
25
31
  def_delegators :auth, :client_id, :auth_options
26
- def_delegators :@rest_client, :environment, :use_tls?, :protocol
27
- def_delegators :@rest_client, :logger, :log_level
32
+ def_delegators :@rest_client, :encoders
33
+ def_delegators :@rest_client, :environment, :use_tls?, :protocol, :protocol_binary?
34
+ def_delegators :@rest_client, :log_level
28
35
  def_delegators :@rest_client, :time, :stats
29
36
 
30
37
  # Creates a {Ably::Realtime::Client Realtime Client} and configures the {Ably::Auth} object for the connection.
@@ -49,10 +56,11 @@ module Ably
49
56
  # client = Ably::Realtime::Client.new(api_key: 'key.id:secret', client_id: 'john')
50
57
  #
51
58
  def initialize(options, &auth_block)
52
- @rest_client = Ably::Rest::Client.new(options, &auth_block)
53
- @auth = @rest_client.auth
54
- @channels = Ably::Realtime::Channels.new(self)
55
- @echo_messages = @rest_client.options.fetch(:echo_messages, true) == false ? false : true
59
+ @rest_client = Ably::Rest::Client.new(options, &auth_block)
60
+ @auth = @rest_client.auth
61
+ @channels = Ably::Realtime::Channels.new(self)
62
+ @echo_messages = @rest_client.options.fetch(:echo_messages, true) == false ? false : true
63
+ @custom_socket_host = @rest_client.options[:ws_host]
56
64
  end
57
65
 
58
66
  # Return a {Ably::Realtime::Channel Realtime Channel} for the given name
@@ -84,7 +92,7 @@ module Ably
84
92
  def endpoint
85
93
  URI::Generic.build(
86
94
  scheme: use_tls? ? "wss" : "ws",
87
- host: [environment, DOMAIN].compact.join('-')
95
+ host: custom_socket_host || [environment, DOMAIN].compact.join('-')
88
96
  )
89
97
  end
90
98
 
@@ -93,6 +101,22 @@ module Ably
93
101
  def connection
94
102
  @connection ||= Connection.new(self)
95
103
  end
104
+
105
+ # @!attribute [r] custom_socket_host
106
+ # @return [String,nil] Returns the custom socket host that is being used if it was provided with the option :ws_host when the {Client} was created
107
+ def custom_socket_host
108
+ @custom_socket_host
109
+ end
110
+
111
+ # (see Ably::Rest::Client#register_encoder)
112
+ def register_encoder(encoder)
113
+ rest_client.register_encoder encoder
114
+ end
115
+
116
+ # (see Ably::Rest::Client#logger)
117
+ def logger
118
+ @logger ||= Ably::Logger.new(self, log_level, rest_client.logger.custom_logger)
119
+ end
96
120
  end
97
121
  end
98
122
  end