ably 0.1.5 → 0.1.6

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