ably-rest 0.7.1 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +13 -5
  2. data/.gitmodules +1 -1
  3. data/.rspec +1 -0
  4. data/.travis.yml +7 -3
  5. data/SPEC.md +495 -419
  6. data/ably-rest.gemspec +19 -5
  7. data/lib/ably-rest.rb +9 -1
  8. data/lib/submodules/ably-ruby/.gitignore +6 -0
  9. data/lib/submodules/ably-ruby/.rspec +1 -0
  10. data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
  11. data/lib/submodules/ably-ruby/.travis.yml +10 -0
  12. data/lib/submodules/ably-ruby/Gemfile +4 -0
  13. data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
  14. data/lib/submodules/ably-ruby/README.md +122 -0
  15. data/lib/submodules/ably-ruby/Rakefile +34 -0
  16. data/lib/submodules/ably-ruby/SPEC.md +1794 -0
  17. data/lib/submodules/ably-ruby/ably.gemspec +36 -0
  18. data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
  19. data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
  20. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
  21. data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
  22. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
  23. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
  24. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
  25. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
  26. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
  27. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
  28. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
  29. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
  30. data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
  31. data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
  32. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
  33. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
  34. data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
  35. data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
  36. data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
  37. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
  38. data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
  39. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
  40. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
  41. data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
  42. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
  43. data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
  44. data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
  45. data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
  46. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
  47. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
  48. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
  49. data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  50. data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
  51. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
  52. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
  53. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
  54. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
  55. data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
  56. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
  57. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
  58. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
  59. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
  60. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
  61. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
  62. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
  63. data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
  64. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
  65. data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
  66. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
  67. data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
  68. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
  69. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
  70. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
  71. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
  72. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  73. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
  74. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
  75. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
  76. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
  77. data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
  78. data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
  79. data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
  80. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
  81. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
  82. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
  83. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
  84. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
  85. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
  86. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
  87. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
  88. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
  89. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
  90. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
  91. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
  92. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
  93. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
  94. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
  95. data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
  96. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
  97. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
  98. data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
  99. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
  100. data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
  101. data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
  102. data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
  103. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
  104. data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
  105. data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
  106. data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
  107. data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
  108. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
  109. data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
  110. data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
  111. data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
  112. data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
  113. data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
  114. data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
  115. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
  116. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
  117. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
  118. data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
  119. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
  120. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
  121. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
  122. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
  123. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
  124. data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
  125. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
  126. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
  127. data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
  128. data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
  129. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
  130. data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
  131. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
  132. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
  133. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
  134. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
  135. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
  136. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
  137. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
  138. data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
  139. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
  140. data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
  141. data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
  142. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
  143. data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
  144. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
  145. data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
  146. data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
  147. data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
  148. metadata +182 -27
@@ -0,0 +1,49 @@
1
+ require 'faraday'
2
+ require 'json'
3
+
4
+ module Ably
5
+ module Rest
6
+ module Middleware
7
+ # Encode the body of the message according to the mime type
8
+ class Encoder < ::Faraday::Response::Middleware
9
+ CONTENT_TYPE = 'Content-Type'.freeze unless defined? CONTENT_TYPE
10
+
11
+ def call(env)
12
+ encode env if env.body
13
+ @app.call env
14
+ end
15
+
16
+ private
17
+ def encode(env)
18
+ env.body = case request_type(env)
19
+ when 'application/x-msgpack'
20
+ to_msgpack(env.body)
21
+ when 'application/json', '', nil
22
+ env.request_headers[CONTENT_TYPE] = 'application/json'
23
+ to_json(env.body)
24
+ else
25
+ env.body
26
+ end
27
+ end
28
+
29
+ def to_msgpack(body)
30
+ body.to_msgpack
31
+ end
32
+
33
+ def to_json(body)
34
+ if body.kind_of?(String)
35
+ body
36
+ else
37
+ body.to_json
38
+ end
39
+ end
40
+
41
+ def request_type(env)
42
+ type = env.request_headers[CONTENT_TYPE].to_s
43
+ type = type.split(';', 2).first if type.index(';')
44
+ type
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,41 @@
1
+ require 'faraday'
2
+ require 'json'
3
+
4
+ module Ably
5
+ module Rest
6
+ module Middleware
7
+ # HTTP exceptions raised by Ably due to an error status code
8
+ # Ably returns JSON/Msgpack error codes and messages so include this if possible in the exception messages
9
+ class Exceptions < Faraday::Response::Middleware
10
+ def on_complete(env)
11
+ if env.status >= 400
12
+ error_status_code = env.status
13
+ error_code = nil
14
+
15
+ if env.body.kind_of?(Hash)
16
+ error = env.body.fetch('error', {})
17
+ error_status_code = error['statusCode'].to_i if error['statusCode']
18
+ error_code = error['code'].to_i if error['code']
19
+
20
+ if error
21
+ message = "#{error['message']} (status: #{error_status_code}, code: #{error_code})"
22
+ else
23
+ message = env.body
24
+ end
25
+ else
26
+ message = env.body
27
+ end
28
+
29
+ message = 'Unknown server error' if message.to_s.strip == ''
30
+
31
+ if env.status >= 500
32
+ raise Ably::Exceptions::ServerError, message
33
+ else
34
+ raise Ably::Exceptions::InvalidRequest.new(message, error_status_code, error_code)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,24 @@
1
+ require 'faraday'
2
+
3
+ module Ably
4
+ module Rest
5
+ module Middleware
6
+ # HTTP exceptions raised due to a status code error on a 3rd party site
7
+ # Used by auth calls
8
+ class ExternalExceptions < Faraday::Response::Middleware
9
+ def on_complete(env)
10
+ if env.status >= 400
11
+ error_status_code = env.status
12
+ message = "Error #{error_status_code}: #{(env.body || '')[0...200]}"
13
+
14
+ if error_status_code >= 500
15
+ raise Ably::Exceptions::ServerError, message
16
+ else
17
+ raise Ably::Exceptions::InvalidRequest, message
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ require 'faraday'
2
+ require 'json'
3
+
4
+ module Ably
5
+ module Rest
6
+ module Middleware
7
+ class FailIfUnsupportedMimeType < Faraday::Response::Middleware
8
+ def on_complete(env)
9
+ unless env.response_headers['Ably-Middleware-Parsed'] == true
10
+ raise Ably::Exceptions::InvalidResponseBody,
11
+ "Content Type #{env.response_headers['Content-Type']} is not supported by this client library"
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,58 @@
1
+ require 'faraday'
2
+
3
+ module Ably
4
+ module Rest
5
+ module Middleware
6
+ class Logger < Faraday::Response::Middleware
7
+ extend Forwardable
8
+
9
+ def initialize(app, logger = nil)
10
+ super(app)
11
+ @logger = logger || begin
12
+ require 'logger'
13
+ ::Logger.new(STDOUT)
14
+ end
15
+ end
16
+
17
+ def_delegators :@logger, :debug, :info, :warn, :error, :fatal
18
+
19
+ def call(env)
20
+ debug "=> URL: #{env.method} #{env.url}, Headers: #{dump_headers env.request_headers}"
21
+ debug "=> Body: #{body_for(env)}"
22
+ super
23
+ end
24
+
25
+ def on_complete(env)
26
+ debug "<= Status: #{env.status}, Headers: #{dump_headers env.response_headers}"
27
+ debug "<= Body: #{body_for(env)}"
28
+ end
29
+
30
+ private
31
+ def dump_headers(headers)
32
+ headers.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")
33
+ end
34
+
35
+ def body_for(env)
36
+ return '' if !env.body || env.body.empty?
37
+
38
+ if env.request_headers['Content-Type'] == 'application/x-msgpack'
39
+ MessagePack.unpack(env.body)
40
+ else
41
+ env.body
42
+ end
43
+
44
+ rescue StandardError
45
+ readable_body(env.body)
46
+ end
47
+
48
+ def readable_body(body)
49
+ if body.respond_to?(:encoding) && body.encoding == Encoding::ASCII_8BIT
50
+ body.unpack('H*')
51
+ else
52
+ body
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,27 @@
1
+ require 'faraday'
2
+ require 'json'
3
+
4
+ module Ably
5
+ module Rest
6
+ module Middleware
7
+ class ParseJson < Faraday::Response::Middleware
8
+ def on_complete(env)
9
+ if env.response_headers['Content-Type'] == 'application/json'
10
+ env.body = parse(env.body) unless env.response_headers['Ably-Middleware-Parsed'] == true
11
+ env.response_headers['Ably-Middleware-Parsed'] = true
12
+ end
13
+ end
14
+
15
+ def parse(body)
16
+ if body.length > 0
17
+ JSON.parse(body)
18
+ else
19
+ body
20
+ end
21
+ rescue JSON::ParserError => e
22
+ raise Ably::Exceptions::InvalidResponseBody, "Expected JSON response: #{e.message}"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ require 'faraday'
2
+ require 'msgpack'
3
+
4
+ module Ably
5
+ module Rest
6
+ module Middleware
7
+ class ParseMessagePack < Faraday::Response::Middleware
8
+ def on_complete(env)
9
+ if env.response_headers['Content-Type'] == 'application/x-msgpack'
10
+ env.body = parse(env.body) unless env.response_headers['Ably-Middleware-Parsed'] == true
11
+ env.response_headers['Ably-Middleware-Parsed'] = true
12
+ end
13
+ end
14
+
15
+ def parse(body)
16
+ if body.length > 0
17
+ MessagePack.unpack(body)
18
+ else
19
+ body
20
+ end
21
+ rescue MessagePack::MalformedFormatError => e
22
+ raise Ably::Exceptions::InvalidResponseBody, "Expected MessagePack response: #{e.message}"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,92 @@
1
+ module Ably
2
+ module Rest
3
+ class Presence
4
+ include Ably::Modules::Conversions
5
+
6
+ # {Ably::Rest::Client} for this Presence object
7
+ # @return {Ably::Rest::Client}
8
+ attr_reader :client
9
+
10
+ # {Ably::Rest::Channel} this Presence object is associated with
11
+ # @return {Ably::Rest::Channel}
12
+ attr_reader :channel
13
+
14
+ # Initialize a new Presence object
15
+ #
16
+ # @param client [Ably::Rest::Client]
17
+ # @param channel [Channel] The channel object
18
+ def initialize(client, channel)
19
+ @client = client
20
+ @channel = channel
21
+ end
22
+
23
+ # Obtain the set of members currently present for a channel
24
+ #
25
+ # @param [Hash] options the options for the set of members present
26
+ # @option options [Integer,Time] :start Time or millisecond since epoch
27
+ # @option options [Integer,Time] :end Time or millisecond since epoch
28
+ # @option options [Symbol] :direction `:forwards` or `:backwards`
29
+ # @option options [Integer] :limit Maximum number of members to retrieve up to 10,000
30
+ #
31
+ # @return [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] An Array of {Ably::Models::PresenceMessage} objects that supports paging (#next_page, #first_page)
32
+ #
33
+ def get(options = {})
34
+ options = options.dup
35
+
36
+ paginated_options = {
37
+ coerce_into: 'Ably::Models::PresenceMessage',
38
+ async_blocking_operations: options.delete(:async_blocking_operations),
39
+ }
40
+
41
+ response = client.get(base_path, options)
42
+
43
+ Ably::Models::PaginatedResource.new(response, base_path, client, paginated_options) do |presence_message|
44
+ presence_message.tap do |presence_message|
45
+ decode_message presence_message
46
+ end
47
+ end
48
+ end
49
+
50
+ # Return the presence messages history for the channel
51
+ #
52
+ # @param [Hash] options the options for the message history request
53
+ # @option options [Integer,Time] :start Time or millisecond since epoch
54
+ # @option options [Integer,Time] :end Time or millisecond since epoch
55
+ # @option options [Symbol] :direction `:forwards` or `:backwards`
56
+ # @option options [Integer] :limit Maximum number of presence messages to retrieve up to 10,000
57
+ #
58
+ # @return [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] An Array of {Ably::Models::PresenceMessage} objects that supports paging (#next_page, #first_page)
59
+ #
60
+ def history(options = {})
61
+ url = "#{base_path}/history"
62
+ options = options.dup
63
+
64
+ [:start, :end].each { |option| options[option] = as_since_epoch(options[option]) if options.has_key?(option) }
65
+
66
+ paginated_options = {
67
+ coerce_into: 'Ably::Models::PresenceMessage',
68
+ async_blocking_operations: options.delete(:async_blocking_operations),
69
+ }
70
+
71
+ response = client.get(url, options)
72
+
73
+ Ably::Models::PaginatedResource.new(response, url, client, paginated_options) do |presence_message|
74
+ presence_message.tap do |presence_message|
75
+ decode_message presence_message
76
+ end
77
+ end
78
+ end
79
+
80
+ private
81
+ def base_path
82
+ "/channels/#{CGI.escape(channel.name)}/presence"
83
+ end
84
+
85
+ def decode_message(presence_message)
86
+ presence_message.decode self.channel
87
+ rescue Ably::Exceptions::CipherError, Ably::Exceptions::EncoderError => e
88
+ client.logger.error "Decoding Error on presence channel '#{channel.name}', presence message client_id '#{presence_message.client_id}'. #{e.class.name}: #{e.message}"
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,105 @@
1
+ require 'msgpack'
2
+ require 'openssl'
3
+
4
+ module Ably::Util
5
+ class Crypto
6
+ DEFAULTS = {
7
+ algorithm: 'AES',
8
+ mode: 'CBC',
9
+ key_length: 128,
10
+ }
11
+
12
+ BLOCK_LENGTH = 16
13
+
14
+ # Configured options for this Crypto object, see {#initialize} for a list of configured options
15
+ #
16
+ # @return [Hash]
17
+ attr_reader :options
18
+
19
+ # Creates a {Ably::Util::Crypto} object
20
+ #
21
+ # @param [Hash] options an options Hash used to configure the Crypto library
22
+ # @option options [String] :key Required secret key used for encrypting and decrypting
23
+ # @option options [String] :algorithm optional (default AES), specify the encryption algorithm supported by {http://ruby-doc.org/stdlib-2.0/libdoc/openssl/rdoc/OpenSSL/Cipher.html OpenSSL::Cipher}
24
+ # @option options [String] :mode optional (default CBC), specify the cipher mode supported by {http://ruby-doc.org/stdlib-2.0/libdoc/openssl/rdoc/OpenSSL/Cipher.html OpenSSL::Cipher}
25
+ # @option options [Integer] :key_length optional (default 128), specify the key length of the cipher supported by {http://ruby-doc.org/stdlib-2.0/libdoc/openssl/rdoc/OpenSSL/Cipher.html OpenSSL::Cipher}
26
+ # @option options [String] :combined optional (default AES-128-CBC), specify in one option the algorithm, key length and cipher of the cipher supported by {http://ruby-doc.org/stdlib-2.0/libdoc/openssl/rdoc/OpenSSL/Cipher.html OpenSSL::Cipher}
27
+ #
28
+ # @return [Ably::Util::Crypto]
29
+ #
30
+ # @example
31
+ # crypto = Ably::Util::Crypto.new(key: 'mysecret')
32
+ # encrypted = crypto.encrypt('secret text')
33
+ # crypto.decrypt(decrypted) # => 'secret text'
34
+ #
35
+ def initialize(options)
36
+ raise ArgumentError, ':key is required' unless options.has_key?(:key)
37
+ @options = DEFAULTS.merge(options).freeze
38
+ end
39
+
40
+ # Encrypt payload using configured Cipher
41
+ #
42
+ # @param [String] payload the payload to be encrypted
43
+ # @param [Hash] encrypt_options an options Hash to configure the encrypt action
44
+ # @option encrypt_options [String] :iv optionally use the provided Initialization Vector instead of a randomly generated IV
45
+ #
46
+ # @return [String] binary string with {Encoding::ASCII_8BIT} encoding
47
+ #
48
+ def encrypt(payload, encrypt_options = {})
49
+ cipher = openssl_cipher
50
+ cipher.encrypt
51
+ cipher.key = key
52
+ iv = encrypt_options[:iv] || options[:iv] || cipher.random_iv
53
+ cipher.iv = iv
54
+
55
+ iv << cipher.update(payload) << cipher.final
56
+ end
57
+
58
+ # Decrypt payload using configured Cipher
59
+ #
60
+ # @param [String] encrypted_payload_with_iv the encrypted payload to be decrypted
61
+ #
62
+ # @return [String]
63
+ #
64
+ def decrypt(encrypted_payload_with_iv)
65
+ raise Ably::Exceptions::CipherError, 'iv is missing or not long enough' unless encrypted_payload_with_iv.length >= BLOCK_LENGTH*2
66
+
67
+ iv = encrypted_payload_with_iv.slice(0...BLOCK_LENGTH)
68
+ encrypted_payload = encrypted_payload_with_iv.slice(BLOCK_LENGTH..-1)
69
+
70
+ decipher = openssl_cipher
71
+ decipher.decrypt
72
+ decipher.key = key
73
+ decipher.iv = iv
74
+
75
+ decipher.update(encrypted_payload) << decipher.final
76
+ end
77
+
78
+ # Generate a random key
79
+ # @return [String]
80
+ def random_key
81
+ openssl_cipher.random_key
82
+ end
83
+
84
+ # Generate a random IV
85
+ # @return [String]
86
+ def random_iv
87
+ openssl_cipher.random_iv
88
+ end
89
+
90
+ # The Cipher algorithm string such as AES-128-CBC
91
+ # @return [String]
92
+ def cipher_type
93
+ (options[:combined] || "#{options[:algorithm]}-#{options[:key_length]}-#{options[:mode]}").to_s.upcase
94
+ end
95
+
96
+ private
97
+ def key
98
+ options[:key]
99
+ end
100
+
101
+ def openssl_cipher
102
+ OpenSSL::Cipher.new(cipher_type)
103
+ end
104
+ end
105
+ end