ably 0.1.5 → 0.1.6

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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -1
  3. data/ably.gemspec +4 -3
  4. data/lib/ably.rb +6 -2
  5. data/lib/ably/auth.rb +24 -16
  6. data/lib/ably/exceptions.rb +16 -5
  7. data/lib/ably/{realtime/models → models}/error_info.rb +9 -11
  8. data/lib/ably/models/idiomatic_ruby_wrapper.rb +57 -26
  9. data/lib/ably/{realtime/models → models}/message.rb +45 -38
  10. data/lib/ably/{realtime/models → models}/nil_channel.rb +4 -4
  11. data/lib/ably/{rest/models/paged_resource.rb → models/paginated_resource.rb} +21 -10
  12. data/lib/ably/models/presence_message.rb +126 -0
  13. data/lib/ably/{realtime/models → models}/protocol_message.rb +76 -38
  14. data/lib/ably/models/token.rb +74 -0
  15. data/lib/ably/modules/channels_collection.rb +49 -0
  16. data/lib/ably/modules/conversions.rb +2 -0
  17. data/lib/ably/modules/event_emitter.rb +43 -8
  18. data/lib/ably/modules/event_machine_helpers.rb +1 -0
  19. data/lib/ably/modules/http_helpers.rb +9 -2
  20. data/lib/ably/modules/message_pack.rb +14 -0
  21. data/lib/ably/modules/model_common.rb +29 -0
  22. data/lib/ably/modules/{state.rb → state_emitter.rb} +8 -7
  23. data/lib/ably/realtime.rb +37 -7
  24. data/lib/ably/realtime/channel.rb +154 -31
  25. data/lib/ably/realtime/channels.rb +47 -0
  26. data/lib/ably/realtime/client.rb +39 -33
  27. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +50 -21
  28. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +9 -11
  29. data/lib/ably/realtime/connection.rb +148 -79
  30. data/lib/ably/realtime/connection/connection_state_machine.rb +111 -0
  31. data/lib/ably/realtime/connection/websocket_transport.rb +161 -0
  32. data/lib/ably/realtime/presence.rb +270 -0
  33. data/lib/ably/rest.rb +14 -3
  34. data/lib/ably/rest/channel.rb +3 -3
  35. data/lib/ably/rest/channels.rb +26 -12
  36. data/lib/ably/rest/client.rb +42 -25
  37. data/lib/ably/rest/middleware/exceptions.rb +21 -23
  38. data/lib/ably/rest/middleware/external_exceptions.rb +8 -10
  39. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  40. data/lib/ably/rest/middleware/parse_json.rb +9 -2
  41. data/lib/ably/rest/middleware/parse_message_pack.rb +6 -2
  42. data/lib/ably/rest/presence.rb +4 -4
  43. data/lib/ably/version.rb +1 -1
  44. data/spec/acceptance/realtime/channel_history_spec.rb +125 -0
  45. data/spec/acceptance/realtime/channel_spec.rb +135 -63
  46. data/spec/acceptance/realtime/connection_spec.rb +86 -0
  47. data/spec/acceptance/realtime/message_spec.rb +116 -94
  48. data/spec/acceptance/realtime/presence_history_spec.rb +0 -0
  49. data/spec/acceptance/realtime/presence_spec.rb +277 -0
  50. data/spec/acceptance/rest/auth_spec.rb +351 -347
  51. data/spec/acceptance/rest/base_spec.rb +43 -26
  52. data/spec/acceptance/rest/channel_spec.rb +88 -83
  53. data/spec/acceptance/rest/channels_spec.rb +32 -28
  54. data/spec/acceptance/rest/presence_spec.rb +83 -63
  55. data/spec/acceptance/rest/stats_spec.rb +38 -37
  56. data/spec/acceptance/rest/time_spec.rb +10 -6
  57. data/spec/integration/modules/{state_spec.rb → state_emitter_spec.rb} +16 -2
  58. data/spec/spec_helper.rb +14 -0
  59. data/spec/support/api_helper.rb +4 -0
  60. data/spec/support/model_helper.rb +28 -9
  61. data/spec/support/protocol_msgbus_helper.rb +8 -1
  62. data/spec/support/test_app.rb +24 -14
  63. data/spec/unit/{realtime → models}/error_info_spec.rb +4 -4
  64. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +46 -9
  65. data/spec/unit/models/message_spec.rb +229 -0
  66. data/spec/unit/{rest/paged_resource_spec.rb → models/paginated_resource_spec.rb} +19 -11
  67. data/spec/unit/models/presence_message_spec.rb +230 -0
  68. data/spec/unit/models/protocol_message_spec.rb +280 -0
  69. data/spec/unit/{token_spec.rb → models/token_spec.rb} +18 -22
  70. data/spec/unit/modules/conversions_spec.rb +1 -1
  71. data/spec/unit/modules/event_emitter_spec.rb +36 -4
  72. data/spec/unit/realtime/channel_spec.rb +76 -2
  73. data/spec/unit/realtime/channels_spec.rb +50 -0
  74. data/spec/unit/realtime/client_spec.rb +31 -1
  75. data/spec/unit/realtime/connection_spec.rb +8 -15
  76. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +6 -6
  77. data/spec/unit/realtime/presence_spec.rb +100 -0
  78. data/spec/unit/rest/channels_spec.rb +48 -0
  79. metadata +72 -38
  80. data/lib/ably/realtime/models/shared.rb +0 -17
  81. data/lib/ably/rest/models/message.rb +0 -64
  82. data/lib/ably/rest/models/presence_message.rb +0 -21
  83. data/lib/ably/token.rb +0 -80
  84. data/spec/unit/realtime/message_spec.rb +0 -117
  85. data/spec/unit/realtime/protocol_message_spec.rb +0 -172
  86. data/spec/unit/rest/message_spec.rb +0 -75
@@ -1,7 +1,13 @@
1
- module Ably::Realtime::Models
1
+ module Ably::Models
2
+ # Convert messsage argument to a {Message} object and associate with a protocol message if provided
3
+ #
4
+ # @param message [Message,Hash] A message object or Hash of message properties
5
+ # @param protocol_message [ProtocolMessage] An optional protocol message to assocate the message with
6
+ #
7
+ # @return [Message]
2
8
  def self.Message(message, protocol_message = nil)
3
9
  case message
4
- when Ably::Realtime::Models::Message
10
+ when Message
5
11
  message.tap do
6
12
  message.assign_to_protocol_message protocol_message
7
13
  end
@@ -19,80 +25,81 @@ module Ably::Realtime::Models
19
25
  # @return [String] The id of the publisher of this message
20
26
  # @!attribute [r] data
21
27
  # @return [Object] The message payload. See the documentation for supported datatypes.
22
- # @!attribute [r] sender_timestamp
23
- # @return [Time] Timestamp when the message was sent according to the publisher client
24
- # @!attribute [r] ably_timestamp
25
- # @return [Time] Timestamp when the message was received by the Ably the service for publishing
26
- # @!attribute [r] message_id
28
+ # @!attribute [r] timestamp
29
+ # @return [Time] Timestamp when the message was received by the Ably the real-time service
30
+ # @!attribute [r] id
27
31
  # @return [String] A globally unique message ID
28
- # @!attribute [r] json
32
+ # @!attribute [r] hash
29
33
  # @return [Hash] Access the protocol message Hash object ruby'fied to use symbolized keys
30
34
  #
31
35
  class Message
32
- include Shared
33
- include Ably::Modules::Conversions
36
+ include Ably::Modules::ModelCommon
34
37
  include EventMachine::Deferrable
35
38
 
36
39
  # {Message} initializer
37
40
  #
38
- # @param json_object [Hash] JSON like object with the underlying message details
41
+ # @param hash_object [Hash] object with the underlying message details
39
42
  # @param protocol_message [ProtocolMessage] if this message has been published, then it is associated with a {ProtocolMessage}
40
43
  #
41
- def initialize(json_object, protocol_message = nil)
44
+ def initialize(hash_object, protocol_message = nil)
42
45
  @protocol_message = protocol_message
43
- @raw_json_object = json_object
44
- @json_object = IdiomaticRubyWrapper(json_object.clone.freeze, stop_at: [:data])
46
+ @raw_hash_object = hash_object
47
+ @hash_object = IdiomaticRubyWrapper(hash_object.clone.freeze, stop_at: [:data])
45
48
  end
46
49
 
47
50
  %w( name client_id ).each do |attribute|
48
51
  define_method attribute do
49
- json[attribute.to_sym]
52
+ hash[attribute.to_sym]
50
53
  end
51
54
  end
52
55
 
53
56
  def data
54
- @data ||= json[:data].freeze
55
- end
56
-
57
- def message_id
58
- "#{connection_id}:#{message_serial}:#{protocol_message_index}"
59
- end
60
-
61
- def sender_timestamp
62
- as_time_from_epoch(json[:timestamp]) if json[:timestamp]
57
+ @data ||= hash[:data].freeze
63
58
  end
64
59
 
65
- def ably_timestamp
66
- protocol_message.timestamp
60
+ def id
61
+ hash[:id] || "#{protocol_message.id!}:#{protocol_message_index}"
67
62
  end
68
63
 
69
- def json
70
- @json_object
64
+ def timestamp
65
+ if hash[:timestamp]
66
+ as_time_from_epoch(hash[:timestamp])
67
+ else
68
+ protocol_message.timestamp
69
+ end
71
70
  end
72
71
 
73
- def to_json_object
74
- raise RuntimeError, ":name is missing, cannot generate valid JSON for Message" unless name
75
-
76
- json.dup.tap do |json_object|
77
- json_object[:timestamp] = as_since_epoch(Time.now) unless sender_timestamp
78
- end
72
+ def hash
73
+ @hash_object
79
74
  end
80
75
 
81
- def to_json(*args)
82
- to_json_object.to_json
76
+ def as_json(*args)
77
+ raise RuntimeError, ":name is missing, cannot generate a valid Hash for Message" unless name
78
+ super
83
79
  end
84
80
 
81
+ # Assign this message to a ProtocolMessage before delivery to the Ably system
82
+ # @api private
85
83
  def assign_to_protocol_message(protocol_message)
86
84
  @protocol_message = protocol_message
87
85
  end
88
86
 
89
- private
87
+ # True if this message is assigned to a ProtocolMessage for delivery to Ably, or received from Ably
88
+ # @return [Boolean]
89
+ # @api private
90
+ def assigned_to_protocol_message?
91
+ !!@protocol_message
92
+ end
90
93
 
94
+ # The optional ProtocolMessage this message is assigned to. If ProtocolMessage is nil, an error will be raised.
95
+ # @return [Ably::Models::ProtocolMessage]
96
+ # @api private
91
97
  def protocol_message
92
- raise RuntimeError, "Message is not yet published with a ProtocolMessage. ProtocolMessage is nil" if @protocol_message.nil?
98
+ raise RuntimeError, "Message is not yet published with a ProtocolMessage. ProtocolMessage is nil" if @protocol_message.nil?
93
99
  @protocol_message
94
100
  end
95
101
 
102
+ private
96
103
  def protocol_message_index
97
104
  protocol_message.messages.index(self)
98
105
  end
@@ -1,10 +1,10 @@
1
- module Ably::Realtime::Models
1
+ module Ably::Models
2
2
  # Nil object for Channels, this object is only used within the internal API of this client library
3
3
  class NilChannel
4
4
  include Ably::Modules::EventEmitter
5
5
  extend Ably::Modules::Enum
6
6
  STATE = ruby_enum('STATE', Ably::Realtime::Channel::STATE)
7
- include Ably::Modules::State
7
+ include Ably::Modules::StateEmitter
8
8
 
9
9
  def initialize
10
10
  @state = STATE.Initialized
@@ -14,8 +14,8 @@ module Ably::Realtime::Models
14
14
  'Nil channel'
15
15
  end
16
16
 
17
- def __incoming_protocol_msgbus__
18
- @__incoming_protocol_msgbus__ ||= Ably::Util::PubSub.new
17
+ def __incoming_msgbus__
18
+ @__incoming_msgbus__ ||= Ably::Util::PubSub.new
19
19
  end
20
20
  end
21
21
  end
@@ -1,23 +1,24 @@
1
- module Ably::Rest::Models
1
+ module Ably::Models
2
2
  # Wraps any Ably HTTP response that supports paging and automatically provides methdos to iterated through
3
3
  # the array of resources using {#first}, {#next}, {#last?} and {#first?}
4
4
  #
5
5
  # Paging information is provided by Ably in the LINK HTTP headers
6
- class PagedResource
6
+ class PaginatedResource
7
7
  include Enumerable
8
8
 
9
9
  # @param [Faraday::Response] http_response Initial HTTP response from an Ably request to a paged resource
10
10
  # @param [String] base_url Base URL for request that generated the http_response so that subsequent paged requests can be made
11
11
  # @param [Client] client {Ably::Client} used to make the request to Ably
12
12
  # @param [Hash] options Options for this paged resource
13
- # @option options [Symbol,String] :coerce_into symbol or string representing class that should be used to create each item in the PagedResource
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
- # @return [PagedResource]
15
+ # @return [PaginatedResource]
16
16
  def initialize(http_response, base_url, client, options = {})
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
+ @raw_body = http_response.body
21
22
 
22
23
  @body = if @coerce_into
23
24
  http_response.body.map do |item|
@@ -30,17 +31,17 @@ module Ably::Rest::Models
30
31
 
31
32
  # Retrieve the first page of results
32
33
  #
33
- # @return [PagedResource]
34
+ # @return [PaginatedResource]
34
35
  def first_page
35
- PagedResource.new(client.get(pagination_url('first')), base_url, client, coerce_into: coerce_into)
36
+ PaginatedResource.new(client.get(pagination_url('first')), base_url, client, coerce_into: coerce_into)
36
37
  end
37
38
 
38
39
  # Retrieve the next page of results
39
40
  #
40
- # @return [PagedResource]
41
+ # @return [PaginatedResource]
41
42
  def next_page
42
43
  raise Ably::Exceptions::InvalidPageError, "There are no more pages" if supports_pagination? && last_page?
43
- PagedResource.new(client.get(pagination_url('next')), base_url, client, coerce_into: coerce_into)
44
+ PaginatedResource.new(client.get(pagination_url('next')), base_url, client, coerce_into: coerce_into)
44
45
  end
45
46
 
46
47
  # True if this is the last page in the paged resource set
@@ -78,7 +79,7 @@ module Ably::Rest::Models
78
79
  alias_method :count, :length
79
80
  alias_method :size, :length
80
81
 
81
- # Method ensuring this {PagedResource} is {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
82
+ # Method ensuring this {PaginatedResource} is {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
82
83
  def each(&block)
83
84
  body.each do |item|
84
85
  if block_given?
@@ -89,8 +90,18 @@ module Ably::Rest::Models
89
90
  end
90
91
  end
91
92
 
93
+ # Last item in this page
94
+ def first
95
+ body.first
96
+ end
97
+
98
+ # Last item in this page
99
+ def last
100
+ body.last
101
+ end
102
+
92
103
  private
93
- attr_reader :body, :http_response, :base_url, :client, :coerce_into
104
+ attr_reader :body, :http_response, :base_url, :client, :coerce_into, :raw_body
94
105
 
95
106
  def pagination_headers
96
107
  link_regex = %r{<(?<url>[^>]+)>; rel="(?<rel>[^"]+)"}
@@ -0,0 +1,126 @@
1
+ module Ably::Models
2
+ # Convert presence_messsage argument to a {PresenceMessage} object and associate with a protocol message if provided
3
+ #
4
+ # @param presence_message [PresenceMessage,Hash] A presence message object or Hash of presence message properties
5
+ # @param protocol_message [ProtocolMessage] An optional protocol message to assocate the presence message with
6
+ #
7
+ # @return [PresenceMessage]
8
+ def self.PresenceMessage(presence_message, protocol_message = nil)
9
+ case presence_message
10
+ when PresenceMessage
11
+ presence_message.tap do
12
+ presence_message.assign_to_protocol_message protocol_message
13
+ end
14
+ else
15
+ PresenceMessage.new(presence_message, protocol_message)
16
+ end
17
+ end
18
+
19
+ # A class representing an individual presence message to be sent or received
20
+ # via the Ably Realtime service.
21
+ #
22
+ # @!attribute [r] action
23
+ # @return [STATE] the state change event signified by a PresenceMessage
24
+ # @!attribute [r] client_id
25
+ # @return [String] The client_id associated with this presence state
26
+ # @!attribute [r] member_id
27
+ # @return [String] A unique member identifier, disambiguating situations where a given client_id is present on multiple connections simultaneously
28
+ # @!attribute [r] client_data
29
+ # @return [Object] Optional client-defined status or other event payload associated with this state
30
+ # @!attribute [r] timestamp
31
+ # @return [Time] Timestamp when the message was received by the Ably the real-time service
32
+ # @!attribute [r] hash
33
+ # @return [Hash] Access the protocol message Hash object ruby'fied to use symbolized keys
34
+ #
35
+ class PresenceMessage
36
+ include Ably::Modules::ModelCommon
37
+ include EventMachine::Deferrable
38
+ extend Ably::Modules::Enum
39
+
40
+ ACTION = ruby_enum('ACTION',
41
+ :enter,
42
+ :leave,
43
+ :update
44
+ )
45
+
46
+ # {Message} initializer
47
+ #
48
+ # @param hash_object [Hash] object with the underlying message details
49
+ # @param protocol_message [ProtocolMessage] if this message has been published, then it is associated with a {ProtocolMessage}
50
+ #
51
+ def initialize(hash_object, protocol_message = nil)
52
+ @protocol_message = protocol_message
53
+ @raw_hash_object = hash_object
54
+ @hash_object = IdiomaticRubyWrapper(hash_object.clone.freeze, stop_at: [:data])
55
+ end
56
+
57
+ %w( client_id member_id client_data ).each do |attribute|
58
+ define_method attribute do
59
+ hash[attribute.to_sym]
60
+ end
61
+ end
62
+
63
+ def id
64
+ hash[:id] || "#{protocol_message.id!}:#{protocol_message_index}"
65
+ end
66
+
67
+ def timestamp
68
+ if hash[:timestamp]
69
+ as_time_from_epoch(hash[:timestamp])
70
+ else
71
+ protocol_message.timestamp
72
+ end
73
+ end
74
+
75
+ def action
76
+ ACTION(hash[:action])
77
+ end
78
+
79
+ def hash
80
+ @hash_object
81
+ end
82
+
83
+ # Return a JSON ready object from the underlying #hash using Ably naming conventions for keys
84
+ def as_json(*args)
85
+ hash.dup.tap do |hash|
86
+ hash['action'] = action.to_i
87
+ end.as_json
88
+ rescue KeyError
89
+ raise KeyError, ":action is missing or invalid, cannot generate a valid Hash for ProtocolMessage"
90
+ end
91
+
92
+ # Assign this presence message to a ProtocolMessage before delivery to the Ably system
93
+ # @api private
94
+ def assign_to_protocol_message(protocol_message)
95
+ @protocol_message = protocol_message
96
+ end
97
+
98
+ # True if this presence message is assigned to a ProtocolMessage for delivery to Ably, or received from Ably
99
+ # @return [Boolean]
100
+ # @api private
101
+ def assigned_to_protocol_message?
102
+ !!@protocol_message
103
+ end
104
+
105
+ # The optional ProtocolMessage this presence message is assigned to. If ProtocolMessage is nil, an error will be raised.
106
+ # @return [Ably::Models::ProtocolMessage]
107
+ # @api private
108
+ def protocol_message
109
+ raise RuntimeError, "Presence Message is not yet published with a ProtocolMessage. ProtocolMessage is nil" if @protocol_message.nil?
110
+ @protocol_message
111
+ end
112
+
113
+ private
114
+ def protocol_message_index
115
+ protocol_message.presence.index(self)
116
+ end
117
+
118
+ def connection_id
119
+ protocol_message.connection_id
120
+ end
121
+
122
+ def message_serial
123
+ protocol_message.message_serial
124
+ end
125
+ end
126
+ end
@@ -1,4 +1,4 @@
1
- module Ably::Realtime::Models
1
+ module Ably::Models
2
2
  # A message sent and received over the Realtime protocol.
3
3
  # A ProtocolMessage always relates to a single channel only, but
4
4
  # can contain multiple individual Messages or PresenceMessages.
@@ -15,26 +15,25 @@ module Ably::Realtime::Models
15
15
  # @!attribute [r] channel
16
16
  # @return [String] Channel name for messages
17
17
  # @!attribute [r] channel_serial
18
- # @return [String] Contains a serial number for amessage on the current channel
18
+ # @return [String] Contains a serial number for a message on the current channel
19
19
  # @!attribute [r] connection_id
20
20
  # @return [String] Contains a string connection ID
21
21
  # @!attribute [r] connection_serial
22
- # @return [Bignum] Contains a serial number for a message on the current connection
22
+ # @return [Bignum] Contains a serial number for a message sent from the server to the client
23
23
  # @!attribute [r] message_serial
24
- # @return [Bignum] Contains a serial number for a message sent from the client to the service.
24
+ # @return [Bignum] Contains a serial number for a message sent from the client to the server
25
25
  # @!attribute [r] timestamp
26
26
  # @return [Time] An optional timestamp, applied by the service in messages sent to the client, to indicate the system time at which the message was sent (milliseconds past epoch)
27
27
  # @!attribute [r] messages
28
28
  # @return [Message] A {ProtocolMessage} with a `:message` action contains one or more messages belonging to a channel.
29
29
  # @!attribute [r] presence
30
30
  # @return [PresenceMessage] A {ProtocolMessage} with a `:presence` action contains one or more presence updates belonging to a channel.
31
- # @!attribute [r] json
31
+ # @!attribute [r] hash
32
32
  # @return [Hash] Access the protocol message Hash object ruby'fied to use symbolized keys
33
33
  #
34
34
  class ProtocolMessage
35
- include Shared
35
+ include Ably::Modules::ModelCommon
36
36
  extend Ably::Modules::Enum
37
- include Ably::Modules::Conversions
38
37
 
39
38
  # Actions which are sent by the Ably Realtime API
40
39
  #
@@ -64,40 +63,55 @@ module Ably::Realtime::Models
64
63
  [ACTION.Presence, ACTION.Message].include?(ACTION(for_action))
65
64
  end
66
65
 
67
- def initialize(json_object)
68
- @raw_json_object = json_object
69
- @json_object = IdiomaticRubyWrapper(@raw_json_object.clone.freeze)
66
+ def initialize(hash_object)
67
+ @raw_hash_object = hash_object
68
+ @hash_object = IdiomaticRubyWrapper(@raw_hash_object.clone)
69
+
70
+ raise ArgumentError, "Invalid ProtocolMessage, action cannot be nil" if @hash_object[:action].nil?
71
+ @hash_object[:action] = ACTION(@hash_object[:action]).to_i unless @hash_object[:action].kind_of?(Integer)
72
+
73
+ @hash_object.freeze
70
74
  end
71
75
 
72
- %w( channel channel_serial
73
- connection_id connection_serial ).each do |attribute|
76
+ %w( id channel channel_serial connection_id ).each do |attribute|
74
77
  define_method attribute do
75
- json[attribute.to_sym]
78
+ hash[attribute.to_sym]
76
79
  end
77
80
  end
78
81
 
82
+ def id!
83
+ raise RuntimeError, "ProtocolMessage #id is nil" unless id
84
+ id
85
+ end
86
+
79
87
  def action
80
- ACTION(json[:action])
88
+ ACTION(hash[:action])
81
89
  rescue KeyError
82
- raise KeyError, "Action '#{json[:action]}' is not supported by ProtocolMessage"
90
+ raise KeyError, "Action '#{hash[:action]}' is not supported by ProtocolMessage"
83
91
  end
84
92
 
85
93
  def error
86
- @error_info ||= ErrorInfo.new(json[:error]) if json[:error]
94
+ @error_info ||= ErrorInfo.new(hash[:error]) if hash[:error]
87
95
  end
88
96
 
89
97
  def timestamp
90
- as_time_from_epoch(json[:timestamp]) if json[:timestamp]
98
+ as_time_from_epoch(hash[:timestamp]) if hash[:timestamp]
91
99
  end
92
100
 
93
101
  def message_serial
94
- Integer(json[:msg_serial])
102
+ Integer(hash[:msg_serial])
103
+ rescue TypeError
104
+ raise TypeError, "msg_serial '#{hash[:msg_serial]}' is invalid, a positive Integer is expected for a ProtocolMessage"
105
+ end
106
+
107
+ def connection_serial
108
+ Integer(hash[:connection_serial])
95
109
  rescue TypeError
96
- raise TypeError, "msg_serial '#{json[:msg_serial]}' is invalid, a positive Integer is expected for a ProtocolMessage"
110
+ raise TypeError, "connection_serial '#{hash[:connection_serial]}' is invalid, a positive Integer is expected for a ProtocolMessage"
97
111
  end
98
112
 
99
113
  def count
100
- [1, json[:count].to_i].max
114
+ [1, hash[:count].to_i].max
101
115
  end
102
116
 
103
117
  def has_message_serial?
@@ -106,10 +120,28 @@ module Ably::Realtime::Models
106
120
  false
107
121
  end
108
122
 
123
+ def has_connection_serial?
124
+ connection_serial && true
125
+ rescue TypeError
126
+ false
127
+ end
128
+
129
+ def serial
130
+ if has_connection_serial?
131
+ connection_serial
132
+ else
133
+ message_serial
134
+ end
135
+ end
136
+
137
+ def has_serial?
138
+ has_connection_serial? || has_message_serial?
139
+ end
140
+
109
141
  def messages
110
142
  @messages ||=
111
- Array(json[:messages]).map do |message|
112
- Ably::Realtime::Models.Message(message, self)
143
+ Array(hash[:messages]).map do |message|
144
+ Ably::Models.Message(message, self)
113
145
  end
114
146
  end
115
147
 
@@ -119,37 +151,43 @@ module Ably::Realtime::Models
119
151
 
120
152
  def presence
121
153
  @presence ||=
122
- Array(json[:presence]).map do |message|
123
- PresenceMessage(message, self)
154
+ Array(hash[:presence]).map do |message|
155
+ Ably::Models.PresenceMessage(message, self)
124
156
  end
125
157
  end
126
158
 
127
- def json
128
- @json_object
129
- end
130
-
131
159
  # Indicates this protocol message will generate an ACK response when sent
132
160
  # Examples of protocol messages required ACK include :message and :presence
133
161
  def ack_required?
134
162
  self.class.ack_required?(action)
135
163
  end
136
164
 
137
- def to_json_object
138
- raise TypeError, ":action is missing, cannot generate valid JSON for ProtocolMessage" unless action
139
- raise TypeError, ":msg_serial is missing, cannot generate valid JSON for ProtocolMessage" if ack_required? && !has_message_serial?
140
-
141
- json.dup.tap do |json_object|
142
- json_object[:messages] = messages.map(&:to_json_object) unless messages.empty?
143
- json_object[:presence] = presence.map(&:to_json_object) unless presence.empty?
144
- end
165
+ def hash
166
+ @hash_object
145
167
  end
146
168
 
147
- def to_json(*args)
148
- to_json_object.to_json
169
+ # Return a JSON ready object from the underlying #hash using Ably naming conventions for keys
170
+ def as_json(*args)
171
+ raise TypeError, ":action is missing, cannot generate a valid Hash for ProtocolMessage" unless action
172
+ raise TypeError, ":msg_serial or :connection_serial is missing, cannot generate a valid Hash for ProtocolMessage" if ack_required? && !has_serial?
173
+
174
+ hash.dup.tap do |hash_object|
175
+ hash_object['action'] = action.to_i
176
+ hash_object['messages'] = messages.map(&:as_json) unless messages.empty?
177
+ hash_object['presence'] = presence.map(&:as_json) unless presence.empty?
178
+ end.as_json
149
179
  end
150
180
 
151
181
  def to_s
152
182
  to_json
153
183
  end
184
+
185
+ # True if the ProtocolMessage appears to be invalid, however this is not a guarantee
186
+ # @return [Boolean]
187
+ # @api private
188
+ def invalid?
189
+ action_enum = action rescue nil
190
+ !action_enum || (ack_required? && !has_serial?)
191
+ end
154
192
  end
155
193
  end