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