ably-rest 0.7.1 → 0.7.3

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 (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,108 @@
1
+ require 'ably/modules/conversions'
2
+
3
+ # MessageEncoders are registered with the Ably client library and are responsible
4
+ # for encoding & decoding messages.
5
+ #
6
+ # For example, if a message body is detected as JSON, it is encoded as a String and the encoding attribute
7
+ # of the message is defined as 'json'.
8
+ # Encrypted messages are encoded & decoded by the Cipher encoder.
9
+ #
10
+ module Ably::Models::MessageEncoders
11
+ extend Ably::Modules::Conversions
12
+
13
+ # Base interface for an Ably Encoder
14
+ #
15
+ class Base
16
+ attr_reader :client
17
+
18
+ def initialize(client)
19
+ @client = client
20
+ end
21
+
22
+ # #encode is called once before a message is sent to Ably
23
+ #
24
+ # It is the responsibility of the #encode method to detect the intended encoding and modify the :data & :encoding properties of the message object.
25
+ #
26
+ # @param [Hash] message the message as a Hash object received directly from Ably.
27
+ # The message contains properties :name, :data, :encoding, :timestamp, and optionally :id and :client_id.
28
+ # This #encode method should modify the message Hash if any encoding action is to be taken
29
+ # @param [Hash] channel_options the options used to initialize the channel that this message was received on
30
+ #
31
+ # @return [void]
32
+ def encode(message, channel_options)
33
+ raise "Not yet implemented"
34
+ end
35
+
36
+ # #decode is called once for every encoding step
37
+ # i.e. if message encoding arrives with 'utf-8/cipher+aes-128-cbc/base64'
38
+ # the decoder will call #decode once for each encoding part such as 'base64', then 'cipher+aes-128-cbc', and finally 'utf-8'
39
+ #
40
+ # It is the responsibility of the #decode method to detect the current encoding part and modify the :data & :encoding properties of the message object.
41
+ #
42
+ # @param [Hash] message the message as a Hash object received directly from Ably.
43
+ # The message contains properties :name, :data, :encoding, :timestamp, and optionally :id and :client_id.
44
+ # This #encode method should modify the message Hash if any decoding action is to be taken
45
+ # @param [Hash] channel_options the options used to initialize the channel that this message was received on
46
+ #
47
+ # @return [void]
48
+ def decode(message, channel_options)
49
+ raise "Not yet implemented"
50
+ end
51
+
52
+ # Add encoding to the message Hash.
53
+ # Ensures that encoding delimeter is used where required i.e utf-8/cipher+aes-128-cbc/base64
54
+ #
55
+ # @param [Hash] message the message as a Hash object received directly from Ably.
56
+ # @param [String] encoding encoding to add to the current encoding
57
+ #
58
+ # @return [void]
59
+ def add_encoding_to_message(encoding, message)
60
+ message[:encoding] = [message[:encoding], encoding].compact.join('/')
61
+ end
62
+
63
+ # Returns the right most encoding form a meessage encoding, and nil if none exists
64
+ # i.e. current_encoding_part('utf-8/cipher+aes-128-cbc/base64') => 'base64'
65
+ #
66
+ # @return [String,nil]
67
+ def current_encoding_part(message)
68
+ if message[:encoding]
69
+ message[:encoding].split('/')[-1]
70
+ end
71
+ end
72
+
73
+ # Strip the current encoding part within the message Hash.
74
+ #
75
+ # For example, calling this method on an :encoding value of 'utf-8/cipher+aes-128-cbc/base64' would update the attribute
76
+ # :encoding to 'utf-8/cipher+aes-128-cbc'
77
+ #
78
+ # @param [Hash] message the message as a Hash object received directly from Ably.
79
+ #
80
+ # @return [void]
81
+ def strip_current_encoding_part(message)
82
+ raise "Cannot strip encoding when there is no encoding for this message" unless message[:encoding]
83
+ message[:encoding] = message[:encoding].split('/')[0...-1].join('/')
84
+ message[:encoding] = nil if message[:encoding].empty?
85
+ end
86
+
87
+ # True of the message data payload is empty
88
+ #
89
+ # @param [Hash] message the message as a Hash object received directly from Ably.
90
+ #
91
+ # @return [Boolean]
92
+ def is_empty?(message)
93
+ message[:data].nil? || message[:data] == ''
94
+ end
95
+ end
96
+
97
+ def self.register_default_encoders(client)
98
+ client.register_encoder Ably::Models::MessageEncoders::Utf8
99
+ client.register_encoder Ably::Models::MessageEncoders::Json
100
+ client.register_encoder Ably::Models::MessageEncoders::Cipher
101
+ client.register_encoder Ably::Models::MessageEncoders::Base64
102
+ end
103
+ end
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'
@@ -0,0 +1,40 @@
1
+ require 'base64'
2
+ require 'ably/models/message_encoders/base'
3
+
4
+ module Ably::Models::MessageEncoders
5
+ # Base64 binary Encoder and Decoder
6
+ # Uses encoding identifier 'base64'
7
+ #
8
+ class Base64 < Base
9
+ ENCODING_ID = 'base64'
10
+
11
+ def encode(message, channel_options)
12
+ return if is_empty?(message)
13
+
14
+ if is_binary?(message) && transport_protocol_text?
15
+ message[:data] = ::Base64.encode64(message[:data])
16
+ add_encoding_to_message ENCODING_ID, message
17
+ end
18
+ end
19
+
20
+ def decode(message, channel_options)
21
+ if is_base64_encoded?(message)
22
+ message[:data] = ::Base64.decode64(message[:data])
23
+ strip_current_encoding_part message
24
+ end
25
+ end
26
+
27
+ private
28
+ def is_binary?(message)
29
+ message[:data].kind_of?(String) && message[:data].encoding == Encoding::ASCII_8BIT
30
+ end
31
+
32
+ def is_base64_encoded?(message)
33
+ current_encoding_part(message).to_s.match(/^#{ENCODING_ID}$/i)
34
+ end
35
+
36
+ def transport_protocol_text?
37
+ !client.protocol_binary?
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,83 @@
1
+ require 'ably/exceptions'
2
+ require 'ably/models/message_encoders/base'
3
+ require 'ably/util/crypto'
4
+
5
+ module Ably::Models::MessageEncoders
6
+ # Cipher Encoder & Decoder that automatically encrypts & decrypts messages using Ably::Util::Crypto
7
+ # when a channel has option encrypted: true.
8
+ #
9
+ class Cipher < Base
10
+ ENCODING_ID = 'cipher'
11
+
12
+ def initialize(*args)
13
+ super
14
+ @cryptos = Hash.new
15
+ end
16
+
17
+ def encode(message, channel_options)
18
+ return if is_empty?(message)
19
+ return if already_encrypted?(message)
20
+
21
+ if channel_configured_for_encryption?(channel_options)
22
+ add_encoding_to_message 'utf-8', message unless is_binary?(message) || is_utf8_encoded?(message)
23
+
24
+ crypto = crypto_for(channel_options)
25
+ message[:data] = crypto.encrypt(message[:data])
26
+ add_encoding_to_message "#{ENCODING_ID}+#{crypto.cipher_type.downcase}", message
27
+ end
28
+ rescue ArgumentError => e
29
+ raise Ably::Exceptions::CipherError.new(e.message, nil, 92005)
30
+ rescue RuntimeError => e
31
+ if e.message.match(/unsupported cipher algorithm/i)
32
+ raise Ably::Exceptions::CipherError.new(e.message, nil, 92004)
33
+ end
34
+ end
35
+
36
+ def decode(message, channel_options)
37
+ if is_cipher_encoded?(message)
38
+ unless channel_configured_for_encryption?(channel_options)
39
+ raise Ably::Exceptions::CipherError.new('Message cannot be decrypted as the channel is not set up for encryption & decryption', nil, 92001)
40
+ end
41
+
42
+ crypto = crypto_for(channel_options)
43
+ unless crypto.cipher_type == cipher_algorithm(message).upcase
44
+ raise Ably::Exceptions::CipherError.new("Cipher algorithm #{crypto.cipher_type} does not match message cipher algorithm of #{cipher_algorithm(message).upcase}", nil, 92002)
45
+ end
46
+
47
+ message[:data] = crypto.decrypt(message[:data])
48
+ strip_current_encoding_part message
49
+ end
50
+ rescue OpenSSL::Cipher::CipherError => e
51
+ raise Ably::Exceptions::CipherError.new("CipherError decrypting data, the private key may not be correct", nil, 92003)
52
+ end
53
+
54
+ private
55
+ def is_binary?(message)
56
+ message.fetch(:data, '').encoding == Encoding::ASCII_8BIT
57
+ end
58
+
59
+ def is_utf8_encoded?(message)
60
+ current_encoding_part(message).to_s.match(/^utf-8$/i)
61
+ end
62
+
63
+ def crypto_for(channel_options)
64
+ @cryptos[channel_options.fetch(:cipher_params, :default)] ||= Ably::Util::Crypto.new(channel_options.fetch(:cipher_params, {}))
65
+ end
66
+
67
+ def channel_configured_for_encryption?(channel_options)
68
+ channel_options.fetch(:encrypted, false)
69
+ end
70
+
71
+ def is_cipher_encoded?(message)
72
+ !cipher_algorithm(message).nil?
73
+ end
74
+
75
+ def cipher_algorithm(message)
76
+ current_encoding_part(message).to_s[/^#{ENCODING_ID}\+([\w_-]+)$/, 1]
77
+ end
78
+
79
+ def already_encrypted?(message)
80
+ message.fetch(:encoding, '').to_s.match(%r{(^|/)#{ENCODING_ID}\+([\w_-]+)($|/)})
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,34 @@
1
+ require 'json'
2
+ require 'ably/models/message_encoders/base'
3
+
4
+ module Ably::Models::MessageEncoders
5
+ # JSON Encoder and Decoder
6
+ # Uses encoding identifier 'json' and encodes all objects that are not strings or byte arrays
7
+ #
8
+ class Json < Base
9
+ ENCODING_ID = 'json'
10
+
11
+ def encode(message, channel_options)
12
+ if needs_json_encoding?(message)
13
+ message[:data] = ::JSON.dump(message[:data])
14
+ add_encoding_to_message ENCODING_ID, message
15
+ end
16
+ end
17
+
18
+ def decode(message, channel_options)
19
+ if is_json_encoded?(message)
20
+ message[:data] = ::JSON.parse(message[:data])
21
+ strip_current_encoding_part message
22
+ end
23
+ end
24
+
25
+ private
26
+ def needs_json_encoding?(message)
27
+ !message[:data].kind_of?(String) && !message[:data].nil?
28
+ end
29
+
30
+ def is_json_encoded?(message)
31
+ current_encoding_part(message).to_s.match(/^#{ENCODING_ID}$/i)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,26 @@
1
+ require 'ably/models/message_encoders/base'
2
+
3
+ module Ably::Models::MessageEncoders
4
+ # Utf8 Encoder and Decoder
5
+ # Uses encoding identifier 'utf-8' and encodes all JSON objects as UTF-8, and sets the encoding when decoding
6
+ #
7
+ class Utf8 < Base
8
+ ENCODING_ID = 'utf-8'
9
+
10
+ def encode(message, channel_options)
11
+ # no encoding of UTF-8 required
12
+ end
13
+
14
+ def decode(message, channel_options)
15
+ if is_utf8_encoded?(message)
16
+ message[:data] = message[:data].force_encoding(Encoding::UTF_8)
17
+ strip_current_encoding_part message
18
+ end
19
+ end
20
+
21
+ private
22
+ def is_utf8_encoded?(message)
23
+ current_encoding_part(message).to_s.match(/^#{ENCODING_ID}$/i)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ module Ably::Models
2
+ # When Log Level set to none, this NilLogger is used to silence all logging
3
+ # NilLogger provides a Ruby Logger compatible interface
4
+ class NilLogger
5
+ def null_method(*args)
6
+ end
7
+
8
+ def level
9
+ :none
10
+ end
11
+
12
+ def level=(value)
13
+ level
14
+ end
15
+
16
+ [:fatal, :error, :warn, :info, :debug].each do |method|
17
+ alias_method method, :null_method
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,173 @@
1
+ module Ably::Models
2
+ # Wraps any Ably HTTP response that supports paging and automatically provides methods to iterate through
3
+ # the array of resources using {#first_page}, {#next_page}, {#first_page?} and {#last_page?}
4
+ #
5
+ # Paging information is provided by Ably in the LINK HTTP headers
6
+ class PaginatedResource
7
+ include Enumerable
8
+ include Ably::Modules::AsyncWrapper if defined?(Ably::Realtime)
9
+
10
+ # @param [Faraday::Response] http_response Initial HTTP response from an Ably request to a paged resource
11
+ # @param [String] base_url Base URL for request that generated the http_response so that subsequent paged requests can be made
12
+ # @param [Client] client {Ably::Client} used to make the request to Ably
13
+ # @param [Hash] options Options for this paged resource
14
+ # @option options [Symbol,String] :coerce_into symbol or string representing class that should be used to create each item in the PaginatedResource
15
+ #
16
+ # @yield [Object] block will be called for each resource object for the current page. This is a useful way to apply a transformation to any page resources after they are retrieved
17
+ #
18
+ # @return [PaginatedResource]
19
+ def initialize(http_response, base_url, client, options = {}, &each_block)
20
+ @http_response = http_response
21
+ @client = client
22
+ @base_url = "#{base_url.gsub(%r{/[^/]*$}, '')}/"
23
+ @coerce_into = options[:coerce_into]
24
+ @raw_body = http_response.body
25
+ @each_block = each_block
26
+ @make_async = options.fetch(:async_blocking_operations, false)
27
+
28
+ @body = http_response.body
29
+ @body = coerce_items_into(body, @coerce_into) if @coerce_into
30
+ @body = body.map { |item| yield item } if block_given?
31
+ end
32
+
33
+ # Retrieve the first page of results.
34
+ # When used as part of the {Ably::Realtime} library, it will return a {EventMachine::Deferrable} object,
35
+ # and allows an optional success callback block to be provided.
36
+ #
37
+ # @return [PaginatedResource,EventMachine::Deferrable]
38
+ def first_page(&success_callback)
39
+ async_wrap_if(make_async, success_callback) do
40
+ PaginatedResource.new(client.get(pagination_url('first')), base_url, client, pagination_options, &each_block)
41
+ end
42
+ end
43
+
44
+ # Retrieve the next page of results.
45
+ # When used as part of the {Ably::Realtime} library, it will return a {EventMachine::Deferrable} object,
46
+ # and allows an optional success callback block to be provided.
47
+ #
48
+ # @return [PaginatedResource,EventMachine::Deferrable]
49
+ def next_page(&success_callback)
50
+ async_wrap_if(make_async, success_callback) do
51
+ raise Ably::Exceptions::InvalidPageError, 'There are no more pages' if supports_pagination? && last_page?
52
+ PaginatedResource.new(client.get(pagination_url('next')), base_url, client, pagination_options, &each_block)
53
+ end
54
+ end
55
+
56
+ # True if this is the last page in the paged resource set
57
+ #
58
+ # @return [Boolean]
59
+ def last_page?
60
+ !supports_pagination? ||
61
+ pagination_header('next').nil?
62
+ end
63
+
64
+ # True if this is the first page in the paged resource set
65
+ #
66
+ # @return [Boolean]
67
+ def first_page?
68
+ !supports_pagination? ||
69
+ pagination_header('first') == pagination_header('current')
70
+ end
71
+
72
+ # True if the HTTP response supports paging with the expected LINK HTTP headers
73
+ #
74
+ # @return [Boolean]
75
+ def supports_pagination?
76
+ !pagination_headers.empty?
77
+ end
78
+
79
+ # Standard Array accessor method
80
+ def [](index)
81
+ body[index]
82
+ end
83
+
84
+ # Returns number of items within this page, not the total number of items in the entire paged resource set
85
+ def length
86
+ body.length
87
+ end
88
+ alias_method :count, :length
89
+ alias_method :size, :length
90
+
91
+ # Method to allow {PaginatedResource} to be {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
92
+ def each(&block)
93
+ return to_enum(:each) unless block_given?
94
+ body.each(&block)
95
+ end
96
+
97
+ # First item in this page
98
+ def first
99
+ body.first
100
+ end
101
+
102
+ # Last item in this page
103
+ def last
104
+ body.last
105
+ end
106
+
107
+ def inspect
108
+ <<-EOF.gsub(/^ /, '')
109
+ #<#{self.class.name}:#{self.object_id}
110
+ @base_url="#{base_url}",
111
+ @first_page?=#{!!first_page?},
112
+ @last_page?=#{!!first_page?},
113
+ @body=
114
+ #{body.map { |item| item.inspect }.join(",\n ") }
115
+ >
116
+ EOF
117
+ end
118
+
119
+ private
120
+ attr_reader :body, :http_response, :base_url, :client, :coerce_into, :raw_body, :each_block, :make_async
121
+
122
+ def coerce_items_into(items, type_string)
123
+ items.map do |item|
124
+ @coerce_into.split('::').inject(Kernel) do |base, klass_name|
125
+ base.public_send(:const_get, klass_name)
126
+ end.new(item)
127
+ end
128
+ end
129
+
130
+ def pagination_headers
131
+ link_regex = %r{<(?<url>[^>]+)>; rel="(?<rel>[^"]+)"}
132
+ @pagination_headers ||= begin
133
+ # All `Link:` headers are concatenated by Faraday into a comma separated list
134
+ # Finding matching `<url>; rel="rel"` pairs
135
+ link_headers = http_response.headers['link'] || ''
136
+ link_headers.scan(link_regex).each_with_object({}) do |val_array, hash|
137
+ url, rel = val_array
138
+ hash[rel] = url
139
+ end
140
+ end
141
+ end
142
+
143
+ def pagination_header(id)
144
+ pagination_headers[id]
145
+ end
146
+
147
+ def pagination_url(id)
148
+ raise Ably::Exceptions::InvalidPageError, "Paging header link #{id} does not exist" unless pagination_header(id)
149
+
150
+ if pagination_header(id).match(%r{^\./})
151
+ "#{base_url}#{pagination_header(id)[2..-1]}"
152
+ else
153
+ pagination_header[id]
154
+ end
155
+ end
156
+
157
+ def pagination_options
158
+ {
159
+ coerce_into: coerce_into,
160
+ async_blocking_operations: make_async
161
+ }
162
+ end
163
+
164
+ def async_wrap_if(is_realtime, success_callback, &operation)
165
+ if is_realtime
166
+ raise 'EventMachine is required for asynchronous operations' unless defined?(EventMachine)
167
+ async_wrap success_callback, &operation
168
+ else
169
+ yield
170
+ end
171
+ end
172
+ end
173
+ end