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,225 @@
|
|
|
1
|
+
module Ably::Realtime
|
|
2
|
+
class Client
|
|
3
|
+
# IncomingMessageDispatcher is a (private) class that is used to dispatch {Ably::Models::ProtocolMessage} that are
|
|
4
|
+
# received from Ably via the {Ably::Realtime::Connection}
|
|
5
|
+
class IncomingMessageDispatcher
|
|
6
|
+
ACTION = Ably::Models::ProtocolMessage::ACTION
|
|
7
|
+
|
|
8
|
+
def initialize(client, connection)
|
|
9
|
+
@client = client
|
|
10
|
+
@connection = connection
|
|
11
|
+
|
|
12
|
+
subscribe_to_incoming_protocol_messages
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
def client
|
|
17
|
+
@client
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def connection
|
|
21
|
+
@connection
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def channels
|
|
25
|
+
client.channels
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def get_channel(channel_name)
|
|
29
|
+
channels.fetch(channel_name) do
|
|
30
|
+
logger.warn { "Received channel message for non-existent channel" }
|
|
31
|
+
Ably::Realtime::Models::NilChannel.new
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def logger
|
|
36
|
+
client.logger
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def dispatch_protocol_message(*args)
|
|
40
|
+
protocol_message = args.first
|
|
41
|
+
|
|
42
|
+
unless protocol_message.kind_of?(Ably::Models::ProtocolMessage)
|
|
43
|
+
raise ArgumentError, "Expected a ProtocolMessage. Received #{protocol_message}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# RTL15b
|
|
47
|
+
if protocol_message.has_channel_serial? &&
|
|
48
|
+
(
|
|
49
|
+
protocol_message.action == :message ||
|
|
50
|
+
protocol_message.action == :presence ||
|
|
51
|
+
protocol_message.action == :attached
|
|
52
|
+
)
|
|
53
|
+
get_channel(protocol_message.channel).tap do |channel|
|
|
54
|
+
logger.info "Setting channel serial for channel #{channel.name}, " <<
|
|
55
|
+
"Previous: #{channel.properties.channel_serial}, New: #{protocol_message.channel_serial}"
|
|
56
|
+
channel.properties.channel_serial = protocol_message.channel_serial
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
unless protocol_message.action.match_any?(:nack, :error)
|
|
61
|
+
logger.debug { "#{protocol_message.action} received: #{protocol_message}" }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
connection.set_connection_confirmed_alive
|
|
65
|
+
|
|
66
|
+
case protocol_message.action
|
|
67
|
+
when ACTION.Heartbeat
|
|
68
|
+
when ACTION.Ack
|
|
69
|
+
ack_pending_queue_for_message_serial(protocol_message) if protocol_message.has_message_serial?
|
|
70
|
+
|
|
71
|
+
when ACTION.Nack
|
|
72
|
+
logger.warn { "NACK received: #{protocol_message}" }
|
|
73
|
+
nack_pending_queue_for_message_serial(protocol_message) if protocol_message.has_message_serial?
|
|
74
|
+
|
|
75
|
+
when ACTION.Connect
|
|
76
|
+
when ACTION.Connected
|
|
77
|
+
if connection.closing?
|
|
78
|
+
logger.debug { "Out-of-order incoming CONNECTED ProtocolMessage discarded as connection has moved on and is in state: #{connection.state}" }
|
|
79
|
+
elsif connection.disconnected? || connection.closing? || connection.closed? || connection.failed?
|
|
80
|
+
logger.warn { "Out-of-order incoming CONNECTED ProtocolMessage discarded as connection has moved on and is in state: #{connection.state}" }
|
|
81
|
+
elsif connection.connected?
|
|
82
|
+
logger.debug { "Updated CONNECTED ProtocolMessage received (whilst connected)" }
|
|
83
|
+
process_connected_update_message protocol_message
|
|
84
|
+
connection.set_connection_confirmed_alive # Connection protocol messages can change liveness settings such as max_idle_interval
|
|
85
|
+
else
|
|
86
|
+
process_connected_message protocol_message
|
|
87
|
+
connection.set_connection_confirmed_alive # Connection protocol messages can change liveness settings such as max_idle_interval
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
when ACTION.Disconnect, ACTION.Disconnected
|
|
91
|
+
connection.transition_state_machine :disconnected, reason: protocol_message.error unless connection.disconnected?
|
|
92
|
+
|
|
93
|
+
when ACTION.Close
|
|
94
|
+
when ACTION.Closed
|
|
95
|
+
connection.transition_state_machine :closed unless connection.closed?
|
|
96
|
+
|
|
97
|
+
when ACTION.Error
|
|
98
|
+
if protocol_message.channel
|
|
99
|
+
dispatch_channel_error protocol_message
|
|
100
|
+
else
|
|
101
|
+
process_connection_error protocol_message
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
when ACTION.Attach
|
|
105
|
+
when ACTION.Attached
|
|
106
|
+
get_channel(protocol_message.channel).tap do |channel|
|
|
107
|
+
if channel.attached?
|
|
108
|
+
channel.manager.duplicate_attached_received protocol_message
|
|
109
|
+
else
|
|
110
|
+
if channel.failed?
|
|
111
|
+
logger.warn "Ably::Realtime::Client::IncomingMessageDispatcher - Received an ATTACHED protocol message for FAILED channel #{channel.name}. Ignoring ATTACHED message"
|
|
112
|
+
else
|
|
113
|
+
channel.transition_state_machine :attached, reason: protocol_message.error, resumed: protocol_message.has_channel_resumed_flag?, protocol_message: protocol_message
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
when ACTION.Detach
|
|
119
|
+
when ACTION.Detached
|
|
120
|
+
get_channel(protocol_message.channel).tap do |channel|
|
|
121
|
+
channel.manager.detached_received protocol_message.error
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
when ACTION.Sync
|
|
125
|
+
presence = get_channel(protocol_message.channel).presence
|
|
126
|
+
presence.manager.sync_process_messages protocol_message.channel_serial, protocol_message.presence
|
|
127
|
+
|
|
128
|
+
when ACTION.Presence
|
|
129
|
+
presence = get_channel(protocol_message.channel).presence
|
|
130
|
+
protocol_message.presence.each do |presence_message|
|
|
131
|
+
presence.__incoming_msgbus__.publish :presence, presence_message
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
when ACTION.Message
|
|
135
|
+
channel = get_channel(protocol_message.channel)
|
|
136
|
+
protocol_message.messages.each do |message|
|
|
137
|
+
channel.__incoming_msgbus__.publish :message, message
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
when ACTION.Auth
|
|
141
|
+
client.auth.authorize
|
|
142
|
+
|
|
143
|
+
else
|
|
144
|
+
error = Ably::Exceptions::ProtocolError.new("Protocol Message Action #{protocol_message.action} is unsupported by this MessageDispatcher", 400, Ably::Exceptions::Codes::PROTOCOL_ERROR)
|
|
145
|
+
logger.fatal error.message
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def dispatch_channel_error(protocol_message)
|
|
150
|
+
logger.warn { "Channel Error message received: #{protocol_message.error}" }
|
|
151
|
+
if !protocol_message.has_message_serial?
|
|
152
|
+
get_channel(protocol_message.channel).transition_state_machine :failed, reason: protocol_message.error
|
|
153
|
+
else
|
|
154
|
+
logger.fatal { "Cannot process ProtocolMessage ERROR with message serial as not yet implemented: #{protocol_message}" }
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def process_connection_error(protocol_message)
|
|
159
|
+
connection.manager.error_received_from_server(protocol_message.error || Ably::Models::ErrorInfo.new(message: 'Error reason unknown'))
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def process_connected_message(protocol_message)
|
|
163
|
+
if client.auth.token_client_id_allowed?(protocol_message.connection_details.client_id)
|
|
164
|
+
connection.transition_state_machine :connected, reason: protocol_message.error, protocol_message: protocol_message
|
|
165
|
+
else
|
|
166
|
+
reason = Ably::Exceptions::IncompatibleClientId.new("Client ID '#{protocol_message.connection_details.client_id}' specified by the server is incompatible with the library's configured client ID '#{client.client_id}'")
|
|
167
|
+
connection.transition_state_machine :failed, reason: reason, protocol_message: protocol_message
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def process_connected_update_message(protocol_message)
|
|
172
|
+
if client.auth.token_client_id_allowed?(protocol_message.connection_details.client_id)
|
|
173
|
+
connection.manager.connected_update protocol_message
|
|
174
|
+
else
|
|
175
|
+
reason = Ably::Exceptions::IncompatibleClientId.new("Client ID '#{protocol_message.connection_details.client_id}' in CONNECTED update specified by the server is incompatible with the library's configured client ID '#{client.client_id}'")
|
|
176
|
+
connection.transition_state_machine :failed, reason: reason, protocol_message: protocol_message
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def ack_pending_queue_for_message_serial(ack_protocol_message)
|
|
181
|
+
drop_pending_queue_from_ack(ack_protocol_message) do |protocol_message|
|
|
182
|
+
ack_messages protocol_message.messages
|
|
183
|
+
ack_messages protocol_message.presence
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def nack_pending_queue_for_message_serial(nack_protocol_message)
|
|
188
|
+
drop_pending_queue_from_ack(nack_protocol_message) do |protocol_message|
|
|
189
|
+
nack_messages protocol_message.messages, nack_protocol_message
|
|
190
|
+
nack_messages protocol_message.presence, nack_protocol_message
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def ack_messages(messages)
|
|
195
|
+
messages.each do |message|
|
|
196
|
+
logger.debug { "Calling ACK success callbacks for #{message.class.name} - #{message.to_json}" }
|
|
197
|
+
message.succeed message
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def nack_messages(messages, protocol_message)
|
|
202
|
+
messages.each do |message|
|
|
203
|
+
logger.debug { "Calling NACK failure callbacks for #{message.class.name} - #{message.to_json}, protocol message: #{protocol_message}" }
|
|
204
|
+
message.fail protocol_message.error
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def drop_pending_queue_from_ack(ack_protocol_message)
|
|
209
|
+
message_serial_up_to = ack_protocol_message.message_serial + ack_protocol_message.count - 1
|
|
210
|
+
|
|
211
|
+
while !connection.__pending_message_ack_queue__.empty?
|
|
212
|
+
next_message = connection.__pending_message_ack_queue__.first
|
|
213
|
+
return if next_message.message_serial > message_serial_up_to
|
|
214
|
+
yield connection.__pending_message_ack_queue__.shift
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def subscribe_to_incoming_protocol_messages
|
|
219
|
+
connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |*args|
|
|
220
|
+
dispatch_protocol_message(*args)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module Ably::Realtime
|
|
2
|
+
class Client
|
|
3
|
+
# OutgoingMessageDispatcher is a (private) class that is used to deliver
|
|
4
|
+
# outgoing {Ably::Models::ProtocolMessage}s using the {Ably::Realtime::Connection}
|
|
5
|
+
# when the connection state is capable of delivering messages
|
|
6
|
+
class OutgoingMessageDispatcher
|
|
7
|
+
include Ably::Modules::EventMachineHelpers
|
|
8
|
+
|
|
9
|
+
ACTION = Ably::Models::ProtocolMessage::ACTION
|
|
10
|
+
|
|
11
|
+
def initialize(client, connection)
|
|
12
|
+
@client = client
|
|
13
|
+
@connection = connection
|
|
14
|
+
|
|
15
|
+
subscribe_to_outgoing_protocol_message_queue
|
|
16
|
+
setup_event_handlers
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
def client
|
|
21
|
+
@client
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def connection
|
|
25
|
+
@connection
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def can_send_messages?
|
|
29
|
+
connection.connected? || connection.closing?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def messages_in_outgoing_queue?
|
|
33
|
+
!outgoing_queue.empty?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def outgoing_queue
|
|
37
|
+
connection.__outgoing_message_queue__
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def pending_ack_queue
|
|
41
|
+
connection.__pending_message_ack_queue__
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def current_transport_outgoing_message_bus
|
|
45
|
+
connection.transport.__outgoing_protocol_msgbus__
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def deliver_queued_protocol_messages
|
|
49
|
+
condition = -> { can_send_messages? && messages_in_outgoing_queue? }
|
|
50
|
+
|
|
51
|
+
non_blocking_loop_while(condition) do
|
|
52
|
+
protocol_message = outgoing_queue.shift
|
|
53
|
+
|
|
54
|
+
if (!connection.transport)
|
|
55
|
+
protocol_message.fail Ably::Exceptions::TransportClosed.new('Transport disconnected unexpectedly', nil, Ably::Exceptions::Codes::DISCONNECTED)
|
|
56
|
+
next
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
current_transport_outgoing_message_bus.publish :protocol_message, protocol_message
|
|
60
|
+
|
|
61
|
+
if protocol_message.ack_required?
|
|
62
|
+
pending_ack_queue << protocol_message
|
|
63
|
+
else
|
|
64
|
+
protocol_message.succeed protocol_message
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def subscribe_to_outgoing_protocol_message_queue
|
|
70
|
+
connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |*args|
|
|
71
|
+
deliver_queued_protocol_messages
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def setup_event_handlers
|
|
76
|
+
connection.unsafe_on(:connected) do
|
|
77
|
+
# Give connection manager enough time to prevent message delivery if necessary
|
|
78
|
+
# For example, if reconnecting and connection and channel state is lost,
|
|
79
|
+
# then the queued messages must be NACK'd
|
|
80
|
+
EventMachine.next_tick do
|
|
81
|
+
deliver_queued_protocol_messages
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
require 'ably/realtime/channel/publisher'
|
|
3
|
+
require 'ably/realtime/recovery_key_context'
|
|
4
|
+
|
|
5
|
+
module Ably
|
|
6
|
+
module Realtime
|
|
7
|
+
# A client that extends the functionality of the {Ably::Realtime::Client} and provides additional realtime-specific features.
|
|
8
|
+
#
|
|
9
|
+
class Client
|
|
10
|
+
include Ably::Modules::AsyncWrapper
|
|
11
|
+
include Ably::Realtime::Channel::Publisher
|
|
12
|
+
include Ably::Modules::Conversions
|
|
13
|
+
|
|
14
|
+
extend Forwardable
|
|
15
|
+
using Ably::Util::AblyExtensions
|
|
16
|
+
|
|
17
|
+
DOMAIN = 'realtime.ably.io'
|
|
18
|
+
|
|
19
|
+
# A {Aby::Realtime::Channels} object.
|
|
20
|
+
#
|
|
21
|
+
# @spec RTC3, RTS1
|
|
22
|
+
#
|
|
23
|
+
# @return [Aby::Realtime::Channels]
|
|
24
|
+
#
|
|
25
|
+
attr_reader :channels
|
|
26
|
+
|
|
27
|
+
# An {Ably::Auth} object.
|
|
28
|
+
#
|
|
29
|
+
# @spec RTC4
|
|
30
|
+
#
|
|
31
|
+
# @return [Ably::Auth]
|
|
32
|
+
#
|
|
33
|
+
attr_reader :auth
|
|
34
|
+
|
|
35
|
+
# A {Aby::Realtime::Connection} object.
|
|
36
|
+
#
|
|
37
|
+
# @spec RTC2
|
|
38
|
+
#
|
|
39
|
+
# @return [Aby::Realtime::Connection]
|
|
40
|
+
#
|
|
41
|
+
attr_reader :connection
|
|
42
|
+
|
|
43
|
+
# The {Ably::Rest::Client REST client} instantiated with the same credentials and configuration that is used for all REST operations such as authentication
|
|
44
|
+
# @return [Ably::Rest::Client]
|
|
45
|
+
|
|
46
|
+
# @private
|
|
47
|
+
attr_reader :rest_client
|
|
48
|
+
|
|
49
|
+
# When false the client suppresses messages originating from this connection being echoed back on the same connection. Defaults to true
|
|
50
|
+
# @return [Boolean]
|
|
51
|
+
attr_reader :echo_messages
|
|
52
|
+
|
|
53
|
+
# If false, this disables the default behaviour whereby the library queues messages on a connection in the disconnected or connecting states. Defaults to true
|
|
54
|
+
# @return [Boolean]
|
|
55
|
+
attr_reader :queue_messages
|
|
56
|
+
|
|
57
|
+
# The custom realtime websocket host that is being used if it was provided with the option `:ws_host` when the {Client} was created
|
|
58
|
+
# @return [String,Nil]
|
|
59
|
+
attr_reader :custom_realtime_host
|
|
60
|
+
|
|
61
|
+
# When true, as soon as the client library is instantiated it will connect to Ably. If this attribute is false, a connection must be opened explicitly
|
|
62
|
+
# @return [Boolean]
|
|
63
|
+
attr_reader :auto_connect
|
|
64
|
+
|
|
65
|
+
# When a recover option is specified a connection inherits the state of a previous connection that may have existed under a different instance of the Realtime library, please refer to the API documentation for further information on connection state recovery
|
|
66
|
+
# @return [String,Nil]
|
|
67
|
+
attr_reader :recover
|
|
68
|
+
|
|
69
|
+
# Additional parameters to be sent in the querystring when initiating a realtime connection
|
|
70
|
+
# @return [Hash]
|
|
71
|
+
attr_reader :transport_params
|
|
72
|
+
|
|
73
|
+
def_delegators :auth, :client_id, :auth_options
|
|
74
|
+
def_delegators :@rest_client, :encoders
|
|
75
|
+
def_delegators :@rest_client, :use_tls?, :protocol, :protocol_binary?
|
|
76
|
+
def_delegators :@rest_client, :environment, :custom_host, :custom_port, :custom_tls_port
|
|
77
|
+
def_delegators :@rest_client, :log_level
|
|
78
|
+
def_delegators :@rest_client, :options
|
|
79
|
+
|
|
80
|
+
# Creates a {Ably::Realtime::Client Realtime Client} and configures the {Ably::Auth} object for the connection.
|
|
81
|
+
#
|
|
82
|
+
# @spec RSC1
|
|
83
|
+
#
|
|
84
|
+
# @param (see {Ably::Rest::Client#initialize})
|
|
85
|
+
# @option options (see Ably::Rest::Client#initialize) An options {Hash} object.
|
|
86
|
+
# @option options [Proc] :auth_callback when provided, the Proc will be called with the token params hash as the first argument, whenever a new token is required.
|
|
87
|
+
# Whilst the proc is called synchronously, it does not block the EventMachine reactor as it is run in a separate thread.
|
|
88
|
+
# The Proc should return a token string, {Ably::Models::TokenDetails} or JSON equivalent, {Ably::Models::TokenRequest} or JSON equivalent
|
|
89
|
+
# @option options [Boolean] :queue_messages If false, this disables the default behaviour whereby the library queues messages on a connection in the disconnected or connecting states
|
|
90
|
+
# @option options [Boolean] :echo_messages If false, prevents messages originating from this connection being echoed back on the same connection
|
|
91
|
+
# @option options [String] :recover When a recover option is specified a connection inherits the state of a previous connection that may have existed under a different instance of the Realtime library, please refer to the API documentation for further information on connection state recovery
|
|
92
|
+
# @option options [Boolean] :auto_connect By default as soon as the client library is instantiated it will connect to Ably. You can optionally set this to false and explicitly connect.
|
|
93
|
+
# @option options [Hash] :transport_params Additional parameters to be sent in the querystring when initiating a realtime connection. Keys are Strings, values are Stringifiable(a value must respond to #to_s)
|
|
94
|
+
#
|
|
95
|
+
# @option options [Integer] :channel_retry_timeout (15 seconds). When a channel becomes SUSPENDED, after this delay in seconds, the channel will automatically attempt to reattach if the connection is CONNECTED
|
|
96
|
+
# @option options [Integer] :disconnected_retry_timeout (15 seconds). When the connection enters the DISCONNECTED state, after this delay in seconds, if the state is still DISCONNECTED, the client library will attempt to reconnect automatically
|
|
97
|
+
# @option options [Integer] :suspended_retry_timeout (30 seconds). When the connection enters the SUSPENDED state, after this delay in seconds, if the state is still SUSPENDED, the client library will attempt to reconnect automatically
|
|
98
|
+
# @option options [Boolean] :disable_websocket_heartbeats WebSocket heartbeats are more efficient than protocol level heartbeats, however they can be disabled for development purposes
|
|
99
|
+
#
|
|
100
|
+
# @return [Ably::Realtime::Client]
|
|
101
|
+
#
|
|
102
|
+
# @example
|
|
103
|
+
# # Constructs a {Ably::Realtime::Client} object using an Ably API key or token string.
|
|
104
|
+
# client = Ably::Realtime::Client.new('key.id:secret')
|
|
105
|
+
#
|
|
106
|
+
# # Constructs a {Ably::Realtime::Client} object using an Ably options object.
|
|
107
|
+
# client = Ably::Realtime::Client.new(key: 'key.id:secret', client_id: 'john')
|
|
108
|
+
#
|
|
109
|
+
def initialize(options)
|
|
110
|
+
raise ArgumentError, 'Options Hash is expected' if options.nil?
|
|
111
|
+
|
|
112
|
+
options = options.clone
|
|
113
|
+
if options.kind_of?(String)
|
|
114
|
+
options = if options.match(Ably::Auth::API_KEY_REGEX)
|
|
115
|
+
{ key: options }
|
|
116
|
+
else
|
|
117
|
+
{ token: options }
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
@transport_params = options.delete(:transport_params).to_h.each_with_object({}) do |(key, value), acc|
|
|
122
|
+
acc[key.to_s] = value.to_s
|
|
123
|
+
end
|
|
124
|
+
@rest_client = Ably::Rest::Client.new(options.merge(realtime_client: self))
|
|
125
|
+
@echo_messages = rest_client.options.fetch_with_default(:echo_messages, true)
|
|
126
|
+
@queue_messages = rest_client.options.fetch_with_default(:queue_messages, true)
|
|
127
|
+
@custom_realtime_host = rest_client.options[:realtime_host] || rest_client.options[:ws_host]
|
|
128
|
+
@auto_connect = rest_client.options.fetch_with_default(:auto_connect, true)
|
|
129
|
+
@recover = rest_client.options.fetch_with_default(:recover, '')
|
|
130
|
+
|
|
131
|
+
@auth = Ably::Realtime::Auth.new(self)
|
|
132
|
+
@channels = Ably::Realtime::Channels.new(self)
|
|
133
|
+
@connection = Ably::Realtime::Connection.new(self, options)
|
|
134
|
+
|
|
135
|
+
unless @recover.nil_or_empty?
|
|
136
|
+
recovery_context = RecoveryKeyContext.from_json(@recover, logger)
|
|
137
|
+
unless recovery_context.nil?
|
|
138
|
+
@channels.set_channel_serials recovery_context.channel_serials # RTN16j
|
|
139
|
+
@connection.set_msg_serial_from_recover = recovery_context.msg_serial # RTN16f
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Return a {Ably::Realtime::Channel Realtime Channel} for the given name
|
|
145
|
+
#
|
|
146
|
+
# @param (see Ably::Realtime::Channels#get)
|
|
147
|
+
# @return (see Ably::Realtime::Channels#get)
|
|
148
|
+
#
|
|
149
|
+
def channel(name, channel_options = {})
|
|
150
|
+
channels.get(name, channel_options)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Retrieves the time from the Ably service as milliseconds since the Unix epoch. Clients that do not have access
|
|
154
|
+
# to a sufficiently well maintained time source and wish to issue Ably {Ably::Models::TokenRequests} with
|
|
155
|
+
# a more accurate timestamp should use the queryTime property instead of this method.
|
|
156
|
+
#
|
|
157
|
+
# @spec RTC6a
|
|
158
|
+
#
|
|
159
|
+
# @yield [Time] The time as milliseconds since the Unix epoch.
|
|
160
|
+
# @return [Ably::Util::SafeDeferrable]
|
|
161
|
+
#
|
|
162
|
+
def time(&success_callback)
|
|
163
|
+
async_wrap(success_callback) do
|
|
164
|
+
rest_client.time
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Queries the REST /stats API and retrieves your application's usage statistics.
|
|
169
|
+
# Returns a {Ably::Util::SafeDeferrable} object, containing an array of {Ably::Models::Stats} objects. See the Stats docs.
|
|
170
|
+
#
|
|
171
|
+
# @spec RTC5
|
|
172
|
+
#
|
|
173
|
+
# @param (see Ably::Rest::Client#stats)
|
|
174
|
+
# @option options (see Ably::Rest::Client#stats)
|
|
175
|
+
#
|
|
176
|
+
# @yield [Ably::Models::PaginatedResult<Ably::Models::Stats>] A {Ably::Util::SafeDeferrable} object containing an array of {Ably::Models::Stats} objects.
|
|
177
|
+
#
|
|
178
|
+
# @return [Ably::Util::SafeDeferrable]
|
|
179
|
+
#
|
|
180
|
+
def stats(options = {}, &success_callback)
|
|
181
|
+
async_wrap(success_callback) do
|
|
182
|
+
rest_client.stats(options)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Calls {Connection#close} and causes the connection to close, entering the closing state.
|
|
187
|
+
# Once closed, the library will not attempt to re-establish the connection without an explicit call to {Connection#connect}.
|
|
188
|
+
# @spec RTN12
|
|
189
|
+
# (see Ably::Realtime::Connection#close)
|
|
190
|
+
def close(&block)
|
|
191
|
+
connection.close(&block)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Calls {Ably::Realtime::Connection#connect} and causes the connection to open, entering the connecting
|
|
195
|
+
# state. Explicitly calling connect() is unnecessary unless the autoConnect property is disabled.
|
|
196
|
+
# @spec RTN11
|
|
197
|
+
# (see Ably::Realtime::Connection#connect)
|
|
198
|
+
def connect(&block)
|
|
199
|
+
connection.connect(&block)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# A {Ably::Realtime::Push} object.
|
|
203
|
+
# @return [Ably::Realtime::Push]
|
|
204
|
+
def push
|
|
205
|
+
@push ||= Push.new(self)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Makes a REST request to a provided path. This is provided as a convenience for developers who wish to use REST
|
|
209
|
+
# API functionality that is either not documented or is not yet included in the public API, without having to
|
|
210
|
+
# directly handle features such as authentication, paging, fallback hosts, MsgPack and JSON support.
|
|
211
|
+
#
|
|
212
|
+
# @spec RTC9
|
|
213
|
+
#
|
|
214
|
+
# (see {Ably::Rest::Client#request})
|
|
215
|
+
# @yield [Ably::Models::HttpPaginatedResponse<>] An Array of Stats
|
|
216
|
+
#
|
|
217
|
+
# @return [Ably::Util::SafeDeferrable] An {Ably::Util::SafeDeferrable} response object returned by the HTTP request, containing an empty or JSON-encodable object.
|
|
218
|
+
def request(method, path, params = {}, body = nil, headers = {}, &callback)
|
|
219
|
+
async_wrap(callback) do
|
|
220
|
+
rest_client.request(method, path, params, body, headers, async_blocking_operations: true)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Publish one or more messages to the specified channel.
|
|
225
|
+
#
|
|
226
|
+
# This method allows messages to be efficiently published to Ably without instancing a {Ably::Realtime::Channel} object.
|
|
227
|
+
# If you want to publish a high rate of messages to Ably without instancing channels or using the REST API, then this method
|
|
228
|
+
# is recommended. However, channel options such as encryption are not supported with this method. If you need to specify channel options
|
|
229
|
+
# we recommend you use the {Ably::Realtime::Channel} +publish+ method without attaching to each channel, unless you also want to subscribe
|
|
230
|
+
# to published messages on that channel.
|
|
231
|
+
#
|
|
232
|
+
# Note: This feature is still in beta. As such, we cannot guarantee the API will not change in future.
|
|
233
|
+
#
|
|
234
|
+
# @param channel [String] The channel name you want to publish the message(s) to
|
|
235
|
+
# @param name [String, Array<Ably::Models::Message|Hash>, 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
|
|
236
|
+
# @param data [String, ByteArray, nil] The message payload unless an Array of [Ably::Model::Message] objects passed in the first argument
|
|
237
|
+
# @param attributes [Hash, nil] Optional additional message attributes such as :client_id or :connection_id, applied when name attribute is nil or a string
|
|
238
|
+
#
|
|
239
|
+
# @yield [Ably::Models::Message,Array<Ably::Models::Message>] On success, will call the block with the {Ably::Models::Message} if a single message is published, or an Array of {Ably::Models::Message} when multiple messages are published
|
|
240
|
+
# @return [Ably::Util::SafeDeferrable] Deferrable that supports both success (callback) and failure (errback) callbacks
|
|
241
|
+
#
|
|
242
|
+
# @example
|
|
243
|
+
# # Publish a single message
|
|
244
|
+
# client.publish 'activityChannel', click', { x: 1, y: 2 }
|
|
245
|
+
#
|
|
246
|
+
# # Publish an array of message Hashes
|
|
247
|
+
# messages = [
|
|
248
|
+
# { name: 'click', { x: 1, y: 2 } },
|
|
249
|
+
# { name: 'click', { x: 2, y: 3 } }
|
|
250
|
+
# ]
|
|
251
|
+
# client.publish 'activityChannel', messages
|
|
252
|
+
#
|
|
253
|
+
# # Publish an array of Ably::Models::Message objects
|
|
254
|
+
# messages = [
|
|
255
|
+
# Ably::Models::Message(name: 'click', { x: 1, y: 2 })
|
|
256
|
+
# Ably::Models::Message(name: 'click', { x: 2, y: 3 })
|
|
257
|
+
# ]
|
|
258
|
+
# client.publish 'activityChannel', messages
|
|
259
|
+
#
|
|
260
|
+
# client.publish('activityChannel', 'click', 'body') do |message|
|
|
261
|
+
# puts "#{message.name} event received with #{message.data}"
|
|
262
|
+
# end
|
|
263
|
+
#
|
|
264
|
+
# client.publish('activityChannel', 'click', 'body').errback do |error, message|
|
|
265
|
+
# puts "#{message.name} was not received, error #{error.message}"
|
|
266
|
+
# end
|
|
267
|
+
#
|
|
268
|
+
def publish(channel_name, name, data = nil, attributes = {}, &success_block)
|
|
269
|
+
if !connection.can_publish_messages?
|
|
270
|
+
error = Ably::Exceptions::MessageQueueingDisabled.new("Message cannot be published. Client is not allowed to queue messages when connection is in state #{connection.state}")
|
|
271
|
+
return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
messages = if name.kind_of?(Enumerable)
|
|
275
|
+
name
|
|
276
|
+
else
|
|
277
|
+
name = ensure_utf_8(:name, name, allow_nil: true)
|
|
278
|
+
ensure_supported_payload data
|
|
279
|
+
[{ name: name, data: data }.merge(attributes)]
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
if messages.length > Realtime::Connection::MAX_PROTOCOL_MESSAGE_BATCH_SIZE
|
|
283
|
+
error = Ably::Exceptions::InvalidRequest.new("It is not possible to publish more than #{Realtime::Connection::MAX_PROTOCOL_MESSAGE_BATCH_SIZE} messages with a single publish request.")
|
|
284
|
+
return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
enqueue_messages_on_connection(self, messages, channel_name).tap do |deferrable|
|
|
288
|
+
deferrable.callback(&success_block) if block_given?
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# @!attribute [r] endpoint
|
|
293
|
+
# @return [URI::Generic] Default Ably Realtime endpoint used for all requests
|
|
294
|
+
def endpoint
|
|
295
|
+
endpoint_for_host(custom_realtime_host || [environment, DOMAIN].compact.join('-'))
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# (see Ably::Rest::Client#register_encoder)
|
|
299
|
+
def register_encoder(encoder)
|
|
300
|
+
rest_client.register_encoder encoder
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# (see Ably::Rest::Client#fallback_hosts)
|
|
304
|
+
def fallback_hosts
|
|
305
|
+
rest_client.fallback_hosts
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# (see Ably::Rest::Client#logger)
|
|
309
|
+
def logger
|
|
310
|
+
@logger ||= Ably::Logger.new(self, log_level, rest_client.logger.custom_logger)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Disable connection recovery, typically used after a connection has been recovered
|
|
314
|
+
# @return [void]
|
|
315
|
+
# @api private
|
|
316
|
+
def disable_automatic_connection_recovery
|
|
317
|
+
@recover = nil
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# @!attribute [r] fallback_endpoint
|
|
321
|
+
# @return [URI::Generic] Fallback endpoint used to connect to the realtime Ably service. Note, after each connection attempt, a new random {Ably::FALLBACK_HOSTS fallback host} or provided fallback hosts are used
|
|
322
|
+
# @api private
|
|
323
|
+
def fallback_endpoint
|
|
324
|
+
unless defined?(@fallback_endpoints) && @fallback_endpoints
|
|
325
|
+
@fallback_endpoints = fallback_hosts.shuffle.map { |fallback_host| endpoint_for_host(fallback_host) }
|
|
326
|
+
@fallback_endpoints << endpoint # Try the original host last if all fallbacks have been used
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
fallback_endpoint_index = connection.manager.retry_count_for_state(:disconnected) + connection.manager.retry_count_for_state(:suspended) - 1
|
|
330
|
+
|
|
331
|
+
@fallback_endpoints[fallback_endpoint_index % @fallback_endpoints.count]
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Retrieves a {Ably::Models::LocalDevice} object that represents the current state of the device as a target for push notifications.
|
|
335
|
+
# @spec RSH8
|
|
336
|
+
# @return [Ably::Models::LocalDevice] A {Ably::Models::LocalDevice} object.
|
|
337
|
+
#
|
|
338
|
+
# @note This is unsupported in the Ruby library
|
|
339
|
+
def device
|
|
340
|
+
raise Ably::Exceptions::PushNotificationsNotSupported, 'This device does not support receiving or subscribing to push notifications. The local device object is not unavailable'
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
private
|
|
344
|
+
def endpoint_for_host(host)
|
|
345
|
+
port = if use_tls?
|
|
346
|
+
custom_tls_port
|
|
347
|
+
else
|
|
348
|
+
custom_port
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
raise ArgumentError, "Custom port must be an Integer or nil" if port && !port.kind_of?(Integer)
|
|
352
|
+
|
|
353
|
+
options = {
|
|
354
|
+
scheme: use_tls? ? 'wss' : 'ws',
|
|
355
|
+
host: host
|
|
356
|
+
}
|
|
357
|
+
options.merge!(port: port) if port
|
|
358
|
+
|
|
359
|
+
URI::Generic.build(options)
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|