ably-rest 0.8.9 → 0.8.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (25) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +54 -21
  3. data/ably-rest.gemspec +6 -2
  4. data/lib/ably-rest.rb +2 -0
  5. data/lib/submodules/ably-ruby/CHANGELOG.md +42 -4
  6. data/lib/submodules/ably-ruby/README.md +2 -3
  7. data/lib/submodules/ably-ruby/ably.gemspec +6 -2
  8. data/lib/submodules/ably-ruby/lib/ably/auth.rb +80 -21
  9. data/lib/submodules/ably-ruby/lib/ably/modules/message_emitter.rb +5 -2
  10. data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +22 -4
  11. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +1 -1
  12. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +8 -6
  13. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +1 -1
  14. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +4 -2
  15. data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -1
  16. data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +189 -0
  17. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +19 -0
  18. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +20 -0
  19. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +132 -10
  20. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +19 -0
  21. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +122 -4
  22. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +17 -0
  23. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +3 -3
  24. data/lib/submodules/ably-ruby/spec/unit/models/token_details_spec.rb +28 -0
  25. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b903160b7b1b68664bdce7d8be48cb9758df7ed2
4
- data.tar.gz: c176c6156f450ecdb199da2ed865d0e0573421e8
3
+ metadata.gz: 644dba1824658c7c1b6f8a4fa342e68175d97110
4
+ data.tar.gz: 29b9ef90f7717f4686f59947e4c0290ccc27c859
5
5
  SHA512:
6
- metadata.gz: 0c88be9f37e66826972dd0fecc88865a183e25bec776cb8d0d025ca039ad9ddd55d15f02c8a760be6815ed2cb79c830eb1375ef9722e3b2bc7c7184f628ce178
7
- data.tar.gz: 1adca79ac039224c15d3b3bbe24fc308feee608a7635eb2eeed8c92880c76e353f4ed5b3c954d7a3ef4c4a4bbe4c494696b4467b2655e2157c0df27776655a88
6
+ metadata.gz: 00f1c399652e8cb1fb1e2c4bf5f0b47aefdf9e791f96eb976f7c04d4fc5aa38547a21be2d9b6d594888e191612dbaf50abcafc26933f74500651ead4f53bb00f
7
+ data.tar.gz: 288d66b80b142e4a4b7cd00ff4db8450d7722e181ddc4bb07e59bb62585854b82e5e7437b1496b6c7cf4e0eec471afcc1b79a1c16b4adf66d9418fc842abe732
data/README.md CHANGED
@@ -1,12 +1,14 @@
1
- # [Ably](https://ably.io)
1
+ # [Ably](https://www.ably.io)
2
2
 
3
- [![Build Status](https://travis-ci.org/ably/ably-ruby-rest.png)](https://travis-ci.org/ably/ably-ruby-rest)
4
3
  [![Gem Version](https://badge.fury.io/rb/ably-rest.svg)](http://badge.fury.io/rb/ably-rest)
5
4
 
5
+ A Ruby REST client library for [www.ably.io](https://www.ably.io), the realtime messaging service.
6
6
 
7
- A Ruby REST client library for [ably.io](https://ably.io), the real-time messaging service.
7
+ Note: This library was created solely for developers who do not want EventMachine as a dependency of their application. If this is not a requirement for you, then we recommended you use the combined [REST & Realtime gem](https://rubygems.org/gems/ably).
8
8
 
9
- Note: This library was created solely for developers who do not want EventMachine as a dependency of their application. If this is not a requirement for you, then we recommended you use the combined [REST & Real-time gem](https://rubygems.org/gems/ably).
9
+ ## Documentation
10
+
11
+ Visit https://www.ably.io/documentation for a complete API reference and more examples. The examples and API below is not exhaustive.
10
12
 
11
13
  ## Installation
12
14
 
@@ -42,47 +44,78 @@ channel.publish('myEvent', 'Hello!') #=> true
42
44
  ### Querying the History
43
45
 
44
46
  ```ruby
45
- mesage_history = channel.history #=> #<Ably::Models::PaginatedResource ...>
46
- message_history.first # => #<Ably::Models::Message ...>
47
+ messages_page = channel.history #=> #<Ably::Models::PaginatedResult ...>
48
+ messages_page.items.first #=> #<Ably::Models::Message ...>
49
+ messages_page.items.first.data # payload for the message
50
+ messages_page.next # retrieves the next page => #<Ably::Models::PaginatedResult ...>
51
+ messages_page.has_next? # false, there are more pages
47
52
  ```
48
53
 
49
- ### Presence on a channel
54
+ ### Current presence members on a channel
50
55
 
51
56
  ```ruby
52
- members = channel.presence.get # => #<Ably::Models::PaginatedResource ...>
53
- members.first # => #<Ably::Models::PresenceMessage ...>
57
+ members_page = channel.presence.get # => #<Ably::Models::PaginatedResult ...>
58
+ members_page.items.first # first member present in this page => #<Ably::Models::PresenceMessage ...>
59
+ members_page.items.first.client_id # client ID of first member present
60
+ members_page.next # retrieves the next page => #<Ably::Models::PaginatedResult ...>
61
+ members_page.has_next? # false, there are more pages
54
62
  ```
55
63
 
56
- ### Querying the Presence History
64
+ ### Querying the presence history
57
65
 
58
66
  ```ruby
59
- presence_history = channel.presence.history # => #<Ably::Models::PaginatedResource ...>
60
- presence_history.first # => #<Ably::Models::PresenceMessage ...>
67
+ presence_page = channel.presence.history #=> #<Ably::Models::PaginatedResult ...>
68
+ presence_page.items.first #=> #<Ably::Models::PresenceMessage ...>
69
+ presence_page.items.first.client_id # client ID of first member
70
+ presence_page.next # retrieves the next page => #<Ably::Models::PaginatedResult ...>
61
71
  ```
62
72
 
63
- ### Generate Token and Token Request
73
+ ### Symmetric end-to-end encrypted payloads on a channel
74
+
75
+ When a 128 bit or 256 bit key is provided to the library, all payloads are encrypted and decrypted automatically using that key on the channel. The secret key is never transmitted to Ably and thus it is the developer's responsibility to distribute a secret key to both publishers and subscribers.
64
76
 
65
77
  ```ruby
66
- client.auth.request_token
67
- # => #<Ably::Models::Token ...>
78
+ secret_key = Ably::Util::Crypto.generate_random_key
79
+ channel = client.channels.get('test', cipher: { key: secret_key })
80
+ channel.publish nil, "sensitive data" # data will be encrypted before publish
81
+ messages_page = channel.history
82
+ messages_page.items.first.data #=> "sensitive data"
83
+ ```
68
84
 
69
- token = client.auth.create_token_request
85
+ ### Generate a Token
86
+
87
+ Tokens are issued by Ably and are readily usable by any client to connect to Ably:
88
+
89
+ ```ruby
90
+ token_details = client.auth.request_token
91
+ # => #<Ably::Models::TokenDetails ...>
92
+ token_details.token # => "xVLyHw.CLchevH3hF....MDh9ZC_Q"
93
+ client = Ably::Rest.new(token: token_details)
94
+ ```
95
+
96
+ ### Generate a TokenRequest
97
+
98
+ Token requests are issued by your servers and signed using your private API key. This is the preferred method of authentication as no secrets are ever shared, and the token request can be issued to trusted clients without communicating with Ably.
99
+
100
+ ```ruby
101
+ token_request = client.auth.create_token_request(ttl: 3600, client_id: 'jim')
70
102
  # => {"id"=>...,
71
- # "clientId"=>nil,
103
+ # "clientId"=>"jim",
72
104
  # "ttl"=>3600,
73
105
  # "timestamp"=>...,
74
106
  # "capability"=>"{\"*\":[\"*\"]}",
75
107
  # "nonce"=>...,
76
108
  # "mac"=>...}
77
109
 
78
- client = Ably::Rest.new(token_id: token.id)
110
+ client = Ably::Rest.new(token: token_request)
79
111
  ```
80
112
 
81
113
  ### Fetching your application's stats
82
114
 
83
115
  ```ruby
84
- stats = client.stats #=> #<Ably::Models::PaginatedResource ...>
85
- stats.first = #<Ably::Models::Stat ...>
116
+ stats_page = client.stats #=> #<Ably::Models::PaginatedResult ...>
117
+ stats_page.items.first = #<Ably::Models::Stats ...>
118
+ stats_page.next # retrieves the next page => #<Ably::Models::PaginatedResult ...>
86
119
  ```
87
120
 
88
121
  ### Fetching the Ably service time
@@ -110,4 +143,4 @@ To see what has changed in recent versions of Bundler, see the [CHANGELOG](CHANG
110
143
 
111
144
  ## License
112
145
 
113
- Copyright (c) 2015 Ably Real-time Ltd, Licensed under the Apache License, Version 2.0. Refer to [LICENSE](LICENSE) for the license terms.
146
+ Copyright (c) 2016 Ably Real-time Ltd, Licensed under the Apache License, Version 2.0. Refer to [LICENSE](LICENSE) for the license terms.
@@ -32,7 +32,11 @@ Gem::Specification.new do |spec|
32
32
  spec.require_paths = ['lib']
33
33
 
34
34
  spec.add_runtime_dependency 'faraday', '~> 0.9'
35
- spec.add_runtime_dependency 'json'
35
+ if RUBY_VERSION.match(/^1/)
36
+ spec.add_runtime_dependency 'json', '< 2.0'
37
+ else
38
+ spec.add_runtime_dependency 'json'
39
+ end
36
40
  spec.add_runtime_dependency 'msgpack', '>= 0.6.2'
37
41
  spec.add_runtime_dependency 'addressable', '>= 2.0.0'
38
42
 
@@ -44,7 +48,7 @@ Gem::Specification.new do |spec|
44
48
  spec.add_development_dependency 'yard'
45
49
  spec.add_development_dependency 'webmock'
46
50
 
47
- if RUBY_VERSION.match(/^2/)
51
+ unless RUBY_VERSION.match(/^1/)
48
52
  spec.add_development_dependency 'pry'
49
53
  spec.add_development_dependency 'pry-byebug'
50
54
  end
@@ -1,3 +1,5 @@
1
+ require 'addressable/uri'
2
+
1
3
  File.expand_path('submodules/ably-ruby/lib', File.dirname(__FILE__)).tap do |lib|
2
4
  $LOAD_PATH.unshift lib
3
5
 
@@ -1,8 +1,40 @@
1
1
  # Change Log
2
2
 
3
- ## [v0.8.4](https://github.com/ably/ably-ruby/tree/v0.8.9) (2015-03-01)
3
+ ## [Unreleased](https://github.com/ably/ably-ruby/tree/v0.8.13)
4
4
 
5
- [Full Changelog](https://github.com/ably/ably-ruby/compare/v0.8.7...v0.8.9)
5
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v0.8.12...v0.8.13)
6
+
7
+ **Merged pull requests:**
8
+
9
+ - Ensure interoperability with other libraries with JSON protocol [\#94](https://github.com/ably/ably-ruby/pull/94) ([mattheworiordan](https://github.com/mattheworiordan))
10
+
11
+ ## [v0.8.12](https://github.com/ably/ably-ruby/tree/v0.8.12) (2016-05-23)
12
+
13
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v0.8.11...v0.8.12)
14
+
15
+ **Fixed bugs:**
16
+
17
+ - Ably::Exceptions::ConnectionError: SSL\_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed [\#87](https://github.com/ably/ably-ruby/issues/87)
18
+
19
+ **Merged pull requests:**
20
+
21
+ - Reauthorise [\#90](https://github.com/ably/ably-ruby/pull/90) ([mattheworiordan](https://github.com/mattheworiordan))
22
+
23
+ ## [v0.8.11](https://github.com/ably/ably-ruby/tree/v0.8.11) (2016-04-05)
24
+
25
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v0.8.10...v0.8.11)
26
+
27
+ **Merged pull requests:**
28
+
29
+ - Ensure message emitter callbacks are safe \(i.e. cannot break the EM\) [\#85](https://github.com/ably/ably-ruby/pull/85) ([mattheworiordan](https://github.com/mattheworiordan))
30
+
31
+ ## [v0.8.10](https://github.com/ably/ably-ruby/tree/v0.8.10) (2016-04-01)
32
+
33
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v0.8.9...v0.8.10)
34
+
35
+ ## [v0.8.9](https://github.com/ably/ably-ruby/tree/v0.8.9) (2016-03-01)
36
+
37
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v0.8.8...v0.8.9)
6
38
 
7
39
  **Fixed bugs:**
8
40
 
@@ -14,14 +46,20 @@
14
46
 
15
47
  - New Crypto Spec [\#80](https://github.com/ably/ably-ruby/issues/80)
16
48
 
17
- - Support :key in ClientOptions and deprecate :api\_key [\#73](https://github.com/ably/ably-ruby/issues/73)
18
-
19
49
  **Merged pull requests:**
20
50
 
21
51
  - Various fixes for open issues [\#82](https://github.com/ably/ably-ruby/pull/82) ([mattheworiordan](https://github.com/mattheworiordan))
22
52
 
23
53
  - Encryption spec update [\#81](https://github.com/ably/ably-ruby/pull/81) ([mattheworiordan](https://github.com/mattheworiordan))
24
54
 
55
+ ## [v0.8.8](https://github.com/ably/ably-ruby/tree/v0.8.8) (2016-01-26)
56
+
57
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v0.8.7...v0.8.8)
58
+
59
+ **Closed issues:**
60
+
61
+ - Support :key in ClientOptions and deprecate :api\_key [\#73](https://github.com/ably/ably-ruby/issues/73)
62
+
25
63
  ## [v0.8.7](https://github.com/ably/ably-ruby/tree/v0.8.7) (2015-12-31)
26
64
 
27
65
  [Full Changelog](https://github.com/ably/ably-ruby/compare/v0.8.6...v0.8.7)
@@ -1,6 +1,5 @@
1
1
  # [Ably](https://www.ably.io)
2
2
 
3
- [![Build Status](https://travis-ci.org/ably/ably-ruby.png)](https://travis-ci.org/ably/ably-ruby)
4
3
  [![Gem Version](https://badge.fury.io/rb/ably.svg)](http://badge.fury.io/rb/ably)
5
4
  [![Coverage Status](https://coveralls.io/repos/ably/ably-ruby/badge.svg)](https://coveralls.io/r/ably/ably-ruby)
6
5
 
@@ -227,10 +226,10 @@ secret_key = Ably::Util::Crypto.generate_random_key
227
226
  channel = client.channels.get('test', cipher: { key: secret_key })
228
227
  channel.publish nil, "sensitive data" # data will be encrypted before publish
229
228
  messages_page = channel.history
230
- messages_page.first.data #=> "sensitive data"
229
+ messages_page.items.first.data #=> "sensitive data"
231
230
  ```
232
231
 
233
- ### Generate Token
232
+ ### Generate a Token
234
233
 
235
234
  Tokens are issued by Ably and are readily usable by any client to connect to Ably:
236
235
 
@@ -22,7 +22,11 @@ Gem::Specification.new do |spec|
22
22
  spec.add_runtime_dependency 'em-http-request', '~> 1.1'
23
23
  spec.add_runtime_dependency 'statesman', '~> 1.0.0'
24
24
  spec.add_runtime_dependency 'faraday', '~> 0.9'
25
- spec.add_runtime_dependency 'json'
25
+ if RUBY_VERSION.match(/^1/)
26
+ spec.add_runtime_dependency 'json', '< 2.0'
27
+ else
28
+ spec.add_runtime_dependency 'json'
29
+ end
26
30
  spec.add_runtime_dependency 'websocket-driver', '~> 0.6'
27
31
  spec.add_runtime_dependency 'msgpack', '>= 0.6.2'
28
32
  spec.add_runtime_dependency 'addressable', '>= 2.0.0'
@@ -37,7 +41,7 @@ Gem::Specification.new do |spec|
37
41
 
38
42
  spec.add_development_dependency 'coveralls'
39
43
 
40
- if RUBY_VERSION.match(/^2/)
44
+ unless RUBY_VERSION.match(/^1/)
41
45
  spec.add_development_dependency 'pry'
42
46
  spec.add_development_dependency 'pry-byebug'
43
47
  end
@@ -72,6 +72,7 @@ module Ably
72
72
  end
73
73
 
74
74
  split_api_key_into_key_and_secret! options if options[:key]
75
+ store_and_delete_basic_auth_key_from_options! options
75
76
 
76
77
  if using_basic_auth? && !api_key_present?
77
78
  raise ArgumentError, 'key is missing. Either an API key, token, or token auth method must be provided'
@@ -104,9 +105,9 @@ module Ably
104
105
  #
105
106
  # In the event that a new token request is made, the provided options are used.
106
107
  #
107
- # @param [Hash] token_params the token params used for future token requests
108
- # @param [Hash] auth_options the authentication options used for future token requests
109
- # @option auth_options [Boolean] :force obtains a new token even if the current token is valid
108
+ # @param [Hash, nil] token_params the token params used for future token requests. When nil, previously configured token params are used
109
+ # @param [Hash, nil] auth_options the authentication options used for future token requests. When nil, previously configure authentication options are used
110
+ # @option auth_options [Boolean] :force obtains a new token even if the current token is valid. If the provided +auth_options+ Hash contains only this +:force+ attribute, the existing configured authentication options are not overwriten
110
111
  # @option (see #request_token)
111
112
  #
112
113
  # @return (see #create_token_request)
@@ -122,23 +123,54 @@ module Ably
122
123
  # token_request
123
124
  # end
124
125
  #
125
- def authorise(token_params = {}, auth_options = {})
126
- ensure_valid_auth_attributes auth_options
126
+ def authorise(token_params = nil, auth_options = nil)
127
+ if auth_options == { force: true }
128
+ auth_options = options.merge(force: true)
129
+ elsif auth_options.nil?
130
+ auth_options = options
131
+ else
132
+ ensure_valid_auth_attributes auth_options
127
133
 
128
- auth_options = auth_options.clone
134
+ auth_options = auth_options.clone
129
135
 
130
- if current_token_details && !auth_options.delete(:force)
131
- return current_token_details unless current_token_details.expired?
136
+ if auth_options[:token_params]
137
+ token_params = auth_options.delete(:token_params).merge(token_params || {})
138
+ end
139
+
140
+ # If basic credentials are provided then overwrite existing options
141
+ # otherwise we need to retain the existing credentials in the auth options
142
+ split_api_key_into_key_and_secret! auth_options if auth_options[:key]
143
+ if auth_options[:key_name] && auth_options[:key_secret]
144
+ store_and_delete_basic_auth_key_from_options! auth_options
145
+ end
146
+
147
+ @options = auth_options.clone
148
+
149
+ # Force reauth and query the server time only happens once
150
+ # the otpions remain in auth_options though so they are passed to request_token
151
+ @options.delete(:query_time)
152
+ @options.delete(:force)
153
+
154
+ @options.freeze
132
155
  end
133
156
 
134
- split_api_key_into_key_and_secret! auth_options if auth_options[:key]
135
- @options = @options.merge(auth_options) # update defaults
157
+ unless token_params.nil?
158
+ @token_params = token_params
159
+ @token_params.freeze
160
+ end
136
161
 
137
- token_params = (auth_options.delete(:token_params) || {}).merge(token_params)
138
- @token_params = @token_params.merge(token_params) # update defaults
162
+ if current_token_details && !auth_options[:force]
163
+ return current_token_details unless current_token_details.expired?
164
+ end
139
165
 
140
- authorise_with_token(request_token(token_params, auth_options)).tap do |new_token_details|
166
+ authorise_with_token(request_token(@token_params, auth_options)).tap do |new_token_details|
141
167
  logger.debug "Auth: new token following authorisation: #{new_token_details}"
168
+
169
+ # If authorise was forced allow a block to be called so that the realtime library
170
+ # can force upgrade the authorisation
171
+ if auth_options[:force] && block_given?
172
+ yield new_token_details
173
+ end
142
174
  end
143
175
  end
144
176
 
@@ -240,11 +272,8 @@ module Ably
240
272
 
241
273
  raise Ably::Exceptions::TokenRequestFailed, 'Key Name and Key Secret are required to generate a new token request' unless request_key_name && request_key_secret
242
274
 
243
- timestamp = if auth_options[:query_time]
244
- client.time
245
- else
246
- token_params.delete(:timestamp) || Time.now
247
- end
275
+ ensure_current_time_is_based_on_server_time if auth_options[:query_time]
276
+ timestamp = token_params.delete(:timestamp) || current_time
248
277
  timestamp = Time.at(timestamp) if timestamp.kind_of?(Integer)
249
278
 
250
279
  ttl = [
@@ -261,7 +290,14 @@ module Ably
261
290
  nonce: token_params[:nonce] || SecureRandom.hex.force_encoding('UTF-8')
262
291
  }
263
292
 
264
- token_request[:capability] = JSON.dump(token_request[:capability]) if token_request[:capability].is_a?(Hash)
293
+ if token_request[:capability].is_a?(Hash)
294
+ lexicographic_ordered_capabilities = Hash[
295
+ token_request[:capability].sort_by { |key, value| key }.map do |key, value|
296
+ [key, value.sort]
297
+ end
298
+ ]
299
+ token_request[:capability] = JSON.dump(lexicographic_ordered_capabilities)
300
+ end
265
301
 
266
302
  token_request[:mac] = sign_params(token_request, request_key_secret)
267
303
 
@@ -276,11 +312,11 @@ module Ably
276
312
  end
277
313
 
278
314
  def key_name
279
- options[:key_name]
315
+ @key_name
280
316
  end
281
317
 
282
318
  def key_secret
283
- options[:key_secret]
319
+ @key_secret
284
320
  end
285
321
 
286
322
  # True when Basic Auth is being used to authenticate with Ably
@@ -413,6 +449,24 @@ module Ably
413
449
  @token_option
414
450
  end
415
451
 
452
+ # Returns the current device clock time unless the
453
+ # the server time has previously been requested with query_time: true
454
+ # and the @server_time_offset is configured
455
+ def current_time
456
+ if @server_time_offset
457
+ Time.now + @server_time_offset
458
+ else
459
+ Time.now
460
+ end
461
+ end
462
+
463
+ # Get the difference in time between the server
464
+ # and the local clock and store this for future time requests
465
+ def ensure_current_time_is_based_on_server_time
466
+ server_time = client.time
467
+ @server_time_offset = server_time.to_f - Time.now.to_f
468
+ end
469
+
416
470
  def ensure_valid_auth_attributes(attributes)
417
471
  if attributes[:timestamp]
418
472
  unless attributes[:timestamp].kind_of?(Time) || attributes[:timestamp].kind_of?(Numeric)
@@ -471,6 +525,11 @@ module Ably
471
525
  options.delete :key
472
526
  end
473
527
 
528
+ def store_and_delete_basic_auth_key_from_options!(options)
529
+ @key_name = options.delete(:key_name)
530
+ @key_secret = options.delete(:key_secret)
531
+ end
532
+
474
533
  # Returns the current token if it exists or authorises and retrieves a token
475
534
  def token_auth_string
476
535
  if !current_token_details && token_option
@@ -4,6 +4,9 @@ module Ably::Modules
4
4
  # Message emitter, subscriber and unsubscriber (Pub/Sub) functionality common to Channels and Presence
5
5
  # In addition to standard Pub/Sub functionality, it allows subscribers to subscribe to :all.
6
6
  module MessageEmitter
7
+
8
+ include Ably::Modules::SafeYield
9
+
7
10
  # Subscribe to events on this object
8
11
  #
9
12
  # @param names [String,Symbol] Optional, the event name(s) to subscribe to. Defaults to `:all` events
@@ -50,8 +53,8 @@ module Ably::Modules
50
53
  #
51
54
  # @api private
52
55
  def emit_message(name, payload)
53
- message_emitter_subscriptions[:all].each { |cb| cb.call(payload) }
54
- message_emitter_subscriptions[name].each { |cb| cb.call(payload) } if name
56
+ message_emitter_subscriptions[:all].each { |cb| safe_yield(cb, payload) }
57
+ message_emitter_subscriptions[name].each { |cb| safe_yield(cb, payload) } if name
55
58
  end
56
59
 
57
60
  private