ably 0.1.1 → 0.1.2
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.
- 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
|