ably 0.8.15 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -4
  3. data/CHANGELOG.md +6 -2
  4. data/README.md +5 -1
  5. data/SPEC.md +1473 -852
  6. data/ably.gemspec +11 -8
  7. data/lib/ably/auth.rb +90 -53
  8. data/lib/ably/exceptions.rb +37 -8
  9. data/lib/ably/logger.rb +10 -1
  10. data/lib/ably/models/auth_details.rb +42 -0
  11. data/lib/ably/models/channel_state_change.rb +18 -4
  12. data/lib/ably/models/connection_details.rb +6 -3
  13. data/lib/ably/models/connection_state_change.rb +4 -3
  14. data/lib/ably/models/error_info.rb +1 -1
  15. data/lib/ably/models/message.rb +17 -1
  16. data/lib/ably/models/message_encoders/base.rb +103 -82
  17. data/lib/ably/models/message_encoders/base64.rb +1 -1
  18. data/lib/ably/models/presence_message.rb +16 -1
  19. data/lib/ably/models/protocol_message.rb +20 -3
  20. data/lib/ably/models/token_details.rb +11 -1
  21. data/lib/ably/models/token_request.rb +16 -6
  22. data/lib/ably/modules/async_wrapper.rb +7 -3
  23. data/lib/ably/modules/encodeable.rb +51 -12
  24. data/lib/ably/modules/enum.rb +17 -7
  25. data/lib/ably/modules/event_emitter.rb +29 -14
  26. data/lib/ably/modules/model_common.rb +13 -21
  27. data/lib/ably/modules/state_emitter.rb +7 -4
  28. data/lib/ably/modules/state_machine.rb +2 -4
  29. data/lib/ably/modules/uses_state_machine.rb +7 -3
  30. data/lib/ably/realtime.rb +2 -0
  31. data/lib/ably/realtime/auth.rb +102 -42
  32. data/lib/ably/realtime/channel.rb +68 -26
  33. data/lib/ably/realtime/channel/channel_manager.rb +154 -65
  34. data/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
  35. data/lib/ably/realtime/client.rb +18 -3
  36. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
  37. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
  38. data/lib/ably/realtime/connection.rb +108 -49
  39. data/lib/ably/realtime/connection/connection_manager.rb +167 -61
  40. data/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
  41. data/lib/ably/realtime/connection/websocket_transport.rb +19 -10
  42. data/lib/ably/realtime/presence.rb +70 -45
  43. data/lib/ably/realtime/presence/members_map.rb +201 -36
  44. data/lib/ably/realtime/presence/presence_manager.rb +30 -6
  45. data/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
  46. data/lib/ably/rest.rb +2 -2
  47. data/lib/ably/rest/channel.rb +5 -5
  48. data/lib/ably/rest/client.rb +31 -27
  49. data/lib/ably/rest/middleware/exceptions.rb +1 -3
  50. data/lib/ably/rest/middleware/logger.rb +2 -2
  51. data/lib/ably/rest/presence.rb +2 -2
  52. data/lib/ably/util/pub_sub.rb +1 -1
  53. data/lib/ably/util/safe_deferrable.rb +26 -0
  54. data/lib/ably/version.rb +2 -2
  55. data/spec/acceptance/realtime/auth_spec.rb +470 -111
  56. data/spec/acceptance/realtime/channel_history_spec.rb +5 -3
  57. data/spec/acceptance/realtime/channel_spec.rb +1017 -168
  58. data/spec/acceptance/realtime/client_spec.rb +6 -6
  59. data/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
  60. data/spec/acceptance/realtime/connection_spec.rb +424 -105
  61. data/spec/acceptance/realtime/message_spec.rb +52 -23
  62. data/spec/acceptance/realtime/presence_history_spec.rb +5 -3
  63. data/spec/acceptance/realtime/presence_spec.rb +1110 -96
  64. data/spec/acceptance/rest/auth_spec.rb +222 -59
  65. data/spec/acceptance/rest/base_spec.rb +1 -1
  66. data/spec/acceptance/rest/channel_spec.rb +1 -2
  67. data/spec/acceptance/rest/client_spec.rb +104 -48
  68. data/spec/acceptance/rest/message_spec.rb +42 -15
  69. data/spec/acceptance/rest/presence_spec.rb +4 -11
  70. data/spec/rspec_config.rb +2 -1
  71. data/spec/shared/client_initializer_behaviour.rb +2 -2
  72. data/spec/shared/safe_deferrable_behaviour.rb +6 -2
  73. data/spec/spec_helper.rb +4 -2
  74. data/spec/support/debug_failure_helper.rb +20 -4
  75. data/spec/support/event_machine_helper.rb +32 -1
  76. data/spec/unit/auth_spec.rb +4 -11
  77. data/spec/unit/logger_spec.rb +28 -2
  78. data/spec/unit/models/auth_details_spec.rb +49 -0
  79. data/spec/unit/models/channel_state_change_spec.rb +23 -3
  80. data/spec/unit/models/connection_details_spec.rb +12 -1
  81. data/spec/unit/models/connection_state_change_spec.rb +15 -4
  82. data/spec/unit/models/message_encoders/base64_spec.rb +2 -1
  83. data/spec/unit/models/message_spec.rb +153 -0
  84. data/spec/unit/models/presence_message_spec.rb +192 -0
  85. data/spec/unit/models/protocol_message_spec.rb +64 -6
  86. data/spec/unit/models/token_details_spec.rb +75 -0
  87. data/spec/unit/models/token_request_spec.rb +74 -0
  88. data/spec/unit/modules/async_wrapper_spec.rb +2 -1
  89. data/spec/unit/modules/enum_spec.rb +69 -0
  90. data/spec/unit/modules/event_emitter_spec.rb +149 -22
  91. data/spec/unit/modules/state_emitter_spec.rb +9 -3
  92. data/spec/unit/realtime/client_spec.rb +1 -1
  93. data/spec/unit/realtime/connection_spec.rb +8 -5
  94. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
  95. data/spec/unit/realtime/presence_spec.rb +4 -3
  96. data/spec/unit/rest/client_spec.rb +1 -1
  97. data/spec/unit/util/crypto_spec.rb +3 -3
  98. metadata +22 -19
@@ -0,0 +1,42 @@
1
+ module Ably::Models
2
+ # Convert auth details attributes to a {AuthDetails} object
3
+ #
4
+ # @param attributes (see #initialize)
5
+ #
6
+ # @return [AuthDetails]
7
+ def self.AuthDetails(attributes)
8
+ case attributes
9
+ when AuthDetails
10
+ return attributes
11
+ else
12
+ AuthDetails.new(attributes || {})
13
+ end
14
+ end
15
+
16
+ # AuthDetails are included in an +AUTH+ {Ably::Models::ProtocolMessage#auth} attribute
17
+ # to provide the realtime service with new token authentication details following a re-auth workflow
18
+ #
19
+ class AuthDetails
20
+ include Ably::Modules::ModelCommon
21
+
22
+ # @param attributes [Hash]
23
+ # @option attributes [String] :access_token token string
24
+ #
25
+ def initialize(attributes = {})
26
+ @hash_object = IdiomaticRubyWrapper(attributes.clone)
27
+ self.attributes.freeze
28
+ end
29
+
30
+ %w(access_token).each do |attribute|
31
+ define_method attribute do
32
+ attributes[attribute.to_sym]
33
+ end
34
+ end
35
+
36
+ # @!attribute [r] attributes
37
+ # @return [Hash] Access the token details Hash object ruby'fied to use symbolized keys
38
+ def attributes
39
+ @hash_object
40
+ end
41
+ end
42
+ end
@@ -8,31 +8,45 @@ module Ably::Models
8
8
  # @return [Connection::STATE] Previous channel state
9
9
  # @!attribute [r] reason
10
10
  # @return [Ably::Models::ErrorInfo] Object describing the reason for a state change when not initiated by the consumer of the client library
11
+ # @!attribute [r] resumed
12
+ # @return [Boolean] True when a channel is resumed, false when continuity on the channel is no longer provided indicating that the developer is now responsible for recovering lost messages on this channel through other means, such as using the hisory API
11
13
  #
12
14
  class ChannelStateChange
13
15
  include Ably::Modules::ModelCommon
14
16
 
15
17
  def initialize(hash_object)
16
- unless (hash_object.keys - [:current, :previous, :reason, :protocol_message]).empty?
17
- raise ArgumentError, 'Invalid attributes, expecting :current, :previous, :reason'
18
+ unless (hash_object.keys - [:current, :previous, :event, :reason, :resumed, :protocol_message]).empty?
19
+ raise ArgumentError, 'Invalid attributes, expecting :current, :previous, :event, :reason, :resumed'
18
20
  end
19
21
 
20
22
  @hash_object = {
21
23
  current: hash_object.fetch(:current),
22
24
  previous: hash_object.fetch(:previous),
25
+ event: hash_object[:event],
23
26
  reason: hash_object[:reason],
24
- protocol_message: hash_object[:protocol_message]
27
+ protocol_message: hash_object[:protocol_message],
28
+ resumed: hash_object[:resumed]
25
29
  }
26
30
  rescue KeyError => e
27
31
  raise ArgumentError, e
28
32
  end
29
33
 
30
- %w(current previous reason protocol_message).each do |attribute|
34
+ %w(current previous event reason).each do |attribute|
31
35
  define_method attribute do
32
36
  @hash_object[attribute.to_sym]
33
37
  end
34
38
  end
35
39
 
40
+ def resumed
41
+ !!@hash_object[:resumed]
42
+ end
43
+ alias_method :resumed?, :resumed
44
+
45
+ # @api private
46
+ def protocol_message
47
+ @hash_object[:protocol_message]
48
+ end
49
+
36
50
  def to_s
37
51
  "ChannelStateChange: current state #{current}, previous state #{previous}"
38
52
  end
@@ -27,18 +27,21 @@ module Ably::Models
27
27
  # @option attributes [Integer] :max_message_size maximum individual message size in bytes
28
28
  # @option attributes [Integer] :max_frame_size maximum size for a single frame of data sent to Ably. This restriction applies to a {Ably::Models::ProtocolMessage} sent over a realtime connection, or the total body size for a REST request
29
29
  # @option attributes [Integer] :max_inbound_rate maximum allowable number of requests per second from a client
30
+ # @option attributes [Integer] :max_idle_interval is the maximum length of time in seconds that the server will allow no activity to occur in the server->client direction. After such a period of inactivity, the server will send a @HEARTBEAT@ or transport-level ping to the client. If the value is 0, the server will allow arbitrarily-long levels of inactivity.
30
31
  # @option attributes [Integer] :connection_state_ttl duration in seconds that Ably will persist the connection state when a Realtime client is abruptly disconnected
31
32
  # @option attributes [String] :server_id unique identifier of the Ably server where the connection is established
32
33
  #
33
34
  def initialize(attributes = {})
34
35
  @hash_object = IdiomaticRubyWrapper(attributes.clone)
35
- if self.attributes[:connection_state_ttl]
36
- self.attributes[:connection_state_ttl] = (self.attributes[:connection_state_ttl].to_f / 1000).round
36
+ [:connection_state_ttl, :max_idle_interval].each do |duration_field|
37
+ if self.attributes[duration_field]
38
+ self.attributes[duration_field] = (self.attributes[duration_field].to_f / 1000).round
39
+ end
37
40
  end
38
41
  self.attributes.freeze
39
42
  end
40
43
 
41
- %w(client_id connection_key max_message_size max_frame_size max_inbound_rate connection_state_ttl server_id).each do |attribute|
44
+ %w(client_id connection_key max_message_size max_frame_size max_inbound_rate connection_state_ttl max_idle_interval server_id).each do |attribute|
42
45
  define_method attribute do
43
46
  attributes[attribute.to_sym]
44
47
  end
@@ -15,13 +15,14 @@ module Ably::Models
15
15
  include Ably::Modules::ModelCommon
16
16
 
17
17
  def initialize(hash_object)
18
- unless (hash_object.keys - [:current, :previous, :retry_in, :reason, :protocol_message]).empty?
19
- raise ArgumentError, 'Invalid attributes, expecting :current, :previous, :retry_in, :reason'
18
+ unless (hash_object.keys - [:current, :previous, :event, :retry_in, :reason, :protocol_message]).empty?
19
+ raise ArgumentError, 'Invalid attributes, expecting :current, :previous, :event, :retry_in, :reason'
20
20
  end
21
21
 
22
22
  @hash_object = {
23
23
  current: hash_object.fetch(:current),
24
24
  previous: hash_object.fetch(:previous),
25
+ event: hash_object[:event],
25
26
  retry_in: hash_object[:retry_in],
26
27
  reason: hash_object[:reason],
27
28
  protocol_message: hash_object[:protocol_message]
@@ -30,7 +31,7 @@ module Ably::Models
30
31
  raise ArgumentError, e
31
32
  end
32
33
 
33
- %w(current previous retry_in reason protocol_message).each do |attribute|
34
+ %w(current previous event retry_in reason protocol_message).each do |attribute|
34
35
  define_method attribute do
35
36
  @hash_object[attribute.to_sym]
36
37
  end
@@ -11,7 +11,7 @@ module Ably::Models
11
11
  # @!attribute [r] attributes
12
12
  # @return [Hash] Access the protocol message Hash object ruby'fied to use symbolized keys
13
13
  #
14
- class ErrorInfo
14
+ class ErrorInfo < Ably::Exceptions::BaseAblyException
15
15
  include Ably::Modules::ModelCommon
16
16
 
17
17
  def initialize(hash_object)
@@ -1,3 +1,5 @@
1
+ require 'ably/models/message_encoders/base'
2
+
1
3
  module Ably::Models
2
4
  # Convert messsage argument to a {Message} object and associate with a protocol message if provided
3
5
  #
@@ -43,6 +45,9 @@ module Ably::Models
43
45
  include Ably::Modules::ModelCommon
44
46
  include Ably::Modules::SafeDeferrable if defined?(Ably::Realtime)
45
47
 
48
+ # Statically register a default set of encoders for this class
49
+ Ably::Models::MessageEncoders.register_default_encoders self
50
+
46
51
  # {Message} initializer
47
52
  #
48
53
  # @param attributes [Hash] object with the underlying message detail key value attributes
@@ -119,6 +124,17 @@ module Ably::Models
119
124
  @protocol_message
120
125
  end
121
126
 
127
+ # Contains any arbitrary key value pairs which may also contain other primitive JSON types, JSON-encodable objects or JSON-encodable arrays.
128
+ # The extras field is provided to contain message metadata and/or ancillary payloads in support of specific functionality, e.g. push
129
+ # @api private
130
+ def extras
131
+ attributes[:extras].tap do |val|
132
+ unless val.kind_of?(IdiomaticRubyWrapper) || val.kind_of?(Array) || val.kind_of?(Hash) || val.nil?
133
+ raise ArgumentError, "extras contains an unsupported type #{val.class}"
134
+ end
135
+ end
136
+ end
137
+
122
138
  private
123
139
  def raw_hash_object
124
140
  @raw_hash_object
@@ -129,7 +145,7 @@ module Ably::Models
129
145
  end
130
146
 
131
147
  def set_attributes_object(new_attributes)
132
- @attributes = IdiomaticRubyWrapper(new_attributes.clone.freeze, stop_at: [:data])
148
+ @attributes = IdiomaticRubyWrapper(new_attributes.clone.freeze, stop_at: [:data, :extras])
133
149
  end
134
150
 
135
151
  def logger
@@ -7,98 +7,119 @@ require 'ably/modules/conversions'
7
7
  # of the message is defined as 'json'.
8
8
  # Encrypted messages are encoded & decoded by the Cipher encoder.
9
9
  #
10
- module Ably::Models::MessageEncoders
11
- extend Ably::Modules::Conversions
10
+ module Ably
11
+ module Models
12
+ module MessageEncoders
13
+ extend Ably::Modules::Conversions
12
14
 
13
- # Base interface for an Ably Encoder
14
- #
15
- class Base
16
- attr_reader :client
15
+ # Base interface for an Ably Encoder
16
+ #
17
+ class Base
18
+ attr_reader :client, :options
17
19
 
18
- def initialize(client)
19
- @client = client
20
- end
20
+ def initialize(client, options = {})
21
+ @client = client
22
+ @options = options
23
+ end
21
24
 
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
25
+ # #encode is called once before a message is sent to Ably
26
+ #
27
+ # It is the responsibility of the #encode method to detect the intended encoding and modify the :data & :encoding properties of the message object.
28
+ #
29
+ # @param [Hash] message the message as a Hash object received directly from Ably.
30
+ # The message contains properties :name, :data, :encoding, :timestamp, and optionally :id and :client_id.
31
+ # This #encode method should modify the message Hash if any encoding action is to be taken
32
+ # @param [Hash] channel_options the options used to initialize the channel that this message was received on
33
+ #
34
+ # @return [void]
35
+ def encode(message, channel_options)
36
+ raise "Not yet implemented"
37
+ end
35
38
 
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
39
+ # #decode is called once for every encoding step
40
+ # i.e. if message encoding arrives with 'utf-8/cipher+aes-128-cbc/base64'
41
+ # the decoder will call #decode once for each encoding part such as 'base64', then 'cipher+aes-128-cbc', and finally 'utf-8'
42
+ #
43
+ # It is the responsibility of the #decode method to detect the current encoding part and modify the :data & :encoding properties of the message object.
44
+ #
45
+ # @param [Hash] message the message as a Hash object received directly from Ably.
46
+ # The message contains properties :name, :data, :encoding, :timestamp, and optionally :id and :client_id.
47
+ # This #encode method should modify the message Hash if any decoding action is to be taken
48
+ # @param [Hash] channel_options the options used to initialize the channel that this message was received on
49
+ #
50
+ # @return [void]
51
+ def decode(message, channel_options)
52
+ raise "Not yet implemented"
53
+ end
51
54
 
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
55
+ # Add encoding to the message Hash.
56
+ # Ensures that encoding delimeter is used where required i.e utf-8/cipher+aes-128-cbc/base64
57
+ #
58
+ # @param [Hash] message the message as a Hash object received directly from Ably.
59
+ # @param [String] encoding encoding to add to the current encoding
60
+ #
61
+ # @return [void]
62
+ def add_encoding_to_message(encoding, message)
63
+ message[:encoding] = [message[:encoding], encoding].compact.join('/')
64
+ end
62
65
 
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]
66
+ # Returns the right most encoding form a meessage encoding, and nil if none exists
67
+ # i.e. current_encoding_part('utf-8/cipher+aes-128-cbc/base64') => 'base64'
68
+ #
69
+ # @return [String,nil]
70
+ def current_encoding_part(message)
71
+ if message[:encoding]
72
+ message[:encoding].split('/')[-1]
73
+ end
74
+ end
75
+
76
+ # Strip the current encoding part within the message Hash.
77
+ #
78
+ # For example, calling this method on an :encoding value of 'utf-8/cipher+aes-128-cbc/base64' would update the attribute
79
+ # :encoding to 'utf-8/cipher+aes-128-cbc'
80
+ #
81
+ # @param [Hash] message the message as a Hash object received directly from Ably.
82
+ #
83
+ # @return [void]
84
+ def strip_current_encoding_part(message)
85
+ raise "Cannot strip encoding when there is no encoding for this message" unless message[:encoding]
86
+ message[:encoding] = message[:encoding].split('/')[0...-1].join('/')
87
+ message[:encoding] = nil if message[:encoding].empty?
88
+ end
89
+
90
+ # True of the message data payload is empty
91
+ #
92
+ # @param [Hash] message the message as a Hash object received directly from Ably.
93
+ #
94
+ # @return [Boolean]
95
+ def is_empty?(message)
96
+ message[:data].nil? || message[:data] == ''
97
+ end
70
98
  end
71
- end
72
99
 
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
100
+ # @api private
101
+ def self.register_default_encoders(client, options = {})
102
+ binary_protocol = !!options[:binary_protocol]
103
+ client.register_encoder Ably::Models::MessageEncoders::Utf8
104
+ client.register_encoder Ably::Models::MessageEncoders::Json
105
+ client.register_encoder Ably::Models::MessageEncoders::Cipher
106
+ client.register_encoder Ably::Models::MessageEncoders::Base64, binary_protocol: binary_protocol
107
+ end
86
108
 
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
109
+ # @api private
110
+ def self.encoder_from(encoder, options)
111
+ encoder_klass = if encoder.kind_of?(String)
112
+ encoder.split('::').inject(Kernel) do |base, klass_name|
113
+ base.public_send(:const_get, klass_name)
114
+ end
115
+ else
116
+ encoder
117
+ end
96
118
 
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
119
+ raise "Encoder must inherit from `Ably::Models::MessageEncoders::Base`" unless encoder_klass.ancestors.include?(Ably::Models::MessageEncoders::Base)
120
+ encoder_klass.new(self, options)
121
+ end
122
+ end
102
123
  end
103
124
  end
104
125
 
@@ -34,7 +34,7 @@ module Ably::Models::MessageEncoders
34
34
  end
35
35
 
36
36
  def transport_protocol_text?
37
- !client.protocol_binary?
37
+ !options[:binary_protocol]
38
38
  end
39
39
  end
40
40
  end
@@ -52,6 +52,9 @@ module Ably::Models
52
52
  :update
53
53
  )
54
54
 
55
+ # Statically register a default set of encoders for this class
56
+ Ably::Models::MessageEncoders.register_default_encoders self
57
+
55
58
  # {PresenceMessage} initializer
56
59
  #
57
60
  # @param attributes [Hash] object with the underlying presence message key value attributes
@@ -120,7 +123,6 @@ module Ably::Models
120
123
  end.to_json
121
124
  end
122
125
 
123
-
124
126
  # Assign this presence message to a ProtocolMessage before delivery to the Ably system
125
127
  # @api private
126
128
  def assign_to_protocol_message(protocol_message)
@@ -142,6 +144,19 @@ module Ably::Models
142
144
  @protocol_message
143
145
  end
144
146
 
147
+ # Create a static shallow clone of this object with the optional attributes to overide existing values
148
+ # Shallow clones have no dependency on the originating ProtocolMessage as all field values are stored as opposed to calculated
149
+ # Clones are useful when the original PresenceMessage needs to be mutated, such as storing in a PresenceMap with action :present
150
+ def shallow_clone(new_attributes = {})
151
+ new_attributes = IdiomaticRubyWrapper(new_attributes.clone.freeze, stop_at: [:data])
152
+
153
+ self.class.new(attributes.to_hash.merge(
154
+ id: new_attributes[:id] || id,
155
+ connection_id: new_attributes[:connection_id] || connection_id,
156
+ timestamp: new_attributes[:timestamp] || as_since_epoch(timestamp)
157
+ ).merge(new_attributes.to_hash))
158
+ end
159
+
145
160
  private
146
161
  def raw_hash_object
147
162
  @raw_hash_object