ably-rest 0.7.1 → 0.7.3

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.
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