ably 0.1.6 → 0.2.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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +9 -0
- data/LICENSE.txt +1 -1
- data/README.md +8 -1
- data/Rakefile +10 -0
- data/ably.gemspec +18 -18
- data/lib/ably.rb +6 -5
- data/lib/ably/auth.rb +11 -14
- data/lib/ably/exceptions.rb +18 -15
- data/lib/ably/logger.rb +102 -0
- data/lib/ably/models/error_info.rb +1 -1
- data/lib/ably/models/message.rb +19 -5
- data/lib/ably/models/message_encoders/base.rb +107 -0
- data/lib/ably/models/message_encoders/base64.rb +39 -0
- data/lib/ably/models/message_encoders/cipher.rb +80 -0
- data/lib/ably/models/message_encoders/json.rb +33 -0
- data/lib/ably/models/message_encoders/utf8.rb +33 -0
- data/lib/ably/models/paginated_resource.rb +23 -6
- data/lib/ably/models/presence_message.rb +19 -7
- data/lib/ably/models/protocol_message.rb +5 -4
- data/lib/ably/models/token.rb +2 -2
- data/lib/ably/modules/channels_collection.rb +0 -3
- data/lib/ably/modules/conversions.rb +3 -3
- data/lib/ably/modules/encodeable.rb +68 -0
- data/lib/ably/modules/event_emitter.rb +10 -4
- data/lib/ably/modules/event_machine_helpers.rb +6 -4
- data/lib/ably/modules/http_helpers.rb +7 -2
- data/lib/ably/modules/model_common.rb +2 -0
- data/lib/ably/modules/state_emitter.rb +10 -1
- data/lib/ably/realtime.rb +19 -12
- data/lib/ably/realtime/channel.rb +26 -13
- data/lib/ably/realtime/client.rb +31 -7
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -3
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +13 -4
- data/lib/ably/realtime/connection.rb +152 -46
- data/lib/ably/realtime/connection/connection_manager.rb +168 -0
- data/lib/ably/realtime/connection/connection_state_machine.rb +56 -33
- data/lib/ably/realtime/connection/websocket_transport.rb +56 -29
- data/lib/ably/{models → realtime/models}/nil_channel.rb +1 -1
- data/lib/ably/realtime/presence.rb +38 -13
- data/lib/ably/rest.rb +7 -5
- data/lib/ably/rest/channel.rb +24 -3
- data/lib/ably/rest/client.rb +56 -17
- data/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/ably/rest/middleware/exceptions.rb +3 -2
- data/lib/ably/rest/middleware/logger.rb +37 -0
- data/lib/ably/rest/presence.rb +10 -2
- data/lib/ably/util/crypto.rb +57 -29
- data/lib/ably/util/pub_sub.rb +11 -0
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/channel_spec.rb +65 -7
- data/spec/acceptance/realtime/connection_spec.rb +123 -27
- data/spec/acceptance/realtime/message_spec.rb +319 -34
- data/spec/acceptance/realtime/presence_history_spec.rb +58 -0
- data/spec/acceptance/realtime/presence_spec.rb +160 -18
- data/spec/acceptance/rest/auth_spec.rb +93 -49
- data/spec/acceptance/rest/base_spec.rb +10 -10
- data/spec/acceptance/rest/channel_spec.rb +35 -19
- data/spec/acceptance/rest/channels_spec.rb +8 -8
- data/spec/acceptance/rest/message_spec.rb +224 -0
- data/spec/acceptance/rest/presence_spec.rb +159 -23
- data/spec/acceptance/rest/stats_spec.rb +5 -5
- data/spec/acceptance/rest/time_spec.rb +4 -4
- data/spec/integration/rest/auth.rb +1 -1
- data/spec/resources/crypto-data-128.json +56 -0
- data/spec/resources/crypto-data-256.json +56 -0
- data/spec/rspec_config.rb +39 -0
- data/spec/spec_helper.rb +4 -42
- data/spec/support/api_helper.rb +1 -1
- data/spec/support/event_machine_helper.rb +0 -5
- data/spec/support/protocol_msgbus_helper.rb +3 -3
- data/spec/support/test_app.rb +3 -3
- data/spec/unit/logger_spec.rb +135 -0
- data/spec/unit/models/message_encoders/base64_spec.rb +181 -0
- data/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
- data/spec/unit/models/message_encoders/json_spec.rb +135 -0
- data/spec/unit/models/message_encoders/utf8_spec.rb +100 -0
- data/spec/unit/models/message_spec.rb +16 -1
- data/spec/unit/models/paginated_resource_spec.rb +46 -0
- data/spec/unit/models/presence_message_spec.rb +18 -5
- data/spec/unit/models/token_spec.rb +1 -1
- data/spec/unit/modules/event_emitter_spec.rb +24 -10
- data/spec/unit/realtime/channel_spec.rb +3 -3
- data/spec/unit/realtime/channels_spec.rb +1 -1
- data/spec/unit/realtime/client_spec.rb +44 -2
- data/spec/unit/realtime/connection_spec.rb +2 -2
- data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +4 -4
- data/spec/unit/realtime/presence_spec.rb +1 -1
- data/spec/unit/realtime/realtime_spec.rb +3 -3
- data/spec/unit/realtime/websocket_transport_spec.rb +24 -0
- data/spec/unit/rest/channels_spec.rb +1 -1
- data/spec/unit/rest/client_spec.rb +45 -10
- data/spec/unit/util/crypto_spec.rb +82 -0
- data/spec/unit/{modules → util}/pub_sub_spec.rb +13 -1
- metadata +43 -12
- data/spec/acceptance/crypto.rb +0 -63
@@ -0,0 +1,107 @@
|
|
1
|
+
# MessageEncoders are registered with the Ably client library and are responsible
|
2
|
+
# for encoding & decoding messages.
|
3
|
+
#
|
4
|
+
# For example, if a message body is detected as JSON, it is encoded as a String and the encoding attribute
|
5
|
+
# of the message is defined as 'json'.
|
6
|
+
# Encrypted messages are encoded & decoded by the Cipher encoder.
|
7
|
+
#
|
8
|
+
module Ably::Models::MessageEncoders
|
9
|
+
extend Ably::Modules::Conversions
|
10
|
+
|
11
|
+
# Base interface for an Ably Encoder
|
12
|
+
#
|
13
|
+
class Base
|
14
|
+
attr_reader :client
|
15
|
+
|
16
|
+
def initialize(client)
|
17
|
+
@client = client
|
18
|
+
end
|
19
|
+
|
20
|
+
# #encode is called once before a message is sent to Ably
|
21
|
+
#
|
22
|
+
# It is the responsibility of the #encode method to detect the intended encoding and modify the :data & :encoding properties of the message object.
|
23
|
+
#
|
24
|
+
# @param [Hash] message the message as a Hash object received directly from Ably.
|
25
|
+
# The message contains properties :name, :data, :encoding, :timestamp, and optionally :id and :client_id.
|
26
|
+
# This #encode method should modify the message Hash if any encoding action is to be taken
|
27
|
+
# @param [Hash] channel_options the options used to initialize the channel that this message was received on
|
28
|
+
#
|
29
|
+
# @return [void]
|
30
|
+
def encode(message, channel_options)
|
31
|
+
raise "Not yet implemented"
|
32
|
+
end
|
33
|
+
|
34
|
+
# #decode is called once for every encoding step
|
35
|
+
# i.e. if message encoding arrives with 'utf-8/cipher+aes-128-cbc/base64'
|
36
|
+
# the decoder will call #decode once for each encoding part such as 'base64', then 'cipher+aes-128-cbc', and finally 'utf-8'
|
37
|
+
#
|
38
|
+
# It is the responsibility of the #decode method to detect the current encoding part and modify the :data & :encoding properties of the message object.
|
39
|
+
#
|
40
|
+
# @param [Hash] message the message as a Hash object received directly from Ably.
|
41
|
+
# The message contains properties :name, :data, :encoding, :timestamp, and optionally :id and :client_id.
|
42
|
+
# This #encode method should modify the message Hash if any decoding action is to be taken
|
43
|
+
# @param [Hash] channel_options the options used to initialize the channel that this message was received on
|
44
|
+
#
|
45
|
+
# @return [void]
|
46
|
+
def decode(message, channel_options)
|
47
|
+
raise "Not yet implemented"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add encoding to the message Hash.
|
51
|
+
# Ensures that encoding delimeter is used where required i.e utf-8/cipher+aes-128-cbc/base64
|
52
|
+
#
|
53
|
+
# @param [Hash] message the message as a Hash object received directly from Ably.
|
54
|
+
# @param [String] encoding encoding to add to the current encoding
|
55
|
+
#
|
56
|
+
# @return [void]
|
57
|
+
def add_encoding_to_message(encoding, message)
|
58
|
+
message[:encoding] = [message[:encoding], encoding].compact.join('/')
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the right most encoding form a meessage encoding, and nil if none exists
|
62
|
+
# i.e. current_encoding_part('utf-8/cipher+aes-128-cbc/base64') => 'base64'
|
63
|
+
#
|
64
|
+
# @return [String,nil]
|
65
|
+
def current_encoding_part(message)
|
66
|
+
if message[:encoding]
|
67
|
+
message[:encoding].split('/')[-1]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Strip the current encoding part within the message Hash.
|
72
|
+
#
|
73
|
+
# For example, calling this method on an :encoding value of 'utf-8/cipher+aes-128-cbc/base64' would update the attribute
|
74
|
+
# :encoding to 'utf-8/cipher+aes-128-cbc'
|
75
|
+
#
|
76
|
+
# @param [Hash] message the message as a Hash object received directly from Ably.
|
77
|
+
#
|
78
|
+
# @return [void]
|
79
|
+
def strip_current_encoding_part(message)
|
80
|
+
raise "Cannot strip encoding when there is no encoding for this message" unless message[:encoding]
|
81
|
+
message[:encoding] = message[:encoding].split('/')[0...-1].join('/')
|
82
|
+
message[:encoding] = nil if message[:encoding].empty?
|
83
|
+
end
|
84
|
+
|
85
|
+
# True of the message data payload is empty
|
86
|
+
#
|
87
|
+
# @param [Hash] message the message as a Hash object received directly from Ably.
|
88
|
+
#
|
89
|
+
# @return [Boolean]
|
90
|
+
def is_empty?(message)
|
91
|
+
message[:data].nil? || message[:data] == ''
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
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
|
+
client.register_encoder Ably::Models::MessageEncoders::Utf8
|
102
|
+
client.register_encoder Ably::Models::MessageEncoders::Json
|
103
|
+
client.register_encoder Ably::Models::MessageEncoders::Cipher
|
104
|
+
client.register_encoder Ably::Models::MessageEncoders::Base64
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Ably::Models::MessageEncoders
|
4
|
+
# Base64 binary Encoder and Decoder
|
5
|
+
# Uses encoding identifier 'base64'
|
6
|
+
#
|
7
|
+
class Base64 < Base
|
8
|
+
ENCODING_ID = 'base64'
|
9
|
+
|
10
|
+
def encode(message, channel_options)
|
11
|
+
return if is_empty?(message)
|
12
|
+
|
13
|
+
if is_binary?(message) && transport_protocol_text?
|
14
|
+
message[:data] = ::Base64.encode64(message[:data])
|
15
|
+
add_encoding_to_message ENCODING_ID, message
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def decode(message, channel_options)
|
20
|
+
if is_base64_encoded?(message)
|
21
|
+
message[:data] = ::Base64.decode64(message[:data])
|
22
|
+
strip_current_encoding_part message
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def is_binary?(message)
|
28
|
+
message[:data].kind_of?(String) && message[:data].encoding == Encoding::ASCII_8BIT
|
29
|
+
end
|
30
|
+
|
31
|
+
def is_base64_encoded?(message)
|
32
|
+
current_encoding_part(message).to_s.match(/^#{ENCODING_ID}$/i)
|
33
|
+
end
|
34
|
+
|
35
|
+
def transport_protocol_text?
|
36
|
+
!client.protocol_binary?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Ably::Models::MessageEncoders
|
2
|
+
# Cipher Encoder & Decoder that automatically encrypts & decrypts messages using Ably::Util::Crypto
|
3
|
+
# when a channel has option encrypted: true.
|
4
|
+
#
|
5
|
+
class Cipher < Base
|
6
|
+
ENCODING_ID = 'cipher'
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
super
|
10
|
+
@cryptos = Hash.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def encode(message, channel_options)
|
14
|
+
return if is_empty?(message)
|
15
|
+
return if already_encrypted?(message)
|
16
|
+
|
17
|
+
if channel_configured_for_encryption?(channel_options)
|
18
|
+
add_encoding_to_message 'utf-8', message unless is_binary?(message) || is_utf8_encoded?(message)
|
19
|
+
|
20
|
+
crypto = crypto_for(channel_options)
|
21
|
+
message[:data] = crypto.encrypt(message[:data])
|
22
|
+
add_encoding_to_message "#{ENCODING_ID}+#{crypto.cipher_type.downcase}", message
|
23
|
+
end
|
24
|
+
rescue ArgumentError => e
|
25
|
+
raise Ably::Exceptions::CipherError.new(e.message, nil, 92005)
|
26
|
+
rescue RuntimeError => e
|
27
|
+
if e.message.match(/unsupported cipher algorithm/i)
|
28
|
+
raise Ably::Exceptions::CipherError.new(e.message, nil, 92004)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def decode(message, channel_options)
|
33
|
+
if is_cipher_encoded?(message)
|
34
|
+
unless channel_configured_for_encryption?(channel_options)
|
35
|
+
raise Ably::Exceptions::CipherError.new('Message cannot be decrypted as the channel is not set up for encryption & decryption', nil, 92001)
|
36
|
+
end
|
37
|
+
|
38
|
+
crypto = crypto_for(channel_options)
|
39
|
+
unless crypto.cipher_type == cipher_algorithm(message).upcase
|
40
|
+
raise Ably::Exceptions::CipherError.new("Cipher algorithm #{crypto.cipher_type} does not match message cipher algorithm of #{cipher_algorithm(message).upcase}", nil, 92002)
|
41
|
+
end
|
42
|
+
|
43
|
+
message[:data] = crypto.decrypt(message[:data])
|
44
|
+
message[:data].force_encoding(Encoding::ASCII_8BIT) if is_binary?(message)
|
45
|
+
strip_current_encoding_part message
|
46
|
+
end
|
47
|
+
rescue OpenSSL::Cipher::CipherError => e
|
48
|
+
raise Ably::Exceptions::CipherError.new("CipherError decrypting data, the private key may not be correct", nil, 92003)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def is_binary?(message)
|
53
|
+
message.fetch(:data, '').encoding == Encoding::ASCII_8BIT
|
54
|
+
end
|
55
|
+
|
56
|
+
def is_utf8_encoded?(message)
|
57
|
+
current_encoding_part(message).to_s.match(/^utf-8$/i)
|
58
|
+
end
|
59
|
+
|
60
|
+
def crypto_for(channel_options)
|
61
|
+
@cryptos[channel_options.fetch(:cipher_params, :default)] ||= Ably::Util::Crypto.new(channel_options.fetch(:cipher_params, {}))
|
62
|
+
end
|
63
|
+
|
64
|
+
def channel_configured_for_encryption?(channel_options)
|
65
|
+
channel_options.fetch(:encrypted, false)
|
66
|
+
end
|
67
|
+
|
68
|
+
def is_cipher_encoded?(message)
|
69
|
+
!cipher_algorithm(message).nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
def cipher_algorithm(message)
|
73
|
+
current_encoding_part(message).to_s[/^#{ENCODING_ID}\+([\w\d_-]+)$/, 1]
|
74
|
+
end
|
75
|
+
|
76
|
+
def already_encrypted?(message)
|
77
|
+
message.fetch(:encoding, '').to_s.match(%r{(^|/)#{ENCODING_ID}\+([\w\d_-]+)($|/)})
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Ably::Models::MessageEncoders
|
4
|
+
# JSON Encoder and Decoder
|
5
|
+
# Uses encoding identifier 'json' and encodes all objects that are not strings or byte arrays
|
6
|
+
#
|
7
|
+
class Json < Base
|
8
|
+
ENCODING_ID = 'json'
|
9
|
+
|
10
|
+
def encode(message, channel_options)
|
11
|
+
if needs_json_encoding?(message)
|
12
|
+
message[:data] = ::JSON.dump(message[:data])
|
13
|
+
add_encoding_to_message ENCODING_ID, message
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def decode(message, channel_options)
|
18
|
+
if is_json_encoded?(message)
|
19
|
+
message[:data] = ::JSON.parse(message[:data])
|
20
|
+
strip_current_encoding_part message
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def needs_json_encoding?(message)
|
26
|
+
!message[:data].kind_of?(String) && !message[:data].nil?
|
27
|
+
end
|
28
|
+
|
29
|
+
def is_json_encoded?(message)
|
30
|
+
current_encoding_part(message).to_s.match(/^#{ENCODING_ID}$/i)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'json'
|
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
|
+
if is_json_encoded?(message)
|
12
|
+
message[:data] = message[:data].force_encoding(Encoding::UTF_8)
|
13
|
+
add_encoding_to_message ENCODING_ID, message
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def decode(message, channel_options)
|
18
|
+
if is_utf8_encoded?(message)
|
19
|
+
message[:data] = message[:data].force_encoding(Encoding::UTF_8)
|
20
|
+
strip_current_encoding_part message
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def is_utf8_encoded?(message)
|
26
|
+
current_encoding_part(message).to_s.match(/^#{ENCODING_ID}$/i)
|
27
|
+
end
|
28
|
+
|
29
|
+
def is_json_encoded?(message)
|
30
|
+
current_encoding_part(message).to_s.match(/^json$/i)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Ably::Models
|
2
2
|
# Wraps any Ably HTTP response that supports paging and automatically provides methdos to iterated through
|
3
|
-
# the array of resources using {#
|
3
|
+
# the array of resources using {#first_page}, {#next_page}, {#first_page?} and {#last_page?}
|
4
4
|
#
|
5
5
|
# Paging information is provided by Ably in the LINK HTTP headers
|
6
6
|
class PaginatedResource
|
@@ -13,12 +13,13 @@ module Ably::Models
|
|
13
13
|
# @option options [Symbol,String] :coerce_into symbol or string representing class that should be used to create each item in the PaginatedResource
|
14
14
|
#
|
15
15
|
# @return [PaginatedResource]
|
16
|
-
def initialize(http_response, base_url, client, options = {})
|
16
|
+
def initialize(http_response, base_url, client, options = {}, &each_block)
|
17
17
|
@http_response = http_response
|
18
18
|
@client = client
|
19
19
|
@base_url = "#{base_url.gsub(%r{/[^/]*$}, '')}/"
|
20
20
|
@coerce_into = options[:coerce_into]
|
21
21
|
@raw_body = http_response.body
|
22
|
+
@each_block = each_block
|
22
23
|
|
23
24
|
@body = if @coerce_into
|
24
25
|
http_response.body.map do |item|
|
@@ -27,21 +28,25 @@ module Ably::Models
|
|
27
28
|
else
|
28
29
|
http_response.body
|
29
30
|
end
|
31
|
+
|
32
|
+
@body = @body.map do |resource|
|
33
|
+
each_block.call(resource)
|
34
|
+
end if block_given?
|
30
35
|
end
|
31
36
|
|
32
37
|
# Retrieve the first page of results
|
33
38
|
#
|
34
39
|
# @return [PaginatedResource]
|
35
40
|
def first_page
|
36
|
-
PaginatedResource.new(client.get(pagination_url('first')), base_url, client, coerce_into: coerce_into)
|
41
|
+
PaginatedResource.new(client.get(pagination_url('first')), base_url, client, coerce_into: coerce_into, &each_block)
|
37
42
|
end
|
38
43
|
|
39
44
|
# Retrieve the next page of results
|
40
45
|
#
|
41
46
|
# @return [PaginatedResource]
|
42
47
|
def next_page
|
43
|
-
raise Ably::Exceptions::InvalidPageError,
|
44
|
-
PaginatedResource.new(client.get(pagination_url('next')), base_url, client, coerce_into: coerce_into)
|
48
|
+
raise Ably::Exceptions::InvalidPageError, 'There are no more pages' if supports_pagination? && last_page?
|
49
|
+
PaginatedResource.new(client.get(pagination_url('next')), base_url, client, coerce_into: coerce_into, &each_block)
|
45
50
|
end
|
46
51
|
|
47
52
|
# True if this is the last page in the paged resource set
|
@@ -100,8 +105,20 @@ module Ably::Models
|
|
100
105
|
body.last
|
101
106
|
end
|
102
107
|
|
108
|
+
def inspect
|
109
|
+
<<-EOF.gsub(/^ /, '')
|
110
|
+
#<#{self.class.name}:#{self.object_id}
|
111
|
+
@base_url="#{base_url}",
|
112
|
+
@first_page?=#{!!first_page?},
|
113
|
+
@last_page?=#{!!first_page?},
|
114
|
+
@body=
|
115
|
+
#{body.map { |item| item.inspect }.join(",\n ") }
|
116
|
+
>
|
117
|
+
EOF
|
118
|
+
end
|
119
|
+
|
103
120
|
private
|
104
|
-
attr_reader :body, :http_response, :base_url, :client, :coerce_into, :raw_body
|
121
|
+
attr_reader :body, :http_response, :base_url, :client, :coerce_into, :raw_body, :each_block
|
105
122
|
|
106
123
|
def pagination_headers
|
107
124
|
link_regex = %r{<(?<url>[^>]+)>; rel="(?<rel>[^"]+)"}
|
@@ -25,8 +25,11 @@ module Ably::Models
|
|
25
25
|
# @return [String] The client_id associated with this presence state
|
26
26
|
# @!attribute [r] member_id
|
27
27
|
# @return [String] A unique member identifier, disambiguating situations where a given client_id is present on multiple connections simultaneously
|
28
|
-
# @!attribute [r]
|
28
|
+
# @!attribute [r] data
|
29
29
|
# @return [Object] Optional client-defined status or other event payload associated with this state
|
30
|
+
# @!attribute [r] encoding
|
31
|
+
# @return [Object] The encoding for the message data. Encoding and decoding of messages is handled automatically by the client library.
|
32
|
+
# Therefore, the `encoding` attribute should always be nil unless an Ably library decoding error has occurred.
|
30
33
|
# @!attribute [r] timestamp
|
31
34
|
# @return [Time] Timestamp when the message was received by the Ably the real-time service
|
32
35
|
# @!attribute [r] hash
|
@@ -34,6 +37,7 @@ module Ably::Models
|
|
34
37
|
#
|
35
38
|
class PresenceMessage
|
36
39
|
include Ably::Modules::ModelCommon
|
40
|
+
include Ably::Modules::Encodeable
|
37
41
|
include EventMachine::Deferrable
|
38
42
|
extend Ably::Modules::Enum
|
39
43
|
|
@@ -51,10 +55,11 @@ module Ably::Models
|
|
51
55
|
def initialize(hash_object, protocol_message = nil)
|
52
56
|
@protocol_message = protocol_message
|
53
57
|
@raw_hash_object = hash_object
|
54
|
-
|
58
|
+
|
59
|
+
set_hash_object hash_object
|
55
60
|
end
|
56
61
|
|
57
|
-
%w( client_id member_id
|
62
|
+
%w( client_id member_id data encoding ).each do |attribute|
|
58
63
|
define_method attribute do
|
59
64
|
hash[attribute.to_sym]
|
60
65
|
end
|
@@ -82,11 +87,12 @@ module Ably::Models
|
|
82
87
|
|
83
88
|
# Return a JSON ready object from the underlying #hash using Ably naming conventions for keys
|
84
89
|
def as_json(*args)
|
85
|
-
hash.dup.tap do |
|
86
|
-
|
90
|
+
hash.dup.tap do |presence_message|
|
91
|
+
presence_message['action'] = action.to_i
|
92
|
+
decode_binary_data_before_to_json presence_message
|
87
93
|
end.as_json
|
88
94
|
rescue KeyError
|
89
|
-
raise KeyError,
|
95
|
+
raise KeyError, ':action is missing or invalid, cannot generate a valid Hash for ProtocolMessage'
|
90
96
|
end
|
91
97
|
|
92
98
|
# Assign this presence message to a ProtocolMessage before delivery to the Ably system
|
@@ -106,11 +112,13 @@ module Ably::Models
|
|
106
112
|
# @return [Ably::Models::ProtocolMessage]
|
107
113
|
# @api private
|
108
114
|
def protocol_message
|
109
|
-
raise RuntimeError,
|
115
|
+
raise RuntimeError, 'Presence Message is not yet published with a ProtocolMessage. ProtocolMessage is nil' if @protocol_message.nil?
|
110
116
|
@protocol_message
|
111
117
|
end
|
112
118
|
|
113
119
|
private
|
120
|
+
attr_reader :raw_hash_object
|
121
|
+
|
114
122
|
def protocol_message_index
|
115
123
|
protocol_message.presence.index(self)
|
116
124
|
end
|
@@ -122,5 +130,9 @@ module Ably::Models
|
|
122
130
|
def message_serial
|
123
131
|
protocol_message.message_serial
|
124
132
|
end
|
133
|
+
|
134
|
+
def set_hash_object(hash)
|
135
|
+
@hash_object = IdiomaticRubyWrapper(hash.clone.freeze, stop_at: [:data])
|
136
|
+
end
|
125
137
|
end
|
126
138
|
end
|
@@ -33,6 +33,7 @@ module Ably::Models
|
|
33
33
|
#
|
34
34
|
class ProtocolMessage
|
35
35
|
include Ably::Modules::ModelCommon
|
36
|
+
include EventMachine::Deferrable
|
36
37
|
extend Ably::Modules::Enum
|
37
38
|
|
38
39
|
# Actions which are sent by the Ably Realtime API
|
@@ -67,7 +68,7 @@ module Ably::Models
|
|
67
68
|
@raw_hash_object = hash_object
|
68
69
|
@hash_object = IdiomaticRubyWrapper(@raw_hash_object.clone)
|
69
70
|
|
70
|
-
raise ArgumentError,
|
71
|
+
raise ArgumentError, 'Invalid ProtocolMessage, action cannot be nil' if @hash_object[:action].nil?
|
71
72
|
@hash_object[:action] = ACTION(@hash_object[:action]).to_i unless @hash_object[:action].kind_of?(Integer)
|
72
73
|
|
73
74
|
@hash_object.freeze
|
@@ -80,7 +81,7 @@ module Ably::Models
|
|
80
81
|
end
|
81
82
|
|
82
83
|
def id!
|
83
|
-
raise RuntimeError,
|
84
|
+
raise RuntimeError, 'ProtocolMessage #id is nil' unless id
|
84
85
|
id
|
85
86
|
end
|
86
87
|
|
@@ -168,8 +169,8 @@ module Ably::Models
|
|
168
169
|
|
169
170
|
# Return a JSON ready object from the underlying #hash using Ably naming conventions for keys
|
170
171
|
def as_json(*args)
|
171
|
-
raise TypeError,
|
172
|
-
raise TypeError,
|
172
|
+
raise TypeError, ':action is missing, cannot generate a valid Hash for ProtocolMessage' unless action
|
173
|
+
raise TypeError, ':msg_serial or :connection_serial is missing, cannot generate a valid Hash for ProtocolMessage' if ack_required? && !has_serial?
|
173
174
|
|
174
175
|
hash.dup.tap do |hash_object|
|
175
176
|
hash_object['action'] = action.to_i
|