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