ably 0.6.2 → 0.7.0
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 +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
|