ably-rest 0.7.1 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +13 -5
  2. data/.gitmodules +1 -1
  3. data/.rspec +1 -0
  4. data/.travis.yml +7 -3
  5. data/SPEC.md +495 -419
  6. data/ably-rest.gemspec +19 -5
  7. data/lib/ably-rest.rb +9 -1
  8. data/lib/submodules/ably-ruby/.gitignore +6 -0
  9. data/lib/submodules/ably-ruby/.rspec +1 -0
  10. data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
  11. data/lib/submodules/ably-ruby/.travis.yml +10 -0
  12. data/lib/submodules/ably-ruby/Gemfile +4 -0
  13. data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
  14. data/lib/submodules/ably-ruby/README.md +122 -0
  15. data/lib/submodules/ably-ruby/Rakefile +34 -0
  16. data/lib/submodules/ably-ruby/SPEC.md +1794 -0
  17. data/lib/submodules/ably-ruby/ably.gemspec +36 -0
  18. data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
  19. data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
  20. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
  21. data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
  22. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
  23. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
  24. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
  25. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
  26. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
  27. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
  28. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
  29. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
  30. data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
  31. data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
  32. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
  33. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
  34. data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
  35. data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
  36. data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
  37. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
  38. data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
  39. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
  40. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
  41. data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
  42. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
  43. data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
  44. data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
  45. data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
  46. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
  47. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
  48. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
  49. data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  50. data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
  51. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
  52. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
  53. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
  54. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
  55. data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
  56. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
  57. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
  58. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
  59. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
  60. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
  61. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
  62. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
  63. data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
  64. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
  65. data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
  66. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
  67. data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
  68. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
  69. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
  70. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
  71. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
  72. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  73. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
  74. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
  75. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
  76. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
  77. data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
  78. data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
  79. data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
  80. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
  81. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
  82. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
  83. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
  84. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
  85. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
  86. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
  87. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
  88. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
  89. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
  90. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
  91. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
  92. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
  93. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
  94. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
  95. data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
  96. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
  97. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
  98. data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
  99. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
  100. data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
  101. data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
  102. data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
  103. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
  104. data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
  105. data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
  106. data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
  107. data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
  108. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
  109. data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
  110. data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
  111. data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
  112. data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
  113. data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
  114. data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
  115. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
  116. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
  117. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
  118. data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
  119. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
  120. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
  121. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
  122. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
  123. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
  124. data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
  125. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
  126. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
  127. data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
  128. data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
  129. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
  130. data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
  131. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
  132. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
  133. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
  134. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
  135. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
  136. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
  137. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
  138. data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
  139. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
  140. data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
  141. data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
  142. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
  143. data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
  144. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
  145. data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
  146. data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
  147. data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
  148. metadata +182 -27
@@ -0,0 +1,92 @@
1
+ module Ably::Realtime
2
+ class Channel
3
+ # ChannelManager is responsible for all actions relating to channel state: attaching, detaching or failure
4
+ # Channel state changes are performed by this class and executed from {ChannelStateMachine}
5
+ #
6
+ # This is a private class and should never be used directly by developers as the API is likely to change in future.
7
+ #
8
+ # @api private
9
+ class ChannelManager
10
+ extend Forwardable
11
+
12
+ def initialize(channel, connection)
13
+ @channel = channel
14
+ @connection = connection
15
+
16
+ connection.on(:closed) do
17
+ channel.transition_state_machine :detaching if can_transition_to?(:detaching)
18
+ end
19
+
20
+ connection.on(:failed) do |error|
21
+ channel.transition_state_machine :failed, error if can_transition_to?(:failed)
22
+ end
23
+ end
24
+
25
+ # Commence attachment
26
+ def attach
27
+ if can_transition_to?(:attached)
28
+ connect_if_connection_initialized
29
+ send_attach_protocol_message
30
+ end
31
+ end
32
+
33
+ # Commence attachment
34
+ def detach(error = nil)
35
+ if connection.closed? || connection.connecting?
36
+ channel.transition_state_machine :detached, error
37
+ elsif can_transition_to?(:detached)
38
+ send_detach_protocol_message
39
+ end
40
+ end
41
+
42
+ # Commence presence SYNC if applicable
43
+ def sync(attached_protocol_message)
44
+ if attached_protocol_message.has_presence_flag?
45
+ channel.presence.sync_started
46
+ else
47
+ channel.presence.sync_completed
48
+ end
49
+ end
50
+
51
+ # An error has occurred on the channel
52
+ def emit_error(error)
53
+ logger.error "ChannelManager: Channel '#{channel.name}' error: #{error}"
54
+ channel.trigger :error, error
55
+ end
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
+
62
+ private
63
+
64
+ attr_reader :channel, :connection
65
+ def_delegators :channel, :can_transition_to?
66
+
67
+ # If the connection has not previously connected, connect now
68
+ def connect_if_connection_initialized
69
+ connection.connect if connection.initialized?
70
+ end
71
+
72
+ def send_attach_protocol_message
73
+ send_state_change_protocol_message Ably::Models::ProtocolMessage::ACTION.Attach
74
+ end
75
+
76
+ def send_detach_protocol_message
77
+ send_state_change_protocol_message Ably::Models::ProtocolMessage::ACTION.Detach
78
+ end
79
+
80
+ def send_state_change_protocol_message(state)
81
+ connection.send_protocol_message(
82
+ action: state.to_i,
83
+ channel: channel.name
84
+ )
85
+ end
86
+
87
+ def logger
88
+ connection.logger
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,69 @@
1
+ require 'ably/modules/state_machine'
2
+
3
+ module Ably::Realtime
4
+ class Channel
5
+ # Internal class to manage channel state for {Ably::Realtime::Channel}
6
+ #
7
+ # @api private
8
+ #
9
+ class ChannelStateMachine
10
+ include Ably::Modules::StateMachine
11
+
12
+ # States supported by this StateMachine match #{Channel::STATE}s
13
+ # :initialized
14
+ # :attaching
15
+ # :attached
16
+ # :detaching
17
+ # :detached
18
+ # :failed
19
+ Channel::STATE.each_with_index do |state_enum, index|
20
+ state state_enum.to_sym, initial: index == 0
21
+ end
22
+
23
+ transition :from => :initialized, :to => [:attaching]
24
+ transition :from => :attaching, :to => [:attached, :detaching, :failed]
25
+ transition :from => :attached, :to => [:detaching, :failed]
26
+ transition :from => :detaching, :to => [:detached, :attaching, :failed]
27
+ transition :from => :failed, :to => [:attaching]
28
+
29
+ after_transition do |channel, transition|
30
+ channel.synchronize_state_with_statemachine
31
+ end
32
+
33
+ after_transition(to: [:attaching]) do |channel|
34
+ channel.manager.attach
35
+ end
36
+
37
+ before_transition(to: [:attached]) do |channel, current_transition|
38
+ channel.manager.sync current_transition.metadata
39
+ end
40
+
41
+ after_transition(to: [:detaching]) do |channel, current_transition|
42
+ channel.manager.detach current_transition.metadata
43
+ end
44
+
45
+ after_transition(to: [:detached]) do |channel, current_transition|
46
+ channel.manager.emit_error current_transition.metadata if current_transition.metadata
47
+ end
48
+
49
+ after_transition(to: [:failed]) do |channel, current_transition|
50
+ channel.manager.emit_error current_transition.metadata
51
+ end
52
+
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
57
+ end
58
+
59
+ private
60
+ def channel
61
+ object
62
+ end
63
+
64
+ def logger
65
+ channel.logger
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,50 @@
1
+ module Ably
2
+ module Realtime
3
+ # Class that maintains a map of Channels ensuring Channels are reused
4
+ class Channels
5
+ include Ably::Modules::ChannelsCollection
6
+
7
+ # @return [Ably::Realtime::Channels]
8
+ def initialize(client)
9
+ super client, Ably::Realtime::Channel
10
+ end
11
+
12
+ # @!method get(name, channel_options = {})
13
+ # Return a {Ably::Realtime::Channel} for the given name
14
+ #
15
+ # @param name [String] The name of the channel
16
+ # @param channel_options [Hash] Channel options, currently reserved for Encryption options
17
+ # @return [Ably::Realtime::Channel}
18
+ #
19
+ def get(*args)
20
+ super
21
+ end
22
+
23
+ # @!method fetch(name, &missing_block)
24
+ # Return a {Ably::Realtime::Channel} for the given name if it exists, else the block will be called.
25
+ # This method is intentionally similar to {http://ruby-doc.org/core-2.1.3/Hash.html#method-i-fetch Hash#fetch} providing a simple way to check if a channel exists or not without creating one
26
+ #
27
+ # @param name [String] The name of the channel
28
+ # @yield [options] (optional) if a missing_block is passed to this method and no channel exists matching the name, this block is called
29
+ # @yieldparam [String] name of the missing channel
30
+ # @return [Ably::Realtime::Channel]
31
+ #
32
+ def fetch(*args)
33
+ super
34
+ end
35
+
36
+ # Detaches the {Ably::Realtime::Channel Realtime Channel} and releases all associated resources.
37
+ #
38
+ # Releasing a Realtime Channel is not typically necessary as a channel, once detached, consumes no resources other than
39
+ # the memory footprint of the {Ably::Realtime::Channel Realtime Channel object}. Release channels to free up resources if required
40
+ #
41
+ # @return [void]
42
+ #
43
+ def release(channel)
44
+ get(channel).detach do
45
+ @channels.delete(channel)
46
+ end if @channels.has_key?(channel)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,184 @@
1
+ module Ably
2
+ module Realtime
3
+ # Client for the Ably Realtime API
4
+ #
5
+ # @!attribute [r] client_id
6
+ # (see Ably::Rest::Client#client_id)
7
+ # @!attribute [r] auth_options
8
+ # (see Ably::Rest::Client#auth_options)
9
+ # @!attribute [r] environment
10
+ # (see Ably::Rest::Client#environment)
11
+ # @!attribute [r] channels
12
+ # @return [Aby::Realtime::Channels] The collection of {Ably::Realtime::Channel}s that have been created
13
+ # @!attribute [r] encoders
14
+ # (see Ably::Rest::Client#encoders)
15
+ # @!attribute [r] protocol
16
+ # (see Ably::Rest::Client#protocol)
17
+ # @!attribute [r] protocol_binary?
18
+ # (see Ably::Rest::Client#protocol_binary?)
19
+ #
20
+ class Client
21
+ include Ably::Modules::AsyncWrapper
22
+ extend Forwardable
23
+
24
+ DOMAIN = 'realtime.ably.io'
25
+
26
+ # The collection of {Ably::Realtime::Channel}s that have been created
27
+ # @return [Aby::Realtime::Channels]
28
+ attr_reader :channels
29
+
30
+ # (see Ably::Rest::Client#auth)
31
+ attr_reader :auth
32
+
33
+ # The {Ably::Rest::Client REST client} instantiated with the same credentials and configuration that is used for all REST operations such as authentication
34
+ # @return [Ably::Rest::Client]
35
+ attr_reader :rest_client
36
+
37
+ # When false the client suppresses messages originating from this connection being echoed back on the same connection. Defaults to true
38
+ # @return [Boolean]
39
+ attr_reader :echo_messages
40
+
41
+ # The custom realtime websocket host that is being used if it was provided with the option `:ws_host` when the {Client} was created
42
+ # @return [String,Nil]
43
+ attr_reader :custom_realtime_host
44
+
45
+ # When true, as soon as the client library is instantiated it will connect to Ably. If this attribute is false, a connection must be opened explicitly
46
+ # @return [Boolean]
47
+ attr_reader :connect_automatically
48
+
49
+ # When a recover option is specified a connection inherits the state of a previous connection that may have existed under a different instance of the Realtime library, please refer to the API documentation for further information on connection state recovery
50
+ # @return [String,Nil]
51
+ attr_reader :recover
52
+
53
+ def_delegators :auth, :client_id, :auth_options
54
+ def_delegators :@rest_client, :encoders
55
+ def_delegators :@rest_client, :environment, :use_tls?, :protocol, :protocol_binary?, :custom_host
56
+ def_delegators :@rest_client, :log_level
57
+
58
+ # Creates a {Ably::Realtime::Client Realtime Client} and configures the {Ably::Auth} object for the connection.
59
+ #
60
+ # @param (see Ably::Rest::Client#initialize)
61
+ # @option options (see Ably::Rest::Client#initialize)
62
+ # @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
63
+ # @option options [Boolean] :echo_messages If false, prevents messages originating from this connection being echoed back on the same connection
64
+ # @option options [String] :recover When a recover option is specified a connection inherits the state of a previous connection that may have existed under a different instance of the Realtime library, please refer to the API documentation for further information on connection state recovery
65
+ # @option options [Boolean] :connect_automatically By default as soon as the client library is instantiated it will connect to Ably. You can optionally set this to false and explicitly connect.
66
+ #
67
+ # @yield (see Ably::Rest::Client#initialize)
68
+ # @yieldparam (see Ably::Rest::Client#initialize)
69
+ # @yieldreturn (see Ably::Rest::Client#initialize)
70
+ #
71
+ # @return [Ably::Realtime::Client]
72
+ #
73
+ # @example
74
+ # # create a new client authenticating with basic auth
75
+ # client = Ably::Realtime::Client.new('key.id:secret')
76
+ #
77
+ # # create a new client and configure a client ID used for presence
78
+ # client = Ably::Realtime::Client.new(api_key: 'key.id:secret', client_id: 'john')
79
+ #
80
+ def initialize(options, &token_request_block)
81
+ @rest_client = Ably::Rest::Client.new(options, &token_request_block)
82
+ @auth = @rest_client.auth
83
+ @channels = Ably::Realtime::Channels.new(self)
84
+ @echo_messages = @rest_client.options.fetch(:echo_messages, true) == false ? false : true
85
+ @custom_realtime_host = @rest_client.options[:realtime_host] || @rest_client.options[:ws_host]
86
+ @connect_automatically = @rest_client.options.fetch(:connect_automatically, true) == false ? false : true
87
+ @recover = @rest_client.options[:recover]
88
+
89
+ raise ArgumentError, "Recovery key is invalid" if @recover && !@recover.match(Connection::RECOVER_REGEX)
90
+ end
91
+
92
+ # Return a {Ably::Realtime::Channel Realtime Channel} for the given name
93
+ #
94
+ # @param (see Ably::Realtime::Channels#get)
95
+ # @return (see Ably::Realtime::Channels#get)
96
+ #
97
+ def channel(name, channel_options = {})
98
+ channels.get(name, channel_options)
99
+ end
100
+
101
+ # Retrieve the Ably service time
102
+ #
103
+ # @yield [Time] The time as reported by the Ably service
104
+ # @return [EventMachine::Deferrable]
105
+ #
106
+ def time(&success_callback)
107
+ async_wrap(success_callback) do
108
+ rest_client.time
109
+ end
110
+ end
111
+
112
+ # Retrieve the stats for the application
113
+ #
114
+ # @param (see Ably::Rest::Client#stats)
115
+ # @option options (see Ably::Rest::Client#stats)
116
+ #
117
+ # @yield [Ably::Models::PaginatedResource<Ably::Models::Stat>] An Array of Stats
118
+ #
119
+ # @return [EventMachine::Deferrable]
120
+ #
121
+ def stats(options = {}, &success_callback)
122
+ async_wrap(success_callback) do
123
+ rest_client.stats(options)
124
+ end
125
+ end
126
+
127
+ # (see Ably::Realtime::Connection#close)
128
+ def close(&block)
129
+ connection.close(&block)
130
+ end
131
+
132
+ # @!attribute [r] endpoint
133
+ # @return [URI::Generic] Default Ably Realtime endpoint used for all requests
134
+ def endpoint
135
+ endpoint_for_host(custom_realtime_host || [environment, DOMAIN].compact.join('-'))
136
+ end
137
+
138
+ # @!attribute [r] connection
139
+ # @return [Aby::Realtime::Connection] The underlying connection for this client
140
+ def connection
141
+ @connection ||= Connection.new(self)
142
+ end
143
+
144
+ # (see Ably::Rest::Client#register_encoder)
145
+ def register_encoder(encoder)
146
+ rest_client.register_encoder encoder
147
+ end
148
+
149
+ # (see Ably::Rest::Client#logger)
150
+ def logger
151
+ @logger ||= Ably::Logger.new(self, log_level, rest_client.logger.custom_logger)
152
+ end
153
+
154
+ # Disable connection recovery, typically used after a connection has been recovered
155
+ # @return [void]
156
+ # @api private
157
+ def disable_automatic_connection_recovery
158
+ @recover = nil
159
+ end
160
+
161
+ # @!attribute [r] fallback_endpoint
162
+ # @return [URI::Generic] Fallback endpoint used to connect to the realtime Ably service. Note, after each connection attempt, a new random {Ably::FALLBACK_HOSTS fallback host} is used
163
+ # @api private
164
+ def fallback_endpoint
165
+ unless @fallback_endpoints
166
+ @fallback_endpoints = Ably::FALLBACK_HOSTS.shuffle.map { |fallback_host| endpoint_for_host(fallback_host) }
167
+ end
168
+
169
+ fallback_endpoint_index = connection.manager.retry_count_for_state(:disconnected) + connection.manager.retry_count_for_state(:suspended)
170
+
171
+ @fallback_endpoints[fallback_endpoint_index % @fallback_endpoints.count]
172
+ end
173
+
174
+ private
175
+ def endpoint_for_host(host)
176
+ URI::Generic.build(
177
+ scheme: use_tls? ? 'wss' : 'ws',
178
+ host: host
179
+ )
180
+ end
181
+ end
182
+ end
183
+ end
184
+
@@ -0,0 +1,184 @@
1
+ module Ably::Realtime
2
+ class Client
3
+ # IncomingMessageDispatcher is a (private) class that is used to dispatch {Ably::Models::ProtocolMessage} that are
4
+ # received from Ably via the {Ably::Realtime::Connection}
5
+ class IncomingMessageDispatcher
6
+ ACTION = Ably::Models::ProtocolMessage::ACTION
7
+
8
+ def initialize(client, connection)
9
+ @client = client
10
+ @connection = connection
11
+
12
+ subscribe_to_incoming_protocol_messages
13
+ end
14
+
15
+ private
16
+ attr_reader :client, :connection
17
+
18
+ def channels
19
+ client.channels
20
+ end
21
+
22
+ def get_channel(channel_name)
23
+ channels.fetch(channel_name) do
24
+ logger.warn "Received channel message for non-existent channel"
25
+ Ably::Realtime::Models::NilChannel.new
26
+ end
27
+ end
28
+
29
+ def logger
30
+ client.logger
31
+ end
32
+
33
+ def dispatch_protocol_message(*args)
34
+ protocol_message = args.first
35
+
36
+ unless protocol_message.kind_of?(Ably::Models::ProtocolMessage)
37
+ raise ArgumentError, "Expected a ProtocolMessage. Received #{protocol_message}"
38
+ end
39
+
40
+ unless [:nack, :error].include?(protocol_message.action)
41
+ logger.debug "#{protocol_message.action} received: #{protocol_message}"
42
+ end
43
+
44
+ update_connection_recovery_info protocol_message
45
+
46
+ case protocol_message.action
47
+ when ACTION.Heartbeat
48
+ when ACTION.Ack
49
+ ack_pending_queue_for_message_serial(protocol_message) if protocol_message.has_message_serial?
50
+
51
+ when ACTION.Nack
52
+ logger.warn "NACK received: #{protocol_message}"
53
+ nack_pending_queue_for_message_serial(protocol_message) if protocol_message.has_message_serial?
54
+
55
+ when ACTION.Connect
56
+ when ACTION.Connected
57
+ connection.transition_state_machine :connected, protocol_message.error
58
+
59
+ when ACTION.Disconnect, ACTION.Disconnected
60
+ connection.transition_state_machine :disconnected, protocol_message.error
61
+
62
+ when ACTION.Close
63
+ when ACTION.Closed
64
+ connection.transition_state_machine :closed
65
+
66
+ when ACTION.Error
67
+ if protocol_message.channel && !protocol_message.has_message_serial?
68
+ dispatch_channel_error protocol_message
69
+ else
70
+ process_connection_error protocol_message
71
+ end
72
+
73
+ when ACTION.Attach
74
+ when ACTION.Attached
75
+ get_channel(protocol_message.channel).transition_state_machine :attached, protocol_message
76
+
77
+ when ACTION.Detach
78
+ when ACTION.Detached
79
+ get_channel(protocol_message.channel).transition_state_machine :detached
80
+
81
+ when ACTION.Sync
82
+ presence = get_channel(protocol_message.channel).presence
83
+ protocol_message.presence.each do |presence_message|
84
+ presence.__incoming_msgbus__.publish :sync, presence_message
85
+ end
86
+ presence.update_sync_serial protocol_message.channel_serial
87
+
88
+ when ACTION.Presence
89
+ presence = get_channel(protocol_message.channel).presence
90
+ protocol_message.presence.each do |presence_message|
91
+ presence.__incoming_msgbus__.publish :presence, presence_message
92
+ end
93
+
94
+ when ACTION.Message
95
+ channel = get_channel(protocol_message.channel)
96
+ protocol_message.messages.each do |message|
97
+ channel.__incoming_msgbus__.publish :message, message
98
+ end
99
+
100
+ else
101
+ raise ArgumentError, "Protocol Message Action #{protocol_message.action} is unsupported by this MessageDispatcher"
102
+ end
103
+ end
104
+
105
+ def dispatch_channel_error(protocol_message)
106
+ logger.warn "Channel Error message received: #{protocol_message.error}"
107
+ if !protocol_message.has_message_serial?
108
+ get_channel(protocol_message.channel).transition_state_machine :failed, protocol_message.error
109
+ else
110
+ logger.fatal "Cannot process ProtocolMessage as not yet implemented: #{protocol_message}"
111
+ end
112
+ end
113
+
114
+ def process_connection_error(protocol_message)
115
+ connection.manager.error_received_from_server protocol_message.error
116
+ end
117
+
118
+ def update_connection_recovery_info(protocol_message)
119
+ if protocol_message.connection_key && (protocol_message.connection_key != connection.key)
120
+ logger.debug "New connection ID set to #{protocol_message.connection_id} with connection key #{protocol_message.connection_key}"
121
+ detach_attached_channels protocol_message.error if protocol_message.error
122
+ connection.configure_new protocol_message.connection_id, protocol_message.connection_key, protocol_message.connection_serial
123
+ end
124
+
125
+ if protocol_message.has_connection_serial?
126
+ connection.update_connection_serial protocol_message.connection_serial
127
+ end
128
+ end
129
+
130
+ def detach_attached_channels(error)
131
+ channels.select do |channel|
132
+ channel.attached? || channel.attaching?
133
+ end.each do |channel|
134
+ logger.warn "Detaching channel '#{channel.name}': #{error}"
135
+ channel.manager.suspend error
136
+ end
137
+ end
138
+
139
+ def ack_pending_queue_for_message_serial(ack_protocol_message)
140
+ drop_pending_queue_from_ack(ack_protocol_message) do |protocol_message|
141
+ ack_messages protocol_message.messages
142
+ ack_messages protocol_message.presence
143
+ end
144
+ end
145
+
146
+ def nack_pending_queue_for_message_serial(nack_protocol_message)
147
+ drop_pending_queue_from_ack(nack_protocol_message) do |protocol_message|
148
+ nack_messages protocol_message.messages, nack_protocol_message
149
+ nack_messages protocol_message.presence, nack_protocol_message
150
+ end
151
+ end
152
+
153
+ def ack_messages(messages)
154
+ messages.each do |message|
155
+ logger.debug "Calling ACK success callbacks for #{message.class.name} - #{message.to_json}"
156
+ message.succeed message
157
+ end
158
+ end
159
+
160
+ def nack_messages(messages, protocol_message)
161
+ messages.each do |message|
162
+ logger.debug "Calling NACK failure callbacks for #{message.class.name} - #{message.to_json}, protocol message: #{protocol_message}"
163
+ message.fail message, protocol_message.error
164
+ end
165
+ end
166
+
167
+ def drop_pending_queue_from_ack(ack_protocol_message)
168
+ message_serial_up_to = ack_protocol_message.message_serial + ack_protocol_message.count - 1
169
+ connection.__pending_message_queue__.drop_while do |protocol_message|
170
+ if protocol_message.message_serial <= message_serial_up_to
171
+ yield protocol_message
172
+ true
173
+ end
174
+ end
175
+ end
176
+
177
+ def subscribe_to_incoming_protocol_messages
178
+ connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |*args|
179
+ dispatch_protocol_message *args
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end