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.
- checksums.yaml +7 -0
- data/lib/ably/agent.rb +8 -0
- data/lib/ably/auth.rb +791 -0
- data/lib/ably/exceptions.rb +169 -0
- data/lib/ably/logger.rb +119 -0
- data/lib/ably/models/auth_details.rb +45 -0
- data/lib/ably/models/channel_details.rb +69 -0
- data/lib/ably/models/channel_metrics.rb +107 -0
- data/lib/ably/models/channel_occupancy.rb +50 -0
- data/lib/ably/models/channel_options.rb +120 -0
- data/lib/ably/models/channel_state_change.rb +84 -0
- data/lib/ably/models/channel_status.rb +63 -0
- data/lib/ably/models/cipher_params.rb +137 -0
- data/lib/ably/models/connection_details.rb +149 -0
- data/lib/ably/models/connection_state_change.rb +83 -0
- data/lib/ably/models/delta_extras.rb +28 -0
- data/lib/ably/models/device_details.rb +126 -0
- data/lib/ably/models/device_push_details.rb +94 -0
- data/lib/ably/models/error_info.rb +104 -0
- data/lib/ably/models/http_paginated_response.rb +112 -0
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +235 -0
- data/lib/ably/models/message.rb +223 -0
- data/lib/ably/models/message_encoders/base.rb +135 -0
- data/lib/ably/models/message_encoders/base64.rb +40 -0
- data/lib/ably/models/message_encoders/cipher.rb +84 -0
- data/lib/ably/models/message_encoders/json.rb +34 -0
- data/lib/ably/models/message_encoders/utf8.rb +26 -0
- data/lib/ably/models/nil_logger.rb +20 -0
- data/lib/ably/models/paginated_result.rb +201 -0
- data/lib/ably/models/presence_message.rb +244 -0
- data/lib/ably/models/protocol_message.rb +288 -0
- data/lib/ably/models/push_channel_subscription.rb +114 -0
- data/lib/ably/models/stats.rb +242 -0
- data/lib/ably/models/stats_types.rb +107 -0
- data/lib/ably/models/token_details.rb +148 -0
- data/lib/ably/models/token_request.rb +180 -0
- data/lib/ably/modules/ably.rb +26 -0
- data/lib/ably/modules/async_wrapper.rb +69 -0
- data/lib/ably/modules/channels_collection.rb +105 -0
- data/lib/ably/modules/conversions.rb +153 -0
- data/lib/ably/modules/encodeable.rb +111 -0
- data/lib/ably/modules/enum.rb +230 -0
- data/lib/ably/modules/event_emitter.rb +200 -0
- data/lib/ably/modules/event_machine_helpers.rb +26 -0
- data/lib/ably/modules/exception_codes.rb +128 -0
- data/lib/ably/modules/http_helpers.rb +41 -0
- data/lib/ably/modules/message_emitter.rb +78 -0
- data/lib/ably/modules/message_pack.rb +14 -0
- data/lib/ably/modules/model_common.rb +84 -0
- data/lib/ably/modules/safe_deferrable.rb +83 -0
- data/lib/ably/modules/safe_yield.rb +41 -0
- data/lib/ably/modules/state_emitter.rb +181 -0
- data/lib/ably/modules/state_machine.rb +59 -0
- data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/ably/modules/uses_state_machine.rb +111 -0
- data/lib/ably/realtime/auth.rb +283 -0
- data/lib/ably/realtime/channel/channel_manager.rb +272 -0
- data/lib/ably/realtime/channel/channel_properties.rb +40 -0
- data/lib/ably/realtime/channel/channel_state_machine.rb +104 -0
- data/lib/ably/realtime/channel/publisher.rb +82 -0
- data/lib/ably/realtime/channel/push_channel.rb +69 -0
- data/lib/ably/realtime/channel.rb +440 -0
- data/lib/ably/realtime/channels.rb +71 -0
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +225 -0
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +87 -0
- data/lib/ably/realtime/client.rb +363 -0
- data/lib/ably/realtime/connection/connection_manager.rb +594 -0
- data/lib/ably/realtime/connection/connection_state_machine.rb +144 -0
- data/lib/ably/realtime/connection/websocket_transport.rb +287 -0
- data/lib/ably/realtime/connection.rb +738 -0
- data/lib/ably/realtime/models/nil_channel.rb +30 -0
- data/lib/ably/realtime/presence/members_map.rb +395 -0
- data/lib/ably/realtime/presence/presence_manager.rb +78 -0
- data/lib/ably/realtime/presence/presence_state_machine.rb +57 -0
- data/lib/ably/realtime/presence.rb +502 -0
- data/lib/ably/realtime/push/admin.rb +75 -0
- data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
- data/lib/ably/realtime/push/device_registrations.rb +105 -0
- data/lib/ably/realtime/push.rb +25 -0
- data/lib/ably/realtime/recovery_key_context.rb +36 -0
- data/lib/ably/realtime.rb +60 -0
- data/lib/ably/rest/channel/push_channel.rb +59 -0
- data/lib/ably/rest/channel.rb +194 -0
- data/lib/ably/rest/channels.rb +47 -0
- data/lib/ably/rest/client.rb +727 -0
- data/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/ably/rest/middleware/exceptions.rb +53 -0
- data/lib/ably/rest/middleware/external_exceptions.rb +24 -0
- data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +22 -0
- data/lib/ably/rest/middleware/logger.rb +58 -0
- data/lib/ably/rest/middleware/parse_json.rb +27 -0
- data/lib/ably/rest/middleware/parse_message_pack.rb +43 -0
- data/lib/ably/rest/presence.rb +113 -0
- data/lib/ably/rest/push/admin.rb +58 -0
- data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
- data/lib/ably/rest/push/device_registrations.rb +114 -0
- data/lib/ably/rest/push.rb +25 -0
- data/lib/ably/rest.rb +40 -0
- data/lib/ably/util/ably_extensions.rb +29 -0
- data/lib/ably/util/crypto.rb +134 -0
- data/lib/ably/util/pub_sub.rb +45 -0
- data/lib/ably/util/safe_deferrable.rb +44 -0
- data/lib/ably/version.rb +7 -0
- data/lib/ably.rb +16 -0
- 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
|