ably 0.7.0 → 0.7.1
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.
- data/.travis.yml +2 -2
- data/Rakefile +2 -0
- data/SPEC.md +230 -194
- data/ably.gemspec +2 -0
- data/lib/ably/auth.rb +7 -5
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +5 -7
- data/lib/ably/models/paginated_resource.rb +14 -21
- data/lib/ably/models/protocol_message.rb +1 -1
- data/lib/ably/modules/ably.rb +4 -0
- data/lib/ably/modules/async_wrapper.rb +2 -2
- data/lib/ably/modules/channels_collection.rb +31 -8
- data/lib/ably/modules/conversions.rb +10 -0
- data/lib/ably/modules/enum.rb +2 -3
- data/lib/ably/modules/state_emitter.rb +8 -8
- data/lib/ably/modules/state_machine.rb +7 -3
- data/lib/ably/realtime/channel.rb +6 -5
- data/lib/ably/realtime/channel/channel_manager.rb +11 -10
- data/lib/ably/realtime/channel/channel_state_machine.rb +10 -9
- data/lib/ably/realtime/channels.rb +3 -0
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +11 -1
- data/lib/ably/realtime/connection.rb +55 -16
- data/lib/ably/realtime/connection/connection_manager.rb +25 -8
- data/lib/ably/realtime/connection/connection_state_machine.rb +9 -9
- data/lib/ably/realtime/connection/websocket_transport.rb +2 -2
- data/lib/ably/realtime/presence.rb +16 -17
- data/lib/ably/util/crypto.rb +1 -1
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/channel_history_spec.rb +6 -5
- data/spec/acceptance/realtime/connection_failures_spec.rb +103 -27
- data/spec/acceptance/realtime/connection_spec.rb +81 -17
- data/spec/acceptance/realtime/presence_spec.rb +82 -30
- data/spec/acceptance/rest/auth_spec.rb +22 -19
- data/spec/acceptance/rest/client_spec.rb +4 -4
- data/spec/acceptance/rest/presence_spec.rb +12 -6
- data/spec/rspec_config.rb +9 -0
- data/spec/shared/model_behaviour.rb +1 -1
- data/spec/spec_helper.rb +4 -1
- data/spec/support/event_machine_helper.rb +26 -37
- data/spec/support/markdown_spec_formatter.rb +96 -68
- data/spec/support/rest_testapp_before_retry.rb +15 -0
- data/spec/support/test_app.rb +4 -0
- data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +20 -2
- data/spec/unit/models/message_spec.rb +1 -1
- data/spec/unit/models/paginated_resource_spec.rb +15 -1
- data/spec/unit/modules/enum_spec.rb +10 -0
- data/spec/unit/realtime/channels_spec.rb +30 -0
- data/spec/unit/rest/channels_spec.rb +30 -0
- metadata +101 -35
- checksums.yaml +0 -7
data/ably.gemspec
CHANGED
@@ -19,6 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
21
|
spec.add_runtime_dependency 'eventmachine', '~> 1.0'
|
22
|
+
spec.add_runtime_dependency 'em-http-request', '~> 1.1'
|
22
23
|
spec.add_runtime_dependency 'statesman', '~> 1.0.0'
|
23
24
|
spec.add_runtime_dependency 'faraday', '~> 0.9'
|
24
25
|
spec.add_runtime_dependency 'json'
|
@@ -29,6 +30,7 @@ Gem::Specification.new do |spec|
|
|
29
30
|
spec.add_development_dependency 'rake'
|
30
31
|
spec.add_development_dependency 'redcarpet'
|
31
32
|
spec.add_development_dependency 'rspec', '~> 3.1.0' # version lock, see event_machine_helper.rb#patch_example_block_with_surrounding_eventmachine_reactor
|
33
|
+
spec.add_development_dependency 'rspec-retry'
|
32
34
|
spec.add_development_dependency 'yard'
|
33
35
|
spec.add_development_dependency 'webmock'
|
34
36
|
end
|
data/lib/ably/auth.rb
CHANGED
@@ -37,6 +37,7 @@ module Ably
|
|
37
37
|
# @param options (see Ably::Rest::Client#initialize)
|
38
38
|
# @option (see Ably::Rest::Client#initialize)
|
39
39
|
# @yield (see Ably::Rest::Client#initialize)
|
40
|
+
#
|
40
41
|
def initialize(client, options, &token_request_block)
|
41
42
|
auth_options = options.dup
|
42
43
|
|
@@ -140,12 +141,12 @@ module Ably
|
|
140
141
|
# token_request
|
141
142
|
# end
|
142
143
|
#
|
143
|
-
def request_token(options = {}
|
144
|
+
def request_token(options = {})
|
144
145
|
token_options = self.auth_options.merge(options)
|
145
146
|
|
146
147
|
auth_url = token_options.delete(:auth_url)
|
147
148
|
token_request = if block_given?
|
148
|
-
|
149
|
+
yield token_options
|
149
150
|
elsif default_token_block
|
150
151
|
default_token_block.call(token_options)
|
151
152
|
elsif auth_url
|
@@ -203,7 +204,7 @@ module Ably
|
|
203
204
|
|
204
205
|
token_request = {
|
205
206
|
id: request_key_id,
|
206
|
-
|
207
|
+
clientId: client_id,
|
207
208
|
ttl: Ably::Models::Token::DEFAULTS[:ttl],
|
208
209
|
timestamp: timestamp,
|
209
210
|
capability: Ably::Models::Token::DEFAULTS[:capability],
|
@@ -217,7 +218,8 @@ module Ably
|
|
217
218
|
ensure_utf_8 :nonce, token_request[:nonce], allow_nil: true
|
218
219
|
|
219
220
|
token_request[:mac] = sign_params(token_request, request_key_secret)
|
220
|
-
|
221
|
+
|
222
|
+
convert_to_mixed_case_hash(token_request)
|
221
223
|
end
|
222
224
|
|
223
225
|
def api_key
|
@@ -350,7 +352,7 @@ module Ably
|
|
350
352
|
).map { |t| "#{t}\n" }.join("")
|
351
353
|
|
352
354
|
encode64(
|
353
|
-
|
355
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, secret, text)
|
354
356
|
)
|
355
357
|
end
|
356
358
|
|
@@ -76,7 +76,7 @@ module Ably::Models
|
|
76
76
|
hash[source_key_for(key)] = value
|
77
77
|
end
|
78
78
|
|
79
|
-
def fetch(key, default = nil
|
79
|
+
def fetch(key, default = nil)
|
80
80
|
if has_key?(key)
|
81
81
|
self[key]
|
82
82
|
else
|
@@ -107,15 +107,13 @@ module Ably::Models
|
|
107
107
|
end
|
108
108
|
|
109
109
|
# Method ensuring this {IdiomaticRubyWrapper} is {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
|
110
|
-
def each
|
110
|
+
def each
|
111
|
+
return to_enum(:each) unless block_given?
|
112
|
+
|
111
113
|
hash.each do |key, value|
|
112
114
|
key = convert_to_snake_case_symbol(key)
|
113
115
|
value = self[key]
|
114
|
-
|
115
|
-
block.call key, value
|
116
|
-
else
|
117
|
-
yield key, value
|
118
|
-
end
|
116
|
+
yield key, value
|
119
117
|
end
|
120
118
|
end
|
121
119
|
|
@@ -5,7 +5,7 @@ module Ably::Models
|
|
5
5
|
# Paging information is provided by Ably in the LINK HTTP headers
|
6
6
|
class PaginatedResource
|
7
7
|
include Enumerable
|
8
|
-
include Ably::Modules::AsyncWrapper if defined?(
|
8
|
+
include Ably::Modules::AsyncWrapper if defined?(Ably::Realtime)
|
9
9
|
|
10
10
|
# @param [Faraday::Response] http_response Initial HTTP response from an Ably request to a paged resource
|
11
11
|
# @param [String] base_url Base URL for request that generated the http_response so that subsequent paged requests can be made
|
@@ -25,19 +25,9 @@ module Ably::Models
|
|
25
25
|
@each_block = each_block
|
26
26
|
@make_async = options.fetch(:async_blocking_operations, false)
|
27
27
|
|
28
|
-
@body =
|
29
|
-
|
30
|
-
|
31
|
-
base.public_send(:const_get, klass_name)
|
32
|
-
end.new(item)
|
33
|
-
end
|
34
|
-
else
|
35
|
-
http_response.body
|
36
|
-
end
|
37
|
-
|
38
|
-
@body = @body.map do |resource|
|
39
|
-
each_block.call(resource)
|
40
|
-
end if block_given?
|
28
|
+
@body = http_response.body
|
29
|
+
@body = coerce_items_into(body, @coerce_into) if @coerce_into
|
30
|
+
@body = body.map { |item| yield item } if block_given?
|
41
31
|
end
|
42
32
|
|
43
33
|
# Retrieve the first page of results.
|
@@ -100,13 +90,8 @@ module Ably::Models
|
|
100
90
|
|
101
91
|
# Method to allow {PaginatedResource} to be {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
|
102
92
|
def each(&block)
|
103
|
-
|
104
|
-
|
105
|
-
block.call item
|
106
|
-
else
|
107
|
-
yield item
|
108
|
-
end
|
109
|
-
end
|
93
|
+
return to_enum(:each) unless block_given?
|
94
|
+
body.each(&block)
|
110
95
|
end
|
111
96
|
|
112
97
|
# First item in this page
|
@@ -134,6 +119,14 @@ module Ably::Models
|
|
134
119
|
private
|
135
120
|
attr_reader :body, :http_response, :base_url, :client, :coerce_into, :raw_body, :each_block, :make_async
|
136
121
|
|
122
|
+
def coerce_items_into(items, type_string)
|
123
|
+
items.map do |item|
|
124
|
+
@coerce_into.split('::').inject(Kernel) do |base, klass_name|
|
125
|
+
base.public_send(:const_get, klass_name)
|
126
|
+
end.new(item)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
137
130
|
def pagination_headers
|
138
131
|
link_regex = %r{<(?<url>[^>]+)>; rel="(?<rel>[^"]+)"}
|
139
132
|
@pagination_headers ||= begin
|
@@ -37,7 +37,7 @@ module Ably::Models
|
|
37
37
|
#
|
38
38
|
class ProtocolMessage
|
39
39
|
include Ably::Modules::ModelCommon
|
40
|
-
include EventMachine::Deferrable
|
40
|
+
include EventMachine::Deferrable if defined?(Ably::Realtime)
|
41
41
|
extend Ably::Modules::Enum
|
42
42
|
|
43
43
|
# Actions which are sent by the Ably Realtime API
|
data/lib/ably/modules/ably.rb
CHANGED
@@ -8,4 +8,8 @@ module Ably
|
|
8
8
|
# network failures either at the client, between the client and Ably, within an Ably data center, or at the IO domain registrar
|
9
9
|
#
|
10
10
|
FALLBACK_HOSTS = %w(A.ably-realtime.com B.ably-realtime.com C.ably-realtime.com D.ably-realtime.com E.ably-realtime.com)
|
11
|
+
INTERNET_CHECK = {
|
12
|
+
url: 'http://internet-up.ably.io/is-the-internet-up.txt', #ably-realtime.com
|
13
|
+
ok_text: 'yes'
|
14
|
+
}
|
11
15
|
end
|
@@ -37,8 +37,8 @@ module Ably::Modules
|
|
37
37
|
# @yield [Object] operation block that is run in a thread
|
38
38
|
# @return [EventMachine::Deferrable]
|
39
39
|
#
|
40
|
-
def async_wrap(success_callback = nil
|
41
|
-
raise ArgumentError,
|
40
|
+
def async_wrap(success_callback = nil)
|
41
|
+
raise ArgumentError, 'Block required' unless block_given?
|
42
42
|
|
43
43
|
EventMachine::DefaultDeferrable.new.tap do |deferrable|
|
44
44
|
deferrable.callback &success_callback if success_callback
|
@@ -2,6 +2,8 @@ module Ably::Modules
|
|
2
2
|
# ChannelsCollection module provides common functionality to the Rest and Realtime Channels objects
|
3
3
|
# such as #get, #[], #fetch, and #release
|
4
4
|
module ChannelsCollection
|
5
|
+
include Enumerable
|
6
|
+
|
5
7
|
def initialize(client, channel_klass)
|
6
8
|
@client = client
|
7
9
|
@channel_klass = channel_klass
|
@@ -11,11 +13,12 @@ module Ably::Modules
|
|
11
13
|
# Return a Channel for the given name
|
12
14
|
#
|
13
15
|
# @param name [String] The name of the channel
|
14
|
-
# @param channel_options [Hash] Channel options
|
16
|
+
# @param channel_options [Hash] Channel options including the encryption options
|
17
|
+
#
|
18
|
+
# @return [Channel]
|
15
19
|
#
|
16
|
-
# @return Channel
|
17
20
|
def get(name, channel_options = {})
|
18
|
-
|
21
|
+
channels[name] ||= channel_klass.new(client, name, channel_options)
|
19
22
|
end
|
20
23
|
alias_method :[], :get
|
21
24
|
|
@@ -27,20 +30,40 @@ module Ably::Modules
|
|
27
30
|
# @yield [options] (optional) if a missing_block is passed to this method and no channel exists matching the name, this block is called
|
28
31
|
# @yieldparam [String] name of the missing channel
|
29
32
|
#
|
30
|
-
# @return Channel
|
33
|
+
# @return [Channel]
|
34
|
+
#
|
31
35
|
def fetch(name, &missing_block)
|
32
|
-
|
36
|
+
channels.fetch(name, &missing_block)
|
33
37
|
end
|
34
38
|
|
35
39
|
# Destroy the Channel and releases the associated resources.
|
36
40
|
#
|
37
41
|
# Releasing a Channel is not typically necessary as a channel consumes no resources other than the memory footprint of the
|
38
42
|
# Channel object. Explicitly release channels to free up resources if required
|
39
|
-
|
40
|
-
|
43
|
+
#
|
44
|
+
# @param name [String] The name of the channel
|
45
|
+
#
|
46
|
+
# @return [void]
|
47
|
+
#
|
48
|
+
def release(name)
|
49
|
+
channels.delete(name)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @!attribute [r] length
|
53
|
+
# @return [Integer] number of channels created
|
54
|
+
def length
|
55
|
+
channels.length
|
56
|
+
end
|
57
|
+
alias_method :count, :length
|
58
|
+
alias_method :size, :length
|
59
|
+
|
60
|
+
# Method to allow {ChannelsCollection} to be {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
|
61
|
+
def each(&block)
|
62
|
+
return to_enum(:each) unless block_given?
|
63
|
+
channels.values.each(&block)
|
41
64
|
end
|
42
65
|
|
43
66
|
private
|
44
|
-
attr_reader :client, :channel_klass
|
67
|
+
attr_reader :client, :channel_klass, :channels
|
45
68
|
end
|
46
69
|
end
|
@@ -58,6 +58,16 @@ module Ably::Modules
|
|
58
58
|
join
|
59
59
|
end
|
60
60
|
|
61
|
+
# Convert a Hash into a mixed case Hash objet
|
62
|
+
# i.e. { client_id: 1 } becomes { 'clientId' => 1 }
|
63
|
+
def convert_to_mixed_case_hash(hash, options = {})
|
64
|
+
raise ArgumentError, 'Hash expected' unless hash.kind_of?(Hash)
|
65
|
+
hash.each_with_object({}) do |pair, new_hash|
|
66
|
+
key, val = pair
|
67
|
+
new_hash[convert_to_mixed_case(key, options)] = val
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
61
71
|
# Convert key to :snake_case from snakeCase
|
62
72
|
def convert_to_snake_case_symbol(key)
|
63
73
|
key.to_s.gsub(/::/, '/').
|
data/lib/ably/modules/enum.rb
CHANGED
@@ -80,9 +80,8 @@ module Ably::Modules
|
|
80
80
|
|
81
81
|
# Method ensuring this {Enum} is {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
|
82
82
|
def each(&block)
|
83
|
-
|
84
|
-
|
85
|
-
end
|
83
|
+
return to_enum(:each) unless block_given?
|
84
|
+
by_symbol.values.each(&block)
|
86
85
|
end
|
87
86
|
|
88
87
|
# The name provided in the constructor for this Enum
|
@@ -69,17 +69,17 @@ module Ably::Modules
|
|
69
69
|
# @yield block is called if the state is matched immediately or once when the state is reached
|
70
70
|
#
|
71
71
|
# @return [void]
|
72
|
-
def once_or_if(target_states, options = {}
|
73
|
-
raise ArgumentError, 'Block
|
72
|
+
def once_or_if(target_states, options = {})
|
73
|
+
raise ArgumentError, 'Block required' unless block_given?
|
74
74
|
|
75
75
|
if Array(target_states).any? { |target_state| state == target_state }
|
76
|
-
|
76
|
+
yield
|
77
77
|
else
|
78
78
|
failure_block = options.fetch(:else, nil)
|
79
79
|
failure_wrapper = nil
|
80
80
|
|
81
81
|
success_wrapper = Proc.new do
|
82
|
-
|
82
|
+
yield
|
83
83
|
off &success_wrapper
|
84
84
|
off &failure_wrapper if failure_wrapper
|
85
85
|
end
|
@@ -107,7 +107,7 @@ module Ably::Modules
|
|
107
107
|
#
|
108
108
|
# @api private
|
109
109
|
def once_state_changed(&block)
|
110
|
-
raise ArgumentError, 'Block
|
110
|
+
raise ArgumentError, 'Block required' unless block_given?
|
111
111
|
|
112
112
|
once_block = proc do |*args|
|
113
113
|
off *self.class::STATE.map, &once_block
|
@@ -120,13 +120,13 @@ module Ably::Modules
|
|
120
120
|
private
|
121
121
|
|
122
122
|
# Returns an {EventMachine::Deferrable} and once the target state is reached, the
|
123
|
-
#
|
123
|
+
# success block if provided and {EventMachine::Deferrable#callback} is called.
|
124
124
|
# If the state changes to any other state, the {EventMachine::Deferrable#errback} is called.
|
125
125
|
#
|
126
|
-
def deferrable_for_state_change_to(target_state
|
126
|
+
def deferrable_for_state_change_to(target_state)
|
127
127
|
EventMachine::DefaultDeferrable.new.tap do |deferrable|
|
128
128
|
once_or_if(target_state, else: proc { |*args| deferrable.fail self, *args }) do
|
129
|
-
|
129
|
+
yield self if block_given?
|
130
130
|
deferrable.succeed self
|
131
131
|
end
|
132
132
|
end
|
@@ -13,6 +13,7 @@ module Ably::Modules
|
|
13
13
|
include Statesman::Machine
|
14
14
|
end
|
15
15
|
klass.extend Ably::Modules::StatesmanMonkeyPatch
|
16
|
+
klass.extend ClassMethods
|
16
17
|
end
|
17
18
|
|
18
19
|
# Alternative to Statesman's #transition_to that:
|
@@ -45,9 +46,12 @@ module Ably::Modules
|
|
45
46
|
Ably::Exceptions::StateChangeError.new(error_message, nil, 80020)
|
46
47
|
end
|
47
48
|
|
48
|
-
|
49
|
-
|
50
|
-
|
49
|
+
module ClassMethods
|
50
|
+
private
|
51
|
+
|
52
|
+
def is_error_type?(error)
|
53
|
+
error.kind_of?(Ably::Models::ErrorInfo) || error.kind_of?(StandardError)
|
54
|
+
end
|
51
55
|
end
|
52
56
|
end
|
53
57
|
end
|
@@ -214,6 +214,12 @@ module Ably
|
|
214
214
|
@error_reason = error
|
215
215
|
end
|
216
216
|
|
217
|
+
# Used by {Ably::Modules::StateEmitter} to debug state changes
|
218
|
+
# @api private
|
219
|
+
def logger
|
220
|
+
client.logger
|
221
|
+
end
|
222
|
+
|
217
223
|
private
|
218
224
|
attr_reader :queue, :subscriptions
|
219
225
|
|
@@ -276,11 +282,6 @@ module Ably
|
|
276
282
|
client.rest_client.channel(name)
|
277
283
|
end
|
278
284
|
|
279
|
-
# Used by {Ably::Modules::StateEmitter} to debug state changes
|
280
|
-
def logger
|
281
|
-
client.logger
|
282
|
-
end
|
283
|
-
|
284
285
|
def connection
|
285
286
|
client.connection
|
286
287
|
end
|
@@ -20,10 +20,6 @@ module Ably::Realtime
|
|
20
20
|
connection.on(:failed) do |error|
|
21
21
|
channel.transition_state_machine :failed, error if can_transition_to?(:failed)
|
22
22
|
end
|
23
|
-
|
24
|
-
channel.on(:attached, :detached) do
|
25
|
-
channel.set_failed_channel_error_reason nil
|
26
|
-
end
|
27
23
|
end
|
28
24
|
|
29
25
|
# Commence attachment
|
@@ -35,9 +31,9 @@ module Ably::Realtime
|
|
35
31
|
end
|
36
32
|
|
37
33
|
# Commence attachment
|
38
|
-
def detach
|
39
|
-
if connection.closed?
|
40
|
-
channel.transition_state_machine :detached
|
34
|
+
def detach(error = nil)
|
35
|
+
if connection.closed? || connection.connecting?
|
36
|
+
channel.transition_state_machine :detached, error
|
41
37
|
elsif can_transition_to?(:detached)
|
42
38
|
send_detach_protocol_message
|
43
39
|
end
|
@@ -52,12 +48,17 @@ module Ably::Realtime
|
|
52
48
|
end
|
53
49
|
end
|
54
50
|
|
55
|
-
#
|
56
|
-
def
|
57
|
-
logger.error "Channel #{channel.name} error: #{error}"
|
51
|
+
# An error has occurred on the channel
|
52
|
+
def emit_error(error)
|
53
|
+
logger.error "ChannelManager: Channel '#{channel.name}' error: #{error}"
|
58
54
|
channel.trigger :error, error
|
59
55
|
end
|
60
56
|
|
57
|
+
# Detach a channel as a result of an error
|
58
|
+
def suspend(error)
|
59
|
+
channel.transition_state_machine! :detaching, error
|
60
|
+
end
|
61
|
+
|
61
62
|
private
|
62
63
|
|
63
64
|
attr_reader :channel, :connection
|
@@ -38,21 +38,22 @@ module Ably::Realtime
|
|
38
38
|
channel.manager.sync current_transition.metadata
|
39
39
|
end
|
40
40
|
|
41
|
-
after_transition(to: [:detaching]) do |channel|
|
42
|
-
channel.manager.detach
|
41
|
+
after_transition(to: [:detaching]) do |channel, current_transition|
|
42
|
+
channel.manager.detach current_transition.metadata
|
43
43
|
end
|
44
44
|
|
45
|
-
after_transition(to: [:
|
46
|
-
channel.manager.
|
45
|
+
after_transition(to: [:detached]) do |channel, current_transition|
|
46
|
+
channel.manager.emit_error current_transition.metadata if current_transition.metadata
|
47
47
|
end
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
channel.set_failed_channel_error_reason current_transition.metadata
|
49
|
+
after_transition(to: [:failed]) do |channel, current_transition|
|
50
|
+
channel.manager.emit_error current_transition.metadata
|
52
51
|
end
|
53
52
|
|
54
|
-
|
55
|
-
|
53
|
+
# Transitions responsible for updating channel#error_reason
|
54
|
+
before_transition(to: [:attached, :detached, :failed]) do |channel, current_transition|
|
55
|
+
reason = current_transition.metadata if is_error_type?(current_transition.metadata)
|
56
|
+
channel.set_failed_channel_error_reason reason
|
56
57
|
end
|
57
58
|
|
58
59
|
private
|