ably 0.6.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.ruby-version.old +1 -0
- data/.travis.yml +0 -2
- data/Rakefile +22 -4
- data/SPEC.md +1676 -0
- data/ably.gemspec +1 -1
- data/lib/ably.rb +0 -8
- data/lib/ably/auth.rb +54 -46
- data/lib/ably/exceptions.rb +19 -5
- data/lib/ably/logger.rb +1 -1
- data/lib/ably/models/error_info.rb +1 -1
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +11 -9
- data/lib/ably/models/message.rb +15 -12
- data/lib/ably/models/message_encoders/base.rb +6 -5
- data/lib/ably/models/message_encoders/base64.rb +1 -0
- data/lib/ably/models/message_encoders/cipher.rb +6 -3
- data/lib/ably/models/message_encoders/json.rb +1 -0
- data/lib/ably/models/message_encoders/utf8.rb +2 -9
- data/lib/ably/models/nil_logger.rb +20 -0
- data/lib/ably/models/paginated_resource.rb +5 -2
- data/lib/ably/models/presence_message.rb +21 -12
- data/lib/ably/models/protocol_message.rb +22 -6
- data/lib/ably/modules/ably.rb +11 -0
- data/lib/ably/modules/async_wrapper.rb +2 -0
- data/lib/ably/modules/conversions.rb +23 -3
- data/lib/ably/modules/encodeable.rb +2 -1
- data/lib/ably/modules/enum.rb +2 -0
- data/lib/ably/modules/event_emitter.rb +7 -1
- data/lib/ably/modules/event_machine_helpers.rb +2 -0
- data/lib/ably/modules/http_helpers.rb +2 -0
- data/lib/ably/modules/model_common.rb +12 -2
- data/lib/ably/modules/state_emitter.rb +76 -0
- data/lib/ably/modules/state_machine.rb +53 -0
- data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/ably/modules/uses_state_machine.rb +74 -0
- data/lib/ably/realtime.rb +4 -2
- data/lib/ably/realtime/channel.rb +51 -58
- data/lib/ably/realtime/channel/channel_manager.rb +91 -0
- data/lib/ably/realtime/channel/channel_state_machine.rb +68 -0
- data/lib/ably/realtime/client.rb +70 -26
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +31 -13
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
- data/lib/ably/realtime/connection.rb +135 -92
- data/lib/ably/realtime/connection/connection_manager.rb +216 -33
- data/lib/ably/realtime/connection/connection_state_machine.rb +30 -73
- data/lib/ably/realtime/models/nil_channel.rb +10 -1
- data/lib/ably/realtime/presence.rb +336 -92
- data/lib/ably/rest.rb +2 -2
- data/lib/ably/rest/channel.rb +13 -4
- data/lib/ably/rest/client.rb +138 -38
- data/lib/ably/rest/middleware/logger.rb +24 -3
- data/lib/ably/rest/presence.rb +12 -7
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/channel_history_spec.rb +101 -85
- data/spec/acceptance/realtime/channel_spec.rb +461 -120
- data/spec/acceptance/realtime/client_spec.rb +119 -0
- data/spec/acceptance/realtime/connection_failures_spec.rb +499 -0
- data/spec/acceptance/realtime/connection_spec.rb +571 -97
- data/spec/acceptance/realtime/message_spec.rb +347 -333
- data/spec/acceptance/realtime/presence_history_spec.rb +35 -40
- data/spec/acceptance/realtime/presence_spec.rb +769 -239
- data/spec/acceptance/realtime/stats_spec.rb +14 -22
- data/spec/acceptance/realtime/time_spec.rb +16 -20
- data/spec/acceptance/rest/auth_spec.rb +425 -364
- data/spec/acceptance/rest/base_spec.rb +108 -176
- data/spec/acceptance/rest/channel_spec.rb +89 -89
- data/spec/acceptance/rest/channels_spec.rb +30 -32
- data/spec/acceptance/rest/client_spec.rb +273 -0
- data/spec/acceptance/rest/encoders_spec.rb +185 -0
- data/spec/acceptance/rest/message_spec.rb +186 -163
- data/spec/acceptance/rest/presence_spec.rb +150 -111
- data/spec/acceptance/rest/stats_spec.rb +45 -40
- data/spec/acceptance/rest/time_spec.rb +8 -10
- data/spec/rspec_config.rb +10 -1
- data/spec/shared/client_initializer_behaviour.rb +212 -0
- data/spec/{support/model_helper.rb → shared/model_behaviour.rb} +6 -6
- data/spec/{support/protocol_msgbus_helper.rb → shared/protocol_msgbus_behaviour.rb} +1 -1
- data/spec/spec_helper.rb +9 -0
- data/spec/support/api_helper.rb +11 -0
- data/spec/support/event_machine_helper.rb +101 -3
- data/spec/support/markdown_spec_formatter.rb +90 -0
- data/spec/support/private_api_formatter.rb +36 -0
- data/spec/support/protocol_helper.rb +32 -0
- data/spec/support/random_helper.rb +15 -0
- data/spec/support/test_app.rb +4 -0
- data/spec/unit/auth_spec.rb +68 -0
- data/spec/unit/logger_spec.rb +77 -66
- data/spec/unit/models/error_info_spec.rb +1 -1
- data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +2 -3
- data/spec/unit/models/message_encoders/base64_spec.rb +2 -2
- data/spec/unit/models/message_encoders/cipher_spec.rb +2 -2
- data/spec/unit/models/message_encoders/utf8_spec.rb +2 -46
- data/spec/unit/models/message_spec.rb +160 -15
- data/spec/unit/models/paginated_resource_spec.rb +29 -27
- data/spec/unit/models/presence_message_spec.rb +163 -20
- data/spec/unit/models/protocol_message_spec.rb +43 -8
- data/spec/unit/modules/async_wrapper_spec.rb +2 -3
- data/spec/unit/modules/conversions_spec.rb +1 -1
- data/spec/unit/modules/enum_spec.rb +2 -3
- data/spec/unit/modules/event_emitter_spec.rb +62 -5
- data/spec/unit/modules/state_emitter_spec.rb +283 -0
- data/spec/unit/realtime/channel_spec.rb +107 -2
- data/spec/unit/realtime/channels_spec.rb +1 -0
- data/spec/unit/realtime/client_spec.rb +8 -48
- data/spec/unit/realtime/connection_spec.rb +3 -3
- data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +2 -2
- data/spec/unit/realtime/presence_spec.rb +13 -4
- data/spec/unit/realtime/realtime_spec.rb +0 -11
- data/spec/unit/realtime/websocket_transport_spec.rb +2 -2
- data/spec/unit/rest/channel_spec.rb +109 -0
- data/spec/unit/rest/channels_spec.rb +4 -3
- data/spec/unit/rest/client_spec.rb +30 -125
- data/spec/unit/rest/rest_spec.rb +10 -0
- data/spec/unit/util/crypto_spec.rb +10 -5
- data/spec/unit/util/pub_sub_spec.rb +5 -5
- metadata +44 -12
- data/spec/integration/modules/state_emitter_spec.rb +0 -80
- data/spec/integration/rest/auth.rb +0 -9
@@ -1,13 +1,18 @@
|
|
1
1
|
module Ably::Realtime::Models
|
2
2
|
# Nil object for Channels, this object is only used within the internal API of this client library
|
3
|
+
# @api private
|
3
4
|
class NilChannel
|
4
5
|
include Ably::Modules::EventEmitter
|
5
6
|
extend Ably::Modules::Enum
|
6
7
|
STATE = ruby_enum('STATE', Ably::Realtime::Channel::STATE)
|
7
8
|
include Ably::Modules::StateEmitter
|
9
|
+
include Ably::Modules::UsesStateMachine
|
10
|
+
|
11
|
+
attr_reader :state_machine
|
8
12
|
|
9
13
|
def initialize
|
10
|
-
@
|
14
|
+
@state_machine = Ably::Realtime::Channel::ChannelStateMachine.new(self)
|
15
|
+
@state = STATE(state_machine.current_state)
|
11
16
|
end
|
12
17
|
|
13
18
|
def name
|
@@ -17,5 +22,9 @@ module Ably::Realtime::Models
|
|
17
22
|
def __incoming_msgbus__
|
18
23
|
@__incoming_msgbus__ ||= Ably::Util::PubSub.new
|
19
24
|
end
|
25
|
+
|
26
|
+
def logger
|
27
|
+
@logger ||= Ably::Models::NilLogger.new
|
28
|
+
end
|
20
29
|
end
|
21
30
|
end
|
@@ -16,15 +16,21 @@ module Ably::Realtime
|
|
16
16
|
include Ably::Modules::StateEmitter
|
17
17
|
|
18
18
|
# {Ably::Realtime::Channel} this Presence object is associated with
|
19
|
-
# @return
|
19
|
+
# @return [Ably::Realtime::Channel]
|
20
20
|
attr_reader :channel
|
21
21
|
|
22
|
-
# A unique
|
23
|
-
# client_id is present on multiple connections simultaneously.
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
28
34
|
|
29
35
|
def initialize(channel)
|
30
36
|
@channel = channel
|
@@ -32,8 +38,6 @@ module Ably::Realtime
|
|
32
38
|
@members = Hash.new
|
33
39
|
@subscriptions = Hash.new { |hash, key| hash[key] = [] }
|
34
40
|
@client_id = client.client_id
|
35
|
-
@data = nil
|
36
|
-
@member_id = nil
|
37
41
|
|
38
42
|
setup_event_handlers
|
39
43
|
end
|
@@ -41,102 +45,201 @@ module Ably::Realtime
|
|
41
45
|
# Enter this client into this channel. This client will be added to the presence set
|
42
46
|
# and presence subscribers will see an enter message for this client.
|
43
47
|
#
|
44
|
-
# @param [Hash
|
48
|
+
# @param [Hash] options an options Hash to specify client data and/or client ID
|
45
49
|
# @option options [String] :data optional data (eg a status message) for this member
|
46
50
|
# @option options [String] :client_id the optional id of the client.
|
47
51
|
# This option is provided to support connections from server instances that act on behalf of
|
48
52
|
# multiple client_ids. In order to be able to enter the channel with this method, the client
|
49
53
|
# library must have been instanced either with a key, or with a token bound to the wildcard clientId.
|
50
54
|
#
|
51
|
-
# @yield [Ably::Realtime::Presence] On success, will call the block with
|
52
|
-
#
|
53
|
-
# @return [Ably::Models::PresenceMessage] Deferrable {Ably::Models::PresenceMessage} that supports both success (callback) and failure (errback) callbacks
|
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
|
54
57
|
#
|
55
|
-
def enter(options = {}, &
|
58
|
+
def enter(options = {}, &success_block)
|
56
59
|
@client_id = options.fetch(:client_id, client_id)
|
57
60
|
@data = options.fetch(:data, data)
|
61
|
+
deferrable = EventMachine::DefaultDeferrable.new
|
58
62
|
|
59
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
|
60
65
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
ensure_channel_attached do
|
67
|
-
once(STATE.Entered) { blk.call self } if block_given?
|
68
|
-
|
69
|
-
if !entering?
|
70
|
-
change_state STATE.Entering
|
71
|
-
send_presence_protocol_message(Ably::Models::PresenceMessage::ACTION.Enter).tap do |deferrable|
|
72
|
-
deferrable.errback { |message, error| change_state STATE.Failed, error }
|
73
|
-
deferrable.callback { |message| change_state STATE.Entered, message }
|
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
|
74
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
|
+
)
|
75
82
|
end
|
76
83
|
end
|
77
84
|
end
|
78
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
|
+
|
79
108
|
# Leave this client from this channel. This client will be removed from the presence
|
80
109
|
# set and presence subscribers will see a leave message for this client.
|
81
110
|
#
|
82
|
-
# @param
|
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
|
+
#
|
83
114
|
# @yield (see Presence#enter)
|
84
115
|
# @return (see Presence#enter)
|
85
116
|
#
|
86
|
-
def leave(options = {}, &
|
87
|
-
|
117
|
+
def leave(options = {}, &success_block)
|
118
|
+
@data = options.fetch(:data) if options.has_key?(:data)
|
119
|
+
deferrable = EventMachine::DefaultDeferrable.new
|
88
120
|
|
89
|
-
|
90
|
-
|
91
|
-
if state == STATE.Left
|
92
|
-
blk.call self if block_given?
|
93
|
-
return
|
94
|
-
end
|
95
|
-
|
96
|
-
ensure_channel_attached do
|
97
|
-
once(STATE.Left) { blk.call self } if block_given?
|
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
|
98
123
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
deferrable
|
103
|
-
deferrable.callback { |message| change_state STATE.Left }
|
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
|
104
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
|
+
)
|
105
140
|
end
|
106
141
|
end
|
107
142
|
end
|
108
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
|
+
|
109
160
|
# Update the presence data for this client. If the client is not already a member of
|
110
161
|
# the presence set it will be added, and presence subscribers will see an enter or
|
111
162
|
# update message for this client.
|
112
163
|
#
|
113
|
-
# @param
|
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
|
+
#
|
114
167
|
# @yield (see Presence#enter)
|
115
168
|
# @return (see Presence#enter)
|
116
169
|
#
|
117
|
-
def update(options = {}, &
|
118
|
-
@data
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
170
|
+
def update(options = {}, &success_block)
|
171
|
+
@data = options.fetch(:data) if options.has_key?(:data)
|
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
|
+
)
|
127
183
|
end
|
128
184
|
end
|
129
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
|
+
|
130
205
|
# Get the presence state for this Channel.
|
131
|
-
# Optionally get a member's {Ably::Models::PresenceMessage} state by member_id
|
132
206
|
#
|
133
|
-
# @
|
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
|
134
211
|
#
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
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) callback
|
215
|
+
#
|
216
|
+
def get(options = {}, &success_block)
|
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
|
226
|
+
end
|
227
|
+
|
228
|
+
if !wait_for_sync || sync_complete?
|
229
|
+
result = result_block.call
|
230
|
+
success_block.call result if block_given?
|
231
|
+
deferrable.succeed result
|
232
|
+
else
|
233
|
+
sync_pubsub.once(:done) do
|
234
|
+
result = result_block.call
|
235
|
+
success_block.call result if block_given?
|
236
|
+
deferrable.succeed result
|
237
|
+
end
|
238
|
+
|
239
|
+
sync_pubsub.once(:failed) do |error|
|
240
|
+
deferrable.fail error
|
241
|
+
end
|
242
|
+
end
|
140
243
|
end
|
141
244
|
end
|
142
245
|
|
@@ -148,9 +251,9 @@ module Ably::Realtime
|
|
148
251
|
#
|
149
252
|
# @return [void]
|
150
253
|
#
|
151
|
-
def subscribe(action = :all, &
|
254
|
+
def subscribe(action = :all, &callback)
|
152
255
|
ensure_channel_attached do
|
153
|
-
subscriptions[message_action_key(action)] <<
|
256
|
+
subscriptions[message_action_key(action)] << callback
|
154
257
|
end
|
155
258
|
end
|
156
259
|
|
@@ -161,14 +264,14 @@ module Ably::Realtime
|
|
161
264
|
#
|
162
265
|
# @return [void]
|
163
266
|
#
|
164
|
-
def unsubscribe(action = :all, &
|
267
|
+
def unsubscribe(action = :all, &callback)
|
165
268
|
if message_action_key(action) == :all
|
166
269
|
subscriptions.keys
|
167
270
|
else
|
168
271
|
Array(message_action_key(action))
|
169
272
|
end.each do |key|
|
170
273
|
subscriptions[key].delete_if do |block|
|
171
|
-
!block_given? ||
|
274
|
+
!block_given? || callback == block
|
172
275
|
end
|
173
276
|
end
|
174
277
|
end
|
@@ -188,6 +291,60 @@ module Ably::Realtime
|
|
188
291
|
end
|
189
292
|
end
|
190
293
|
|
294
|
+
# When attaching to a channel that has members present, the client and server
|
295
|
+
# initiate a sync automatically so that the client has a complete list of members.
|
296
|
+
#
|
297
|
+
# Whilst this sync is happening, this method returns false
|
298
|
+
#
|
299
|
+
# @return [Boolean]
|
300
|
+
def sync_complete?
|
301
|
+
sync_complete
|
302
|
+
end
|
303
|
+
|
304
|
+
# Expect SYNC ProtocolMessages with a list of current members on this channel from the server
|
305
|
+
#
|
306
|
+
# @return [void]
|
307
|
+
#
|
308
|
+
# @api private
|
309
|
+
def sync_started
|
310
|
+
@sync_complete = false
|
311
|
+
|
312
|
+
sync_pubsub.once(:sync_complete) do
|
313
|
+
sync_changes_backlog.each do |presence_message|
|
314
|
+
apply_member_presence_changes presence_message
|
315
|
+
end
|
316
|
+
sync_completed
|
317
|
+
sync_pubsub.trigger :done
|
318
|
+
end
|
319
|
+
|
320
|
+
channel.once_or_if [:detached, :failed] do |error|
|
321
|
+
sync_completed
|
322
|
+
sync_pubsub.trigger :failed, error
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# The server has indicated that no members are present on this channel and no SYNC is expected,
|
327
|
+
# or that the SYNC has now completed
|
328
|
+
#
|
329
|
+
# @return [void]
|
330
|
+
#
|
331
|
+
# @api private
|
332
|
+
def sync_completed
|
333
|
+
@sync_complete = true
|
334
|
+
@sync_changes_backlog = []
|
335
|
+
end
|
336
|
+
|
337
|
+
# Update the SYNC serial from the ProtocolMessage so that SYNC can be resumed.
|
338
|
+
# If the serial is nil, or the part after the first : is empty, then the SYNC is complete
|
339
|
+
#
|
340
|
+
# @return [void]
|
341
|
+
#
|
342
|
+
# @api private
|
343
|
+
def update_sync_serial(serial)
|
344
|
+
@sync_serial = serial
|
345
|
+
sync_pubsub.trigger :sync_complete if sync_serial_cursor_at_end?
|
346
|
+
end
|
347
|
+
|
191
348
|
# @!attribute [r] __incoming_msgbus__
|
192
349
|
# @return [Ably::Util::PubSub] Client library internal channel incoming message bus
|
193
350
|
# @api private
|
@@ -198,19 +355,34 @@ module Ably::Realtime
|
|
198
355
|
end
|
199
356
|
|
200
357
|
private
|
201
|
-
attr_reader :members, :subscriptions, :
|
358
|
+
attr_reader :members, :subscriptions, :sync_serial, :sync_complete
|
202
359
|
|
203
|
-
|
360
|
+
|
361
|
+
# A simple PubSub class used to publish synchronisation state changes
|
362
|
+
def sync_pubsub
|
363
|
+
@sync_pubsub ||= Ably::Util::PubSub.new
|
364
|
+
end
|
365
|
+
|
366
|
+
# During a SYNC of presence members, all enter, update and leave events are queued for processing once the SYNC is complete
|
367
|
+
def sync_changes_backlog
|
368
|
+
@sync_changes_backlog ||= []
|
369
|
+
end
|
370
|
+
|
371
|
+
# When channel serial in ProtocolMessage SYNC is nil or
|
372
|
+
# an empty cursor appears after the ':' such as 'cf30e75054887:psl_7g:client:189'
|
373
|
+
# then there are no more SYNC messages to come
|
374
|
+
def sync_serial_cursor_at_end?
|
375
|
+
sync_serial.nil? || sync_serial.to_s.match(/^[\w-]+:?$/)
|
376
|
+
end
|
377
|
+
|
378
|
+
def able_to_leave?
|
204
379
|
entering? || entered?
|
205
380
|
end
|
206
381
|
|
207
382
|
def setup_event_handlers
|
208
|
-
__incoming_msgbus__.subscribe(:presence) do |
|
209
|
-
|
210
|
-
update_members_from_presence_message
|
211
|
-
|
212
|
-
subscriptions[:all].each { |cb| cb.call(presence) }
|
213
|
-
subscriptions[presence.action].each { |cb| cb.call(presence) }
|
383
|
+
__incoming_msgbus__.subscribe(:presence, :sync) do |presence_message|
|
384
|
+
presence_message.decode self.channel
|
385
|
+
update_members_from_presence_message presence_message
|
214
386
|
end
|
215
387
|
|
216
388
|
channel.on(Channel::STATE.Detaching) do
|
@@ -226,13 +398,13 @@ module Ably::Realtime
|
|
226
398
|
end
|
227
399
|
|
228
400
|
on(STATE.Entered) do |message|
|
229
|
-
@
|
401
|
+
@connection_id = message.connection_id
|
230
402
|
end
|
231
403
|
end
|
232
404
|
|
233
405
|
# @return [Ably::Models::PresenceMessage] presence message is returned allowing callbacks to be added
|
234
|
-
def send_presence_protocol_message(presence_action)
|
235
|
-
presence_message = create_presence_message(presence_action)
|
406
|
+
def send_presence_protocol_message(presence_action, client_id, options = {})
|
407
|
+
presence_message = create_presence_message(presence_action, client_id, options)
|
236
408
|
unless presence_message.client_id
|
237
409
|
raise Ably::Exceptions::Standard.new('Unable to enter create presence message without a client_id', 400, 91000)
|
238
410
|
end
|
@@ -248,12 +420,12 @@ module Ably::Realtime
|
|
248
420
|
presence_message
|
249
421
|
end
|
250
422
|
|
251
|
-
def create_presence_message(action)
|
423
|
+
def create_presence_message(action, client_id, options = {})
|
252
424
|
model = {
|
253
425
|
action: Ably::Models::PresenceMessage.ACTION(action).to_i,
|
254
|
-
clientId: client_id
|
426
|
+
clientId: client_id
|
255
427
|
}
|
256
|
-
model.merge!(data: data) if data
|
428
|
+
model.merge!(data: options.fetch(:data)) if options.has_key?(:data)
|
257
429
|
|
258
430
|
Ably::Models::PresenceMessage.new(model, nil).tap do |presence_message|
|
259
431
|
presence_message.encode self.channel
|
@@ -261,31 +433,103 @@ module Ably::Realtime
|
|
261
433
|
end
|
262
434
|
|
263
435
|
def update_members_from_presence_message(presence_message)
|
264
|
-
unless presence_message.
|
265
|
-
|
436
|
+
unless presence_message.connection_id
|
437
|
+
Ably::Exceptions::ProtocolError.new("Protocol error, presence message is missing connectionId", 400, 80013)
|
266
438
|
end
|
267
439
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
440
|
+
if sync_complete?
|
441
|
+
apply_member_presence_changes presence_message
|
442
|
+
else
|
443
|
+
if presence_message.action == Ably::Models::PresenceMessage::ACTION.Present
|
444
|
+
add_presence_member presence_message
|
445
|
+
publish_presence_member_state_change presence_message
|
446
|
+
else
|
447
|
+
sync_changes_backlog << presence_message
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
274
451
|
|
452
|
+
def apply_member_presence_changes(presence_message)
|
453
|
+
case presence_message.action
|
454
|
+
when Ably::Models::PresenceMessage::ACTION.Enter, Ably::Models::PresenceMessage::ACTION.Update
|
455
|
+
add_presence_member presence_message
|
275
456
|
when Ably::Models::PresenceMessage::ACTION.Leave
|
276
|
-
|
277
|
-
|
457
|
+
remove_presence_member presence_message
|
278
458
|
else
|
279
|
-
|
459
|
+
Ably::Exceptions::ProtocolError.new("Protocol error, unknown presence action #{presence_message.action}", 400, 80013)
|
280
460
|
end
|
461
|
+
|
462
|
+
publish_presence_member_state_change presence_message
|
463
|
+
end
|
464
|
+
|
465
|
+
def add_presence_member(presence_message)
|
466
|
+
members[presence_message.member_key] = presence_message
|
281
467
|
end
|
282
468
|
|
283
|
-
def
|
469
|
+
def remove_presence_member(presence_message)
|
470
|
+
members.delete presence_message.member_key
|
471
|
+
end
|
472
|
+
|
473
|
+
def publish_presence_member_state_change(presence_message)
|
474
|
+
subscriptions[:all].each { |cb| cb.call(presence_message) }
|
475
|
+
subscriptions[presence_message.action].each { |cb| cb.call(presence_message) }
|
476
|
+
end
|
477
|
+
|
478
|
+
def ensure_channel_attached(deferrable = nil)
|
284
479
|
if channel.attached?
|
285
480
|
yield
|
286
481
|
else
|
287
482
|
attach_channel_then { yield }
|
288
483
|
end
|
484
|
+
deferrable
|
485
|
+
end
|
486
|
+
|
487
|
+
def send_protocol_message_and_transition_state_to(action, options = {}, &success_block)
|
488
|
+
deferrable = options.fetch(:deferrable) { raise ArgumentError, 'option :deferrable is required' }
|
489
|
+
client_id = options.fetch(:client_id) { raise ArgumentError, 'option :client_id is required' }
|
490
|
+
target_state = options.fetch(:target_state, nil)
|
491
|
+
failed_state = options.fetch(:failed_state, nil)
|
492
|
+
|
493
|
+
protocol_message_options = if options.has_key?(:data)
|
494
|
+
{ data: options.fetch(:data) }
|
495
|
+
else
|
496
|
+
{ }
|
497
|
+
end
|
498
|
+
|
499
|
+
send_presence_protocol_message(action, client_id, protocol_message_options).tap do |protocol_message|
|
500
|
+
protocol_message.callback do |message|
|
501
|
+
change_state target_state, message if target_state
|
502
|
+
deferrable_succeed deferrable, &success_block
|
503
|
+
end
|
504
|
+
|
505
|
+
protocol_message.errback do |message, error|
|
506
|
+
change_state failed_state, error if failed_state
|
507
|
+
deferrable_fail deferrable, error
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
def deferrable_succeed(deferrable, *args, &block)
|
513
|
+
block.call self, *args if block_given?
|
514
|
+
EventMachine.next_tick { deferrable.succeed self, *args } # allow callback to be added to the returned Deferrable
|
515
|
+
deferrable
|
516
|
+
end
|
517
|
+
|
518
|
+
def deferrable_fail(deferrable, *args, &block)
|
519
|
+
block.call self, *args if block_given?
|
520
|
+
EventMachine.next_tick { deferrable.fail self, *args } # allow errback to be added to the returned Deferrable
|
521
|
+
deferrable
|
522
|
+
end
|
523
|
+
|
524
|
+
def send_presence_action_for_client(action, client_id, options = {}, &success_block)
|
525
|
+
deferrable = EventMachine::DefaultDeferrable.new
|
526
|
+
|
527
|
+
ensure_channel_attached(deferrable) do
|
528
|
+
send_presence_protocol_message(action, client_id, options).tap do |protocol_message|
|
529
|
+
protocol_message.callback { |message| deferrable_succeed deferrable, &success_block }
|
530
|
+
protocol_message.errback { |message| deferrable_fail deferrable }
|
531
|
+
end
|
532
|
+
end
|
289
533
|
end
|
290
534
|
|
291
535
|
def attach_channel_then
|