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,272 @@
1
+ module Ably::Realtime
2
+ class Channel
3
+ # ChannelManager is responsible for all actions relating to channel state: attaching, detaching or failure
4
+ # Channel state changes are performed by this class and executed from {ChannelStateMachine}
5
+ #
6
+ # This is a private class and should never be used directly by developers as the API is likely to change in future.
7
+ #
8
+ # @api private
9
+ class ChannelManager
10
+ extend Forwardable
11
+
12
+ def initialize(channel, connection)
13
+ @channel = channel
14
+ @connection = connection
15
+ end
16
+
17
+ # Commence attachment
18
+ def attach
19
+ if can_transition_to?(:attached)
20
+ connect_if_connection_initialized
21
+ send_attach_protocol_message if connection.state?(:connected) # RTL4i
22
+ end
23
+ end
24
+
25
+ # Commence attachment
26
+ def detach(error, previous_state)
27
+ if connection.closed? || connection.connecting? || connection.suspended?
28
+ channel.transition_state_machine :detached, reason: error
29
+ elsif can_transition_to?(:detached)
30
+ send_detach_protocol_message previous_state
31
+ end
32
+ end
33
+
34
+ # Channel is attached, notify presence if sync is expected
35
+ def attached(attached_protocol_message)
36
+ # If no attached ProtocolMessage then this attached request was triggered by the client
37
+ # library, such as returning to attached when detach has failed
38
+ if attached_protocol_message
39
+ channel.presence.manager.on_attach attached_protocol_message.has_presence_flag?
40
+ channel.properties.set_attach_serial(attached_protocol_message.channel_serial)
41
+ channel.options.set_modes_from_flags(attached_protocol_message.flags)
42
+ channel.options.set_params(attached_protocol_message.params)
43
+ end
44
+ end
45
+
46
+ # An error has occurred on the channel
47
+ def log_channel_error(error)
48
+ logger.error { "ChannelManager: Channel '#{channel.name}' error: #{error}" }
49
+ end
50
+
51
+ # Request channel to be reattached by sending an attach protocol message
52
+ # @param [Ably::Models::ErrorInfo] reason
53
+ def request_reattach(reason = nil)
54
+ channel.set_channel_error_reason(reason) if reason
55
+ channel.transition_state_machine! :attaching, reason: reason unless channel.attaching?
56
+ send_attach_protocol_message
57
+ logger.debug { "Explicit channel reattach request sent to Ably due to #{reason}" }
58
+ end
59
+
60
+ def duplicate_attached_received(protocol_message)
61
+ logger.debug { "Server initiated ATTACHED message received for channel '#{channel.name}' with state #{channel.state}" }
62
+ if protocol_message.error
63
+ channel.set_channel_error_reason protocol_message.error
64
+ log_channel_error protocol_message.error
65
+ end
66
+
67
+ channel.properties.set_attach_serial(protocol_message.channel_serial)
68
+ channel.options.set_modes_from_flags(protocol_message.flags)
69
+
70
+ unless protocol_message.has_channel_resumed_flag?
71
+ channel.emit :update, Ably::Models::ChannelStateChange.new(
72
+ current: channel.state,
73
+ previous: channel.state,
74
+ event: Ably::Realtime::Channel::EVENT(:update),
75
+ reason: protocol_message.error,
76
+ resumed: false,
77
+ )
78
+ channel.presence.manager.on_attach protocol_message.has_presence_flag?
79
+ end
80
+ end
81
+
82
+ # Handle DETACED messages, see #RTL13 for server-initated detaches
83
+ def detached_received(reason)
84
+ case channel.state.to_sym
85
+ when :detaching
86
+ channel.transition_state_machine :detached, reason: reason
87
+ when :attached, :suspended
88
+ channel.transition_state_machine :attaching, reason: reason
89
+ else
90
+ logger.debug { "ChannelManager: DETACHED ProtocolMessage received, but no action to take as not DETACHING, ATTACHED OR SUSPENDED" }
91
+ end
92
+ end
93
+
94
+ # When continuity on the connection is interrupted or channel becomes suspended (implying loss of continuity)
95
+ # then all messages published but awaiting an ACK from Ably should be failed with a NACK
96
+ # @param [Hash] options
97
+ # @option options [Boolean] :immediately
98
+ def fail_messages_awaiting_ack(error, options = {})
99
+ immediately = options[:immediately] || false
100
+
101
+ fail_proc = lambda do
102
+ error = Ably::Exceptions::MessageDeliveryFailed.new("Continuity of connection was lost so published messages awaiting ACK have failed") unless error
103
+ fail_messages_in_queue connection.__pending_message_ack_queue__, error
104
+ end
105
+
106
+ # Allow a short time for other queued operations to complete before failing all messages
107
+ if immediately
108
+ fail_proc.call
109
+ else
110
+ EventMachine.add_timer(0.1) { fail_proc.call }
111
+ end
112
+ end
113
+
114
+ # When a channel becomes suspended or failed,
115
+ # all queued messages should be failed immediately as we don't queue in
116
+ # any of those states
117
+ def fail_queued_messages(error)
118
+ error = Ably::Exceptions::MessageDeliveryFailed.new("Queued messages on channel '#{channel.name}' in state '#{channel.state}' will never be delivered") unless error
119
+ fail_messages_in_queue connection.__outgoing_message_queue__, error
120
+ end
121
+
122
+ def fail_messages_in_queue(queue, error)
123
+ queue.delete_if do |protocol_message|
124
+ if protocol_message.action.match_any?(:presence, :message)
125
+ if protocol_message.channel == channel.name
126
+ nack_messages protocol_message, error
127
+ true
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ def nack_messages(protocol_message, error)
134
+ (protocol_message.messages + protocol_message.presence).each do |message|
135
+ nack_message message, error, protocol_message
136
+ end
137
+ logger.debug { "Calling NACK failure callbacks for #{protocol_message.class.name} - #{protocol_message.to_json}" }
138
+ protocol_message.fail error
139
+ end
140
+
141
+ def nack_message(message, error, protocol_message = nil)
142
+ logger.debug { "Calling NACK failure callbacks for #{message.class.name} - #{message.to_json} #{"protocol message: #{protocol_message}" if protocol_message}" }
143
+ message.fail error
144
+ end
145
+
146
+ def drop_pending_queue_from_ack(ack_protocol_message)
147
+ message_serial_up_to = ack_protocol_message.message_serial + ack_protocol_message.count - 1
148
+ connection.__pending_message_ack_queue__.drop_while do |protocol_message|
149
+ if protocol_message.message_serial <= message_serial_up_to
150
+ yield protocol_message
151
+ true
152
+ end
153
+ end
154
+ end
155
+
156
+ # If the connection is still connected and the channel still suspended after
157
+ # channel_retry_timeout has passed, then attempt to reattach automatically, see #RTL13b
158
+ def start_attach_from_suspended_timer
159
+ cancel_attach_from_suspended_timer
160
+ if connection.connected?
161
+ channel.unsafe_once { |event| cancel_attach_from_suspended_timer unless event == :update }
162
+ connection.unsafe_once { |event| cancel_attach_from_suspended_timer unless event == :update }
163
+
164
+ @attach_from_suspended_timer = EventMachine::Timer.new(channel_retry_timeout) do
165
+ channel.transition_state_machine! :attaching
166
+ end
167
+ end
168
+ end
169
+
170
+ # RTL13c
171
+ def notify_state_change
172
+ @pending_state_change_timer.cancel if @pending_state_change_timer
173
+ @pending_state_change_timer = nil
174
+ end
175
+
176
+ private
177
+ attr_reader :pending_state_change_timer
178
+
179
+ def channel
180
+ @channel
181
+ end
182
+
183
+ def connection
184
+ @connection
185
+ end
186
+
187
+ def_delegators :channel, :can_transition_to?
188
+
189
+ def cancel_attach_from_suspended_timer
190
+ @attach_from_suspended_timer.cancel if @attach_from_suspended_timer
191
+ @attach_from_suspended_timer = nil
192
+ end
193
+
194
+ # If the connection has not previously connected, connect now
195
+ def connect_if_connection_initialized
196
+ connection.connect if connection.initialized?
197
+ end
198
+
199
+ def realtime_request_timeout
200
+ connection.defaults.fetch(:realtime_request_timeout)
201
+ end
202
+
203
+ def channel_retry_timeout
204
+ connection.defaults.fetch(:channel_retry_timeout)
205
+ end
206
+
207
+ def send_attach_protocol_message
208
+ message_options = {}
209
+ message_options[:params] = channel.options.params if channel.options.params.any?
210
+ message_options[:flags] = channel.options.modes_to_flags if channel.options.modes
211
+ if channel.attach_resume
212
+ message_options[:flags] = message_options[:flags].to_i | Ably::Models::ProtocolMessage::ATTACH_FLAGS_MAPPING[:resume]
213
+ end
214
+
215
+ message_options[:channelSerial] = channel.properties.channel_serial # RTL4c1
216
+
217
+ state_at_time_of_request = channel.state
218
+ attach_action = Ably::Models::ProtocolMessage::ACTION.Attach
219
+ # RTL4f
220
+ @pending_state_change_timer = EventMachine::Timer.new(realtime_request_timeout) do
221
+ if channel.state == state_at_time_of_request
222
+ error = Ably::Models::ErrorInfo.new(code: Ably::Exceptions::Codes::CHANNEL_OPERATION_FAILED_NO_RESPONSE_FROM_SERVER, message: "Channel #{attach_action} operation failed (timed out)")
223
+ channel.transition_state_machine :suspended, reason: error # return to suspended state if failed
224
+ end
225
+ end
226
+ # Shouldn't queue attach message as per RTL4i, so message is added top of the queue
227
+ # to be sent immediately while processing next message
228
+ connection.send_protocol_message_immediately(
229
+ action: attach_action.to_i,
230
+ channel: channel.name,
231
+ **message_options.to_h
232
+ )
233
+ end
234
+
235
+ def send_detach_protocol_message(previous_state)
236
+ state_at_time_of_request = channel.state
237
+ detach_action = Ably::Models::ProtocolMessage::ACTION.Detach
238
+
239
+ @pending_state_change_timer = EventMachine::Timer.new(realtime_request_timeout) do
240
+ if channel.state == state_at_time_of_request
241
+ error = Ably::Models::ErrorInfo.new(code: Ably::Exceptions::Codes::CHANNEL_OPERATION_FAILED_NO_RESPONSE_FROM_SERVER, message: "Channel #{detach_action} operation failed (timed out)")
242
+ channel.transition_state_machine previous_state, reason: error # return to previous state if failed
243
+ end
244
+ end
245
+
246
+ on_disconnected_and_connected = lambda do |&block|
247
+ connection.unsafe_once(:disconnected) do
248
+ connection.unsafe_once(:connected) do
249
+ block.call if pending_state_change_timer
250
+ end if pending_state_change_timer
251
+ end
252
+ end
253
+
254
+ send_detach_message = lambda do
255
+ on_disconnected_and_connected.call do
256
+ send_detach_message.call
257
+ end
258
+ connection.send_protocol_message(
259
+ action: detach_action.to_i,
260
+ channel: channel.name
261
+ )
262
+ end
263
+
264
+ send_detach_message.call
265
+ end
266
+
267
+ def logger
268
+ connection.logger
269
+ end
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,40 @@
1
+ module Ably::Realtime
2
+ class Channel
3
+ # Describes the properties of the channel state.
4
+ class ChannelProperties
5
+ # {Ably::Realtime::Channel} this object associated with
6
+ #
7
+ # @return [Ably::Realtime::Channel]
8
+ #
9
+ attr_reader :channel
10
+
11
+ # Starts unset when a channel is instantiated, then updated with the channelSerial from each
12
+ # {Ably::Realtime::Channel::STATE.Attached} event that matches the channel.
13
+ # Used as the value for {Ably::Realtime::Channel#history}.
14
+ #
15
+ # @spec CP2a
16
+ #
17
+ # @return [String]
18
+ #
19
+ attr_reader :attach_serial
20
+
21
+ # ChannelSerial contains the channelSerial from latest ProtocolMessage of action type
22
+ # Message/PresenceMessage received on the channel.
23
+ #
24
+ # @spec CP2b, RTL15b
25
+ #
26
+ # @return [String]
27
+ #
28
+ attr_accessor :channel_serial
29
+
30
+ def initialize(channel)
31
+ @channel = channel
32
+ end
33
+
34
+ # @api private
35
+ def set_attach_serial(attach_serial)
36
+ @attach_serial = attach_serial
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,104 @@
1
+ require 'ably/modules/state_machine'
2
+
3
+ module Ably::Realtime
4
+ class Channel
5
+ # Internal class to manage channel state for {Ably::Realtime::Channel}
6
+ #
7
+ # @api private
8
+ #
9
+ class ChannelStateMachine
10
+ include Ably::Modules::StateMachine
11
+
12
+ # States supported by this StateMachine match #{Channel::STATE}s
13
+ # :initialized
14
+ # :attaching
15
+ # :attached
16
+ # :detaching
17
+ # :detached
18
+ # :failed
19
+ Channel::STATE.each_with_index do |state_enum, index|
20
+ state state_enum.to_sym, initial: index == 0
21
+ end
22
+
23
+ transition :from => :initialized, :to => [:attaching, :failed]
24
+ transition :from => :attaching, :to => [:attached, :detaching, :failed, :suspended]
25
+ transition :from => :attached, :to => [:attaching, :detaching, :detached, :failed, :suspended]
26
+ transition :from => :detaching, :to => [:detached, :attaching, :attached, :failed, :suspended]
27
+ transition :from => :detached, :to => [:attaching, :attached, :failed]
28
+ transition :from => :suspended, :to => [:attaching, :attached, :detached, :failed]
29
+ transition :from => :failed, :to => [:attaching, :initialized]
30
+
31
+ after_transition do |channel, transition|
32
+ channel.manager.notify_state_change # RTL13c
33
+ channel.synchronize_state_with_statemachine
34
+ end
35
+
36
+ after_transition(to: [:initialized]) do |channel|
37
+ channel.clear_error_reason
38
+ end
39
+
40
+ after_transition(to: [:attaching]) do |channel|
41
+ channel.manager.attach
42
+ end
43
+
44
+ before_transition(to: [:attached]) do |channel, current_transition|
45
+ channel.manager.attached current_transition.metadata.protocol_message
46
+ channel.attach_resume!
47
+ end
48
+
49
+ before_transition(to: [:detaching, :failed]) do |channel, _current_transition|
50
+ channel.reset_attach_resume!
51
+ end
52
+
53
+ after_transition(to: [:detaching]) do |channel, current_transition|
54
+ err = error_from_state_change(current_transition)
55
+ channel.manager.detach err, current_transition.metadata.previous
56
+ end
57
+
58
+ after_transition(to: [:detached, :failed, :suspended]) do |channel, current_transition|
59
+ channel.properties.channel_serial = nil # RTP5a1
60
+ err = error_from_state_change(current_transition)
61
+ channel.manager.fail_queued_messages(err) if channel.failed? or channel.suspended? #RTL11
62
+ channel.manager.log_channel_error err if err
63
+ end
64
+
65
+ after_transition(to: [:suspended]) do |channel, current_transition|
66
+ channel.manager.start_attach_from_suspended_timer
67
+ end
68
+
69
+ # Transitions responsible for updating channel#error_reason
70
+ before_transition(to: [:failed, :suspended]) do |channel, current_transition|
71
+ err = error_from_state_change(current_transition)
72
+ channel.set_channel_error_reason err if err
73
+ end
74
+
75
+ before_transition(to: [:attached, :detached]) do |channel, current_transition|
76
+ err = error_from_state_change(current_transition)
77
+ if err
78
+ channel.set_channel_error_reason err
79
+ else
80
+ # Attached & Detached are "healthy" final states so reset the error reason
81
+ channel.clear_error_reason
82
+ end
83
+ end
84
+
85
+ def self.error_from_state_change(current_transition)
86
+ # ChannelStateChange object is always passed in current_transition metadata object
87
+ connection_state_change = current_transition.metadata
88
+ # Reason attribute contains errors
89
+ err = connection_state_change && connection_state_change.reason
90
+ err if is_error_type?(err)
91
+ end
92
+
93
+ private
94
+ def channel
95
+ object
96
+ end
97
+
98
+ # Logged needs to be defined as it is used by {Ably::Modules::StateMachine}
99
+ def logger
100
+ channel.logger
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,82 @@
1
+ module Ably::Realtime
2
+ class Channel
3
+ # Publisher module adds publishing capabilities to the current object
4
+ module Publisher
5
+ private
6
+
7
+ # Prepare and queue messages on the connection queue immediately
8
+ #
9
+ # @return [Ably::Util::SafeDeferrable]
10
+ #
11
+ def enqueue_messages_on_connection(client, raw_messages, channel_name, channel_options = {})
12
+ messages = Array(raw_messages).map do |raw_msg|
13
+ create_message(client, raw_msg, channel_options).tap do |message|
14
+ next if message.client_id.nil?
15
+ if message.client_id == '*'
16
+ raise Ably::Exceptions::IncompatibleClientId.new('Wildcard client_id is reserved and cannot be used when publishing messages')
17
+ end
18
+ if message.client_id && !message.client_id.kind_of?(String)
19
+ raise Ably::Exceptions::IncompatibleClientId.new('client_id must be a String when publishing messages')
20
+ end
21
+ unless client.auth.can_assume_client_id?(message.client_id)
22
+ raise Ably::Exceptions::IncompatibleClientId.new("Cannot publish with client_id '#{message.client_id}' as it is incompatible with the current configured client_id '#{client.client_id}'")
23
+ end
24
+ end
25
+ end
26
+
27
+ max_message_size = connection.details && connection.details.max_message_size || Ably::Models::ConnectionDetails::MAX_MESSAGE_SIZE
28
+ if messages.sum(&:size) > max_message_size
29
+ error = Ably::Exceptions::MaxMessageSizeExceeded.new("Message size exceeded #{max_message_size} bytes.")
30
+ return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
31
+ end
32
+
33
+ connection.send_protocol_message(
34
+ action: Ably::Models::ProtocolMessage::ACTION.Message.to_i,
35
+ channel: channel_name,
36
+ messages: messages
37
+ )
38
+
39
+ if messages.count == 1
40
+ # A message is a Deferrable so, if publishing only one message, simply return that Deferrable
41
+ messages.first
42
+ else
43
+ deferrable_for_multiple_messages(messages)
44
+ end
45
+ end
46
+
47
+ # A deferrable object that calls the success callback once all messages are delivered
48
+ # If any message fails, the errback is called immediately
49
+ # Only one callback or errback is ever called i.e. if a group of messages all fail, only once
50
+ # errback will be invoked
51
+ def deferrable_for_multiple_messages(messages)
52
+ expected_deliveries = messages.count
53
+ actual_deliveries = 0
54
+ failed = false
55
+
56
+ Ably::Util::SafeDeferrable.new(logger).tap do |deferrable|
57
+ messages.each do |message|
58
+ message.callback do
59
+ next if failed
60
+ actual_deliveries += 1
61
+ deferrable.succeed messages if actual_deliveries == expected_deliveries
62
+ end
63
+ message.errback do |error|
64
+ next if failed
65
+ failed = true
66
+ deferrable.fail error, message
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ def create_message(client, message, channel_options)
73
+ Ably::Models::Message(message.dup).tap do |msg|
74
+ msg.encode(client.encoders, channel_options) do |encode_error, error_message|
75
+ client.logger.error error_message
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+
@@ -0,0 +1,69 @@
1
+ module Ably::Realtime
2
+ class Channel
3
+ # Enables devices to subscribe to push notifications for a channel.
4
+ #
5
+ class PushChannel
6
+ attr_reader :channel
7
+
8
+ def initialize(channel)
9
+ raise ArgumentError, "Unsupported channel type '#{channel.class}'" unless channel.kind_of?(Ably::Realtime::Channel)
10
+ @channel = channel
11
+ end
12
+
13
+ def to_s
14
+ "<PushChannel: name=#{channel.name}>"
15
+ end
16
+
17
+ # Subscribes the device to push notifications for the channel.
18
+ #
19
+ # @spec RSH7a
20
+ #
21
+ # @note This is unsupported in the Ruby library
22
+ def subscribe_device(*args)
23
+ raise_unsupported
24
+ end
25
+
26
+ # Subscribes all devices associated with the current device's clientId to push notifications for the channel.
27
+ #
28
+ # @spec RSH7b
29
+ #
30
+ # @note This is unsupported in the Ruby library
31
+ def subscribe_client_id(*args)
32
+ raise_unsupported
33
+ end
34
+
35
+ # Unsubscribes the device from receiving push notifications for the channel.
36
+ #
37
+ # @spec RSH7c
38
+ #
39
+ # @note This is unsupported in the Ruby library
40
+ def unsubscribe_device(*args)
41
+ raise_unsupported
42
+ end
43
+
44
+ # Unsubscribes all devices associated with the current device's clientId from receiving push notifications for the channel.
45
+ #
46
+ # @spec RSH7d
47
+ #
48
+ # @note This is unsupported in the Ruby library
49
+ def unsubscribe_client_id(*args)
50
+ raise_unsupported
51
+ end
52
+
53
+ # Retrieves all push subscriptions for the channel. Subscriptions can be filtered using a params object.
54
+ # Returns a {Ably::Models::PaginatedResult} object containing an array of {Ably::Models::PushChannelSubscription} objects.
55
+ #
56
+ # @spec RSH7e
57
+ #
58
+ # @note This is unsupported in the Ruby library
59
+ def get_subscriptions(*args)
60
+ raise_unsupported
61
+ end
62
+
63
+ private
64
+ def raise_unsupported
65
+ raise Ably::Exceptions::PushNotificationsNotSupported, 'This device does not support receiving or subscribing to push notifications. All PushChannel methods are unavailable'
66
+ end
67
+ end
68
+ end
69
+ end