ably-pubsub-core 0.0.1.pre → 2.0.0

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ably/auth.rb +5 -5
  3. data/lib/ably/exceptions.rb +5 -0
  4. data/lib/ably/internal.rb +115 -0
  5. data/lib/ably/logger.rb +3 -3
  6. data/lib/ably/models/channel_details.rb +1 -1
  7. data/lib/ably/models/channel_metrics.rb +1 -1
  8. data/lib/ably/models/channel_occupancy.rb +1 -1
  9. data/lib/ably/models/channel_state_change.rb +7 -7
  10. data/lib/ably/models/channel_status.rb +1 -1
  11. data/lib/ably/models/cipher_params.rb +1 -1
  12. data/lib/ably/models/connection_details.rb +2 -2
  13. data/lib/ably/models/connection_state_change.rb +6 -6
  14. data/lib/ably/models/http_paginated_response.rb +3 -3
  15. data/lib/ably/models/message.rb +69 -2
  16. data/lib/ably/models/message_operation.rb +53 -0
  17. data/lib/ably/models/paginated_result.rb +2 -2
  18. data/lib/ably/models/presence_message.rb +1 -1
  19. data/lib/ably/models/protocol_message.rb +14 -1
  20. data/lib/ably/models/publish_result.rb +32 -0
  21. data/lib/ably/models/stats.rb +41 -84
  22. data/lib/ably/models/update_delete_result.rb +32 -0
  23. data/lib/ably/modules/ably.rb +13 -12
  24. data/lib/ably/modules/channels_collection.rb +1 -1
  25. data/lib/ably/modules/http_helpers.rb +11 -11
  26. data/lib/ably/{rest → pubsub/http}/channel/push_channel.rb +3 -3
  27. data/lib/ably/pubsub/http/channel.rb +284 -0
  28. data/lib/ably/pubsub/http/channels.rb +49 -0
  29. data/lib/ably/pubsub/http/client.rb +738 -0
  30. data/lib/ably/pubsub/http/middleware/encoder.rb +51 -0
  31. data/lib/ably/pubsub/http/middleware/exceptions.rb +55 -0
  32. data/lib/ably/pubsub/http/middleware/external_exceptions.rb +26 -0
  33. data/lib/ably/pubsub/http/middleware/fail_if_unsupported_mime_type.rb +24 -0
  34. data/lib/ably/pubsub/http/middleware/logger.rb +60 -0
  35. data/lib/ably/pubsub/http/middleware/parse_json.rb +29 -0
  36. data/lib/ably/pubsub/http/middleware/parse_message_pack.rb +45 -0
  37. data/lib/ably/pubsub/http/presence.rb +117 -0
  38. data/lib/ably/{rest → pubsub/http}/push/admin.rb +5 -5
  39. data/lib/ably/{rest → pubsub/http}/push/channel_subscriptions.rb +1 -1
  40. data/lib/ably/{rest → pubsub/http}/push/device_registrations.rb +1 -1
  41. data/lib/ably/pubsub/http/push.rb +27 -0
  42. data/lib/ably/pubsub/http.rb +38 -0
  43. data/lib/ably/pubsub/realtime/auth.rb +285 -0
  44. data/lib/ably/{realtime → pubsub/realtime}/channel/channel_manager.rb +2 -2
  45. data/lib/ably/{realtime → pubsub/realtime}/channel/channel_properties.rb +5 -5
  46. data/lib/ably/{realtime → pubsub/realtime}/channel/channel_state_machine.rb +2 -2
  47. data/lib/ably/{realtime → pubsub/realtime}/channel/publisher.rb +9 -4
  48. data/lib/ably/{realtime → pubsub/realtime}/channel/push_channel.rb +2 -2
  49. data/lib/ably/pubsub/realtime/channel.rb +545 -0
  50. data/lib/ably/pubsub/realtime/channels.rb +92 -0
  51. data/lib/ably/{realtime → pubsub/realtime}/client/incoming_message_dispatcher.rb +43 -8
  52. data/lib/ably/{realtime → pubsub/realtime}/client/outgoing_message_dispatcher.rb +2 -2
  53. data/lib/ably/pubsub/realtime/client.rb +372 -0
  54. data/lib/ably/{realtime → pubsub/realtime}/connection/connection_manager.rb +6 -6
  55. data/lib/ably/{realtime → pubsub/realtime}/connection/connection_state_machine.rb +2 -2
  56. data/lib/ably/{realtime → pubsub/realtime}/connection/websocket_transport.rb +1 -1
  57. data/lib/ably/pubsub/realtime/connection.rb +724 -0
  58. data/lib/ably/{realtime → pubsub/realtime}/models/nil_channel.rb +3 -3
  59. data/lib/ably/{realtime → pubsub/realtime}/presence/members_map.rb +2 -2
  60. data/lib/ably/{realtime → pubsub/realtime}/presence/presence_manager.rb +3 -3
  61. data/lib/ably/{realtime → pubsub/realtime}/presence/presence_state_machine.rb +2 -2
  62. data/lib/ably/{realtime → pubsub/realtime}/presence.rb +23 -23
  63. data/lib/ably/{realtime → pubsub/realtime}/push/admin.rb +8 -8
  64. data/lib/ably/{realtime → pubsub/realtime}/push/channel_subscriptions.rb +6 -6
  65. data/lib/ably/{realtime → pubsub/realtime}/push/device_registrations.rb +6 -6
  66. data/lib/ably/pubsub/realtime/push.rb +27 -0
  67. data/lib/ably/pubsub/realtime/recovery_key_context.rb +38 -0
  68. data/lib/ably/pubsub/realtime.rb +58 -0
  69. data/lib/ably/pubsub.rb +11 -0
  70. data/lib/ably/version.rb +2 -2
  71. data/lib/ably.rb +6 -2
  72. metadata +50 -46
  73. data/lib/ably/models/stats_types.rb +0 -107
  74. data/lib/ably/realtime/auth.rb +0 -283
  75. data/lib/ably/realtime/channel.rb +0 -440
  76. data/lib/ably/realtime/channels.rb +0 -71
  77. data/lib/ably/realtime/client.rb +0 -363
  78. data/lib/ably/realtime/connection.rb +0 -738
  79. data/lib/ably/realtime/push.rb +0 -25
  80. data/lib/ably/realtime/recovery_key_context.rb +0 -36
  81. data/lib/ably/realtime.rb +0 -60
  82. data/lib/ably/rest/channel.rb +0 -194
  83. data/lib/ably/rest/channels.rb +0 -47
  84. data/lib/ably/rest/client.rb +0 -727
  85. data/lib/ably/rest/middleware/encoder.rb +0 -49
  86. data/lib/ably/rest/middleware/exceptions.rb +0 -53
  87. data/lib/ably/rest/middleware/external_exceptions.rb +0 -24
  88. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +0 -22
  89. data/lib/ably/rest/middleware/logger.rb +0 -58
  90. data/lib/ably/rest/middleware/parse_json.rb +0 -27
  91. data/lib/ably/rest/middleware/parse_message_pack.rb +0 -43
  92. data/lib/ably/rest/presence.rb +0 -113
  93. data/lib/ably/rest/push.rb +0 -25
  94. data/lib/ably/rest.rb +0 -40
@@ -1,5 +1,3 @@
1
- require 'ably/models/stats_types'
2
-
3
1
  module Ably::Models
4
2
  # Convert stat argument to a {Stats} object
5
3
  #
@@ -17,16 +15,15 @@ module Ably::Models
17
15
 
18
16
  # A class representing an individual statistic for a specified {#interval_id}
19
17
  #
18
+ # @spec TS12
19
+ #
20
20
  class Stats
21
21
  include Ably::Modules::ModelCommon
22
22
  extend Ably::Modules::Enum
23
23
 
24
24
  # Describes the interval unit over which statistics are gathered.
25
25
  #
26
- # MINUTE Interval unit over which statistics are gathered as minutes.
27
- # HOUR Interval unit over which statistics are gathered as hours.
28
- # DAY Interval unit over which statistics are gathered as days.
29
- # MONTH Interval unit over which statistics are gathered as months.
26
+ # @spec TS12c
30
27
  #
31
28
  GRANULARITY = ruby_enum('GRANULARITY',
32
29
  :minute,
@@ -103,123 +100,83 @@ module Ably::Models
103
100
 
104
101
  # {Stats} initializer
105
102
  #
106
- # @param hash_object [Hash] object with the underlying stat details
103
+ # @param hash_object [Hash] object with the underlying stat details
107
104
  #
108
105
  def initialize(hash_object)
109
- @raw_hash_object = hash_object
106
+ @raw_hash_object = hash_object
110
107
  set_attributes_object hash_object
111
108
  end
112
109
 
113
- # A {Ably::Models::Stats::MessageTypes} object containing the aggregate count of all message stats.
110
+ # The interval ID for this stats object.
114
111
  #
115
- # @spec TS12e
116
- #
117
- # @return [Stats::MessageTypes]
118
- #
119
- def all
120
- @all ||= Stats::MessageTypes.new(attributes[:all])
121
- end
122
-
123
- # A {Ably::Models::Stats::MessageTraffic} object containing the aggregate count of inbound message stats.
124
- #
125
- # @spec TS12f
126
- #
127
- # @return [Ably::Models::Stats::MessageTraffic]
128
- #
129
- def inbound
130
- @inbound ||= Stats::MessageTraffic.new(attributes[:inbound])
131
- end
132
-
133
- # A {Ably::Models::Stats::MessageTraffic} object containing the aggregate count of outbound message stats.
134
- #
135
- # @spec TS12g
136
- #
137
- # @return [Ably::Models::Stats::MessageTraffic]
138
- #
139
- def outbound
140
- @outbound ||= Stats::MessageTraffic.new(attributes[:outbound])
141
- end
142
-
143
- # A {Ably::Models::Stats::MessageTraffic} object containing the aggregate count of persisted message stats.
144
- #
145
- # @spec TS12h
112
+ # @spec TS12a
146
113
  #
147
- # @return [Ably::Models::Stats::MessageTraffic]
114
+ # @return [String]
148
115
  #
149
- def persisted
150
- @persisted ||= Stats::MessageTypes.new(attributes[:persisted])
116
+ def interval_id
117
+ attributes.fetch(:interval_id)
151
118
  end
152
119
 
153
- # A {Ably::Models::Stats::ConnectionTypes} object containing a breakdown of connection related stats, such as min, mean and peak connections.
120
+ # Represents the intervalId as a time object.
154
121
  #
155
- # @spec TS12i
122
+ # @spec TS12p
156
123
  #
157
- # @return [Ably::Models::Stats::ConnectionTypes]
124
+ # @return [Time]
158
125
  #
159
- def connections
160
- @connections ||= Stats::ConnectionTypes.new(attributes[:connections])
126
+ def interval_time
127
+ self.class.from_interval_id(interval_id)
161
128
  end
162
129
 
163
- # A {Ably::Models::Stats::ResourceCount} object containing a breakdown of connection related stats, such as min, mean and peak connections.
130
+ # The unit of the interval, as provided by the API response.
164
131
  #
165
- # @spec TS12j
132
+ # @spec TS12c
166
133
  #
167
- # @return [Ably::Models::Stats::ResourceCount]
134
+ # @return [String]
168
135
  #
169
- def channels
170
- @channels ||= Stats::ResourceCount.new(attributes[:channels])
136
+ def unit
137
+ attributes[:unit]
171
138
  end
172
139
 
173
- # A {Ably::Models::Stats::RequestCount} object containing a breakdown of API Requests.
140
+ # For entries that are still in progress, the last sub-interval included.
174
141
  #
175
- # @spec TS12k
142
+ # @spec TS12q
176
143
  #
177
- # @return [Ably::Models::Stats::RequestCount]
144
+ # @return [String, nil]
178
145
  #
179
- def api_requests
180
- @api_requests ||= Stats::RequestCount.new(attributes[:api_requests])
146
+ def in_progress
147
+ attributes[:in_progress]
181
148
  end
182
149
 
183
- # A {Ably::Models::Stats::RequestCount} object containing a breakdown of Ably Token requests.
150
+ # A flat dictionary of statistics entries with dot-separated keys.
184
151
  #
185
- # @spec TS12l
152
+ # @spec TS12r
186
153
  #
187
- # @return [Ably::Models::Stats::RequestCount]
154
+ # @return [Hash]
188
155
  #
189
- def token_requests
190
- @token_requests ||= Stats::RequestCount.new(attributes[:token_requests])
156
+ def entries
157
+ raw_entries = raw_hash_object[:entries] || raw_hash_object['entries']
158
+ return {} unless raw_entries
159
+ raw_entries.is_a?(Hash) ? raw_entries : {}
191
160
  end
192
161
 
193
- # The UTC time at which the time period covered begins. If unit is set to minute this will be in
194
- # the format YYYY-mm-dd:HH:MM, if hour it will be YYYY-mm-dd:HH, if day it will be YYYY-mm-dd:00
195
- # and if month it will be YYYY-mm-01:00.
162
+ # The JSON schema URI for this stats object.
196
163
  #
197
- # @spec TS12a
164
+ # @spec TS12s
198
165
  #
199
166
  # @return [String]
200
167
  #
201
- def interval_id
202
- attributes.fetch(:interval_id)
168
+ def schema
169
+ attributes[:schema]
203
170
  end
204
171
 
205
- # Represents the intervalId as a time object.
172
+ # The Ably application ID this stats object relates to.
206
173
  #
207
- # @spec TS12b
174
+ # @spec TS12t
208
175
  #
209
- # @return [Time]
210
- #
211
- def interval_time
212
- self.class.from_interval_id(interval_id)
213
- end
214
-
215
- # The length of the interval the stats span. Values will be a [StatsIntervalGranularity]{@link StatsIntervalGranularity}.
216
- #
217
- # @spec TS12c
218
- #
219
- # @return [GRANULARITY] The granularity of the interval for the stat such as :day, :hour, :minute, see {GRANULARITY}
176
+ # @return [String]
220
177
  #
221
- def interval_granularity
222
- self.class.granularity_from_interval_id(interval_id)
178
+ def app_id
179
+ attributes[:app_id]
223
180
  end
224
181
 
225
182
  def attributes
@@ -0,0 +1,32 @@
1
+ module Ably::Models
2
+ # Contains the result of an update or delete message operation.
3
+ #
4
+ # @spec UDR
5
+ #
6
+ class UpdateDeleteResult
7
+ include Ably::Modules::ModelCommon
8
+
9
+ # @param attributes [Hash]
10
+ # @option attributes [String, nil] :version_serial The new version serial string of the updated or deleted message.
11
+ # Will be nil if the message was superseded by a subsequent update before it could be published.
12
+ #
13
+ def initialize(attributes = {})
14
+ @hash_object = IdiomaticRubyWrapper(attributes || {})
15
+ @hash_object.freeze
16
+ end
17
+
18
+ # The version serial of the updated or deleted message, or nil if superseded.
19
+ #
20
+ # @spec UDR2a
21
+ #
22
+ # @return [String, nil]
23
+ #
24
+ def version_serial
25
+ attributes[:version_serial]
26
+ end
27
+
28
+ def attributes
29
+ @hash_object
30
+ end
31
+ end
32
+ end
@@ -1,23 +1,24 @@
1
- # Ably is the base namespace for the Ably {Ably::Realtime Realtime} & {Ably::Rest Rest} client libraries.
1
+ # Ably is the base namespace for the Ably {Ably::PubSub::Realtime Realtime} & {Ably::PubSub::Http Rest} client libraries.
2
2
  #
3
3
  # Please refer to the {file:README.md Readme} on getting started.
4
4
  #
5
5
  # @see file:README.md README
6
6
  module Ably
7
- # Fallback hosts to use when a connection to rest/realtime.ably.io is not possible due to
8
- # network failures either at the client, between the client and Ably, within an Ably data center, or at the IO domain registrar
7
+ # Default routing policy used when the +:endpoint+ client option is not provided
8
+ # @spec REC1a
9
+ ENDPOINT = 'main'.freeze
10
+
11
+ PROD_ROOT_DOMAIN = 'ably.net'.freeze
12
+ PROD_FALLBACK_DOMAIN = 'ably-realtime.com'.freeze
13
+
14
+ # Fallback hosts are used when a connection to the primary domain is not possible due to
15
+ # network failures either at the client, between the client and Ably, within an Ably data center, or at the domain registrar
9
16
  # see https://ably.com/docs/client-lib-development-guide/features/#RSC15a
10
- #
11
- FALLBACK_DOMAIN = 'ably-realtime.com'.freeze
12
17
  FALLBACK_IDS = %w(a b c d e).freeze
13
18
 
14
- # Default production fallbacks a.ably-realtime.com ... e.ably-realtime.com
15
- FALLBACK_HOSTS = FALLBACK_IDS.map { |host| "#{host}.#{FALLBACK_DOMAIN}".freeze }.freeze
16
-
17
- # Custom environment default fallbacks {ENV}-a-fallback.ably-realtime.com ... {ENV}-a-fallback.ably-realtime.com
18
- CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES = FALLBACK_IDS.map do |host|
19
- "-#{host}-fallback.#{FALLBACK_DOMAIN}".freeze
20
- end.freeze
19
+ # Default production fallbacks main.a.fallback.ably-realtime.com ... main.e.fallback.ably-realtime.com
20
+ # @spec REC2c1
21
+ FALLBACK_HOSTS = FALLBACK_IDS.map { |id| "#{ENDPOINT}.#{id}.fallback.#{PROD_FALLBACK_DOMAIN}".freeze }.freeze
21
22
 
22
23
  INTERNET_CHECK = {
23
24
  url: '//internet-up.ably-realtime.com/is-the-internet-up.txt',
@@ -1,5 +1,5 @@
1
1
  module Ably::Modules
2
- # ChannelsCollection module provides common functionality to the Rest and Realtime Channels objects
2
+ # ChannelsCollection module provides common functionality to the HTTP and Realtime Channels objects
3
3
  # such as #get, #[], #fetch, and #release
4
4
  module ChannelsCollection
5
5
  include Enumerable
@@ -2,12 +2,12 @@ require 'base64'
2
2
 
3
3
  require 'ably/version'
4
4
 
5
- require 'ably/rest/middleware/encoder'
6
- require 'ably/rest/middleware/external_exceptions'
7
- require 'ably/rest/middleware/fail_if_unsupported_mime_type'
8
- require 'ably/rest/middleware/logger'
9
- require 'ably/rest/middleware/parse_json'
10
- require 'ably/rest/middleware/parse_message_pack'
5
+ require 'ably/pubsub/http/middleware/encoder'
6
+ require 'ably/pubsub/http/middleware/external_exceptions'
7
+ require 'ably/pubsub/http/middleware/fail_if_unsupported_mime_type'
8
+ require 'ably/pubsub/http/middleware/logger'
9
+ require 'ably/pubsub/http/middleware/parse_json'
10
+ require 'ably/pubsub/http/middleware/parse_message_pack'
11
11
 
12
12
  module Ably::Modules
13
13
  # HttpHelpers provides common private methods to classes to simplify HTTP interactions with Ably
@@ -23,19 +23,19 @@ module Ably::Modules
23
23
 
24
24
  def setup_outgoing_middleware(builder)
25
25
  # Convert request params to "www-form-urlencoded"
26
- builder.use Ably::Rest::Middleware::Encoder
26
+ builder.use Ably::PubSub::Http::Middleware::Encoder
27
27
  end
28
28
 
29
29
  def setup_incoming_middleware(builder, logger, options = {})
30
- builder.use Ably::Rest::Middleware::Logger, logger
30
+ builder.use Ably::PubSub::Http::Middleware::Logger, logger
31
31
 
32
32
  # Parse JSON / MsgPack response bodies. ParseJson must be first (default) parsing middleware
33
33
  if options[:fail_if_unsupported_mime_type] == true
34
- builder.use Ably::Rest::Middleware::FailIfUnsupportedMimeType
34
+ builder.use Ably::PubSub::Http::Middleware::FailIfUnsupportedMimeType
35
35
  end
36
36
 
37
- builder.use Ably::Rest::Middleware::ParseJson
38
- builder.use Ably::Rest::Middleware::ParseMessagePack
37
+ builder.use Ably::PubSub::Http::Middleware::ParseJson
38
+ builder.use Ably::PubSub::Http::Middleware::ParseMessagePack
39
39
  end
40
40
  end
41
41
  end
@@ -1,13 +1,13 @@
1
- module Ably::Rest
1
+ module Ably::PubSub::Http
2
2
  class Channel
3
3
  # A push channel used for push notifications
4
- # Each PushChannel maps to exactly one Rest Channel
4
+ # Each PushChannel maps to exactly one HTTP Channel
5
5
  #
6
6
  class PushChannel
7
7
  attr_reader :channel
8
8
 
9
9
  def initialize(channel)
10
- raise ArgumentError, "Unsupported channel type '#{channel.class}'" unless channel.kind_of?(Ably::Rest::Channel)
10
+ raise ArgumentError, "Unsupported channel type '#{channel.class}'" unless channel.kind_of?(Ably::PubSub::Http::Channel)
11
11
  @channel = channel
12
12
  end
13
13
 
@@ -0,0 +1,284 @@
1
+ module Ably
2
+ module PubSub
3
+ module Http
4
+ # Enables messages to be published and historic messages to be retrieved for a channel.
5
+ #
6
+ class Channel
7
+ include Ably::Modules::Conversions
8
+
9
+ # Ably client associated with this channel
10
+ # @return [Ably::PubSub::Realtime::Client]
11
+ # @api private
12
+ attr_reader :client
13
+
14
+ # The channel name.
15
+ # @return [String]
16
+ attr_reader :name
17
+
18
+ attr_reader :options
19
+
20
+ # A {Ably::PubSub::Http::Channel::PushChannel} object
21
+ # @spec RSH4
22
+ # @return [Ably::PubSub::Http::Channel::PushChannel]
23
+ # @api private
24
+ attr_reader :push
25
+
26
+ IDEMPOTENT_LIBRARY_GENERATED_ID_LENGTH = 9 # See spec RSL1k1
27
+
28
+ # Initialize a new Channel object
29
+ #
30
+ # @param client [Ably::PubSub::Http::Client]
31
+ # @param name [String] The name of the channel
32
+ # @param channel_options [Hash, Ably::Models::ChannelOptions] A hash of options or a {Ably::Models::ChannelOptions}
33
+ #
34
+ def initialize(client, name, channel_options = {})
35
+ name = (ensure_utf_8 :name, name)
36
+
37
+ @options = Ably::Models::ChannelOptions(channel_options)
38
+ @client = client
39
+ @name = name
40
+ @push = PushChannel.new(self)
41
+ end
42
+
43
+ # Publishes a message to the channel. A callback may optionally be passed in to this call to be notified of success or failure of the operation.
44
+ #
45
+ # @spec RSL1
46
+ #
47
+ # @param name [String, Array<Ably::Models::Message|Hash>, Ably::Models::Message, nil] The event name of the message to publish, or an Array of [Ably::Model::Message] objects or [Hash] objects with +:name+ and +:data+ pairs, or a single Ably::Model::Message object
48
+ # @param data [String, Array, Hash, nil] The message payload unless an Array of [Ably::Model::Message] objects passed in the first argument, in which case an optional hash of query parameters
49
+ # @param attributes [Hash, nil] Optional additional message attributes such as :extras, :id, :client_id or :connection_id, applied when name attribute is nil or a string (Deprecated, will be removed in 2.0 in favour of constructing a Message object)
50
+ # @return [Ably::Models::PublishResult] A {Ably::Models::PublishResult} containing the serials of the published messages.
51
+ #
52
+ # @example
53
+ # # Publish a single message with (name, data) form
54
+ # channel.publish 'click', { x: 1, y: 2 }
55
+ #
56
+ # # Publish a single message with single Hash form
57
+ # message = { name: 'click', data: { x: 1, y: 2 } }
58
+ # channel.publish message
59
+ #
60
+ # # Publish an array of message Hashes form
61
+ # messages = [
62
+ # { name: 'click', data: { x: 1, y: 2 } },
63
+ # { name: 'click', data: { x: 2, y: 3 } }
64
+ # ]
65
+ # channel.publish messages
66
+ #
67
+ # # Publish an array of Ably::Models::Message objects form
68
+ # messages = [
69
+ # Ably::Models::Message(name: 'click', data: { x: 1, y: 2 })
70
+ # Ably::Models::Message(name: 'click', data: { x: 2, y: 3 })
71
+ # ]
72
+ # channel.publish messages
73
+ #
74
+ # # Publish a single Ably::Models::Message object form
75
+ # message = Ably::Models::Message(name: 'click', data: { x: 1, y: 2 })
76
+ # channel.publish message
77
+ #
78
+ def publish(name, data = nil, attributes = {})
79
+ qs_params = nil
80
+ qs_params = data if name.kind_of?(Enumerable) || name.kind_of?(Ably::Models::Message)
81
+
82
+ messages = build_messages(name, data, attributes) # (RSL1a, RSL1b)
83
+
84
+ if messages.sum(&:size) > (max_message_size = client.max_message_size || Ably::PubSub::Http::Client::MAX_MESSAGE_SIZE)
85
+ raise Ably::Exceptions::MaxMessageSizeExceeded.new("Maximum message size exceeded #{max_message_size} bytes.")
86
+ end
87
+
88
+ payload = messages.map do |message|
89
+ Ably::Models::Message(message.dup).tap do |msg|
90
+ msg.encode client.encoders, options
91
+
92
+ next if msg.client_id.nil?
93
+ if msg.client_id == '*'
94
+ raise Ably::Exceptions::IncompatibleClientId.new('Wildcard client_id is reserved and cannot be used when publishing messages')
95
+ end
96
+ unless client.auth.can_assume_client_id?(msg.client_id)
97
+ raise Ably::Exceptions::IncompatibleClientId.new("Cannot publish with client_id '#{msg.client_id}' as it is incompatible with the current configured client_id '#{client.client_id}'")
98
+ end
99
+ end.as_json
100
+ end.tap do |payload|
101
+ if client.idempotent_rest_publishing
102
+ # We cannot mutate for idempotent publishing if one or more messages already has an ID
103
+ if payload.all? { |msg| !msg['id'] }
104
+ # Mutate the JSON to support idempotent publishing where a Message.id does not exist
105
+ idempotent_publish_id = SecureRandom.base64(IDEMPOTENT_LIBRARY_GENERATED_ID_LENGTH)
106
+ payload.each_with_index do |msg, idx|
107
+ msg['id'] = "#{idempotent_publish_id}:#{idx}"
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ options = qs_params ? { qs_params: qs_params } : {}
114
+ response = client.post("#{base_path}/publish", payload.length == 1 ? payload.first : payload, options)
115
+
116
+ parse_publish_result(response)
117
+ end
118
+
119
+ # Updates a previously published message on the channel.
120
+ #
121
+ # @spec RSL15
122
+ #
123
+ # @param message [Ably::Models::Message, Hash] A Message object or Hash containing a populated :serial field
124
+ # and the fields to update.
125
+ # @param operation [Hash, Ably::Models::MessageOperation, nil] Optional operation metadata containing
126
+ # :description and/or :metadata fields.
127
+ # @param params [Hash, nil] Optional parameters sent as part of the query string.
128
+ #
129
+ # @return [Ably::Models::UpdateDeleteResult] The result containing the version_serial.
130
+ #
131
+ def update_message(message, operation = nil, params = {})
132
+ send_message_action(message, Ably::Models::Message::ACTION.MessageUpdate, operation, params)
133
+ end
134
+
135
+ # Deletes a previously published message on the channel.
136
+ #
137
+ # @spec RSL15
138
+ #
139
+ # @param message [Ably::Models::Message, Hash] A Message object or Hash containing a populated :serial field.
140
+ # @param operation [Hash, Ably::Models::MessageOperation, nil] Optional operation metadata containing
141
+ # :description and/or :metadata fields.
142
+ # @param params [Hash, nil] Optional parameters sent as part of the query string.
143
+ #
144
+ # @return [Ably::Models::UpdateDeleteResult] The result containing the version_serial.
145
+ #
146
+ def delete_message(message, operation = nil, params = {})
147
+ send_message_action(message, Ably::Models::Message::ACTION.MessageDelete, operation, params)
148
+ end
149
+
150
+ # Appends data to a previously published message on the channel.
151
+ #
152
+ # @spec RSL15
153
+ #
154
+ # @param message [Ably::Models::Message, Hash] A Message object or Hash containing a populated :serial field
155
+ # and the data to append.
156
+ # @param operation [Hash, Ably::Models::MessageOperation, nil] Optional operation metadata containing
157
+ # :description and/or :metadata fields.
158
+ # @param params [Hash, nil] Optional parameters sent as part of the query string.
159
+ #
160
+ # @return [Ably::Models::UpdateDeleteResult] The result containing the version_serial.
161
+ #
162
+ def append_message(message, operation = nil, params = {})
163
+ send_message_action(message, Ably::Models::Message::ACTION.MessageAppend, operation, params)
164
+ end
165
+
166
+ # Retrieves a {Ably::Models::PaginatedResult} object, containing an array of historical {Ably::Models::Message}
167
+ # objects for the channel. If the channel is configured to persist messages, then messages can be retrieved from
168
+ # history for up to 72 hours in the past. If not, messages can only be retrieved from history for up to two minutes in the past.
169
+ #
170
+ # @spec RSL2a
171
+ #
172
+ # @param [Hash] options the options for the message history request
173
+ # @option options [Integer,Time] :start The time from which messages are retrieved, specified as milliseconds since the Unix epoch. RSL2b1
174
+ # @option options [Integer,Time] :end The time until messages are retrieved, specified as milliseconds since the Unix epoch. RSL2b1
175
+ # @option options [Symbol] :direction The order for which messages are returned in. Valid values are backwards which orders messages from most recent to oldest, or forwards which orders messages from oldest to most recent. The default is backwards. RSL2b2
176
+ # @option options [Integer] :limit An upper limit on the number of messages returned. The default is 100, and the maximum is 1000. RSL2b3
177
+ #
178
+ # @return [Ably::Models::PaginatedResult<Ably::Models::Message>] A {Ably::Models::PaginatedResult} object containing an array of {Ably::Models::Message} objects.
179
+ #
180
+ def history(options = {})
181
+ url = "#{base_path}/messages"
182
+ options = {
183
+ :direction => :backwards,
184
+ :limit => 100
185
+ }.merge(options)
186
+
187
+ [:start, :end].each { |option| options[option] = as_since_epoch(options[option]) if options.has_key?(option) }
188
+ raise ArgumentError, ":end must be equal to or after :start" if options[:start] && options[:end] && (options[:start] > options[:end])
189
+
190
+ paginated_options = {
191
+ coerce_into: 'Ably::Models::Message',
192
+ async_blocking_operations: options.delete(:async_blocking_operations),
193
+ }
194
+
195
+ response = client.get(url, options)
196
+
197
+ Ably::Models::PaginatedResult.new(response, url, client, paginated_options) do |message|
198
+ message.tap do |msg|
199
+ decode_message msg
200
+ end
201
+ end
202
+ end
203
+
204
+ # A {Ably::PubSub::Http::Presence} object.
205
+ # @spec RSL3
206
+ # @return [Ably::PubSub::Http::Presence]
207
+ def presence
208
+ @presence ||= Presence.new(client, self)
209
+ end
210
+
211
+ # Sets the {Ably::Models::ChannelOptions} for the channel.
212
+ # @spec RSL7
213
+ # @param channel_options [Hash, Ably::Models::ChannelOptions] A hash of options or a {Ably::Models::ChannelOptions}
214
+ # @return [Ably::Models::ChannelOptions]
215
+ def set_options(channel_options)
216
+ @options = Ably::Models::ChannelOptions(channel_options)
217
+ end
218
+ alias options= set_options
219
+
220
+ # Retrieves a {Ably::Models::ChannelDetails} object for the channel, which includes status and occupancy metrics.
221
+ # @spec RSL8
222
+ # @return [Ably::Models::ChannelDetails] A {Ably::Models::ChannelDetails} object.
223
+ def status
224
+ Ably::Models::ChannelDetails.new(client.get(base_path).body)
225
+ end
226
+
227
+ private
228
+
229
+ def send_message_action(message, action_enum, operation = nil, params = {})
230
+ message = Ably::Models::Message(message)
231
+
232
+ raise Ably::Exceptions::InvalidRequest.new(
233
+ 'Message serial is required. Ensure the message has a serial field.'
234
+ ) unless message.serial
235
+
236
+ update_attrs = message.as_json
237
+ update_attrs['action'] = action_enum.to_i
238
+
239
+ if operation
240
+ op_hash = operation.respond_to?(:as_json) ? operation.as_json : operation
241
+ update_attrs['version'] = op_hash
242
+ end
243
+
244
+ updated_message = Ably::Models::Message.new(update_attrs)
245
+ updated_message.encode client.encoders, options
246
+
247
+ payload = updated_message.as_json
248
+ serial = message.serial
249
+
250
+ request_options = params && !params.empty? ? { qs_params: params } : {}
251
+ response = client.patch(
252
+ "#{base_path}/messages/#{URI.encode_www_form_component(serial)}",
253
+ payload,
254
+ request_options
255
+ )
256
+
257
+ Ably::Models::UpdateDeleteResult.new(response.body || {})
258
+ end
259
+
260
+ def parse_publish_result(response)
261
+ body = response.body
262
+ if body.is_a?(Hash)
263
+ serials = body['serials'] || body[:serials] || []
264
+ Ably::Models::PublishResult.new(serials: serials)
265
+ else
266
+ Ably::Models::PublishResult.new(serials: [])
267
+ end
268
+ end
269
+
270
+ def base_path
271
+ "/channels/#{URI.encode_www_form_component(name)}"
272
+ end
273
+
274
+ def decode_message(message)
275
+ message.decode client.encoders, options
276
+ rescue Ably::Exceptions::CipherError, Ably::Exceptions::EncoderError => e
277
+ client.logger.error { "Decoding Error on channel '#{name}', message event name '#{message.name}'. #{e.class.name}: #{e.message}" }
278
+ end
279
+ end
280
+ end
281
+ end
282
+ end
283
+
284
+ require 'ably/pubsub/http/channel/push_channel'
@@ -0,0 +1,49 @@
1
+ module Ably
2
+ module PubSub
3
+ module Http
4
+ class Channels
5
+ include Ably::Modules::ChannelsCollection
6
+
7
+ # @return [Ably::PubSub::Http::Channels]
8
+ def initialize(client)
9
+ super client, Ably::PubSub::Http::Channel
10
+ end
11
+
12
+ # Return a {Ably::PubSub::Http::Channel} for the given name
13
+ #
14
+ # @param name [String] The name of the channel
15
+ # @param channel_options [Hash] Channel options, currently reserved for Encryption options
16
+ #
17
+ # @return [Ably::PubSub::Http::Channel]
18
+ #
19
+ def get(*args)
20
+ super
21
+ end
22
+
23
+ # Return a {Ably::PubSub::Http::Channel} for the given name if it exists, else the block will be called.
24
+ # This method is intentionally similar to {http://ruby-doc.org/core-2.1.3/Hash.html#method-i-fetch Hash#fetch} providing a simple way to check if a channel exists or not without creating one
25
+ #
26
+ # @param name [String] The name of the channel
27
+ # @yield [options] (optional) if a missing_block is passed to this method and no channel exists matching the name, this block is called
28
+ # @yieldparam [String] name of the missing channel
29
+ #
30
+ # @return [Ably::PubSub::Http::Channel]
31
+ #
32
+ def fetch(*args)
33
+ super
34
+ end
35
+
36
+ # Destroy the {Ably::PubSub::Http::Channel} and releases the associated resources.
37
+ #
38
+ # Releasing a {Ably::PubSub::Http::Channel} is not typically necessary as a channel consumes no resources other than the memory footprint of the
39
+ # {Ably::PubSub::Http::Channel} object. Explicitly release channels to free up resources if required
40
+ #
41
+ # @return [void]
42
+ #
43
+ def release(*args)
44
+ super
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end