ably 0.7.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|