ably-rest 0.7.3 → 0.7.5

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.
Files changed (69) hide show
  1. checksums.yaml +8 -8
  2. data/.travis.yml +1 -0
  3. data/SPEC.md +480 -472
  4. data/lib/ably-rest.rb +1 -1
  5. data/lib/submodules/ably-ruby/LICENSE.txt +1 -1
  6. data/lib/submodules/ably-ruby/README.md +107 -24
  7. data/lib/submodules/ably-ruby/SPEC.md +531 -398
  8. data/lib/submodules/ably-ruby/lib/ably/auth.rb +24 -16
  9. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +9 -0
  10. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +17 -9
  11. data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +12 -8
  12. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +18 -10
  13. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +15 -4
  14. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +4 -3
  15. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +31 -2
  16. data/lib/submodules/ably-ruby/lib/ably/modules/message_emitter.rb +77 -0
  17. data/lib/submodules/ably-ruby/lib/ably/modules/safe_deferrable.rb +71 -0
  18. data/lib/submodules/ably-ruby/lib/ably/modules/safe_yield.rb +41 -0
  19. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +28 -8
  20. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +0 -5
  21. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +24 -29
  22. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +54 -11
  23. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +21 -6
  24. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +7 -2
  25. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +29 -26
  26. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +4 -4
  27. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +41 -9
  28. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +72 -24
  29. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +26 -4
  30. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +19 -6
  31. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +74 -208
  32. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +264 -0
  33. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_manager.rb +59 -0
  34. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_state_machine.rb +64 -0
  35. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +1 -1
  36. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +6 -2
  37. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
  38. data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +3 -1
  39. data/lib/submodules/ably-ruby/lib/ably/util/safe_deferrable.rb +18 -0
  40. data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
  41. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +2 -2
  42. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +28 -6
  43. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +116 -46
  44. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +55 -10
  45. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +32 -0
  46. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +456 -96
  47. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +2 -2
  48. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +2 -2
  49. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +96 -7
  50. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +8 -0
  51. data/lib/submodules/ably-ruby/spec/shared/safe_deferrable_behaviour.rb +71 -0
  52. data/lib/submodules/ably-ruby/spec/support/api_helper.rb +1 -1
  53. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +1 -1
  54. data/lib/submodules/ably-ruby/spec/support/test_app.rb +13 -7
  55. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +15 -14
  56. data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +4 -4
  57. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +17 -17
  58. data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +4 -4
  59. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +28 -9
  60. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +50 -0
  61. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +76 -2
  62. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +51 -20
  63. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +3 -3
  64. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +30 -0
  65. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +52 -26
  66. data/lib/submodules/ably-ruby/spec/unit/realtime/safe_deferrable_spec.rb +12 -0
  67. data/spec/spec_helper.rb +5 -0
  68. metadata +12 -4
  69. data/lib/submodules/ably-ruby/.ruby-version.old +0 -1
@@ -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
- response = client.post("/keys/#{token_request.fetch(:id)}/requestToken", token_request.hash, send_auth_header: false, disable_automatic_reauthorise: true)
161
- body = IdiomaticRubyWrapper(response.body)
162
-
163
- Ably::Models::Token.new(body.fetch(:access_token))
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
@@ -188,9 +190,10 @@ module Ably
188
190
  # # :mac=>"881oZHeFo6oMim7N64y2vFHtSlpQ2gn/uE56a8gUxHw="
189
191
  # # }
190
192
  def create_token_request(options = {})
191
- token_attributes = %w(id client_id ttl timestamp capability nonce)
193
+ token_attributes = %w(id client_id ttl timestamp capability nonce persisted)
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
@@ -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
@@ -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 protocol_message [ProtocolMessage] An optional protocol message to assocate the message with
5
+ # @param [Hash] options (see Message#initialize)
6
6
  #
7
7
  # @return [Message]
8
- def self.Message(message, protocol_message = nil)
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, protocol_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 EventMachine::Deferrable
44
+ include Ably::Modules::SafeDeferrable if defined?(Ably::Realtime)
45
45
 
46
46
  # {Message} initializer
47
47
  #
48
- # @param hash_object [Hash] object with the underlying message details
49
- # @param protocol_message [ProtocolMessage] if this message has been published, then it is associated with a {ProtocolMessage}
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, protocol_message = nil)
52
- @protocol_message = protocol_message
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 {EventMachine::Deferrable} object,
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,EventMachine::Deferrable]
37
+ # @return [PaginatedResource,Ably::Util::SafeDeferrable]
38
38
  def first_page(&success_callback)
39
- async_wrap_if(make_async, success_callback) do
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 {EventMachine::Deferrable} object,
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,EventMachine::Deferrable]
48
+ # @return [PaginatedResource,Ably::Util::SafeDeferrable]
49
49
  def next_page(&success_callback)
50
- async_wrap_if(make_async, success_callback) do
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 async_wrap_if(is_realtime, success_callback, &operation)
165
- if is_realtime
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 protocol_message [ProtocolMessage] An optional protocol message to assocate the presence message with
5
+ # @param [Hash] options (see PresenceMessage#initialize)
6
6
  #
7
7
  # @return [PresenceMessage]
8
- def self.PresenceMessage(presence_message, protocol_message = nil)
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, protocol_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 EventMachine::Deferrable
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
- # {Message} initializer
55
+ # {PresenceMessage} initializer
56
56
  #
57
- # @param hash_object [Hash] object with the underlying message details
58
- # @param protocol_message [ProtocolMessage] if this message has been published, then it is associated with a {ProtocolMessage}
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, protocol_message = nil)
61
- @protocol_message = protocol_message
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 EventMachine::Deferrable if defined?(Ably::Realtime)
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
- def initialize(hash_object)
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 {EventMachine::Deferrable http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Deferrable}
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 [EventMachine::Deferrable]
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
- EventMachine::DefaultDeferrable.new.tap do |deferrable|
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[:trigger_proc].call(*args)
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