ably 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ably.rb +2 -2
- data/lib/ably/auth.rb +39 -7
- data/lib/ably/modules/conversions.rb +58 -0
- data/lib/ably/{support.rb → modules/http_helpers.rb} +3 -3
- data/lib/ably/realtime.rb +5 -23
- data/lib/ably/realtime/channel.rb +62 -18
- data/lib/ably/realtime/client.rb +76 -22
- data/lib/ably/realtime/connection.rb +41 -14
- data/lib/ably/realtime/models/error_info.rb +38 -0
- data/lib/ably/realtime/models/message.rb +85 -0
- data/lib/ably/realtime/models/protocol_message.rb +149 -0
- data/lib/ably/realtime/models/shared.rb +17 -0
- data/lib/ably/rest.rb +16 -3
- data/lib/ably/rest/channel.rb +2 -2
- data/lib/ably/rest/client.rb +17 -20
- data/lib/ably/rest/models/message.rb +62 -0
- data/lib/ably/rest/models/paged_resource.rb +117 -0
- data/lib/ably/rest/presence.rb +4 -4
- data/lib/ably/token.rb +1 -1
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/channel_spec.rb +86 -0
- data/spec/acceptance/rest/auth_spec.rb +14 -5
- data/spec/acceptance/rest/channel_spec.rb +2 -2
- data/spec/spec_helper.rb +1 -0
- data/spec/support/event_machine_helper.rb +22 -0
- data/spec/support/model_helper.rb +67 -0
- data/spec/unit/realtime/error_info_spec.rb +10 -0
- data/spec/unit/realtime/message_spec.rb +115 -0
- data/spec/unit/realtime/protocol_message_spec.rb +102 -0
- data/spec/unit/realtime/realtime_spec.rb +20 -0
- data/spec/unit/rest/message_spec.rb +74 -0
- data/spec/unit/{rest_spec.rb → rest/rest_spec.rb} +14 -0
- metadata +28 -13
- data/lib/ably/message.rb +0 -70
- data/lib/ably/rest/paged_resource.rb +0 -117
- data/spec/acceptance/realtime_client_spec.rb +0 -12
- data/spec/unit/message_spec.rb +0 -73
- data/spec/unit/realtime_spec.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da8f27b90ddc5d18cefb088124f42507675fba7e
|
4
|
+
data.tar.gz: d4079770ea27bc17d35eb4791b1f05820a940877
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a2413536347bdd4da0c993ddf3c9926f6639be60c84945e627e7a4127b8b6360d1ae77e55ed6132c71d8138b87323ff83087ba5cb57f5ef1a6ed2c9572ec322
|
7
|
+
data.tar.gz: 4562af003c001e631344c160685b91f2e7f7018a11cb32e2c2572a8c949939a6b3ab16a8b25a0957077f2b96a7b07e71b7d8ef4b93420b10bf76859987bbfc28
|
data/lib/ably.rb
CHANGED
data/lib/ably/auth.rb
CHANGED
@@ -26,7 +26,7 @@ module Ably
|
|
26
26
|
# @return [Hash] {Ably::Auth} options configured for this client
|
27
27
|
|
28
28
|
class Auth
|
29
|
-
include Ably::
|
29
|
+
include Ably::Modules::HttpHelpers
|
30
30
|
|
31
31
|
attr_reader :options, :current_token
|
32
32
|
alias_method :auth_options, :options
|
@@ -84,7 +84,7 @@ module Ably
|
|
84
84
|
# @option options [Integer] :ttl validity time in seconds for the requested {Ably::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
|
85
85
|
# @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
|
86
86
|
# @option options [Boolean] :query_time when true will query the {https://ably.io Ably} system for the current time instead of using the local time
|
87
|
-
# @option options [
|
87
|
+
# @option options [Time] :timestamp the time of the of the request
|
88
88
|
# @option options [String] :nonce an unquoted, unescaped random string of at least 16 characters
|
89
89
|
# @option options [Boolean] :force obtains a new token even if the current token is valid
|
90
90
|
#
|
@@ -126,7 +126,7 @@ module Ably
|
|
126
126
|
# @option options [Integer] :ttl validity time in seconds for the requested {Ably::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
|
127
127
|
# @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
|
128
128
|
# @option options [Boolean] :query_time when true will query the {https://ably.io Ably} system for the current time instead of using the local time
|
129
|
-
# @option options [
|
129
|
+
# @option options [Time] :timestamp the time of the of the request
|
130
130
|
# @option options [String] :nonce an unquoted, unescaped random string of at least 16 characters
|
131
131
|
#
|
132
132
|
# @yield [options] (optional) if an auth block is passed to this method, then this block will be called to create a new token request object
|
@@ -174,7 +174,7 @@ module Ably
|
|
174
174
|
# @option options [Integer] :ttl validity time in seconds for the requested {Ably::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
|
175
175
|
# @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
|
176
176
|
# @option options [Boolean] :query_time when true will query the {https://ably.io Ably} system for the current time instead of using the local time
|
177
|
-
# @option options [
|
177
|
+
# @option options [Time] :timestamp the time of the of the request
|
178
178
|
# @option options [String] :nonce an unquoted, unescaped random string of at least 16 characters
|
179
179
|
# @return [Hash]
|
180
180
|
#
|
@@ -201,7 +201,7 @@ module Ably
|
|
201
201
|
timestamp = if token_options[:query_time]
|
202
202
|
client.time
|
203
203
|
else
|
204
|
-
Time.now
|
204
|
+
token_options.delete(:timestamp) || Time.now
|
205
205
|
end.to_i
|
206
206
|
|
207
207
|
token_request = {
|
@@ -263,6 +263,17 @@ module Ably
|
|
263
263
|
end
|
264
264
|
end
|
265
265
|
|
266
|
+
# Auth params used in URI endpoint for Realtime connections
|
267
|
+
#
|
268
|
+
# @return [Hash] Auth params for a new Realtime connection
|
269
|
+
def auth_params
|
270
|
+
if using_token_auth?
|
271
|
+
token_auth_params
|
272
|
+
else
|
273
|
+
basic_auth_params
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
266
277
|
# True if prerequisites for creating a new token request are present
|
267
278
|
#
|
268
279
|
# One of the following criterion must be met:
|
@@ -278,19 +289,40 @@ module Ably
|
|
278
289
|
private
|
279
290
|
attr_reader :auth_callback
|
280
291
|
|
292
|
+
# Basic Auth HTTP Authorization header value
|
281
293
|
def basic_auth_header
|
282
294
|
raise Ably::Exceptions::InsecureRequestError, "Cannot use Basic Auth over non-TLS connections" unless client.use_tls?
|
283
295
|
"Basic #{encode64("#{api_key}")}"
|
284
296
|
end
|
285
297
|
|
286
|
-
def
|
298
|
+
def token_auth_id
|
287
299
|
current_token_id = if token_id
|
288
300
|
token_id
|
289
301
|
else
|
290
302
|
authorise.id
|
291
303
|
end
|
304
|
+
end
|
292
305
|
|
293
|
-
|
306
|
+
# Token Auth HTTP Authorization header value
|
307
|
+
def token_auth_header
|
308
|
+
"Bearer #{encode64(token_auth_id)}"
|
309
|
+
end
|
310
|
+
|
311
|
+
# Basic Auth params to authenticate the Realtime connection
|
312
|
+
def basic_auth_params
|
313
|
+
raise Ably::Exceptions::InsecureRequestError, "Cannot use Basic Auth over non-TLS connections" unless client.use_tls?
|
314
|
+
# TODO: Change to key_secret when API is updated
|
315
|
+
{
|
316
|
+
key_id: key_id,
|
317
|
+
key_value: key_secret
|
318
|
+
}
|
319
|
+
end
|
320
|
+
|
321
|
+
# Token Auth params to authenticate the Realtime connection
|
322
|
+
def token_auth_params
|
323
|
+
{
|
324
|
+
access_token: token_auth_id
|
325
|
+
}
|
294
326
|
end
|
295
327
|
|
296
328
|
# Sign the request params using the secret
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Ably::Modules
|
2
|
+
module Conversions
|
3
|
+
private
|
4
|
+
# Take a Hash object and make it more Ruby like converting all keys
|
5
|
+
# into symbols with snake_case notation
|
6
|
+
def rubify(*args)
|
7
|
+
convert_hash_recursively(*args) do |key|
|
8
|
+
convert_to_snake_case(key).to_sym
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Take a Hash object and make it more Java like converting all keys
|
13
|
+
# into strings with mixedCase notation
|
14
|
+
def javify(*args)
|
15
|
+
convert_hash_recursively(*args) do |key|
|
16
|
+
convert_to_mixed_case(key).to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def convert_hash_recursively(hash, ignore: [], &processing_block)
|
21
|
+
raise ArgumentError, "Processing block is missing" unless block_given?
|
22
|
+
|
23
|
+
return hash unless hash.kind_of?(Hash)
|
24
|
+
|
25
|
+
Hash[hash.map do |key, val|
|
26
|
+
key_sym = yield(key)
|
27
|
+
converted_val = if ignore.include?(key_sym)
|
28
|
+
val
|
29
|
+
else
|
30
|
+
convert_hash_recursively(val, ignore: ignore, &processing_block)
|
31
|
+
end
|
32
|
+
|
33
|
+
[key_sym, converted_val]
|
34
|
+
end]
|
35
|
+
end
|
36
|
+
|
37
|
+
def convert_to_mixed_case(string_like)
|
38
|
+
string_like.to_s.
|
39
|
+
split('_').
|
40
|
+
each_with_index.map do |str, index|
|
41
|
+
if index > 0
|
42
|
+
str.capitalize
|
43
|
+
else
|
44
|
+
str
|
45
|
+
end
|
46
|
+
end.
|
47
|
+
join
|
48
|
+
end
|
49
|
+
|
50
|
+
def convert_to_snake_case(string_like)
|
51
|
+
string_like.to_s.gsub(/::/, '/').
|
52
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
53
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
54
|
+
tr("-", "_").
|
55
|
+
downcase
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/ably/realtime.rb
CHANGED
@@ -6,31 +6,13 @@ require "ably/realtime/channel"
|
|
6
6
|
require "ably/realtime/client"
|
7
7
|
require "ably/realtime/connection"
|
8
8
|
|
9
|
+
require "ably/realtime/models/shared"
|
10
|
+
require "ably/realtime/models/error_info"
|
11
|
+
require "ably/realtime/models/message"
|
12
|
+
require "ably/realtime/models/protocol_message"
|
13
|
+
|
9
14
|
module Ably
|
10
15
|
module Realtime
|
11
|
-
# Actions which are sent by the Ably Realtime API
|
12
|
-
#
|
13
|
-
# The values correspond to the ints which the API
|
14
|
-
# understands.
|
15
|
-
ACTIONS = {
|
16
|
-
heartbeat: 0,
|
17
|
-
ack: 1,
|
18
|
-
nack: 2,
|
19
|
-
connect: 3,
|
20
|
-
connected: 4,
|
21
|
-
disconnect: 5,
|
22
|
-
disconnected: 6,
|
23
|
-
close: 7,
|
24
|
-
closed: 8,
|
25
|
-
error: 9,
|
26
|
-
attach: 10,
|
27
|
-
attached: 11,
|
28
|
-
detach: 12,
|
29
|
-
detached: 13,
|
30
|
-
presence: 14,
|
31
|
-
message: 15
|
32
|
-
}
|
33
|
-
|
34
16
|
def self.new(*args)
|
35
17
|
Ably::Realtime::Client.new(*args)
|
36
18
|
end
|
@@ -3,49 +3,93 @@ module Ably
|
|
3
3
|
class Channel
|
4
4
|
include Callbacks
|
5
5
|
|
6
|
+
STATES = {
|
7
|
+
initialised: 1,
|
8
|
+
attaching: 2,
|
9
|
+
attached: 3,
|
10
|
+
detaching: 4,
|
11
|
+
detached: 5,
|
12
|
+
failed: 6
|
13
|
+
}.freeze
|
14
|
+
|
6
15
|
attr_reader :client, :name
|
7
16
|
|
17
|
+
# Retrieve a state symbol by the integer value
|
18
|
+
def self.state_sym_for(state_int)
|
19
|
+
@states_index_by_int ||= STATES.invert.freeze
|
20
|
+
@states_index_by_int[state_int]
|
21
|
+
end
|
22
|
+
|
8
23
|
def initialize(client, name)
|
9
|
-
@
|
10
|
-
@
|
11
|
-
@
|
12
|
-
@
|
24
|
+
@client = client
|
25
|
+
@name = name
|
26
|
+
@subscriptions = Hash.new { |hash, key| hash[key] = [] }
|
27
|
+
@queue = []
|
13
28
|
|
14
|
-
|
15
|
-
event = message[:name]
|
29
|
+
set_state :initialised
|
16
30
|
|
31
|
+
on(:message) do |message|
|
17
32
|
@subscriptions[:all].each { |cb| cb.call(message) }
|
18
|
-
@subscriptions[
|
33
|
+
@subscriptions[message.name].each { |cb| cb.call(message) }
|
34
|
+
end
|
35
|
+
|
36
|
+
on(:attached) do
|
37
|
+
set_state :attached
|
38
|
+
process_queue
|
19
39
|
end
|
20
40
|
end
|
21
41
|
|
42
|
+
# Current Channel state, will always be one of {STATES}
|
43
|
+
#
|
44
|
+
# @return [Symbol] state
|
45
|
+
def state
|
46
|
+
self.class.state_sym_for(@state)
|
47
|
+
end
|
48
|
+
|
49
|
+
def state?(check_state)
|
50
|
+
check_state = STATES.fetch(check_state) if check_state.kind_of?(Symbol)
|
51
|
+
@state == check_state
|
52
|
+
end
|
53
|
+
|
22
54
|
def publish(event, data)
|
23
|
-
|
55
|
+
queue << { name: event, data: data, timestamp: Time.now.to_i * 1000 }
|
24
56
|
|
25
57
|
if attached?
|
26
|
-
|
58
|
+
process_queue
|
27
59
|
else
|
28
|
-
on(:attached) { client.send_message(name, message) }
|
29
60
|
attach
|
30
61
|
end
|
31
62
|
end
|
32
63
|
|
33
64
|
def subscribe(event = :all, &blk)
|
65
|
+
event = event.to_s unless event == :all
|
66
|
+
attach unless attached?
|
34
67
|
@subscriptions[event] << blk
|
35
68
|
end
|
36
69
|
|
37
|
-
private
|
38
|
-
def attached?
|
39
|
-
@state == :attached
|
40
|
-
end
|
41
|
-
|
42
70
|
def attach
|
43
|
-
unless
|
44
|
-
|
71
|
+
unless state?(:attaching)
|
72
|
+
set_state :attaching
|
45
73
|
client.attach_to_channel(name)
|
46
|
-
on(:attached) { @state = :attached }
|
47
74
|
end
|
48
75
|
end
|
76
|
+
|
77
|
+
def attached?
|
78
|
+
state?(:attached)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
attr_reader :queue
|
83
|
+
|
84
|
+
def set_state(new_state)
|
85
|
+
new_state = STATES.fetch(new_state) if new_state.kind_of?(Symbol)
|
86
|
+
raise ArgumentError, "#{new_state} is not a valid state" unless STATES.values.include?(new_state)
|
87
|
+
@state = new_state
|
88
|
+
end
|
89
|
+
|
90
|
+
def process_queue
|
91
|
+
client.send_messages(name, queue.shift(100)) until queue.empty?
|
92
|
+
end
|
49
93
|
end
|
50
94
|
end
|
51
95
|
end
|
data/lib/ably/realtime/client.rb
CHANGED
@@ -1,31 +1,71 @@
|
|
1
1
|
module Ably
|
2
2
|
module Realtime
|
3
|
-
#
|
3
|
+
# Client for the Ably Realtime API
|
4
|
+
#
|
5
|
+
# @!attribute [r] auth
|
6
|
+
# (see Ably::Rest::Client#auth)
|
7
|
+
# @!attribute [r] client_id
|
8
|
+
# (see Ably::Rest::Client#client_id)
|
9
|
+
# @!attribute [r] auth_options
|
10
|
+
# (see Ably::Rest::Client#auth_options)
|
11
|
+
# @!attribute [r] tls
|
12
|
+
# (see Ably::Rest::Client#tls)
|
13
|
+
# @!attribute [r] environment
|
14
|
+
# (see Ably::Rest::Client#environment)
|
4
15
|
class Client
|
5
16
|
include Callbacks
|
17
|
+
extend Forwardable
|
6
18
|
|
7
|
-
DOMAIN =
|
19
|
+
DOMAIN = 'realtime.ably.io'
|
8
20
|
|
21
|
+
attr_reader :channels, :auth
|
22
|
+
def_delegators :auth, :client_id, :auth_options
|
23
|
+
def_delegators :@rest_client, :tls, :environment, :use_tls?
|
24
|
+
|
25
|
+
# Creates a {Ably::Realtime::Client Realtime Client} and configures the {Ably::Auth} object for the connection.
|
26
|
+
#
|
27
|
+
# @param (see Ably::Rest::Client#initialize)
|
28
|
+
# @option options (see Ably::Rest::Client#initialize)
|
29
|
+
# @option options [Boolean] :queue_messages If false, this disables the default behaviour whereby the library queues messages on a connection in the disconnected or connecting states
|
30
|
+
# @option options [Boolean] :echo_messages If false, prevents messages originating from this connection being echoed back on the same connection
|
31
|
+
# @option options [String] :recover This option allows a connection to inherit the state of a previous connection that may have existed under an different instance of the Realtime library.
|
32
|
+
# @option options [Boolean] :debug_http Send HTTP & websocket debugging information for all messages/requests sent and received to STDOUT
|
33
|
+
#
|
34
|
+
# @yield (see Ably::Rest::Client#initialize)
|
35
|
+
# @yieldparam (see Ably::Rest::Client#initialize)
|
36
|
+
# @yieldreturn (see Ably::Rest::Client#initialize)
|
37
|
+
#
|
38
|
+
# @return [Ably::Realtime::Client]
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# # create a new client authenticating with basic auth
|
42
|
+
# client = Ably::Realtime::Client.new('key.id:secret')
|
43
|
+
#
|
44
|
+
# # create a new client and configure a client ID used for presence
|
45
|
+
# client = Ably::Realtime::Client.new(api_key: 'key.id:secret', client_id: 'john')
|
46
|
+
#
|
9
47
|
def initialize(options)
|
10
|
-
@rest_client
|
48
|
+
@rest_client = Ably::Rest::Client.new(options)
|
49
|
+
@auth = @rest_client.auth
|
50
|
+
@message_serial = 0
|
11
51
|
|
12
|
-
on(:attached) do |
|
13
|
-
channel = channel(
|
52
|
+
on(:attached) do |protocol_message|
|
53
|
+
channel = channel(protocol_message.channel)
|
14
54
|
|
15
55
|
channel.trigger(:attached)
|
16
56
|
end
|
17
57
|
|
18
|
-
on(:message) do |
|
19
|
-
channel = channel(
|
58
|
+
on(:message) do |protocol_message|
|
59
|
+
channel = channel(protocol_message.channel)
|
20
60
|
|
21
|
-
|
61
|
+
protocol_message.messages.each do |message|
|
22
62
|
channel.trigger(:message, message)
|
23
63
|
end
|
24
64
|
end
|
25
65
|
end
|
26
66
|
|
27
67
|
def token
|
28
|
-
@token ||=
|
68
|
+
@token ||= rest_client.request_token
|
29
69
|
end
|
30
70
|
|
31
71
|
# Return a Realtime Channel for the given name
|
@@ -37,34 +77,34 @@ module Ably
|
|
37
77
|
@channels[name] ||= Ably::Realtime::Channel.new(self, name)
|
38
78
|
end
|
39
79
|
|
40
|
-
def
|
80
|
+
def send_messages(channel_name, messages)
|
41
81
|
payload = {
|
42
|
-
action:
|
82
|
+
action: Models::ProtocolMessage.action!(:message),
|
43
83
|
channel: channel_name,
|
44
|
-
messages:
|
45
|
-
}
|
84
|
+
messages: messages
|
85
|
+
}
|
86
|
+
|
87
|
+
payload.merge!(clientId: client_id) unless client_id.nil?
|
46
88
|
|
47
89
|
connection.send(payload)
|
48
90
|
end
|
49
91
|
|
50
92
|
def attach_to_channel(channel_name)
|
51
93
|
payload = {
|
52
|
-
action:
|
94
|
+
action: Models::ProtocolMessage.action!(:attach),
|
53
95
|
channel: channel_name
|
54
|
-
}
|
96
|
+
}
|
55
97
|
|
56
98
|
connection.send(payload)
|
57
99
|
end
|
58
100
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
101
|
+
# Default Ably Realtime endpoint used for all requests
|
102
|
+
#
|
103
|
+
# @return [URI::Generic]
|
63
104
|
def endpoint
|
64
|
-
|
105
|
+
URI::Generic.build(
|
65
106
|
scheme: use_tls? ? "wss" : "ws",
|
66
|
-
host: DOMAIN
|
67
|
-
query: "access_token=#{token.id}&binary=false×tamp=#{Time.now.to_i}"
|
107
|
+
host: [environment, DOMAIN].compact.join('-')
|
68
108
|
)
|
69
109
|
end
|
70
110
|
|
@@ -76,6 +116,20 @@ module Ably
|
|
76
116
|
EventMachine.connect(host, port, Connection, self)
|
77
117
|
end
|
78
118
|
end
|
119
|
+
|
120
|
+
# When true, will send HTTP & websocket debugging information for all messages/requests sent and received to STDOUT
|
121
|
+
#
|
122
|
+
# @return [Boolean]
|
123
|
+
def debug_http?
|
124
|
+
rest_client.debug_http?
|
125
|
+
end
|
126
|
+
|
127
|
+
def log_http(message)
|
128
|
+
$stdout.puts "#{Time.now.strftime('%H:%M:%S')} #{message}" if debug_http?
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
attr_reader :rest_client
|
79
133
|
end
|
80
134
|
end
|
81
135
|
end
|