ably 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|