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,33 @@
1
+ module Ably::Modules
2
+ # @api private
3
+ module StatesmanMonkeyPatch
4
+ # Override Statesman's #before_transition to support :from arrays
5
+ # This can be removed once https://github.com/gocardless/statesman/issues/95 is solved
6
+ def before_transition(options = nil, &block)
7
+ arrayify_transition(options) do |options_without_from_array|
8
+ super *options_without_from_array, &block
9
+ end
10
+ end
11
+
12
+ # Override Statesman's #after_transition to support :from arrays
13
+ # This can be removed once https://github.com/gocardless/statesman/issues/95 is solved
14
+ def after_transition(options = nil, &block)
15
+ arrayify_transition(options) do |options_without_from_array|
16
+ super *options_without_from_array, &block
17
+ end
18
+ end
19
+
20
+ private
21
+ def arrayify_transition(options, &block)
22
+ if options.nil?
23
+ yield []
24
+ elsif options.fetch(:from, nil).kind_of?(Array)
25
+ options[:from].each do |from_state|
26
+ yield [options.merge(from: from_state)]
27
+ end
28
+ else
29
+ yield [options]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,74 @@
1
+ module Ably::Modules
2
+ # Mixing module that assists with {https://github.com/gocardless/statesman Statemans State Machine} state transitions
3
+ # and maintaining state of this object's #state.
4
+ #
5
+ # Expects:
6
+ # - @state_machine is set to the StateMachine
7
+ # - StateEmitter is included in the object
8
+ #
9
+ module UsesStateMachine
10
+ extend Forwardable
11
+
12
+ # Call #transition_to on the StateMachine
13
+ #
14
+ # @return [Boolean] true if new_state can be transitioned to by state machine
15
+ # @api private
16
+ def transition_state_machine(new_state, emit_object = nil)
17
+ state_machine.transition_state(new_state, emit_object)
18
+ end
19
+
20
+ # Call #transition_to! on the StateMachine
21
+ # An exception wil be raised if new_state cannot be transitioned to by state machine
22
+ #
23
+ # @return [void]
24
+ # @api private
25
+ def transition_state_machine!(new_state, emit_object = nil)
26
+ state_machine.transition_to!(new_state, emit_object)
27
+ end
28
+
29
+ # Provides an internal method for this object's state to match the StateMachine's current state.
30
+ # The current object's state will be changed to the StateMachine state and will emit an event
31
+ # @api private
32
+ def synchronize_state_with_statemachine(*args)
33
+ log_state_machine_state_change
34
+ change_state state_machine.current_state, state_machine.last_transition.metadata
35
+ end
36
+
37
+ # @!attribute [r] previous_state
38
+ # @return [STATE,nil] The previous state for this connection
39
+ # @api private
40
+ def previous_state
41
+ if state_machine.previous_state
42
+ STATE(state_machine.previous_state)
43
+ end
44
+ end
45
+
46
+ # @!attribute [r] state_history
47
+ # @return [Array<Hash>] All previous states including the current state in date ascending order with Hash properties :state, :metadata, :transitioned_at
48
+ # @api private
49
+ def state_history
50
+ state_machine.history.map do |transition|
51
+ {
52
+ state: STATE(transition.to_state),
53
+ metadata: transition.metadata,
54
+ transitioned_at: transition.created_at
55
+ }
56
+ end
57
+ end
58
+
59
+ def_delegators :state_machine, :can_transition_to?
60
+
61
+ private
62
+ attr_reader :state_machine
63
+
64
+ def_delegators :state_machine, :exception_for_state_change_to
65
+
66
+ def log_state_machine_state_change
67
+ if state_machine.previous_state
68
+ logger.debug "#{self.class.name}: Transitioned from #{state_machine.previous_state} => #{state_machine.current_state}"
69
+ else
70
+ logger.debug "#{self.class.name}: Transitioned to #{state_machine.current_state}"
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,64 @@
1
+ require 'eventmachine'
2
+ require 'websocket/driver'
3
+
4
+ require 'ably/modules/event_emitter'
5
+
6
+ require 'ably/realtime/channel'
7
+ require 'ably/realtime/channels'
8
+ require 'ably/realtime/channel/channel_manager'
9
+ require 'ably/realtime/channel/channel_state_machine'
10
+ require 'ably/realtime/client'
11
+ require 'ably/realtime/connection'
12
+ require 'ably/realtime/connection/connection_manager'
13
+ require 'ably/realtime/connection/connection_state_machine'
14
+ require 'ably/realtime/connection/websocket_transport'
15
+ require 'ably/realtime/presence'
16
+
17
+ Dir.glob(File.expand_path("models/*.rb", File.dirname(__FILE__))).each do |file|
18
+ require file
19
+ end
20
+
21
+ Dir.glob(File.expand_path("realtime/models/*.rb", File.dirname(__FILE__))).each do |file|
22
+ require file
23
+ end
24
+
25
+ require 'ably/models/message_encoders/base'
26
+
27
+ require 'ably/realtime/client/incoming_message_dispatcher'
28
+ require 'ably/realtime/client/outgoing_message_dispatcher'
29
+
30
+ module Ably
31
+ # Realtime provides the top-level class to be instanced for the Ably Realtime library
32
+ #
33
+ # @example
34
+ # client = Ably::Realtime.new("xxxxx")
35
+ # channel = client.channel("test")
36
+ # channel.subscribe do |message|
37
+ # message[:name] #=> "greeting"
38
+ # end
39
+ # channel.publish "greeting", "data"
40
+ #
41
+ module Realtime
42
+ # Convenience method providing an alias to {Ably::Realtime::Client} constructor.
43
+ #
44
+ # @param (see Ably::Realtime::Client#initialize)
45
+ # @option options (see Ably::Realtime::Client#initialize)
46
+ #
47
+ # @yield (see Ably::Realtime::Client#initialize)
48
+ # @yieldparam (see Ably::Realtime::Client#initialize)
49
+ # @yieldreturn (see Ably::Realtime::Client#initialize)
50
+ #
51
+ # @return [Ably::Realtime::Client]
52
+ #
53
+ # @example
54
+ # # create a new client authenticating with basic auth
55
+ # client = Ably::Realtime.new('key.id:secret')
56
+ #
57
+ # # create a new client authenticating with basic auth and a client_id
58
+ # client = Ably::Realtime.new(api_key: 'key.id:secret', client_id: 'john')
59
+ #
60
+ def self.new(options, &token_request_block)
61
+ Ably::Realtime::Client.new(options, &token_request_block)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,298 @@
1
+ module Ably
2
+ module Realtime
3
+ # The Channel class represents a Channel belonging to this application.
4
+ # The Channel instance allows messages to be published and
5
+ # received, and controls the lifecycle of this instance's
6
+ # attachment to the channel.
7
+ #
8
+ # Channels will always be in one of the following states:
9
+ #
10
+ # initialized: 0
11
+ # attaching: 1
12
+ # attached: 2
13
+ # detaching: 3
14
+ # detached: 4
15
+ # failed: 5
16
+ #
17
+ # Note that the states are available as Enum-like constants:
18
+ #
19
+ # Channel::STATE.Initialized
20
+ # Channel::STATE.Attaching
21
+ # Channel::STATE.Attached
22
+ # Channel::STATE.Detaching
23
+ # Channel::STATE.Detached
24
+ # Channel::STATE.Failed
25
+ #
26
+ # Channels emit errors - use `on(:error)` to subscribe to errors
27
+ #
28
+ # @!attribute [r] state
29
+ # @return {Ably::Realtime::Connection::STATE} channel state
30
+ #
31
+ class Channel
32
+ include Ably::Modules::Conversions
33
+ include Ably::Modules::EventEmitter
34
+ include Ably::Modules::EventMachineHelpers
35
+ include Ably::Modules::AsyncWrapper
36
+ extend Ably::Modules::Enum
37
+
38
+ STATE = ruby_enum('STATE',
39
+ :initialized,
40
+ :attaching,
41
+ :attached,
42
+ :detaching,
43
+ :detached,
44
+ :failed
45
+ )
46
+ include Ably::Modules::StateEmitter
47
+ include Ably::Modules::UsesStateMachine
48
+
49
+ # Max number of messages to bundle in a single ProtocolMessage
50
+ MAX_PROTOCOL_MESSAGE_BATCH_SIZE = 50
51
+
52
+ # {Ably::Realtime::Client} associated with this channel
53
+ # @return [Ably::Realtime::Client]
54
+ attr_reader :client
55
+
56
+ # Channel name
57
+ # @return [String]
58
+ attr_reader :name
59
+
60
+ # Channel options configured for this channel, see {#initialize} for channel_options
61
+ # @return [Hash]
62
+ attr_reader :options
63
+
64
+ # When a channel failure occurs this attribute contains the Ably Exception
65
+ # @return [Ably::Models::ErrorInfo,Ably::Exceptions::BaseAblyException]
66
+ attr_reader :error_reason
67
+
68
+ # The Channel manager responsible for attaching, detaching and handling failures for this channel
69
+ # @return [Ably::Realtime::Channel::ChannelManager]
70
+ # @api private
71
+ attr_reader :manager
72
+
73
+ # Initialize a new Channel object
74
+ #
75
+ # @param client [Ably::Rest::Client]
76
+ # @param name [String] The name of the channel
77
+ # @param channel_options [Hash] Channel options, currently reserved for Encryption options
78
+ # @option channel_options [Boolean] :encrypted setting this to true for this channel will encrypt & decrypt all messages automatically
79
+ # @option channel_options [Hash] :cipher_params A hash of options to configure the encryption. *:key* is required, all other options are optional. See {Ably::Util::Crypto#initialize} for a list of `cipher_params` options
80
+ #
81
+ def initialize(client, name, channel_options = {})
82
+ ensure_utf_8 :name, name
83
+
84
+ @client = client
85
+ @name = name
86
+ @options = channel_options.clone.freeze
87
+ @subscriptions = Hash.new { |hash, key| hash[key] = [] }
88
+ @queue = []
89
+
90
+ @state_machine = ChannelStateMachine.new(self)
91
+ @state = STATE(state_machine.current_state)
92
+ @manager = ChannelManager.new(self, client.connection)
93
+
94
+ setup_event_handlers
95
+ end
96
+
97
+ # Publish a message on the channel
98
+ #
99
+ # @param name [String] The event name of the message
100
+ # @param data [String,ByteArray] payload for the message
101
+ # @yield [Ably::Models::Message] On success, will call the block with the {Ably::Models::Message}
102
+ # @return [Ably::Models::Message] Deferrable {Ably::Models::Message} that supports both success (callback) and failure (errback) callbacks
103
+ #
104
+ # @example
105
+ # channel.publish('click', 'body')
106
+ #
107
+ # channel.publish('click', 'body') do |message|
108
+ # puts "#{message.name} event received with #{message.data}"
109
+ # end
110
+ #
111
+ # channel.publish('click', 'body').errback do |message, error|
112
+ # puts "#{message.name} was not received, error #{error.message}"
113
+ # end
114
+ #
115
+ def publish(name, data, &success_block)
116
+ ensure_utf_8 :name, name
117
+
118
+ create_message(name, data).tap do |message|
119
+ message.callback(&success_block) if block_given?
120
+ queue_message message
121
+ end
122
+ end
123
+
124
+ # Subscribe to messages matching providing event name, or all messages if event name not provided
125
+ #
126
+ # @param name [String] The event name of the message to subscribe to if provided. Defaults to all events.
127
+ # @yield [Ably::Models::Message] For each message received, the block is called
128
+ #
129
+ # @return [void]
130
+ #
131
+ def subscribe(name = :all, &callback)
132
+ attach unless attached? || attaching?
133
+ subscriptions[message_name_key(name)] << callback
134
+ end
135
+
136
+ # Unsubscribe the matching block for messages matching providing event name, or all messages if event name not provided.
137
+ # If a block is not provided, all subscriptions will be unsubscribed
138
+ #
139
+ # @param name [String] The event name of the message to subscribe to if provided. Defaults to all events.
140
+ #
141
+ # @return [void]
142
+ #
143
+ def unsubscribe(name = :all, &callback)
144
+ if message_name_key(name) == :all
145
+ subscriptions.keys
146
+ else
147
+ Array(message_name_key(name))
148
+ end.each do |key|
149
+ subscriptions[key].delete_if do |block|
150
+ !block_given? || callback == block
151
+ end
152
+ end
153
+ end
154
+
155
+ # Attach to this channel, and call the block if provided when attached.
156
+ # Attaching to a channel is implicit in when a message is published or #subscribe is called, so it is uncommon
157
+ # to need to call attach explicitly.
158
+ #
159
+ # @yield [Ably::Realtime::Channel] Block is called as soon as this channel is in the Attached state
160
+ # @return [EventMachine::Deferrable] Deferrable that supports both success (callback) and failure (errback) callback
161
+ #
162
+ def attach(&success_block)
163
+ transition_state_machine :attaching if can_transition_to?(:attaching)
164
+ deferrable_for_state_change_to(STATE.Attached, &success_block)
165
+ end
166
+
167
+ # Detach this channel, and call the block if provided when in a Detached or Failed state
168
+ #
169
+ # @yield [Ably::Realtime::Channel] Block is called as soon as this channel is in the Detached or Failed state
170
+ # @return [void]
171
+ #
172
+ def detach(&success_block)
173
+ raise exception_for_state_change_to(:detaching) if failed? || initialized?
174
+ transition_state_machine :detaching if can_transition_to?(:detaching)
175
+ deferrable_for_state_change_to(STATE.Detached, &success_block)
176
+ end
177
+
178
+ # Presence object for this Channel. This controls this client's
179
+ # presence on the channel and may also be used to obtain presence information
180
+ # and change events for other members of the channel.
181
+ #
182
+ # @return {Ably::Realtime::Presence}
183
+ #
184
+ def presence
185
+ attach if initialized?
186
+ @presence ||= Presence.new(self)
187
+ end
188
+
189
+ # Return the message history of the channel
190
+ #
191
+ # @param (see Ably::Rest::Channel#history)
192
+ # @option options (see Ably::Rest::Channel#history)
193
+ #
194
+ # @yield [Ably::Models::PaginatedResource<Ably::Models::Message>] An Array of {Ably::Models::Message} objects that supports paging (#next_page, #first_page)
195
+ #
196
+ # @return [EventMachine::Deferrable]
197
+ def history(options = {}, &callback)
198
+ async_wrap(callback) do
199
+ rest_channel.history(options.merge(async_blocking_operations: true))
200
+ end
201
+ end
202
+
203
+ # @!attribute [r] __incoming_msgbus__
204
+ # @return [Ably::Util::PubSub] Client library internal channel incoming message bus
205
+ # @api private
206
+ def __incoming_msgbus__
207
+ @__incoming_msgbus__ ||= Ably::Util::PubSub.new(
208
+ coerce_into: Proc.new { |event| Ably::Models::ProtocolMessage::ACTION(event) }
209
+ )
210
+ end
211
+
212
+ # @api private
213
+ def set_failed_channel_error_reason(error)
214
+ @error_reason = error
215
+ end
216
+
217
+ # Used by {Ably::Modules::StateEmitter} to debug state changes
218
+ # @api private
219
+ def logger
220
+ client.logger
221
+ end
222
+
223
+ private
224
+ attr_reader :queue, :subscriptions
225
+
226
+ def setup_event_handlers
227
+ __incoming_msgbus__.subscribe(:message) do |message|
228
+ message.decode self
229
+
230
+ subscriptions[:all].each { |cb| cb.call(message) }
231
+ subscriptions[message.name].each { |cb| cb.call(message) }
232
+ end
233
+
234
+ on(STATE.Attached) do
235
+ process_queue
236
+ end
237
+ end
238
+
239
+ # Queue message and process queue if channel is attached.
240
+ # If channel is not yet attached, attempt to attach it before the message queue is processed.
241
+ def queue_message(message)
242
+ queue << message
243
+
244
+ if attached?
245
+ process_queue
246
+ else
247
+ attach
248
+ end
249
+ end
250
+
251
+ def messages_in_queue?
252
+ !queue.empty?
253
+ end
254
+
255
+ # Move messages from Channel Queue into Outgoing Connection Queue
256
+ def process_queue
257
+ condition = -> { attached? && messages_in_queue? }
258
+ non_blocking_loop_while(condition) do
259
+ send_messages_within_protocol_message queue.shift(MAX_PROTOCOL_MESSAGE_BATCH_SIZE)
260
+ end
261
+ end
262
+
263
+ def send_messages_within_protocol_message(messages)
264
+ client.connection.send_protocol_message(
265
+ action: Ably::Models::ProtocolMessage::ACTION.Message.to_i,
266
+ channel: name,
267
+ messages: messages
268
+ )
269
+ end
270
+
271
+ def create_message(name, data)
272
+ message = { name: name }
273
+ message.merge!(data: data) unless data.nil?
274
+ message.merge!(clientId: client.client_id) if client.client_id
275
+
276
+ Ably::Models::Message.new(message, nil).tap do |message|
277
+ message.encode self
278
+ end
279
+ end
280
+
281
+ def rest_channel
282
+ client.rest_client.channel(name)
283
+ end
284
+
285
+ def connection
286
+ client.connection
287
+ end
288
+
289
+ def message_name_key(name)
290
+ if name == :all
291
+ :all
292
+ else
293
+ name.to_s
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end