ably-rest 0.7.1 → 0.7.3
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 +13 -5
- data/.gitmodules +1 -1
- data/.rspec +1 -0
- data/.travis.yml +7 -3
- data/SPEC.md +495 -419
- data/ably-rest.gemspec +19 -5
- data/lib/ably-rest.rb +9 -1
- data/lib/submodules/ably-ruby/.gitignore +6 -0
- data/lib/submodules/ably-ruby/.rspec +1 -0
- data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
- data/lib/submodules/ably-ruby/.travis.yml +10 -0
- data/lib/submodules/ably-ruby/Gemfile +4 -0
- data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
- data/lib/submodules/ably-ruby/README.md +122 -0
- data/lib/submodules/ably-ruby/Rakefile +34 -0
- data/lib/submodules/ably-ruby/SPEC.md +1794 -0
- data/lib/submodules/ably-ruby/ably.gemspec +36 -0
- data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
- data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
- data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
- data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
- data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
- data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
- data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
- data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
- data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
- data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
- data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
- data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
- data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
- data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
- data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
- data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
- data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
- data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
- data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
- data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
- metadata +182 -27
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
module Ably::Realtime
|
|
2
|
+
class Connection
|
|
3
|
+
# EventMachine WebSocket transport
|
|
4
|
+
# @api private
|
|
5
|
+
class WebsocketTransport < EventMachine::Connection
|
|
6
|
+
include Ably::Modules::EventEmitter
|
|
7
|
+
extend Ably::Modules::Enum
|
|
8
|
+
|
|
9
|
+
# Valid WebSocket connection states
|
|
10
|
+
STATE = ruby_enum('STATE',
|
|
11
|
+
:initialized,
|
|
12
|
+
:connecting,
|
|
13
|
+
:connected,
|
|
14
|
+
:disconnecting,
|
|
15
|
+
:disconnected
|
|
16
|
+
)
|
|
17
|
+
include Ably::Modules::StateEmitter
|
|
18
|
+
|
|
19
|
+
def initialize(connection, url)
|
|
20
|
+
@connection = connection
|
|
21
|
+
@state = STATE.Initialized
|
|
22
|
+
@url = url
|
|
23
|
+
|
|
24
|
+
setup_event_handlers
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Disconnect the socket transport connection and write all pending text.
|
|
28
|
+
# If Disconnected state is not automatically triggered, it will be triggered automatically
|
|
29
|
+
# @return [void]
|
|
30
|
+
# @api public
|
|
31
|
+
def disconnect
|
|
32
|
+
close_connection_after_writing
|
|
33
|
+
change_state STATE.Disconnecting
|
|
34
|
+
create_timer(2) do
|
|
35
|
+
# if connection is not disconnected within 2s, set state as disconnected
|
|
36
|
+
change_state STATE.Disconnected
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Network connection has been established
|
|
41
|
+
# Required {http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Connection EventMachine::Connection} interface
|
|
42
|
+
def post_init
|
|
43
|
+
clear_timer
|
|
44
|
+
change_state STATE.Connecting
|
|
45
|
+
setup_driver
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Remote TCP connection attempt completes successfully
|
|
49
|
+
# Required {http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Connection EventMachine::Connection} interface
|
|
50
|
+
def connection_completed
|
|
51
|
+
change_state STATE.Connected
|
|
52
|
+
start_tls if client.use_tls?
|
|
53
|
+
driver.start
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Called by the event loop whenever data has been received by the network connection.
|
|
57
|
+
# Simply pass onto the WebSocket driver to process and determine content boundaries.
|
|
58
|
+
# Required {http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Connection EventMachine::Connection} interface
|
|
59
|
+
def receive_data(data)
|
|
60
|
+
driver.parse(data)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Called whenever a connection (either a server or client connection) is closed
|
|
64
|
+
# Required {http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Connection EventMachine::Connection} interface
|
|
65
|
+
def unbind
|
|
66
|
+
change_state STATE.Disconnected
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# URL end point including initialization configuration
|
|
70
|
+
# {http://www.rubydoc.info/gems/websocket-driver/0.3.5/WebSocket/Driver WebSocket::Driver} interface
|
|
71
|
+
def url
|
|
72
|
+
@url
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# {http://www.rubydoc.info/gems/websocket-driver/0.3.5/WebSocket/Driver WebSocket::Driver} interface
|
|
76
|
+
def write(data)
|
|
77
|
+
send_data(data)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# True if socket connection is ready to be released
|
|
81
|
+
# i.e. it is not currently connecting or connected
|
|
82
|
+
def ready_for_release?
|
|
83
|
+
!connecting? && !connected?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @!attribute [r] __incoming_protocol_msgbus__
|
|
87
|
+
# @return [Ably::Util::PubSub] Websocket Transport internal incoming protocol message bus
|
|
88
|
+
# @api private
|
|
89
|
+
def __incoming_protocol_msgbus__
|
|
90
|
+
@__incoming_protocol_msgbus__ ||= create_pub_sub_message_bus
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @!attribute [r] __outgoing_protocol_msgbus__
|
|
94
|
+
# @return [Ably::Util::PubSub] Websocket Transport internal outgoing protocol message bus
|
|
95
|
+
# @api private
|
|
96
|
+
def __outgoing_protocol_msgbus__
|
|
97
|
+
@__outgoing_protocol_msgbus__ ||= create_pub_sub_message_bus
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
attr_reader :connection, :driver
|
|
102
|
+
|
|
103
|
+
# Send object down the WebSocket driver connection as a serialized string/byte array based on protocol
|
|
104
|
+
# @param [Object] object to serialize and send to the WebSocket driver
|
|
105
|
+
# @api public
|
|
106
|
+
def send_object(object)
|
|
107
|
+
case client.protocol
|
|
108
|
+
when :json
|
|
109
|
+
driver.text(object.to_json)
|
|
110
|
+
when :msgpack
|
|
111
|
+
driver.binary(object.to_msgpack.unpack('C*'))
|
|
112
|
+
else
|
|
113
|
+
client.logger.fatal "WebsocketTransport: Unsupported protocol '#{client.protocol}' for serialization, object cannot be serialized and sent to Ably over this WebSocket"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def setup_event_handlers
|
|
118
|
+
__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
|
119
|
+
send_object protocol_message
|
|
120
|
+
client.logger.debug "WebsocketTransport: Prot msg sent =>: #{protocol_message.action} #{protocol_message}"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def clear_timer
|
|
125
|
+
if @timer
|
|
126
|
+
@timer.cancel
|
|
127
|
+
@timer = nil
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def create_timer(period)
|
|
132
|
+
@timer = EventMachine::Timer.new(period) do
|
|
133
|
+
yield
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def setup_driver
|
|
138
|
+
@driver = WebSocket::Driver.client(self)
|
|
139
|
+
|
|
140
|
+
driver.on("open") do
|
|
141
|
+
logger.debug "WebsocketTransport: socket opened to #{url}, waiting for Connected protocol message"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
driver.on("message") do |event|
|
|
145
|
+
event_data = parse_event_data(event.data).freeze
|
|
146
|
+
protocol_message = Ably::Models::ProtocolMessage.new(event_data)
|
|
147
|
+
logger.debug "WebsocketTransport: Prot msg recv <=: #{protocol_message.action} #{event_data}"
|
|
148
|
+
|
|
149
|
+
if protocol_message.invalid?
|
|
150
|
+
logger.fatal "WebsocketTransport: Invalid Protocol Message received: #{event_data}\nNo action taken"
|
|
151
|
+
else
|
|
152
|
+
__incoming_protocol_msgbus__.publish :protocol_message, protocol_message
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def client
|
|
158
|
+
connection.client
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Used to log transport messages
|
|
162
|
+
def logger
|
|
163
|
+
connection.logger
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def parse_event_data(data)
|
|
167
|
+
case client.protocol
|
|
168
|
+
when :json
|
|
169
|
+
JSON.parse(data)
|
|
170
|
+
when :msgpack
|
|
171
|
+
MessagePack.unpack(data.pack('C*'))
|
|
172
|
+
else
|
|
173
|
+
client.logger.fatal "WebsocketTransport: Unsupported Protocol Message format #{client.protocol}"
|
|
174
|
+
data
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def create_pub_sub_message_bus
|
|
179
|
+
Ably::Util::PubSub.new(
|
|
180
|
+
coerce_into: Proc.new do |event|
|
|
181
|
+
raise KeyError, "Expected :protocol_message, :#{event} is disallowed" unless event == :protocol_message
|
|
182
|
+
:protocol_message
|
|
183
|
+
end
|
|
184
|
+
)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Ably::Realtime::Models
|
|
2
|
+
# Nil object for Channels, this object is only used within the internal API of this client library
|
|
3
|
+
# @api private
|
|
4
|
+
class NilChannel
|
|
5
|
+
include Ably::Modules::EventEmitter
|
|
6
|
+
extend Ably::Modules::Enum
|
|
7
|
+
STATE = ruby_enum('STATE', Ably::Realtime::Channel::STATE)
|
|
8
|
+
include Ably::Modules::StateEmitter
|
|
9
|
+
include Ably::Modules::UsesStateMachine
|
|
10
|
+
|
|
11
|
+
attr_reader :state_machine
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@state_machine = Ably::Realtime::Channel::ChannelStateMachine.new(self)
|
|
15
|
+
@state = STATE(state_machine.current_state)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def name
|
|
19
|
+
'Nil channel'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def __incoming_msgbus__
|
|
23
|
+
@__incoming_msgbus__ ||= Ably::Util::PubSub.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def logger
|
|
27
|
+
@logger ||= Ably::Models::NilLogger.new
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
module Ably::Realtime
|
|
2
|
+
# Presence provides access to presence operations and state for the associated Channel
|
|
3
|
+
class Presence
|
|
4
|
+
include Ably::Modules::EventEmitter
|
|
5
|
+
include Ably::Modules::AsyncWrapper
|
|
6
|
+
extend Ably::Modules::Enum
|
|
7
|
+
|
|
8
|
+
STATE = ruby_enum('STATE',
|
|
9
|
+
:initialized,
|
|
10
|
+
:entering,
|
|
11
|
+
:entered,
|
|
12
|
+
:leaving,
|
|
13
|
+
:left,
|
|
14
|
+
:failed
|
|
15
|
+
)
|
|
16
|
+
include Ably::Modules::StateEmitter
|
|
17
|
+
|
|
18
|
+
# {Ably::Realtime::Channel} this Presence object is associated with
|
|
19
|
+
# @return [Ably::Realtime::Channel]
|
|
20
|
+
attr_reader :channel
|
|
21
|
+
|
|
22
|
+
# A unique identifier for this channel client based on their connection, disambiguating situations
|
|
23
|
+
# where a given client_id is present on multiple connections simultaneously.
|
|
24
|
+
# @return [String]
|
|
25
|
+
attr_reader :connection_id
|
|
26
|
+
|
|
27
|
+
# The client_id for the member present on this channel
|
|
28
|
+
# @return [String]
|
|
29
|
+
attr_reader :client_id
|
|
30
|
+
|
|
31
|
+
# The data for the member present on this channel
|
|
32
|
+
# @return [String]
|
|
33
|
+
attr_reader :data
|
|
34
|
+
|
|
35
|
+
def initialize(channel)
|
|
36
|
+
@channel = channel
|
|
37
|
+
@state = STATE.Initialized
|
|
38
|
+
@members = Hash.new
|
|
39
|
+
@subscriptions = Hash.new { |hash, key| hash[key] = [] }
|
|
40
|
+
@client_id = client.client_id
|
|
41
|
+
|
|
42
|
+
setup_event_handlers
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Enter this client into this channel. This client will be added to the presence set
|
|
46
|
+
# and presence subscribers will see an enter message for this client.
|
|
47
|
+
#
|
|
48
|
+
# @param [Hash] options an options Hash to specify client data and/or client ID
|
|
49
|
+
# @option options [String] :data optional data (eg a status message) for this member
|
|
50
|
+
# @option options [String] :client_id the optional id of the client.
|
|
51
|
+
# This option is provided to support connections from server instances that act on behalf of
|
|
52
|
+
# multiple client_ids. In order to be able to enter the channel with this method, the client
|
|
53
|
+
# library must have been instanced either with a key, or with a token bound to the wildcard clientId.
|
|
54
|
+
#
|
|
55
|
+
# @yield [Ably::Realtime::Presence] On success, will call the block with this {Ably::Realtime::Presence} object
|
|
56
|
+
# @return [EventMachine::Deferrable] Deferrable that supports both success (callback) and failure (errback) callbacks
|
|
57
|
+
#
|
|
58
|
+
def enter(options = {}, &success_block)
|
|
59
|
+
@client_id = options.fetch(:client_id, client_id)
|
|
60
|
+
@data = options.fetch(:data, nil)
|
|
61
|
+
deferrable = EventMachine::DefaultDeferrable.new
|
|
62
|
+
|
|
63
|
+
raise Ably::Exceptions::Standard.new('Unable to enter presence channel without a client_id', 400, 91000) unless client_id
|
|
64
|
+
return deferrable_succeed(deferrable, &success_block) if state == STATE.Entered
|
|
65
|
+
|
|
66
|
+
ensure_channel_attached(deferrable) do
|
|
67
|
+
if entering?
|
|
68
|
+
once_or_if(STATE.Entered, else: proc { |args| deferrable_fail deferrable, *args }) do
|
|
69
|
+
deferrable_succeed deferrable, &success_block
|
|
70
|
+
end
|
|
71
|
+
else
|
|
72
|
+
change_state STATE.Entering
|
|
73
|
+
send_protocol_message_and_transition_state_to(
|
|
74
|
+
Ably::Models::PresenceMessage::ACTION.Enter,
|
|
75
|
+
deferrable: deferrable,
|
|
76
|
+
target_state: STATE.Entered,
|
|
77
|
+
client_id: client_id,
|
|
78
|
+
data: data,
|
|
79
|
+
failed_state: STATE.Failed,
|
|
80
|
+
&success_block
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Enter the specified client_id into this channel. The given client will be added to the
|
|
87
|
+
# presence set and presence subscribers will see a corresponding presence message.
|
|
88
|
+
# This method is provided to support connections (e.g. connections from application
|
|
89
|
+
# server instances) that act on behalf of multiple client_ids. In order to be able to
|
|
90
|
+
# enter the channel with this method, the client library must have been instanced
|
|
91
|
+
# either with a key, or with a token bound to the wildcard client_id
|
|
92
|
+
#
|
|
93
|
+
# @param [String] client_id id of the client
|
|
94
|
+
#
|
|
95
|
+
# @param [Hash] options an options Hash for this client event
|
|
96
|
+
# @option options [String] :data optional data (eg a status message) for this member
|
|
97
|
+
#
|
|
98
|
+
# @yield [Ably::Realtime::Presence] On success, will call the block with this {Ably::Realtime::Presence} object
|
|
99
|
+
# @return [EventMachine::Deferrable] Deferrable that supports both success (callback) and failure (errback) callbacks
|
|
100
|
+
#
|
|
101
|
+
def enter_client(client_id, options = {}, &success_block)
|
|
102
|
+
raise ArgumentError, 'options must be a Hash' unless options.kind_of?(Hash)
|
|
103
|
+
raise Ably::Exceptions::Standard.new('Unable to enter presence channel without a client_id', 400, 91000) unless client_id
|
|
104
|
+
|
|
105
|
+
send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Enter, client_id, options, &success_block)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Leave this client from this channel. This client will be removed from the presence
|
|
109
|
+
# set and presence subscribers will see a leave message for this client.
|
|
110
|
+
#
|
|
111
|
+
# @param [Hash,String] options an options Hash to specify client data and/or client ID
|
|
112
|
+
# @option options [String] :data optional data (eg a status message) for this member
|
|
113
|
+
#
|
|
114
|
+
# @yield (see Presence#enter)
|
|
115
|
+
# @return (see Presence#enter)
|
|
116
|
+
#
|
|
117
|
+
def leave(options = {}, &success_block)
|
|
118
|
+
@data = options.fetch(:data, data) # nil value defaults leave data to existing value
|
|
119
|
+
deferrable = EventMachine::DefaultDeferrable.new
|
|
120
|
+
|
|
121
|
+
raise Ably::Exceptions::Standard.new('Unable to leave presence channel that is not entered', 400, 91002) unless able_to_leave?
|
|
122
|
+
return deferrable_succeed(deferrable, &success_block) if state == STATE.Left
|
|
123
|
+
|
|
124
|
+
ensure_channel_attached(deferrable) do
|
|
125
|
+
if leaving?
|
|
126
|
+
once_or_if(STATE.Left, else: proc { |error|deferrable_fail deferrable, *args }) do
|
|
127
|
+
deferrable_succeed deferrable, &success_block
|
|
128
|
+
end
|
|
129
|
+
else
|
|
130
|
+
change_state STATE.Leaving
|
|
131
|
+
send_protocol_message_and_transition_state_to(
|
|
132
|
+
Ably::Models::PresenceMessage::ACTION.Leave,
|
|
133
|
+
deferrable: deferrable,
|
|
134
|
+
target_state: STATE.Left,
|
|
135
|
+
client_id: client_id,
|
|
136
|
+
data: data,
|
|
137
|
+
failed_state: STATE.Failed,
|
|
138
|
+
&success_block
|
|
139
|
+
)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Leave a given client_id from this channel. This client will be removed from the
|
|
145
|
+
# presence set and presence subscribers will see a leave message for this client.
|
|
146
|
+
#
|
|
147
|
+
# @param (see Presence#enter_client)
|
|
148
|
+
# @option options (see Presence#enter_client)
|
|
149
|
+
#
|
|
150
|
+
# @yield (see Presence#enter_client)
|
|
151
|
+
# @return (see Presence#enter_client)
|
|
152
|
+
#
|
|
153
|
+
def leave_client(client_id, options = {}, &success_block)
|
|
154
|
+
raise ArgumentError, 'options must be a Hash' unless options.kind_of?(Hash)
|
|
155
|
+
raise Ably::Exceptions::Standard.new('Unable to leave presence channel without a client_id', 400, 91000) unless client_id
|
|
156
|
+
|
|
157
|
+
send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Leave, client_id, options, &success_block)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Update the presence data for this client. If the client is not already a member of
|
|
161
|
+
# the presence set it will be added, and presence subscribers will see an enter or
|
|
162
|
+
# update message for this client.
|
|
163
|
+
#
|
|
164
|
+
# @param [Hash,String] options an options Hash to specify client data
|
|
165
|
+
# @option options [String] :data optional data (eg a status message) for this member
|
|
166
|
+
#
|
|
167
|
+
# @yield (see Presence#enter)
|
|
168
|
+
# @return (see Presence#enter)
|
|
169
|
+
#
|
|
170
|
+
def update(options = {}, &success_block)
|
|
171
|
+
@data = options.fetch(:data, nil)
|
|
172
|
+
deferrable = EventMachine::DefaultDeferrable.new
|
|
173
|
+
|
|
174
|
+
ensure_channel_attached(deferrable) do
|
|
175
|
+
send_protocol_message_and_transition_state_to(
|
|
176
|
+
Ably::Models::PresenceMessage::ACTION.Update,
|
|
177
|
+
deferrable: deferrable,
|
|
178
|
+
target_state: STATE.Entered,
|
|
179
|
+
client_id: client_id,
|
|
180
|
+
data: data,
|
|
181
|
+
&success_block
|
|
182
|
+
)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Update the presence data for a specified client_id into this channel.
|
|
187
|
+
# If the client is not already a member of the presence set it will be added, and
|
|
188
|
+
# presence subscribers will see an enter or update message for this client.
|
|
189
|
+
# As with {#enter_client}, the connection must be authenticated in a way that
|
|
190
|
+
# enables it to represent an arbitrary clientId.
|
|
191
|
+
#
|
|
192
|
+
# @param (see Presence#enter_client)
|
|
193
|
+
# @option options (see Presence#enter_client)
|
|
194
|
+
#
|
|
195
|
+
# @yield (see Presence#enter_client)
|
|
196
|
+
# @return (see Presence#enter_client)
|
|
197
|
+
#
|
|
198
|
+
def update_client(client_id, options = {}, &success_block)
|
|
199
|
+
raise ArgumentError, 'options must be a Hash' unless options.kind_of?(Hash)
|
|
200
|
+
raise Ably::Exceptions::Standard.new('Unable to enter presence channel without a client_id', 400, 91000) unless client_id
|
|
201
|
+
|
|
202
|
+
send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Update, client_id, options, &success_block)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Get the presence state for this Channel.
|
|
206
|
+
#
|
|
207
|
+
# @param [Hash,String] options an options Hash to filter members
|
|
208
|
+
# @option options [String] :client_id optional client_id for the member
|
|
209
|
+
# @option options [String] :connection_id optional connection_id for the member
|
|
210
|
+
# @option options [String] :wait_for_sync defaults to true, if false the get method returns the current list of members and does not wait for the presence sync to complete
|
|
211
|
+
#
|
|
212
|
+
# @yield [Array<Ably::Models::PresenceMessage>] array of members or the member
|
|
213
|
+
#
|
|
214
|
+
# @return [EventMachine::Deferrable] Deferrable that supports both success (callback) and failure (errback) callbacks
|
|
215
|
+
#
|
|
216
|
+
def get(options = {})
|
|
217
|
+
wait_for_sync = options.fetch(:wait_for_sync, true)
|
|
218
|
+
deferrable = EventMachine::DefaultDeferrable.new
|
|
219
|
+
|
|
220
|
+
ensure_channel_attached(deferrable) do
|
|
221
|
+
result_block = proc do
|
|
222
|
+
members.map { |key, presence| presence }.tap do |filtered_members|
|
|
223
|
+
filtered_members.keep_if { |presence| presence.connection_id == options[:connection_id] } if options[:connection_id]
|
|
224
|
+
filtered_members.keep_if { |presence| presence.client_id == options[:client_id] } if options[:client_id]
|
|
225
|
+
end.tap do |current_members|
|
|
226
|
+
yield current_members if block_given?
|
|
227
|
+
deferrable.succeed current_members
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
if !wait_for_sync || sync_complete?
|
|
232
|
+
result_block.call
|
|
233
|
+
else
|
|
234
|
+
sync_pubsub.once(:done) do
|
|
235
|
+
result_block.call
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
sync_pubsub.once(:failed) do |error|
|
|
239
|
+
deferrable.fail error
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Subscribe to presence events on the associated Channel.
|
|
246
|
+
# This implicitly attaches the Channel if it is not already attached.
|
|
247
|
+
#
|
|
248
|
+
# @param action [Ably::Models::PresenceMessage::ACTION] Optional, the state change action to subscribe to. Defaults to all presence actions
|
|
249
|
+
# @yield [Ably::Models::PresenceMessage] For each presence state change event, the block is called
|
|
250
|
+
#
|
|
251
|
+
# @return [void]
|
|
252
|
+
#
|
|
253
|
+
def subscribe(action = :all, &callback)
|
|
254
|
+
ensure_channel_attached do
|
|
255
|
+
subscriptions[message_action_key(action)] << callback
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Unsubscribe the matching block for presence events on the associated Channel.
|
|
260
|
+
# If a block is not provided, all subscriptions will be unsubscribed
|
|
261
|
+
#
|
|
262
|
+
# @param action [Ably::Models::PresenceMessage::ACTION] Optional, the state change action to subscribe to. Defaults to all presence actions
|
|
263
|
+
#
|
|
264
|
+
# @return [void]
|
|
265
|
+
#
|
|
266
|
+
def unsubscribe(action = :all, &callback)
|
|
267
|
+
if message_action_key(action) == :all
|
|
268
|
+
subscriptions.keys
|
|
269
|
+
else
|
|
270
|
+
Array(message_action_key(action))
|
|
271
|
+
end.each do |key|
|
|
272
|
+
subscriptions[key].delete_if do |block|
|
|
273
|
+
!block_given? || callback == block
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Return the presence messages history for the channel
|
|
279
|
+
#
|
|
280
|
+
# @param (see Ably::Rest::Presence#history)
|
|
281
|
+
# @option options (see Ably::Rest::Presence#history)
|
|
282
|
+
#
|
|
283
|
+
# @yield [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] An Array of {Ably::Models::PresenceMessage} objects that supports paging (#next_page, #first_page)
|
|
284
|
+
#
|
|
285
|
+
# @return [EventMachine::Deferrable]
|
|
286
|
+
#
|
|
287
|
+
def history(options = {}, &callback)
|
|
288
|
+
async_wrap(callback) do
|
|
289
|
+
rest_presence.history(options.merge(async_blocking_operations: true))
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# When attaching to a channel that has members present, the client and server
|
|
294
|
+
# initiate a sync automatically so that the client has a complete list of members.
|
|
295
|
+
#
|
|
296
|
+
# Whilst this sync is happening, this method returns false
|
|
297
|
+
#
|
|
298
|
+
# @return [Boolean]
|
|
299
|
+
def sync_complete?
|
|
300
|
+
sync_complete
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Expect SYNC ProtocolMessages with a list of current members on this channel from the server
|
|
304
|
+
#
|
|
305
|
+
# @return [void]
|
|
306
|
+
#
|
|
307
|
+
# @api private
|
|
308
|
+
def sync_started
|
|
309
|
+
@sync_complete = false
|
|
310
|
+
|
|
311
|
+
sync_pubsub.once(:sync_complete) do
|
|
312
|
+
sync_changes_backlog.each do |presence_message|
|
|
313
|
+
apply_member_presence_changes presence_message
|
|
314
|
+
end
|
|
315
|
+
sync_completed
|
|
316
|
+
sync_pubsub.trigger :done
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
channel.once_or_if [:detached, :failed] do |error|
|
|
320
|
+
sync_completed
|
|
321
|
+
sync_pubsub.trigger :failed, error
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# The server has indicated that no members are present on this channel and no SYNC is expected,
|
|
326
|
+
# or that the SYNC has now completed
|
|
327
|
+
#
|
|
328
|
+
# @return [void]
|
|
329
|
+
#
|
|
330
|
+
# @api private
|
|
331
|
+
def sync_completed
|
|
332
|
+
@sync_complete = true
|
|
333
|
+
@sync_changes_backlog = []
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Update the SYNC serial from the ProtocolMessage so that SYNC can be resumed.
|
|
337
|
+
# If the serial is nil, or the part after the first : is empty, then the SYNC is complete
|
|
338
|
+
#
|
|
339
|
+
# @return [void]
|
|
340
|
+
#
|
|
341
|
+
# @api private
|
|
342
|
+
def update_sync_serial(serial)
|
|
343
|
+
@sync_serial = serial
|
|
344
|
+
sync_pubsub.trigger :sync_complete if sync_serial_cursor_at_end?
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# @!attribute [r] __incoming_msgbus__
|
|
348
|
+
# @return [Ably::Util::PubSub] Client library internal channel incoming message bus
|
|
349
|
+
# @api private
|
|
350
|
+
def __incoming_msgbus__
|
|
351
|
+
@__incoming_msgbus__ ||= Ably::Util::PubSub.new(
|
|
352
|
+
coerce_into: Proc.new { |event| Ably::Models::ProtocolMessage::ACTION(event) }
|
|
353
|
+
)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
private
|
|
357
|
+
attr_reader :members, :subscriptions, :sync_serial, :sync_complete
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
# A simple PubSub class used to publish synchronisation state changes
|
|
361
|
+
def sync_pubsub
|
|
362
|
+
@sync_pubsub ||= Ably::Util::PubSub.new
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# During a SYNC of presence members, all enter, update and leave events are queued for processing once the SYNC is complete
|
|
366
|
+
def sync_changes_backlog
|
|
367
|
+
@sync_changes_backlog ||= []
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# When channel serial in ProtocolMessage SYNC is nil or
|
|
371
|
+
# an empty cursor appears after the ':' such as 'cf30e75054887:psl_7g:client:189'
|
|
372
|
+
# then there are no more SYNC messages to come
|
|
373
|
+
def sync_serial_cursor_at_end?
|
|
374
|
+
sync_serial.nil? || sync_serial.to_s.match(/^[\w-]+:?$/)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def able_to_leave?
|
|
378
|
+
entering? || entered?
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def setup_event_handlers
|
|
382
|
+
__incoming_msgbus__.subscribe(:presence, :sync) do |presence_message|
|
|
383
|
+
presence_message.decode self.channel
|
|
384
|
+
update_members_from_presence_message presence_message
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
channel.on(Channel::STATE.Detaching) do
|
|
388
|
+
change_state STATE.Leaving
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
channel.on(Channel::STATE.Detached) do
|
|
392
|
+
change_state STATE.Left
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
channel.on(Channel::STATE.Failed) do
|
|
396
|
+
change_state STATE.Failed unless left? || initialized?
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
on(STATE.Entered) do |message|
|
|
400
|
+
@connection_id = message.connection_id
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# @return [Ably::Models::PresenceMessage] presence message is returned allowing callbacks to be added
|
|
405
|
+
def send_presence_protocol_message(presence_action, client_id, options = {})
|
|
406
|
+
presence_message = create_presence_message(presence_action, client_id, options)
|
|
407
|
+
unless presence_message.client_id
|
|
408
|
+
raise Ably::Exceptions::Standard.new('Unable to enter create presence message without a client_id', 400, 91000)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
protocol_message = {
|
|
412
|
+
action: Ably::Models::ProtocolMessage::ACTION.Presence,
|
|
413
|
+
channel: channel.name,
|
|
414
|
+
presence: [presence_message]
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
client.connection.send_protocol_message protocol_message
|
|
418
|
+
|
|
419
|
+
presence_message
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def create_presence_message(action, client_id, options = {})
|
|
423
|
+
model = {
|
|
424
|
+
action: Ably::Models::PresenceMessage.ACTION(action).to_i,
|
|
425
|
+
clientId: client_id
|
|
426
|
+
}
|
|
427
|
+
model.merge!(data: options.fetch(:data)) if options.has_key?(:data)
|
|
428
|
+
|
|
429
|
+
Ably::Models::PresenceMessage.new(model, nil).tap do |presence_message|
|
|
430
|
+
presence_message.encode self.channel
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def update_members_from_presence_message(presence_message)
|
|
435
|
+
unless presence_message.connection_id
|
|
436
|
+
Ably::Exceptions::ProtocolError.new("Protocol error, presence message is missing connectionId", 400, 80013)
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
if sync_complete?
|
|
440
|
+
apply_member_presence_changes presence_message
|
|
441
|
+
else
|
|
442
|
+
if presence_message.action == Ably::Models::PresenceMessage::ACTION.Present
|
|
443
|
+
add_presence_member presence_message
|
|
444
|
+
publish_presence_member_state_change presence_message
|
|
445
|
+
else
|
|
446
|
+
sync_changes_backlog << presence_message
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def apply_member_presence_changes(presence_message)
|
|
452
|
+
case presence_message.action
|
|
453
|
+
when Ably::Models::PresenceMessage::ACTION.Enter, Ably::Models::PresenceMessage::ACTION.Update
|
|
454
|
+
add_presence_member presence_message
|
|
455
|
+
when Ably::Models::PresenceMessage::ACTION.Leave
|
|
456
|
+
remove_presence_member presence_message
|
|
457
|
+
else
|
|
458
|
+
Ably::Exceptions::ProtocolError.new("Protocol error, unknown presence action #{presence_message.action}", 400, 80013)
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
publish_presence_member_state_change presence_message
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def add_presence_member(presence_message)
|
|
465
|
+
members[presence_message.member_key] = presence_message
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def remove_presence_member(presence_message)
|
|
469
|
+
members.delete presence_message.member_key
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def publish_presence_member_state_change(presence_message)
|
|
473
|
+
subscriptions[:all].each { |cb| cb.call(presence_message) }
|
|
474
|
+
subscriptions[presence_message.action].each { |cb| cb.call(presence_message) }
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def ensure_channel_attached(deferrable = nil)
|
|
478
|
+
if channel.attached?
|
|
479
|
+
yield
|
|
480
|
+
else
|
|
481
|
+
attach_channel_then { yield }
|
|
482
|
+
end
|
|
483
|
+
deferrable
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def send_protocol_message_and_transition_state_to(action, options = {}, &success_block)
|
|
487
|
+
deferrable = options.fetch(:deferrable) { raise ArgumentError, 'option :deferrable is required' }
|
|
488
|
+
client_id = options.fetch(:client_id) { raise ArgumentError, 'option :client_id is required' }
|
|
489
|
+
target_state = options.fetch(:target_state, nil)
|
|
490
|
+
failed_state = options.fetch(:failed_state, nil)
|
|
491
|
+
|
|
492
|
+
protocol_message_options = if options.has_key?(:data)
|
|
493
|
+
{ data: options.fetch(:data) }
|
|
494
|
+
else
|
|
495
|
+
{ }
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
send_presence_protocol_message(action, client_id, protocol_message_options).tap do |protocol_message|
|
|
499
|
+
protocol_message.callback do |message|
|
|
500
|
+
change_state target_state, message if target_state
|
|
501
|
+
deferrable_succeed deferrable, &success_block
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
protocol_message.errback do |message, error|
|
|
505
|
+
change_state failed_state, error if failed_state
|
|
506
|
+
deferrable_fail deferrable, error
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def deferrable_succeed(deferrable, *args)
|
|
512
|
+
yield self, *args if block_given?
|
|
513
|
+
EventMachine.next_tick { deferrable.succeed self, *args } # allow callback to be added to the returned Deferrable before calling succeed
|
|
514
|
+
deferrable
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def deferrable_fail(deferrable, *args)
|
|
518
|
+
yield self, *args if block_given?
|
|
519
|
+
EventMachine.next_tick { deferrable.fail self, *args } # allow errback to be added to the returned Deferrable
|
|
520
|
+
deferrable
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def send_presence_action_for_client(action, client_id, options = {}, &success_block)
|
|
524
|
+
deferrable = EventMachine::DefaultDeferrable.new
|
|
525
|
+
|
|
526
|
+
ensure_channel_attached(deferrable) do
|
|
527
|
+
send_presence_protocol_message(action, client_id, options).tap do |protocol_message|
|
|
528
|
+
protocol_message.callback { |message| deferrable_succeed deferrable, &success_block }
|
|
529
|
+
protocol_message.errback { |message| deferrable_fail deferrable }
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
def attach_channel_then
|
|
535
|
+
if channel.detached? || channel.failed?
|
|
536
|
+
raise Ably::Exceptions::Standard.new('Unable to enter presence channel in detached or failed action', 400, 91001)
|
|
537
|
+
else
|
|
538
|
+
channel.once(Channel::STATE.Attached) { yield }
|
|
539
|
+
channel.attach
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
def client
|
|
544
|
+
channel.client
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def rest_presence
|
|
548
|
+
client.rest_client.channel(channel.name).presence
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
# Used by {Ably::Modules::StateEmitter} to debug action changes
|
|
552
|
+
def logger
|
|
553
|
+
client.logger
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
def message_action_key(action)
|
|
557
|
+
if action == :all
|
|
558
|
+
:all
|
|
559
|
+
else
|
|
560
|
+
Ably::Models::PresenceMessage.ACTION(action)
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
end
|