ably 0.7.2 → 0.7.4
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +1 -1
- data/README.md +107 -24
- data/SPEC.md +531 -398
- data/lib/ably/auth.rb +23 -15
- data/lib/ably/exceptions.rb +9 -0
- data/lib/ably/models/message.rb +17 -9
- data/lib/ably/models/paginated_resource.rb +12 -8
- data/lib/ably/models/presence_message.rb +18 -10
- data/lib/ably/models/protocol_message.rb +15 -4
- data/lib/ably/modules/async_wrapper.rb +4 -3
- data/lib/ably/modules/event_emitter.rb +31 -2
- data/lib/ably/modules/message_emitter.rb +77 -0
- data/lib/ably/modules/safe_deferrable.rb +71 -0
- data/lib/ably/modules/safe_yield.rb +41 -0
- data/lib/ably/modules/state_emitter.rb +28 -8
- data/lib/ably/realtime.rb +0 -5
- data/lib/ably/realtime/channel.rb +24 -29
- data/lib/ably/realtime/channel/channel_manager.rb +54 -11
- data/lib/ably/realtime/channel/channel_state_machine.rb +21 -6
- data/lib/ably/realtime/client.rb +7 -2
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +29 -26
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +4 -4
- data/lib/ably/realtime/connection.rb +41 -9
- data/lib/ably/realtime/connection/connection_manager.rb +72 -24
- data/lib/ably/realtime/connection/connection_state_machine.rb +26 -4
- data/lib/ably/realtime/connection/websocket_transport.rb +19 -6
- data/lib/ably/realtime/presence.rb +74 -208
- data/lib/ably/realtime/presence/members_map.rb +264 -0
- data/lib/ably/realtime/presence/presence_manager.rb +59 -0
- data/lib/ably/realtime/presence/presence_state_machine.rb +64 -0
- data/lib/ably/rest/channel.rb +1 -1
- data/lib/ably/rest/client.rb +6 -2
- data/lib/ably/rest/presence.rb +1 -1
- data/lib/ably/util/pub_sub.rb +3 -1
- data/lib/ably/util/safe_deferrable.rb +18 -0
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/channel_history_spec.rb +2 -2
- data/spec/acceptance/realtime/channel_spec.rb +28 -6
- data/spec/acceptance/realtime/connection_failures_spec.rb +116 -46
- data/spec/acceptance/realtime/connection_spec.rb +55 -10
- data/spec/acceptance/realtime/message_spec.rb +32 -0
- data/spec/acceptance/realtime/presence_spec.rb +456 -96
- data/spec/acceptance/realtime/stats_spec.rb +2 -2
- data/spec/acceptance/realtime/time_spec.rb +2 -2
- data/spec/acceptance/rest/auth_spec.rb +75 -7
- data/spec/shared/client_initializer_behaviour.rb +8 -0
- data/spec/shared/safe_deferrable_behaviour.rb +71 -0
- data/spec/support/api_helper.rb +1 -1
- data/spec/support/event_machine_helper.rb +1 -1
- data/spec/support/test_app.rb +13 -7
- data/spec/unit/models/message_spec.rb +15 -14
- data/spec/unit/models/paginated_resource_spec.rb +4 -4
- data/spec/unit/models/presence_message_spec.rb +17 -17
- data/spec/unit/models/stat_spec.rb +4 -4
- data/spec/unit/modules/async_wrapper_spec.rb +28 -9
- data/spec/unit/modules/event_emitter_spec.rb +50 -0
- data/spec/unit/modules/state_emitter_spec.rb +76 -2
- data/spec/unit/realtime/channel_spec.rb +51 -20
- data/spec/unit/realtime/channels_spec.rb +3 -3
- data/spec/unit/realtime/connection_spec.rb +30 -0
- data/spec/unit/realtime/presence_spec.rb +52 -26
- data/spec/unit/realtime/safe_deferrable_spec.rb +12 -0
- metadata +85 -39
- checksums.yaml +0 -7
- data/.ruby-version.old +0 -1
data/lib/ably/auth.rb
CHANGED
@@ -34,7 +34,7 @@ module Ably
|
|
34
34
|
# Creates an Auth object
|
35
35
|
#
|
36
36
|
# @param [Ably::Rest::Client] client {Ably::Rest::Client} this Auth object uses
|
37
|
-
# @param options (see Ably::Rest::Client#initialize)
|
37
|
+
# @param [Hash] options (see Ably::Rest::Client#initialize)
|
38
38
|
# @option (see Ably::Rest::Client#initialize)
|
39
39
|
# @yield (see Ably::Rest::Client#initialize)
|
40
40
|
#
|
@@ -53,12 +53,7 @@ module Ably
|
|
53
53
|
raise ArgumentError, 'api_key and key_id or key_secret are mutually exclusive. Provider either an api_key or key_id & key_secret'
|
54
54
|
end
|
55
55
|
|
56
|
-
if auth_options[:api_key]
|
57
|
-
api_key_parts = auth_options[:api_key].to_s.match(/(?<id>[\w_-]+\.[\w_-]+):(?<secret>[\w_-]+)/)
|
58
|
-
raise ArgumentError, 'api_key is invalid' unless api_key_parts
|
59
|
-
auth_options[:key_id] = api_key_parts[:id].encode(Encoding::UTF_8)
|
60
|
-
auth_options[:key_secret] = api_key_parts[:secret].encode(Encoding::UTF_8)
|
61
|
-
end
|
56
|
+
split_api_key_into_key_and_secret! auth_options if auth_options[:api_key]
|
62
57
|
|
63
58
|
if using_basic_auth? && !api_key_present?
|
64
59
|
raise ArgumentError, 'api_key is missing. Either an API key, token, or token auth method must be provided'
|
@@ -78,6 +73,7 @@ module Ably
|
|
78
73
|
#
|
79
74
|
# @param [Hash] options the options for the token request
|
80
75
|
# @option options (see #request_token)
|
76
|
+
# @option options [String] :api_key API key comprising the key ID and key secret in a single string
|
81
77
|
# @option options [Boolean] :force obtains a new token even if the current token is valid
|
82
78
|
#
|
83
79
|
# @yield (see #request_token)
|
@@ -102,6 +98,9 @@ module Ably
|
|
102
98
|
return current_token unless current_token.expired?
|
103
99
|
end
|
104
100
|
|
101
|
+
options = options.clone
|
102
|
+
split_api_key_into_key_and_secret! options if options[:api_key]
|
103
|
+
|
105
104
|
@options = @options.merge(options)
|
106
105
|
@default_token_block = token_request_block if block_given?
|
107
106
|
|
@@ -157,10 +156,13 @@ module Ably
|
|
157
156
|
|
158
157
|
token_request = IdiomaticRubyWrapper(token_request)
|
159
158
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
159
|
+
if token_request.has_key?(:issued_at) && token_request.has_key?(:expires)
|
160
|
+
Ably::Models::Token.new(token_request)
|
161
|
+
else
|
162
|
+
response = client.post("/keys/#{token_request.fetch(:id)}/requestToken", token_request.hash, send_auth_header: false, disable_automatic_reauthorise: true)
|
163
|
+
body = IdiomaticRubyWrapper(response.body)
|
164
|
+
Ably::Models::Token.new(body.fetch(:access_token))
|
165
|
+
end
|
164
166
|
end
|
165
167
|
|
166
168
|
# Creates and signs a token request that can then subsequently be used by any client to request a token
|
@@ -191,6 +193,7 @@ module Ably
|
|
191
193
|
token_attributes = %w(id client_id ttl timestamp capability nonce)
|
192
194
|
|
193
195
|
token_options = options.clone
|
196
|
+
|
194
197
|
request_key_id = token_options.delete(:key_id) || key_id
|
195
198
|
request_key_secret = token_options.delete(:key_secret) || key_secret
|
196
199
|
|
@@ -296,7 +299,7 @@ module Ably
|
|
296
299
|
end
|
297
300
|
|
298
301
|
private
|
299
|
-
attr_reader :default_token_block
|
302
|
+
attr_reader :client, :default_token_block
|
300
303
|
|
301
304
|
def ensure_api_key_sent_over_secure_connection
|
302
305
|
raise Ably::Exceptions::InsecureRequestError, 'Cannot use Basic Auth over non-TLS connections' unless authentication_security_requirements_met?
|
@@ -308,6 +311,14 @@ module Ably
|
|
308
311
|
"Basic #{encode64("#{api_key}")}"
|
309
312
|
end
|
310
313
|
|
314
|
+
def split_api_key_into_key_and_secret!(options)
|
315
|
+
api_key_parts = options[:api_key].to_s.match(/(?<id>[\w_-]+\.[\w_-]+):(?<secret>[\w_-]+)/)
|
316
|
+
raise ArgumentError, 'api_key is invalid' unless api_key_parts
|
317
|
+
|
318
|
+
options[:key_id] = api_key_parts[:id].encode(Encoding::UTF_8)
|
319
|
+
options[:key_secret] = api_key_parts[:secret].encode(Encoding::UTF_8)
|
320
|
+
end
|
321
|
+
|
311
322
|
def token_auth_id
|
312
323
|
if token_id
|
313
324
|
token_id
|
@@ -431,8 +442,5 @@ module Ably
|
|
431
442
|
def api_key_present?
|
432
443
|
key_id && key_secret
|
433
444
|
end
|
434
|
-
|
435
|
-
private
|
436
|
-
attr_reader :client
|
437
445
|
end
|
438
446
|
end
|
data/lib/ably/exceptions.rb
CHANGED
@@ -44,9 +44,15 @@ module Ably
|
|
44
44
|
# Connection Timeout accessing Realtime or REST service
|
45
45
|
class ConnectionTimeoutError < ConnectionError; end
|
46
46
|
|
47
|
+
# Connection closed unexpectedly
|
48
|
+
class ConnectionClosedError < ConnectionError; end
|
49
|
+
|
47
50
|
# Invalid State Change error on a {https://github.com/gocardless/statesman Statesman State Machine}
|
48
51
|
class StateChangeError < BaseAblyException; end
|
49
52
|
|
53
|
+
# The state of the object is not suitable for this operation
|
54
|
+
class IncompatibleStateForOperation < BaseAblyException; end
|
55
|
+
|
50
56
|
# A generic Ably exception taht supports a status & code.
|
51
57
|
# See https://github.com/ably/ably-common/blob/master/protocol/errors.json for a list of Ably errors
|
52
58
|
class Standard < BaseAblyException; end
|
@@ -65,5 +71,8 @@ module Ably
|
|
65
71
|
|
66
72
|
# The token request could not be created
|
67
73
|
class TokenRequestError < BaseAblyException; end
|
74
|
+
|
75
|
+
# The message could not be delivered to the server
|
76
|
+
class MessageDeliveryError < BaseAblyException; end
|
68
77
|
end
|
69
78
|
end
|
data/lib/ably/models/message.rb
CHANGED
@@ -2,17 +2,17 @@ module Ably::Models
|
|
2
2
|
# Convert messsage argument to a {Message} object and associate with a protocol message if provided
|
3
3
|
#
|
4
4
|
# @param message [Message,Hash] A message object or Hash of message properties
|
5
|
-
# @param
|
5
|
+
# @param [Hash] options (see Message#initialize)
|
6
6
|
#
|
7
7
|
# @return [Message]
|
8
|
-
def self.Message(message,
|
8
|
+
def self.Message(message, options = {})
|
9
9
|
case message
|
10
10
|
when Message
|
11
11
|
message.tap do
|
12
|
-
message.assign_to_protocol_message protocol_message
|
12
|
+
message.assign_to_protocol_message options[:protocol_message] if options[:protocol_message]
|
13
13
|
end
|
14
14
|
else
|
15
|
-
Message.new(message,
|
15
|
+
Message.new(message, options)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -41,15 +41,18 @@ module Ably::Models
|
|
41
41
|
include Ably::Modules::Conversions
|
42
42
|
include Ably::Modules::Encodeable
|
43
43
|
include Ably::Modules::ModelCommon
|
44
|
-
include
|
44
|
+
include Ably::Modules::SafeDeferrable if defined?(Ably::Realtime)
|
45
45
|
|
46
46
|
# {Message} initializer
|
47
47
|
#
|
48
|
-
# @param
|
49
|
-
# @param
|
48
|
+
# @param hash_object [Hash] object with the underlying message details
|
49
|
+
# @param [Hash] options an options Hash for this initializer
|
50
|
+
# @option options [ProtocolMessage] :protocol_message An optional protocol message to assocate the presence message with
|
51
|
+
# @option options [Logger] :logger An optional Logger to be used by {Ably::Modules::SafeDeferrable} if an exception is caught in a callback
|
50
52
|
#
|
51
|
-
def initialize(hash_object,
|
52
|
-
@
|
53
|
+
def initialize(hash_object, options = {})
|
54
|
+
@logger = options[:logger] # Logger expected for SafeDeferrable
|
55
|
+
@protocol_message = options[:protocol_message]
|
53
56
|
@raw_hash_object = hash_object
|
54
57
|
|
55
58
|
set_hash_object hash_object
|
@@ -128,5 +131,10 @@ module Ably::Models
|
|
128
131
|
def set_hash_object(hash)
|
129
132
|
@hash_object = IdiomaticRubyWrapper(hash.clone.freeze, stop_at: [:data])
|
130
133
|
end
|
134
|
+
|
135
|
+
def logger
|
136
|
+
return logger if logger
|
137
|
+
protocol_message.logger if protocol_message
|
138
|
+
end
|
131
139
|
end
|
132
140
|
end
|
@@ -31,23 +31,23 @@ module Ably::Models
|
|
31
31
|
end
|
32
32
|
|
33
33
|
# Retrieve the first page of results.
|
34
|
-
# When used as part of the {Ably::Realtime} library, it will return a {
|
34
|
+
# When used as part of the {Ably::Realtime} library, it will return a {Ably::Util::SafeDeferrable} object,
|
35
35
|
# and allows an optional success callback block to be provided.
|
36
36
|
#
|
37
|
-
# @return [PaginatedResource,
|
37
|
+
# @return [PaginatedResource,Ably::Util::SafeDeferrable]
|
38
38
|
def first_page(&success_callback)
|
39
|
-
|
39
|
+
async_wrap_if_realtime(success_callback) do
|
40
40
|
PaginatedResource.new(client.get(pagination_url('first')), base_url, client, pagination_options, &each_block)
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
44
|
# Retrieve the next page of results.
|
45
|
-
# When used as part of the {Ably::Realtime} library, it will return a {
|
45
|
+
# When used as part of the {Ably::Realtime} library, it will return a {Ably::Util::SafeDeferrable} object,
|
46
46
|
# and allows an optional success callback block to be provided.
|
47
47
|
#
|
48
|
-
# @return [PaginatedResource,
|
48
|
+
# @return [PaginatedResource,Ably::Util::SafeDeferrable]
|
49
49
|
def next_page(&success_callback)
|
50
|
-
|
50
|
+
async_wrap_if_realtime(success_callback) do
|
51
51
|
raise Ably::Exceptions::InvalidPageError, 'There are no more pages' if supports_pagination? && last_page?
|
52
52
|
PaginatedResource.new(client.get(pagination_url('next')), base_url, client, pagination_options, &each_block)
|
53
53
|
end
|
@@ -161,13 +161,17 @@ module Ably::Models
|
|
161
161
|
}
|
162
162
|
end
|
163
163
|
|
164
|
-
def
|
165
|
-
if
|
164
|
+
def async_wrap_if_realtime(success_callback, &operation)
|
165
|
+
if make_async
|
166
166
|
raise 'EventMachine is required for asynchronous operations' unless defined?(EventMachine)
|
167
167
|
async_wrap success_callback, &operation
|
168
168
|
else
|
169
169
|
yield
|
170
170
|
end
|
171
171
|
end
|
172
|
+
|
173
|
+
def logger
|
174
|
+
client.logger
|
175
|
+
end
|
172
176
|
end
|
173
177
|
end
|
@@ -2,17 +2,17 @@ module Ably::Models
|
|
2
2
|
# Convert presence_messsage argument to a {PresenceMessage} object and associate with a protocol message if provided
|
3
3
|
#
|
4
4
|
# @param presence_message [PresenceMessage,Hash] A presence message object or Hash of presence message properties
|
5
|
-
# @param
|
5
|
+
# @param [Hash] options (see PresenceMessage#initialize)
|
6
6
|
#
|
7
7
|
# @return [PresenceMessage]
|
8
|
-
def self.PresenceMessage(presence_message,
|
8
|
+
def self.PresenceMessage(presence_message, options = {})
|
9
9
|
case presence_message
|
10
10
|
when PresenceMessage
|
11
11
|
presence_message.tap do
|
12
|
-
presence_message.assign_to_protocol_message protocol_message
|
12
|
+
presence_message.assign_to_protocol_message options[:protocol_message] if options[:protocol_message]
|
13
13
|
end
|
14
14
|
else
|
15
|
-
PresenceMessage.new(presence_message,
|
15
|
+
PresenceMessage.new(presence_message, options)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -41,7 +41,7 @@ module Ably::Models
|
|
41
41
|
include Ably::Modules::Conversions
|
42
42
|
include Ably::Modules::Encodeable
|
43
43
|
include Ably::Modules::ModelCommon
|
44
|
-
include
|
44
|
+
include Ably::Modules::SafeDeferrable if defined?(Ably::Realtime)
|
45
45
|
extend Ably::Modules::Enum
|
46
46
|
|
47
47
|
ACTION = ruby_enum('ACTION',
|
@@ -52,13 +52,16 @@ module Ably::Models
|
|
52
52
|
:update
|
53
53
|
)
|
54
54
|
|
55
|
-
# {
|
55
|
+
# {PresenceMessage} initializer
|
56
56
|
#
|
57
|
-
# @param
|
58
|
-
# @param
|
57
|
+
# @param hash_object [Hash] object with the underlying presence message details
|
58
|
+
# @param [Hash] options an options Hash for this initializer
|
59
|
+
# @option options [ProtocolMessage] :protocol_message An optional protocol message to assocate the presence message with
|
60
|
+
# @option options [Logger] :logger An optional Logger to be used by {Ably::Modules::SafeDeferrable} if an exception is caught in a callback
|
59
61
|
#
|
60
|
-
def initialize(hash_object,
|
61
|
-
@
|
62
|
+
def initialize(hash_object, options = {})
|
63
|
+
@logger = options[:logger] # Logger expected for SafeDeferrable
|
64
|
+
@protocol_message = options[:protocol_message]
|
62
65
|
@raw_hash_object = hash_object
|
63
66
|
|
64
67
|
set_hash_object hash_object
|
@@ -143,5 +146,10 @@ module Ably::Models
|
|
143
146
|
def set_hash_object(hash)
|
144
147
|
@hash_object = IdiomaticRubyWrapper(hash.clone.freeze, stop_at: [:data])
|
145
148
|
end
|
149
|
+
|
150
|
+
def logger
|
151
|
+
return logger if logger
|
152
|
+
protocol_message.logger if protocol_message
|
153
|
+
end
|
146
154
|
end
|
147
155
|
end
|
@@ -37,7 +37,7 @@ module Ably::Models
|
|
37
37
|
#
|
38
38
|
class ProtocolMessage
|
39
39
|
include Ably::Modules::ModelCommon
|
40
|
-
include
|
40
|
+
include Ably::Modules::SafeDeferrable if defined?(Ably::Realtime)
|
41
41
|
extend Ably::Modules::Enum
|
42
42
|
|
43
43
|
# Actions which are sent by the Ably Realtime API
|
@@ -69,7 +69,15 @@ module Ably::Models
|
|
69
69
|
[ACTION.Presence, ACTION.Message].include?(ACTION(for_action))
|
70
70
|
end
|
71
71
|
|
72
|
-
|
72
|
+
# {ProtocolMessage} initializer
|
73
|
+
#
|
74
|
+
# @param hash_object [Hash] object with the underlying protocol message data
|
75
|
+
# @param [Hash] options an options Hash for this initializer
|
76
|
+
# @option options [Logger] :logger An optional Logger to be used by {Ably::Modules::SafeDeferrable} if an exception is caught in a callback
|
77
|
+
#
|
78
|
+
def initialize(hash_object, options = {})
|
79
|
+
@logger = options[:logger] # Logger expected for SafeDeferrable
|
80
|
+
|
73
81
|
@raw_hash_object = hash_object
|
74
82
|
@hash_object = IdiomaticRubyWrapper(@raw_hash_object.clone)
|
75
83
|
|
@@ -147,7 +155,7 @@ module Ably::Models
|
|
147
155
|
def messages
|
148
156
|
@messages ||=
|
149
157
|
Array(hash[:messages]).map do |message|
|
150
|
-
Ably::Models.Message(message, self)
|
158
|
+
Ably::Models.Message(message, protocol_message: self)
|
151
159
|
end
|
152
160
|
end
|
153
161
|
|
@@ -158,7 +166,7 @@ module Ably::Models
|
|
158
166
|
def presence
|
159
167
|
@presence ||=
|
160
168
|
Array(hash[:presence]).map do |message|
|
161
|
-
Ably::Models.PresenceMessage(message, self)
|
169
|
+
Ably::Models.PresenceMessage(message, protocol_message: self)
|
162
170
|
end
|
163
171
|
end
|
164
172
|
|
@@ -206,5 +214,8 @@ module Ably::Models
|
|
206
214
|
action_enum = action rescue nil
|
207
215
|
!action_enum || (ack_required? && !has_serial?)
|
208
216
|
end
|
217
|
+
|
218
|
+
private
|
219
|
+
attr_reader :logger
|
209
220
|
end
|
210
221
|
end
|
@@ -7,6 +7,7 @@ module Ably::Modules
|
|
7
7
|
#
|
8
8
|
# @note using this AsyncWrapper should only be used for methods that are used less frequently and typically
|
9
9
|
# not run with levels of concurrency due to the limited number of threads available to EventMachine by default.
|
10
|
+
# This module requires that the method #logger is defined.
|
10
11
|
#
|
11
12
|
# @example
|
12
13
|
# class BlockingOperation
|
@@ -32,15 +33,15 @@ module Ably::Modules
|
|
32
33
|
module AsyncWrapper
|
33
34
|
private
|
34
35
|
|
35
|
-
# Will yield the provided block in a new thread and return an {
|
36
|
+
# Will yield the provided block in a new thread and return an {Ably::Util::SafeDeferrable}
|
36
37
|
#
|
37
38
|
# @yield [Object] operation block that is run in a thread
|
38
|
-
# @return [
|
39
|
+
# @return [Ably::Util::SafeDeferrable]
|
39
40
|
#
|
40
41
|
def async_wrap(success_callback = nil)
|
41
42
|
raise ArgumentError, 'Block required' unless block_given?
|
42
43
|
|
43
|
-
|
44
|
+
Ably::Util::SafeDeferrable.new(logger).tap do |deferrable|
|
44
45
|
deferrable.callback &success_callback if success_callback
|
45
46
|
|
46
47
|
operation_with_exception_handling = proc do
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'ably/modules/safe_yield'
|
2
|
+
|
1
3
|
module Ably
|
2
4
|
module Modules
|
3
5
|
# EventEmitter provides methods to attach to public events and trigger events on any class instance
|
@@ -5,6 +7,8 @@ module Ably
|
|
5
7
|
# EventEmitter are typically used for public interfaces, and as such, may be overriden in
|
6
8
|
# the classes to enforce `event` names match expected values.
|
7
9
|
#
|
10
|
+
# @note This module requires that the method #logger is defined.
|
11
|
+
#
|
8
12
|
# @example
|
9
13
|
# class Example
|
10
14
|
# include Modules::EventEmitter
|
@@ -16,6 +20,8 @@ module Ably
|
|
16
20
|
# #=> "Signal Test received"
|
17
21
|
#
|
18
22
|
module EventEmitter
|
23
|
+
include Ably::Modules::SafeYield
|
24
|
+
|
19
25
|
module ClassMethods
|
20
26
|
attr_reader :event_emitter_coerce_proc
|
21
27
|
|
@@ -49,6 +55,15 @@ module Ably
|
|
49
55
|
end
|
50
56
|
end
|
51
57
|
|
58
|
+
# Equivalent of {#on} but any exception raised in a block will bubble up and cause this client library to fail.
|
59
|
+
# This method should only be used internally by the client library.
|
60
|
+
# @api private
|
61
|
+
def unsafe_on(*event_names, &block)
|
62
|
+
event_names.each do |event_name|
|
63
|
+
callbacks[callbacks_event_coerced(event_name)] << proc_for_block(block, unsafe: true)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
52
67
|
# On receiving an event maching the event_name, call the provided block only once and remove the registered callback
|
53
68
|
#
|
54
69
|
# @param [Array<String>] event_names event name
|
@@ -60,12 +75,25 @@ module Ably
|
|
60
75
|
end
|
61
76
|
end
|
62
77
|
|
78
|
+
# Equivalent of {#once} but any exception raised in a block will bubble up and cause this client library to fail.
|
79
|
+
# This method should only be used internally by the client library.
|
80
|
+
# @api private
|
81
|
+
def unsafe_once(*event_names, &block)
|
82
|
+
event_names.each do |event_name|
|
83
|
+
callbacks[callbacks_event_coerced(event_name)] << proc_for_block(block, delete_once_run: true, unsafe: true)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
63
87
|
# Trigger an event with event_name that will in turn call all matching callbacks setup with `on`
|
64
88
|
def trigger(event_name, *args)
|
65
89
|
callbacks[callbacks_event_coerced(event_name)].
|
66
90
|
clone.
|
67
91
|
select do |proc_hash|
|
68
|
-
proc_hash[:
|
92
|
+
if proc_hash[:unsafe]
|
93
|
+
proc_hash[:trigger_proc].call *args
|
94
|
+
else
|
95
|
+
safe_yield proc_hash[:trigger_proc], *args
|
96
|
+
end
|
69
97
|
end.each do |callback|
|
70
98
|
callbacks[callbacks_event_coerced(event_name)].delete callback
|
71
99
|
end
|
@@ -108,7 +136,8 @@ module Ably
|
|
108
136
|
block.call *args
|
109
137
|
true if options[:delete_once_run]
|
110
138
|
end,
|
111
|
-
block: block
|
139
|
+
block: block,
|
140
|
+
unsafe: options[:unsafe]
|
112
141
|
}
|
113
142
|
end
|
114
143
|
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'ably/util/pub_sub'
|
2
|
+
|
3
|
+
module Ably::Modules
|
4
|
+
# Message emitter, subscriber and unsubscriber (Pub/Sub) functionality common to Channels and Presence
|
5
|
+
# In addition to standard Pub/Sub functionality, it allows subscribers to subscribe to :all.
|
6
|
+
module MessageEmitter
|
7
|
+
# Subscribe to events on this object
|
8
|
+
#
|
9
|
+
# @param name [String,Symbol] Optional, the event name to subscribe to. Defaults to `:all` events
|
10
|
+
# @yield [Object] For each event, the provided block is called with the event payload object
|
11
|
+
#
|
12
|
+
# @return [void]
|
13
|
+
#
|
14
|
+
def subscribe(*names, &callback)
|
15
|
+
raise ArgumentError, 'Block required to subscribe to events' unless block_given?
|
16
|
+
names = :all unless names && !names.empty?
|
17
|
+
Array(names).uniq.each do |name|
|
18
|
+
message_emitter_subscriptions[message_emitter_subscriptions_message_name_key(name)] << callback
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Unsubscribe the matching block for events on the this object.
|
23
|
+
# If a block is not provided, all subscriptions will be unsubscribed
|
24
|
+
#
|
25
|
+
# @param name [String,Symbol] Optional, the event name to unsubscribe from. Defaults to `:all` events
|
26
|
+
#
|
27
|
+
# @return [void]
|
28
|
+
#
|
29
|
+
def unsubscribe(*names, &callback)
|
30
|
+
names = :all unless names && !names.empty?
|
31
|
+
Array(names).each do |name|
|
32
|
+
if name == :all
|
33
|
+
message_emitter_subscriptions.keys
|
34
|
+
else
|
35
|
+
Array(message_emitter_subscriptions_message_name_key(name))
|
36
|
+
end.each do |key|
|
37
|
+
message_emitter_subscriptions[key].delete_if do |block|
|
38
|
+
!block_given? || callback == block
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Emit a message to message subscribers
|
45
|
+
#
|
46
|
+
# param name [String,Symbol] the event name
|
47
|
+
# param payload [Object] the event object to emit
|
48
|
+
#
|
49
|
+
# @return [void]
|
50
|
+
#
|
51
|
+
# @api private
|
52
|
+
def emit_message(name, payload)
|
53
|
+
raise 'Event name is required' unless name
|
54
|
+
|
55
|
+
message_emitter_subscriptions[:all].each { |cb| cb.call(payload) }
|
56
|
+
message_emitter_subscriptions[name].each { |cb| cb.call(payload) }
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def message_emitter_subscriptions
|
61
|
+
@message_emitter_subscriptions ||= Hash.new { |hash, key| hash[key] = [] }
|
62
|
+
end
|
63
|
+
|
64
|
+
def message_emitter_subscriptions_message_name_key(name)
|
65
|
+
if name == :all
|
66
|
+
:all
|
67
|
+
else
|
68
|
+
message_emitter_subscriptions_coerce_message_key(name)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# this method can be overwritten easily to enforce use of set key types§
|
73
|
+
def message_emitter_subscriptions_coerce_message_key(name)
|
74
|
+
name.to_s
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|