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
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency 'bundler', '~> 1.3'
29
29
  spec.add_development_dependency 'rake'
30
30
  spec.add_development_dependency 'redcarpet'
31
- spec.add_development_dependency 'rspec', '~> 3.0'
31
+ spec.add_development_dependency 'rspec', '~> 3.1.0' # version lock, see event_machine_helper.rb#patch_example_block_with_surrounding_eventmachine_reactor
32
32
  spec.add_development_dependency 'yard'
33
33
  spec.add_development_dependency 'webmock'
34
34
  end
@@ -10,11 +10,3 @@ require 'ably/logger'
10
10
  require 'ably/realtime'
11
11
  require 'ably/rest'
12
12
  require 'ably/version'
13
-
14
- # Ably is the base namespace for the Ably {Ably::Realtime Realtime} & {Ably::Rest Rest} client libraries.
15
- #
16
- # Please refer to the {file:README.md Readme} on getting started.
17
- #
18
- # @see file:README.md README
19
- module Ably
20
- end
@@ -34,14 +34,15 @@ module Ably
34
34
  # Creates an Auth object
35
35
  #
36
36
  # @param [Ably::Rest::Client] client {Ably::Rest::Client} this Auth object uses
37
- # @param [Hash] auth_options see {Ably::Rest::Client#initialize}
38
- # @yield [auth_options] see {Ably::Rest::Client#initialize}
39
- def initialize(client, auth_options, &auth_block)
40
- auth_options = auth_options.dup
37
+ # @param options (see Ably::Rest::Client#initialize)
38
+ # @option (see Ably::Rest::Client#initialize)
39
+ # @yield (see Ably::Rest::Client#initialize)
40
+ def initialize(client, options, &token_request_block)
41
+ auth_options = options.dup
41
42
 
42
- @client = client
43
- @options = auth_options
44
- @auth_callback = auth_block if block_given?
43
+ @client = client
44
+ @options = auth_options
45
+ @default_token_block = token_request_block if block_given?
45
46
 
46
47
  unless auth_options.kind_of?(Hash)
47
48
  raise ArgumentError, 'Expected auth_options to be a Hash'
@@ -54,16 +55,17 @@ module Ably
54
55
  if auth_options[:api_key]
55
56
  api_key_parts = auth_options[:api_key].to_s.match(/(?<id>[\w_-]+\.[\w_-]+):(?<secret>[\w_-]+)/)
56
57
  raise ArgumentError, 'api_key is invalid' unless api_key_parts
57
- auth_options[:key_id] = api_key_parts[:id]
58
- auth_options[:key_secret] = api_key_parts[:secret]
58
+ auth_options[:key_id] = api_key_parts[:id].encode(Encoding::UTF_8)
59
+ auth_options[:key_secret] = api_key_parts[:secret].encode(Encoding::UTF_8)
59
60
  end
60
61
 
61
62
  if using_basic_auth? && !api_key_present?
62
63
  raise ArgumentError, 'api_key is missing. Either an API key, token, or token auth method must be provided'
63
64
  end
64
65
 
65
- if has_client_id? && !api_key_present?
66
- raise ArgumentError, 'client_id cannot be provided without a complete API key. Key ID & Secret is needed to authenticate with Ably and obtain a token'
66
+ if has_client_id?
67
+ raise ArgumentError, 'client_id cannot be provided without a complete API key. Key ID & Secret is needed to authenticate with Ably and obtain a token' unless api_key_present?
68
+ ensure_utf_8 :client_id, client_id
67
69
  end
68
70
 
69
71
  @options.freeze
@@ -74,25 +76,14 @@ module Ably
74
76
  # In the event that a new token request is made, the specified options are used.
75
77
  #
76
78
  # @param [Hash] options the options for the token request
77
- # @option options [String] :key_id key ID for the designated application (defaults to client key_id)
78
- # @option options [String] :key_secret key secret for the designated application used to sign token requests (defaults to client key_secret)
79
- # @option options [String] :client_id client ID identifying this connection to other clients (defaults to client client_id if configured)
80
- # @option options [String] :auth_url a URL to be used to GET or POST a set of token request params, to obtain a signed token request.
81
- # @option options [Hash] :auth_headers a set of application-specific headers to be added to any request made to the authUrl
82
- # @option options [Hash] :auth_params a set of application-specific query params to be added to any request made to the authUrl
83
- # @option options [Symbol] :auth_method HTTP method to use with auth_url, must be either `:get` or `:post` (defaults to :get)
84
- # @option options [Integer] :ttl validity time in seconds for the requested {Ably::Models::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
85
- # @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
86
- # @option options [Boolean] :query_time when true will query the {https://ably.io Ably} system for the current time instead of using the local time
87
- # @option options [Time] :timestamp the time of the of the request
88
- # @option options [String] :nonce an unquoted, unescaped random string of at least 16 characters
89
- # @option options [Boolean] :force obtains a new token even if the current token is valid
79
+ # @option options (see #request_token)
80
+ # @option options [Boolean] :force obtains a new token even if the current token is valid
90
81
  #
91
- # @yield [options] (optional) if an auth block is passed to this method, then this block will be called to create a new token request object
92
- # @yieldparam [Hash] options options passed to request_token will be in turn sent to the block in this argument
93
- # @yieldreturn [Hash] valid token request object, see {Auth#create_token_request}
82
+ # @yield (see #request_token)
83
+ # @yieldparam [Hash] options options passed to {#authorise} will be in turn sent to the block in this argument
84
+ # @yieldreturn (see #request_token)
94
85
  #
95
- # @return [Ably::Models::Token]
86
+ # @return (see #request_token)
96
87
  #
97
88
  # @example
98
89
  # # will issue a simple token request using basic auth
@@ -105,12 +96,15 @@ module Ably
105
96
  # token_request
106
97
  # end
107
98
  #
108
- def authorise(options = {}, &block)
99
+ def authorise(options = {}, &token_request_block)
109
100
  if !options[:force] && current_token
110
101
  return current_token unless current_token.expired?
111
102
  end
112
103
 
113
- @current_token = request_token(options, &block)
104
+ @options = @options.merge(options)
105
+ @default_token_block = token_request_block if block_given?
106
+
107
+ @current_token = request_token(options, &token_request_block)
114
108
  end
115
109
 
116
110
  # Request a {Ably::Models::Token} which can be used to make authenticated token based requests
@@ -129,9 +123,9 @@ module Ably
129
123
  # @option options [Time] :timestamp the time of the of the request
130
124
  # @option options [String] :nonce an unquoted, unescaped random string of at least 16 characters
131
125
  #
132
- # @yield [options] (optional) if an auth block is passed to this method, then this block will be called to create a new token request object
133
- # @yieldparam [Hash] options options passed to request_token will be in turn sent to the block in this argument
134
- # @yieldreturn [Hash] valid token request object, see {Auth#create_token_request}
126
+ # @yield [options] (optional) if a token request block is passed to this method, then this block will be called whenever a new token is required
127
+ # @yieldparam [Hash] options options passed to {#request_token} will be in turn sent to the block in this argument
128
+ # @yieldreturn [Hash] expects a valid token request object, see {#create_token_request}
135
129
  #
136
130
  # @return [Ably::Models::Token]
137
131
  #
@@ -146,14 +140,14 @@ module Ably
146
140
  # token_request
147
141
  # end
148
142
  #
149
- def request_token(options = {}, &block)
143
+ def request_token(options = {}, &token_request_block)
150
144
  token_options = self.auth_options.merge(options)
151
145
 
152
146
  auth_url = token_options.delete(:auth_url)
153
147
  token_request = if block_given?
154
- yield(token_options)
155
- elsif auth_callback
156
- auth_callback.call(token_options)
148
+ token_request_block.call(token_options)
149
+ elsif default_token_block
150
+ default_token_block.call(token_options)
157
151
  elsif auth_url
158
152
  token_request_from_auth_url(auth_url, token_options)
159
153
  else
@@ -162,7 +156,7 @@ module Ably
162
156
 
163
157
  token_request = IdiomaticRubyWrapper(token_request)
164
158
 
165
- response = client.post("/keys/#{token_request.fetch(:id)}/requestToken", token_request.hash, send_auth_header: false)
159
+ response = client.post("/keys/#{token_request.fetch(:id)}/requestToken", token_request.hash, send_auth_header: false, disable_automatic_reauthorise: true)
166
160
  body = IdiomaticRubyWrapper(response.body)
167
161
 
168
162
  Ably::Models::Token.new(body.fetch(:access_token))
@@ -220,8 +214,9 @@ module Ably
220
214
  token_request[:capability] = token_request[:capability].to_json
221
215
  end
222
216
 
223
- token_request[:mac] = sign_params(token_request, request_key_secret)
217
+ ensure_utf_8 :nonce, token_request[:nonce], allow_nil: true
224
218
 
219
+ token_request[:mac] = sign_params(token_request, request_key_secret)
225
220
  token_request
226
221
  end
227
222
 
@@ -244,6 +239,7 @@ module Ably
244
239
 
245
240
  # True when Token Auth is being used to authenticate with Ably
246
241
  def using_token_auth?
242
+ return options[:use_token_auth] if options.has_key?(:use_token_auth)
247
243
  token_id || current_token || has_client_id? || token_creatable_externally?
248
244
  end
249
245
 
@@ -280,26 +276,38 @@ module Ably
280
276
  # True if prerequisites for creating a new token request are present
281
277
  #
282
278
  # One of the following criterion must be met:
283
- # * Valid key id and secret
279
+ # * Valid key id and secret and token_id option not provided as token options cannot be determined
284
280
  # * Authentication callback for new token requests
285
281
  # * Authentication URL for new token requests
286
282
  #
287
283
  # @return [Boolean]
288
284
  def token_renewable?
289
- token_creatable_externally? || api_key_present?
285
+ token_creatable_externally? || (api_key_present? && !token_id)
286
+ end
287
+
288
+ # Returns false when attempting to send an API Key over a non-secure connection
289
+ # Token auth must be used for non-secure connections
290
+ #
291
+ # @return [Boolean]
292
+ def authentication_security_requirements_met?
293
+ client.use_tls? || using_token_auth?
290
294
  end
291
295
 
292
296
  private
293
- attr_reader :auth_callback
297
+ attr_reader :default_token_block
298
+
299
+ def ensure_api_key_sent_over_secure_connection
300
+ raise Ably::Exceptions::InsecureRequestError, 'Cannot use Basic Auth over non-TLS connections' unless authentication_security_requirements_met?
301
+ end
294
302
 
295
303
  # Basic Auth HTTP Authorization header value
296
304
  def basic_auth_header
297
- raise Ably::Exceptions::InsecureRequestError, 'Cannot use Basic Auth over non-TLS connections' unless client.use_tls?
305
+ ensure_api_key_sent_over_secure_connection
298
306
  "Basic #{encode64("#{api_key}")}"
299
307
  end
300
308
 
301
309
  def token_auth_id
302
- current_token_id = if token_id
310
+ if token_id
303
311
  token_id
304
312
  else
305
313
  authorise.id
@@ -313,7 +321,7 @@ module Ably
313
321
 
314
322
  # Basic Auth params to authenticate the Realtime connection
315
323
  def basic_auth_params
316
- raise Ably::Exceptions::InsecureRequestError, 'Cannot use Basic Auth over non-TLS connections' unless client.use_tls?
324
+ ensure_api_key_sent_over_secure_connection
317
325
  # TODO: Change to key_secret when API is updated
318
326
  {
319
327
  key_id: key_id,
@@ -403,7 +411,7 @@ module Ably
403
411
  end
404
412
 
405
413
  def token_callback_present?
406
- !!auth_callback
414
+ !!default_token_block
407
415
  end
408
416
 
409
417
  def token_url_present?
@@ -33,23 +33,37 @@ module Ably
33
33
  # Encoding or decoding failure
34
34
  class EncoderError < BaseAblyException; end
35
35
 
36
+ # Connection error from Realtime or REST service
37
+ class ConnectionError < BaseAblyException
38
+ def initialize(message, status = nil, code = nil, base_error = nil)
39
+ super message, status, code
40
+ @base_error = base_error
41
+ end
42
+ end
43
+
44
+ # Connection Timeout accessing Realtime or REST service
45
+ class ConnectionTimeoutError < ConnectionError; end
46
+
47
+ # Invalid State Change error on a {https://github.com/gocardless/statesman Statesman State Machine}
48
+ class StateChangeError < BaseAblyException; end
49
+
36
50
  # A generic Ably exception taht supports a status & code.
37
51
  # See https://github.com/ably/ably-common/blob/master/protocol/errors.json for a list of Ably errors
38
52
  class Standard < BaseAblyException; end
39
53
 
40
54
  # The HTTP request has returned a 500 error
41
- class ServerError < StandardError; end
55
+ class ServerError < BaseAblyException; end
42
56
 
43
57
  # PaginatedResource cannot retrieve the page
44
- class InvalidPageError < StandardError; end
58
+ class InvalidPageError < BaseAblyException; end
45
59
 
46
60
  # The expected response from the server was invalid
47
- class InvalidResponseBody < StandardError; end
61
+ class InvalidResponseBody < BaseAblyException; end
48
62
 
49
63
  # The request cannot be performed because it is insecure
50
- class InsecureRequestError < StandardError; end
64
+ class InsecureRequestError < BaseAblyException; end
51
65
 
52
66
  # The token request could not be created
53
- class TokenRequestError < StandardError; end
67
+ class TokenRequestError < BaseAblyException; end
54
68
  end
55
69
  end
@@ -66,7 +66,7 @@ module Ably
66
66
  end
67
67
 
68
68
  def realtime?
69
- client.respond_to?(:connection)
69
+ defined?(Ably::Realtime::Client) && client.kind_of?(Ably::Realtime::Client)
70
70
  end
71
71
 
72
72
  def default_logger
@@ -19,7 +19,7 @@ module Ably::Models
19
19
  @hash_object = IdiomaticRubyWrapper(hash_object.clone.freeze)
20
20
  end
21
21
 
22
- %w( message code status_code ).each do |attribute|
22
+ %w(message code status_code).each do |attribute|
23
23
  define_method attribute do
24
24
  hash[attribute.to_sym]
25
25
  end
@@ -50,7 +50,9 @@ module Ably::Models
50
50
  # @attribute [Hash] mixedCaseHashObject mixed case Hash object
51
51
  # @attribute [Array<Symbol,String>] stop_at array of keys that this wrapper should stop wrapping at to preserve the underlying Hash as is
52
52
  #
53
- def initialize(mixedCaseHashObject, stop_at: [])
53
+ def initialize(mixedCaseHashObject, options = {})
54
+ stop_at = options.fetch(:stop_at, [])
55
+
54
56
  if mixedCaseHashObject.kind_of?(IdiomaticRubyWrapper)
55
57
  $stderr.puts "<IdiomaticRubyWrapper#initialize> WARNING: Wrapping a IdiomaticRubyWrapper with another IdiomaticRubyWrapper"
56
58
  end
@@ -203,14 +205,14 @@ module Ably::Models
203
205
  # key is not found in mixedCase.
204
206
  def source_key_for(symbolized_key)
205
207
  format_preferences = [
206
- -> (key_sym) { convert_to_mixed_case(key_sym) },
207
- -> (key_sym) { key_sym.to_sym },
208
- -> (key_sym) { key_sym.to_s },
209
- -> (key_sym) { convert_to_mixed_case(key_sym).to_sym },
210
- -> (key_sym) { convert_to_lower_case(key_sym) },
211
- -> (key_sym) { convert_to_lower_case(key_sym).to_sym },
212
- -> (key_sym) { convert_to_mixed_case(key_sym, force_camel: true) },
213
- -> (key_sym) { convert_to_mixed_case(key_sym, force_camel: true).to_sym }
208
+ proc { |key_sym| convert_to_mixed_case(key_sym) },
209
+ proc { |key_sym| key_sym.to_sym },
210
+ proc { |key_sym| key_sym.to_s },
211
+ proc { |key_sym| convert_to_mixed_case(key_sym).to_sym },
212
+ proc { |key_sym| convert_to_lower_case(key_sym) },
213
+ proc { |key_sym| convert_to_lower_case(key_sym).to_sym },
214
+ proc { |key_sym| convert_to_mixed_case(key_sym, force_camel: true) },
215
+ proc { |key_sym| convert_to_mixed_case(key_sym, force_camel: true).to_sym }
214
216
  ]
215
217
 
216
218
  preferred_format = format_preferences.detect do |format|
@@ -32,12 +32,15 @@ module Ably::Models
32
32
  # @return [Time] Timestamp when the message was received by the Ably the real-time service
33
33
  # @!attribute [r] id
34
34
  # @return [String] A globally unique message ID
35
+ # @!attribute [r] connection_id
36
+ # @return [String] The connection_id of the publisher of the message
35
37
  # @!attribute [r] hash
36
38
  # @return [Hash] Access the protocol message Hash object ruby'fied to use symbolized keys
37
39
  #
38
40
  class Message
39
- include Ably::Modules::ModelCommon
41
+ include Ably::Modules::Conversions
40
42
  include Ably::Modules::Encodeable
43
+ include Ably::Modules::ModelCommon
41
44
  include EventMachine::Deferrable
42
45
 
43
46
  # {Message} initializer
@@ -50,9 +53,13 @@ module Ably::Models
50
53
  @raw_hash_object = hash_object
51
54
 
52
55
  set_hash_object hash_object
56
+
57
+ ensure_utf_8 :name, name, allow_nil: true
58
+ ensure_utf_8 :client_id, client_id, allow_nil: true
59
+ ensure_utf_8 :encoding, encoding, allow_nil: true
53
60
  end
54
61
 
55
- %w( name client_id encoding ).each do |attribute|
62
+ %w( name client_id encoding connection_id ).each do |attribute|
56
63
  define_method attribute do
57
64
  hash[attribute.to_sym]
58
65
  end
@@ -63,7 +70,11 @@ module Ably::Models
63
70
  end
64
71
 
65
72
  def id
66
- hash[:id] || "#{protocol_message.id!}:#{protocol_message_index}"
73
+ hash.fetch(:id) { "#{protocol_message.id!}:#{protocol_message_index}" }
74
+ end
75
+
76
+ def connection_id
77
+ hash.fetch(:connection_id) { protocol_message.connection_id if assigned_to_protocol_message? }
67
78
  end
68
79
 
69
80
  def timestamp
@@ -111,15 +122,7 @@ module Ably::Models
111
122
  attr_reader :raw_hash_object
112
123
 
113
124
  def protocol_message_index
114
- protocol_message.messages.index(self)
115
- end
116
-
117
- def connection_id
118
- protocol_message.connection_id
119
- end
120
-
121
- def message_serial
122
- protocol_message.message_serial
125
+ protocol_message.messages.map(&:object_id).index(self.object_id)
123
126
  end
124
127
 
125
128
  def set_hash_object(hash)
@@ -1,3 +1,5 @@
1
+ require 'ably/modules/conversions'
2
+
1
3
  # MessageEncoders are registered with the Ably client library and are responsible
2
4
  # for encoding & decoding messages.
3
5
  #
@@ -93,11 +95,6 @@ module Ably::Models::MessageEncoders
93
95
  end
94
96
 
95
97
  def self.register_default_encoders(client)
96
- Dir.glob(File.expand_path("*.rb", File.dirname(__FILE__))).each do |file|
97
- next if __FILE__ == file
98
- require file
99
- end
100
-
101
98
  client.register_encoder Ably::Models::MessageEncoders::Utf8
102
99
  client.register_encoder Ably::Models::MessageEncoders::Json
103
100
  client.register_encoder Ably::Models::MessageEncoders::Cipher
@@ -105,3 +102,7 @@ module Ably::Models::MessageEncoders
105
102
  end
106
103
  end
107
104
 
105
+ require 'ably/models/message_encoders/base64'
106
+ require 'ably/models/message_encoders/cipher'
107
+ require 'ably/models/message_encoders/json'
108
+ require 'ably/models/message_encoders/utf8'
@@ -1,4 +1,5 @@
1
1
  require 'base64'
2
+ require 'ably/models/message_encoders/base'
2
3
 
3
4
  module Ably::Models::MessageEncoders
4
5
  # Base64 binary Encoder and Decoder
@@ -1,3 +1,7 @@
1
+ require 'ably/exceptions'
2
+ require 'ably/models/message_encoders/base'
3
+ require 'ably/util/crypto'
4
+
1
5
  module Ably::Models::MessageEncoders
2
6
  # Cipher Encoder & Decoder that automatically encrypts & decrypts messages using Ably::Util::Crypto
3
7
  # when a channel has option encrypted: true.
@@ -41,7 +45,6 @@ module Ably::Models::MessageEncoders
41
45
  end
42
46
 
43
47
  message[:data] = crypto.decrypt(message[:data])
44
- message[:data].force_encoding(Encoding::ASCII_8BIT) if is_binary?(message)
45
48
  strip_current_encoding_part message
46
49
  end
47
50
  rescue OpenSSL::Cipher::CipherError => e
@@ -70,11 +73,11 @@ module Ably::Models::MessageEncoders
70
73
  end
71
74
 
72
75
  def cipher_algorithm(message)
73
- current_encoding_part(message).to_s[/^#{ENCODING_ID}\+([\w\d_-]+)$/, 1]
76
+ current_encoding_part(message).to_s[/^#{ENCODING_ID}\+([\w_-]+)$/, 1]
74
77
  end
75
78
 
76
79
  def already_encrypted?(message)
77
- message.fetch(:encoding, '').to_s.match(%r{(^|/)#{ENCODING_ID}\+([\w\d_-]+)($|/)})
80
+ message.fetch(:encoding, '').to_s.match(%r{(^|/)#{ENCODING_ID}\+([\w_-]+)($|/)})
78
81
  end
79
82
  end
80
83
  end