ably 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ably.rb +2 -2
  3. data/lib/ably/auth.rb +39 -7
  4. data/lib/ably/modules/conversions.rb +58 -0
  5. data/lib/ably/{support.rb → modules/http_helpers.rb} +3 -3
  6. data/lib/ably/realtime.rb +5 -23
  7. data/lib/ably/realtime/channel.rb +62 -18
  8. data/lib/ably/realtime/client.rb +76 -22
  9. data/lib/ably/realtime/connection.rb +41 -14
  10. data/lib/ably/realtime/models/error_info.rb +38 -0
  11. data/lib/ably/realtime/models/message.rb +85 -0
  12. data/lib/ably/realtime/models/protocol_message.rb +149 -0
  13. data/lib/ably/realtime/models/shared.rb +17 -0
  14. data/lib/ably/rest.rb +16 -3
  15. data/lib/ably/rest/channel.rb +2 -2
  16. data/lib/ably/rest/client.rb +17 -20
  17. data/lib/ably/rest/models/message.rb +62 -0
  18. data/lib/ably/rest/models/paged_resource.rb +117 -0
  19. data/lib/ably/rest/presence.rb +4 -4
  20. data/lib/ably/token.rb +1 -1
  21. data/lib/ably/version.rb +1 -1
  22. data/spec/acceptance/realtime/channel_spec.rb +86 -0
  23. data/spec/acceptance/rest/auth_spec.rb +14 -5
  24. data/spec/acceptance/rest/channel_spec.rb +2 -2
  25. data/spec/spec_helper.rb +1 -0
  26. data/spec/support/event_machine_helper.rb +22 -0
  27. data/spec/support/model_helper.rb +67 -0
  28. data/spec/unit/realtime/error_info_spec.rb +10 -0
  29. data/spec/unit/realtime/message_spec.rb +115 -0
  30. data/spec/unit/realtime/protocol_message_spec.rb +102 -0
  31. data/spec/unit/realtime/realtime_spec.rb +20 -0
  32. data/spec/unit/rest/message_spec.rb +74 -0
  33. data/spec/unit/{rest_spec.rb → rest/rest_spec.rb} +14 -0
  34. metadata +28 -13
  35. data/lib/ably/message.rb +0 -70
  36. data/lib/ably/rest/paged_resource.rb +0 -117
  37. data/spec/acceptance/realtime_client_spec.rb +0 -12
  38. data/spec/unit/message_spec.rb +0 -73
  39. data/spec/unit/realtime_spec.rb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d9ccb54c36b77720433fde71a6580424b91cbfc0
4
- data.tar.gz: fb242c4a0ce0c6ddd96c217d9904ee5926fcf465
3
+ metadata.gz: da8f27b90ddc5d18cefb088124f42507675fba7e
4
+ data.tar.gz: d4079770ea27bc17d35eb4791b1f05820a940877
5
5
  SHA512:
6
- metadata.gz: 9b681c12026a2fd84f3e5b71dfd901513a90f4c9cdb04ed9bef887543ce8d29a9ce1ef5e18e281090e7eae3940732146743948a8d17efa0fd0d09da0b03a6bc9
7
- data.tar.gz: 116e3a2d88b423cd4af365656af2269467cc21601567897c3dd26efc0425c5d73e578a5d924021aa957661da1c417af527cc49dc8ef83ed4eeb82990e5dd6e7a
6
+ metadata.gz: 7a2413536347bdd4da0c993ddf3c9926f6639be60c84945e627e7a4127b8b6360d1ae77e55ed6132c71d8138b87323ff83087ba5cb57f5ef1a6ed2c9572ec322
7
+ data.tar.gz: 4562af003c001e631344c160685b91f2e7f7018a11cb32e2c2572a8c949939a6b3ab16a8b25a0957077f2b96a7b07e71b7d8ef4b93420b10bf76859987bbfc28
@@ -1,8 +1,8 @@
1
- require "ably/support"
1
+ require "ably/modules/conversions"
2
+ require "ably/modules/http_helpers"
2
3
 
3
4
  require "ably/auth"
4
5
  require "ably/exceptions"
5
- require "ably/message"
6
6
  require "ably/rest"
7
7
  require "ably/realtime"
8
8
  require "ably/token"
@@ -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::Support
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 [Integer] :timestamp the time of the of the request in seconds since the epoch
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 [Integer] :timestamp the time of the of the request in seconds since the epoch
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 [Integer] :timestamp the time of the of the request in seconds since the epoch
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 token_auth_header
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
- "Bearer #{encode64(current_token_id)}"
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
@@ -1,7 +1,7 @@
1
- require "base64"
1
+ require 'base64'
2
2
 
3
- module Ably
4
- module Support
3
+ module Ably::Modules
4
+ module HttpHelpers
5
5
  protected
6
6
  def encode64(text)
7
7
  Base64.encode64(text).gsub("\n", '')
@@ -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
- @state = :initialised
10
- @client = client
11
- @name = name
12
- @subscriptions = Hash.new { |hash, key| hash[key] = [] }
24
+ @client = client
25
+ @name = name
26
+ @subscriptions = Hash.new { |hash, key| hash[key] = [] }
27
+ @queue = []
13
28
 
14
- on(:message) do |message|
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[event].each { |cb| cb.call(message) }
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
- message = { name: event, data: data }
55
+ queue << { name: event, data: data, timestamp: Time.now.to_i * 1000 }
24
56
 
25
57
  if attached?
26
- client.send_message(name, message)
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 @state == :attaching
44
- @state = :attaching
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
@@ -1,31 +1,71 @@
1
1
  module Ably
2
2
  module Realtime
3
- # A client for the Ably Realtime API
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 = "staging-realtime.ably.io"
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 = Ably::Rest::Client.new(options)
48
+ @rest_client = Ably::Rest::Client.new(options)
49
+ @auth = @rest_client.auth
50
+ @message_serial = 0
11
51
 
12
- on(:attached) do |data|
13
- channel = channel(data[: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 |data|
19
- channel = channel(data[:channel])
58
+ on(:message) do |protocol_message|
59
+ channel = channel(protocol_message.channel)
20
60
 
21
- data[:messages].each do |message|
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 ||= @rest_client.request_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 send_message(channel_name, message)
80
+ def send_messages(channel_name, messages)
41
81
  payload = {
42
- action: ACTIONS[:message],
82
+ action: Models::ProtocolMessage.action!(:message),
43
83
  channel: channel_name,
44
- messages: [message]
45
- }.to_json
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: ACTIONS[:attach],
94
+ action: Models::ProtocolMessage.action!(:attach),
53
95
  channel: channel_name
54
- }.to_json
96
+ }
55
97
 
56
98
  connection.send(payload)
57
99
  end
58
100
 
59
- def use_tls?
60
- @rest_client.use_tls?
61
- end
62
-
101
+ # Default Ably Realtime endpoint used for all requests
102
+ #
103
+ # @return [URI::Generic]
63
104
  def endpoint
64
- @endpoint ||= URI::Generic.build(
105
+ URI::Generic.build(
65
106
  scheme: use_tls? ? "wss" : "ws",
66
- host: DOMAIN,
67
- query: "access_token=#{token.id}&binary=false&timestamp=#{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