ably-pubsub-core 0.0.1.pre

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 (105) hide show
  1. checksums.yaml +7 -0
  2. data/lib/ably/agent.rb +8 -0
  3. data/lib/ably/auth.rb +791 -0
  4. data/lib/ably/exceptions.rb +169 -0
  5. data/lib/ably/logger.rb +119 -0
  6. data/lib/ably/models/auth_details.rb +45 -0
  7. data/lib/ably/models/channel_details.rb +69 -0
  8. data/lib/ably/models/channel_metrics.rb +107 -0
  9. data/lib/ably/models/channel_occupancy.rb +50 -0
  10. data/lib/ably/models/channel_options.rb +120 -0
  11. data/lib/ably/models/channel_state_change.rb +84 -0
  12. data/lib/ably/models/channel_status.rb +63 -0
  13. data/lib/ably/models/cipher_params.rb +137 -0
  14. data/lib/ably/models/connection_details.rb +149 -0
  15. data/lib/ably/models/connection_state_change.rb +83 -0
  16. data/lib/ably/models/delta_extras.rb +28 -0
  17. data/lib/ably/models/device_details.rb +126 -0
  18. data/lib/ably/models/device_push_details.rb +94 -0
  19. data/lib/ably/models/error_info.rb +104 -0
  20. data/lib/ably/models/http_paginated_response.rb +112 -0
  21. data/lib/ably/models/idiomatic_ruby_wrapper.rb +235 -0
  22. data/lib/ably/models/message.rb +223 -0
  23. data/lib/ably/models/message_encoders/base.rb +135 -0
  24. data/lib/ably/models/message_encoders/base64.rb +40 -0
  25. data/lib/ably/models/message_encoders/cipher.rb +84 -0
  26. data/lib/ably/models/message_encoders/json.rb +34 -0
  27. data/lib/ably/models/message_encoders/utf8.rb +26 -0
  28. data/lib/ably/models/nil_logger.rb +20 -0
  29. data/lib/ably/models/paginated_result.rb +201 -0
  30. data/lib/ably/models/presence_message.rb +244 -0
  31. data/lib/ably/models/protocol_message.rb +288 -0
  32. data/lib/ably/models/push_channel_subscription.rb +114 -0
  33. data/lib/ably/models/stats.rb +242 -0
  34. data/lib/ably/models/stats_types.rb +107 -0
  35. data/lib/ably/models/token_details.rb +148 -0
  36. data/lib/ably/models/token_request.rb +180 -0
  37. data/lib/ably/modules/ably.rb +26 -0
  38. data/lib/ably/modules/async_wrapper.rb +69 -0
  39. data/lib/ably/modules/channels_collection.rb +105 -0
  40. data/lib/ably/modules/conversions.rb +153 -0
  41. data/lib/ably/modules/encodeable.rb +111 -0
  42. data/lib/ably/modules/enum.rb +230 -0
  43. data/lib/ably/modules/event_emitter.rb +200 -0
  44. data/lib/ably/modules/event_machine_helpers.rb +26 -0
  45. data/lib/ably/modules/exception_codes.rb +128 -0
  46. data/lib/ably/modules/http_helpers.rb +41 -0
  47. data/lib/ably/modules/message_emitter.rb +78 -0
  48. data/lib/ably/modules/message_pack.rb +14 -0
  49. data/lib/ably/modules/model_common.rb +84 -0
  50. data/lib/ably/modules/safe_deferrable.rb +83 -0
  51. data/lib/ably/modules/safe_yield.rb +41 -0
  52. data/lib/ably/modules/state_emitter.rb +181 -0
  53. data/lib/ably/modules/state_machine.rb +59 -0
  54. data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  55. data/lib/ably/modules/uses_state_machine.rb +111 -0
  56. data/lib/ably/realtime/auth.rb +283 -0
  57. data/lib/ably/realtime/channel/channel_manager.rb +272 -0
  58. data/lib/ably/realtime/channel/channel_properties.rb +40 -0
  59. data/lib/ably/realtime/channel/channel_state_machine.rb +104 -0
  60. data/lib/ably/realtime/channel/publisher.rb +82 -0
  61. data/lib/ably/realtime/channel/push_channel.rb +69 -0
  62. data/lib/ably/realtime/channel.rb +440 -0
  63. data/lib/ably/realtime/channels.rb +71 -0
  64. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +225 -0
  65. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +87 -0
  66. data/lib/ably/realtime/client.rb +363 -0
  67. data/lib/ably/realtime/connection/connection_manager.rb +594 -0
  68. data/lib/ably/realtime/connection/connection_state_machine.rb +144 -0
  69. data/lib/ably/realtime/connection/websocket_transport.rb +287 -0
  70. data/lib/ably/realtime/connection.rb +738 -0
  71. data/lib/ably/realtime/models/nil_channel.rb +30 -0
  72. data/lib/ably/realtime/presence/members_map.rb +395 -0
  73. data/lib/ably/realtime/presence/presence_manager.rb +78 -0
  74. data/lib/ably/realtime/presence/presence_state_machine.rb +57 -0
  75. data/lib/ably/realtime/presence.rb +502 -0
  76. data/lib/ably/realtime/push/admin.rb +75 -0
  77. data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
  78. data/lib/ably/realtime/push/device_registrations.rb +105 -0
  79. data/lib/ably/realtime/push.rb +25 -0
  80. data/lib/ably/realtime/recovery_key_context.rb +36 -0
  81. data/lib/ably/realtime.rb +60 -0
  82. data/lib/ably/rest/channel/push_channel.rb +59 -0
  83. data/lib/ably/rest/channel.rb +194 -0
  84. data/lib/ably/rest/channels.rb +47 -0
  85. data/lib/ably/rest/client.rb +727 -0
  86. data/lib/ably/rest/middleware/encoder.rb +49 -0
  87. data/lib/ably/rest/middleware/exceptions.rb +53 -0
  88. data/lib/ably/rest/middleware/external_exceptions.rb +24 -0
  89. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +22 -0
  90. data/lib/ably/rest/middleware/logger.rb +58 -0
  91. data/lib/ably/rest/middleware/parse_json.rb +27 -0
  92. data/lib/ably/rest/middleware/parse_message_pack.rb +43 -0
  93. data/lib/ably/rest/presence.rb +113 -0
  94. data/lib/ably/rest/push/admin.rb +58 -0
  95. data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
  96. data/lib/ably/rest/push/device_registrations.rb +114 -0
  97. data/lib/ably/rest/push.rb +25 -0
  98. data/lib/ably/rest.rb +40 -0
  99. data/lib/ably/util/ably_extensions.rb +29 -0
  100. data/lib/ably/util/crypto.rb +134 -0
  101. data/lib/ably/util/pub_sub.rb +45 -0
  102. data/lib/ably/util/safe_deferrable.rb +44 -0
  103. data/lib/ably/version.rb +7 -0
  104. data/lib/ably.rb +16 -0
  105. metadata +497 -0
@@ -0,0 +1,201 @@
1
+ module Ably::Models
2
+ # Contains a page of results for message or presence history, stats, or REST presence requests.
3
+ # A PaginatedResult response from a REST API paginated query is also accompanied by metadata
4
+ # that indicates the relative queries available to the PaginatedResult object.
5
+ #
6
+ class PaginatedResult
7
+ include Ably::Modules::AsyncWrapper if defined?(Ably::Realtime)
8
+
9
+ # Contains the current page of results; for example, an array of {Ably::Models::Message} or {Ably::Models::PresenceMessage} objects
10
+ # for a channel history request.
11
+ #
12
+ # @spec TG3
13
+ #
14
+ # @return [Array]
15
+ #
16
+ attr_reader :items
17
+
18
+ # @param [Faraday::Response] http_response Initial HTTP response from an Ably request to a paged resource
19
+ # @param [String] base_url Base URL for request that generated the http_response so that subsequent paged requests can be made
20
+ # @param [Client] client {Ably::Client} used to make the request to Ably
21
+ # @param [Hash] options Options for this paged resource
22
+ # @option options [Symbol,String] :coerce_into symbol or string representing class that should be used to create each item in the PaginatedResult
23
+ #
24
+ # @yield [Object] block will be called for each resource object for the current page. This is a useful way to apply a transformation to any page resources after they are retrieved
25
+ #
26
+ # @return [PaginatedResult]
27
+ #
28
+ def initialize(http_response, base_url, client, options = {}, &each_block)
29
+ @http_response = http_response
30
+ @client = client
31
+ @base_url = "#{base_url.gsub(%r{/[^/]*$}, '')}/"
32
+ @coerce_into = options[:coerce_into]
33
+ @raw_body = http_response.body
34
+ @each_block = each_block
35
+ @make_async = options.fetch(:async_blocking_operations, false)
36
+
37
+ @items = http_response.body
38
+ if @items.nil? || @items.to_s.strip.empty?
39
+ @items = []
40
+ end
41
+ @items = [@items] if @items.kind_of?(Hash)
42
+
43
+ @items = coerce_items_into(items, @coerce_into) if @coerce_into
44
+ @items = items.map { |item| yield item } if block_given?
45
+ end
46
+
47
+ # Returns a new PaginatedResult for the first page of results.
48
+ #
49
+ # @spec TG5
50
+ #
51
+ # @return [PaginatedResult, Ably::Util::SafeDeferrable]
52
+ #
53
+ def first(&success_callback)
54
+ async_wrap_if_realtime(success_callback) do
55
+ return nil unless supports_pagination?
56
+ PaginatedResult.new(client.get(pagination_url('first')), base_url, client, pagination_options, &each_block)
57
+ end
58
+ end
59
+
60
+ # Retrieve the next page of results.
61
+ #
62
+ # When used as part of the {Ably::Realtime} library, it will return a {Ably::Util::SafeDeferrable} object,
63
+ # and allows an optional success callback block to be provided.
64
+ #
65
+ # @spec TG4
66
+ #
67
+ # @return [PaginatedResult,Ably::Util::SafeDeferrable]
68
+ #
69
+ def next(&success_callback)
70
+ async_wrap_if_realtime(success_callback) do
71
+ return nil unless has_next?
72
+ PaginatedResult.new(client.get(pagination_url('next')), base_url, client, pagination_options, &each_block)
73
+ end
74
+ end
75
+
76
+ # Returns true if this page is the last page and returns false if there are more pages available by calling next available.
77
+ #
78
+ # @spec TG7
79
+ #
80
+ # @return [Boolean]
81
+ #
82
+ def last?
83
+ !supports_pagination? ||
84
+ pagination_header('next').nil?
85
+ end
86
+
87
+ # Returns true if there are more pages available by calling next and returns false if this page is the last page available.
88
+ #
89
+ # @spec TG6
90
+ #
91
+ # @return [Boolean]
92
+ #
93
+ def has_next?
94
+ supports_pagination? && !last?
95
+ end
96
+
97
+ # True if the HTTP response supports paging with the expected LINK HTTP headers
98
+ #
99
+ # @return [Boolean]
100
+ #
101
+ def supports_pagination?
102
+ !pagination_headers.empty?
103
+ end
104
+
105
+ def inspect
106
+ <<-EOF.gsub(/^ /, '')
107
+ #<#{self.class.name}:#{self.object_id}
108
+ @base_url="#{base_url}",
109
+ @last?=#{!!last?},
110
+ @has_next?=#{!!has_next?},
111
+ @items=
112
+ #{items.map { |item| item.inspect }.join(",\n ") }
113
+ >
114
+ EOF
115
+ end
116
+
117
+ private
118
+ def http_response
119
+ @http_response
120
+ end
121
+
122
+ def base_url
123
+ @base_url
124
+ end
125
+
126
+ def client
127
+ @client
128
+ end
129
+
130
+ def coerce_into
131
+ @coerce_into
132
+ end
133
+
134
+ def raw_body
135
+ @raw_body
136
+ end
137
+
138
+ def each_block
139
+ @each_block
140
+ end
141
+
142
+ def make_async
143
+ @make_async
144
+ end
145
+
146
+ def coerce_items_into(items, type_string)
147
+ items.map do |item|
148
+ @coerce_into.split('::').inject(Kernel) do |base, klass_name|
149
+ base.public_send(:const_get, klass_name)
150
+ end.new(item)
151
+ end
152
+ end
153
+
154
+ def pagination_headers
155
+ link_regex = %r{<(?<url>[^>]+)>; rel="(?<rel>[^"]+)"}
156
+ @pagination_headers ||= begin
157
+ # All `Link:` headers are concatenated by Faraday into a comma separated list
158
+ # Finding matching `<url>; rel="rel"` pairs
159
+ link_headers = http_response.headers['link'] || ''
160
+ link_headers.scan(link_regex).each_with_object({}) do |val_array, hash|
161
+ url, rel = val_array
162
+ hash[rel] = url
163
+ end
164
+ end
165
+ end
166
+
167
+ def pagination_header(id)
168
+ pagination_headers[id]
169
+ end
170
+
171
+ def pagination_url(id)
172
+ raise Ably::Exceptions::PageMissing, "Paging header link #{id} does not exist" unless pagination_header(id)
173
+
174
+ if pagination_header(id).match(%r{^\./})
175
+ "#{base_url}#{pagination_header(id)[2..-1]}"
176
+ else
177
+ pagination_header[id]
178
+ end
179
+ end
180
+
181
+ def pagination_options
182
+ {
183
+ coerce_into: coerce_into,
184
+ async_blocking_operations: make_async
185
+ }
186
+ end
187
+
188
+ def async_wrap_if_realtime(success_callback, &operation)
189
+ if make_async
190
+ raise 'EventMachine is required for asynchronous operations' unless defined?(EventMachine)
191
+ async_wrap success_callback, &operation
192
+ else
193
+ yield
194
+ end
195
+ end
196
+
197
+ def logger
198
+ client.logger
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,244 @@
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 [Hash] options (see PresenceMessage#initialize)
6
+ #
7
+ # @return [PresenceMessage]
8
+ #
9
+ def self.PresenceMessage(presence_message, options = {})
10
+ case presence_message
11
+ when PresenceMessage
12
+ presence_message.tap do
13
+ presence_message.assign_to_protocol_message options[:protocol_message] if options[:protocol_message]
14
+ end
15
+ else
16
+ PresenceMessage.new(presence_message, options)
17
+ end
18
+ end
19
+
20
+ # A class representing an individual presence message to be sent or received
21
+ # via the Ably Realtime service.
22
+ #
23
+ class PresenceMessage
24
+ include Ably::Modules::Conversions
25
+ include Ably::Modules::Encodeable
26
+ include Ably::Modules::ModelCommon
27
+ include Ably::Modules::SafeDeferrable if defined?(Ably::Realtime)
28
+ extend Ably::Modules::Enum
29
+
30
+ # Describes the possible actions members in the presence set can emit.
31
+ #
32
+ # ABSENT TP2 A member is not present in the channel.
33
+ # PRESENT TP2 When subscribing to presence events on a channel that already has members present, this event is emitted for every member already present on the channel before the subscribe listener was registered.
34
+ # ENTER TP2 A new member has entered the channel.
35
+ # LEAVE TP2 A member who was present has now left the channel. This may be a result of an explicit request to leave or implicitly when detaching from the channel. Alternatively, if a member's connection is abruptly disconnected and they do not resume their connection within a minute, Ably treats this as a leave event as the client is no longer present.
36
+ # UPDATE TP2 An already present member has updated their member data. Being notified of member data updates can be very useful, for example, it can be used to update the status of a user when they are typing a message.
37
+ #
38
+ # @spec TP2
39
+ #
40
+ ACTION = ruby_enum('ACTION',
41
+ :absent,
42
+ :present,
43
+ :enter,
44
+ :leave,
45
+ :update
46
+ )
47
+
48
+ # Statically register a default set of encoders for this class
49
+ Ably::Models::MessageEncoders.register_default_encoders self
50
+
51
+ # {PresenceMessage} initializer
52
+ #
53
+ # @param attributes [Hash] object with the underlying presence message key value attributes
54
+ # @param [Hash] options an options Hash for this initializer
55
+ # @option options [ProtocolMessage] :protocol_message An optional protocol message to assocate the presence message with
56
+ # @option options [Logger] :logger An optional Logger to be used by {Ably::Modules::SafeDeferrable} if an exception is caught in a callback
57
+ #
58
+ def initialize(attributes, options = {})
59
+ @logger = options[:logger] # Logger expected for SafeDeferrable
60
+ @protocol_message = options[:protocol_message]
61
+ @raw_hash_object = attributes
62
+
63
+ set_attributes_object attributes
64
+
65
+ self.attributes[:client_id] = ensure_utf_8(:client_id, client_id, allow_nil: true) if client_id
66
+ self.attributes[:connection_id] = ensure_utf_8(:connection_id, connection_id, allow_nil: true) if connection_id
67
+ self.attributes[:encoding] = ensure_utf_8(:encoding, encoding, allow_nil: true) if encoding
68
+
69
+ self.attributes.freeze
70
+ end
71
+
72
+ # The ID of the client that published the PresenceMessage.
73
+ #
74
+ # @spec TP3c
75
+ #
76
+ # @return [String]
77
+ #
78
+ def client_id
79
+ attributes[:client_id]
80
+ end
81
+
82
+ # The payload of the {Ably::Models::PresenceMessage}.
83
+ #
84
+ # @spec TP3e
85
+ #
86
+ # @return [Hash, nil]
87
+ #
88
+ def data
89
+ attributes[:data]
90
+ end
91
+
92
+ # This will typically be empty as all presence messages received from Ably are automatically decoded client-side
93
+ # using this value. However, if the message encoding cannot be processed, this attribute will contain the remaining
94
+ # transformations not applied to the data payload.
95
+ #
96
+ # @spec TP3f
97
+ #
98
+ # @return [String]
99
+ #
100
+ def encoding
101
+ attributes[:encoding]
102
+ end
103
+
104
+ # A unique ID assigned to each {Ably::Models::PresenceMessage} by Ably.
105
+ #
106
+ # @spec TP3a
107
+ #
108
+ # @return [String]
109
+ #
110
+ def id
111
+ attributes.fetch(:id) { "#{protocol_message.id!}:#{protocol_message_index}" }
112
+ end
113
+
114
+ # The ID of the connection associated with the client that published the {Ably::Models::PresenceMessage}.
115
+ #
116
+ # @spec TP3d
117
+ #
118
+ # @return [String]
119
+ #
120
+ def connection_id
121
+ attributes.fetch(:connection_id) { protocol_message.connection_id if assigned_to_protocol_message? }
122
+ end
123
+
124
+ # Combines clientId and connectionId to ensure that multiple connected clients with an identical clientId are uniquely
125
+ # identifiable. A string function that returns the combined clientId and connectionId.
126
+ #
127
+ # @spec TP3h
128
+ #
129
+ # @return [String]
130
+ #
131
+ def member_key
132
+ "#{connection_id}:#{client_id}"
133
+ end
134
+
135
+ # The time the {Ably::Models::PresenceMessage} was received by Ably, as milliseconds since the Unix epoch.
136
+ #
137
+ # @spec TP3g
138
+ #
139
+ # @return [Integer]
140
+ #
141
+ def timestamp
142
+ if attributes[:timestamp]
143
+ as_time_from_epoch(attributes[:timestamp])
144
+ else
145
+ protocol_message.timestamp
146
+ end
147
+ end
148
+
149
+ # The type of {Ably::Models::PresenceMessage::ACTION} the PresenceMessage is for.
150
+ #
151
+ # @spec TP3b
152
+ #
153
+ # @return [Ably::Models::PresenceMessage::ACTION]
154
+ #
155
+ def action
156
+ ACTION(attributes[:action])
157
+ end
158
+
159
+ def attributes
160
+ @attributes
161
+ end
162
+
163
+ # Return a JSON ready object from the underlying #attributes using Ably naming conventions for keys
164
+ def as_json(*args)
165
+ attributes.dup.tap do |presence_message|
166
+ presence_message['action'] = action.to_i
167
+ end.as_json.reject { |key, val| val.nil? }
168
+ rescue KeyError
169
+ raise KeyError, ':action is missing or invalid, cannot generate a valid Hash for ProtocolMessage'
170
+ end
171
+
172
+ def to_json(*args)
173
+ as_json(*args).tap do |presence_message|
174
+ decode_binary_data_before_to_json presence_message
175
+ end.to_json
176
+ end
177
+
178
+ # The size is the sum over data and clientId in bytes (TO3l8a)
179
+ #
180
+ def size
181
+ %w(data client_id).map do |attr|
182
+ if (value = attributes[attr.to_sym]).is_a?(String)
183
+ value.bytesize
184
+ elsif value.nil?
185
+ 0
186
+ else
187
+ value.to_json.bytesize
188
+ end
189
+ end.sum
190
+ end
191
+
192
+ # Assign this presence message to a ProtocolMessage before delivery to the Ably system
193
+ # @api private
194
+ def assign_to_protocol_message(protocol_message)
195
+ @protocol_message = protocol_message
196
+ end
197
+
198
+ # True if this presence message is assigned to a ProtocolMessage for delivery to Ably, or received from Ably
199
+ # @return [Boolean]
200
+ # @api private
201
+ def assigned_to_protocol_message?
202
+ !!@protocol_message
203
+ end
204
+
205
+ # The optional ProtocolMessage this presence message is assigned to. If ProtocolMessage is nil, an error will be raised.
206
+ # @return [Ably::Models::ProtocolMessage]
207
+ # @api private
208
+ def protocol_message
209
+ raise RuntimeError, 'Presence Message is not yet published with a ProtocolMessage. ProtocolMessage is nil' if @protocol_message.nil?
210
+ @protocol_message
211
+ end
212
+
213
+ # Create a static shallow clone of this object with the optional attributes to overide existing values
214
+ # Shallow clones have no dependency on the originating ProtocolMessage as all field values are stored as opposed to calculated
215
+ # Clones are useful when the original PresenceMessage needs to be mutated, such as storing in a PresenceMap with action :present
216
+ def shallow_clone(new_attributes = {})
217
+ new_attributes = IdiomaticRubyWrapper(new_attributes.clone.freeze, stop_at: [:data])
218
+
219
+ self.class.new(attributes.to_hash.merge(
220
+ id: new_attributes[:id] || id,
221
+ connection_id: new_attributes[:connection_id] || connection_id,
222
+ timestamp: new_attributes[:timestamp] || as_since_epoch(timestamp)
223
+ ).merge(new_attributes.to_hash))
224
+ end
225
+
226
+ private
227
+ def raw_hash_object
228
+ @raw_hash_object
229
+ end
230
+
231
+ def protocol_message_index
232
+ protocol_message.presence.index(self)
233
+ end
234
+
235
+ def set_attributes_object(new_attributes)
236
+ @attributes = IdiomaticRubyWrapper(new_attributes.clone, stop_at: [:data])
237
+ end
238
+
239
+ def logger
240
+ return @logger if @logger
241
+ protocol_message.logger if protocol_message
242
+ end
243
+ end
244
+ end