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,128 @@
1
+ module Ably
2
+ module Modules
3
+ # EventEmitter provides methods to attach to public events and trigger events on any class instance
4
+ #
5
+ # EventEmitter are typically used for public interfaces, and as such, may be overriden in
6
+ # the classes to enforce `event` names match expected values.
7
+ #
8
+ # @example
9
+ # class Example
10
+ # include Modules::EventEmitter
11
+ # end
12
+ #
13
+ # event_emitter = Example.new
14
+ # event_emitter.on(:signal) { |name| puts "Signal #{name} received" }
15
+ # event_emitter.trigger :signal, "Test"
16
+ # #=> "Signal Test received"
17
+ #
18
+ module EventEmitter
19
+ module ClassMethods
20
+ attr_reader :event_emitter_coerce_proc
21
+
22
+ # Configure included EventEmitter
23
+ #
24
+ # @param [Hash] options the options for the {EventEmitter}
25
+ # @option options [Proc] :coerce_into A lambda/Proc that is used to coerce the event names for all events. This is useful to ensure the event names conform to a naming or type convention.
26
+ #
27
+ # @example
28
+ # configure_event_emitter coerce_into: Proc.new { |event| event.to_sym }
29
+ #
30
+ def configure_event_emitter(options = {})
31
+ @event_emitter_coerce_proc = options[:coerce_into]
32
+ end
33
+
34
+ # Ensure @event_emitter_coerce_proc option is passed down to any classes that inherit the class with callbacks
35
+ def inherited(subclass)
36
+ subclass.instance_variable_set('@event_emitter_coerce_proc', @event_emitter_coerce_proc)
37
+ super
38
+ end
39
+ end
40
+
41
+ # On receiving an event matching the event_name, call the provided block
42
+ #
43
+ # @param [Array<String>] event_names event name
44
+ #
45
+ # @return [void]
46
+ def on(*event_names, &block)
47
+ event_names.each do |event_name|
48
+ callbacks[callbacks_event_coerced(event_name)] << proc_for_block(block)
49
+ end
50
+ end
51
+
52
+ # On receiving an event maching the event_name, call the provided block only once and remove the registered callback
53
+ #
54
+ # @param [Array<String>] event_names event name
55
+ #
56
+ # @return [void]
57
+ def once(*event_names, &block)
58
+ event_names.each do |event_name|
59
+ callbacks[callbacks_event_coerced(event_name)] << proc_for_block(block, delete_once_run: true)
60
+ end
61
+ end
62
+
63
+ # Trigger an event with event_name that will in turn call all matching callbacks setup with `on`
64
+ def trigger(event_name, *args)
65
+ callbacks[callbacks_event_coerced(event_name)].
66
+ clone.
67
+ select do |proc_hash|
68
+ proc_hash[:trigger_proc].call(*args)
69
+ end.each do |callback|
70
+ callbacks[callbacks_event_coerced(event_name)].delete callback
71
+ end
72
+ end
73
+
74
+ # Remove all callbacks for event_name.
75
+ #
76
+ # If a block is provided, only callbacks matching that block signature will be removed.
77
+ # If block is not provided, all callbacks matching the event_name will be removed.
78
+ #
79
+ # @param [Array<String>] event_names event name
80
+ #
81
+ # @return [void]
82
+ def off(*event_names, &block)
83
+ keys = if event_names.empty?
84
+ callbacks.keys
85
+ else
86
+ event_names
87
+ end
88
+
89
+ keys.each do |event_name|
90
+ if block_given?
91
+ callbacks[callbacks_event_coerced(event_name)].delete_if { |proc_hash| proc_hash[:block] == block }
92
+ else
93
+ callbacks[callbacks_event_coerced(event_name)].clear
94
+ end
95
+ end
96
+ end
97
+
98
+ private
99
+ def self.included(klass)
100
+ klass.extend ClassMethods
101
+ end
102
+
103
+ # Create a Hash with a proc that calls the provided block and returns true if option :delete_once_run is set to true.
104
+ # #trigger automatically deletes any blocks that return true thus allowing a block to be run once
105
+ def proc_for_block(block, options = {})
106
+ {
107
+ trigger_proc: Proc.new do |*args|
108
+ block.call *args
109
+ true if options[:delete_once_run]
110
+ end,
111
+ block: block
112
+ }
113
+ end
114
+
115
+ def callbacks
116
+ @callbacks ||= Hash.new { |hash, key| hash[key] = [] }
117
+ end
118
+
119
+ def callbacks_event_coerced(event_name)
120
+ if self.class.event_emitter_coerce_proc
121
+ self.class.event_emitter_coerce_proc.call(event_name)
122
+ else
123
+ event_name
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,26 @@
1
+ require 'eventmachine'
2
+
3
+ module Ably::Modules
4
+ # EventMachineHelpers module provides common private methods to classes simplifying interaction with EventMachine
5
+ module EventMachineHelpers
6
+ private
7
+
8
+ # This method allows looped blocks to be run at the next EventMachine tick
9
+ # @example
10
+ # x = 0
11
+ # less_than_3 = -> { x < 3 }
12
+ # non_blocking_loop_while(less_than_3) do
13
+ # x += 1
14
+ # end
15
+ def non_blocking_loop_while(lambda_condition, &execution_block)
16
+ if lambda_condition.call
17
+ EventMachine.next_tick do
18
+ if lambda_condition.call # ensure condition is still met following #next_tick
19
+ yield
20
+ non_blocking_loop_while(lambda_condition, &execution_block)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ require 'base64'
2
+
3
+ require 'ably/version'
4
+
5
+ require 'ably/rest/middleware/encoder'
6
+ require 'ably/rest/middleware/external_exceptions'
7
+ require 'ably/rest/middleware/fail_if_unsupported_mime_type'
8
+ require 'ably/rest/middleware/logger'
9
+ require 'ably/rest/middleware/parse_json'
10
+ require 'ably/rest/middleware/parse_message_pack'
11
+
12
+ module Ably::Modules
13
+ # HttpHelpers provides common private methods to classes to simplify HTTP interactions with Ably
14
+ module HttpHelpers
15
+ protected
16
+ def encode64(text)
17
+ Base64.encode64(text).gsub("\n", '')
18
+ end
19
+
20
+ def user_agent
21
+ "Ably Ruby client #{Ably::VERSION} (https://ably.io)"
22
+ end
23
+
24
+ def setup_outgoing_middleware(builder)
25
+ # Convert request params to "www-form-urlencoded"
26
+ builder.use Ably::Rest::Middleware::Encoder
27
+ end
28
+
29
+ def setup_incoming_middleware(builder, logger, options = {})
30
+ builder.use Ably::Rest::Middleware::Logger, logger
31
+
32
+ # Parse JSON / MsgPack response bodies. ParseJson must be first (default) parsing middleware
33
+ if options[:fail_if_unsupported_mime_type] == true
34
+ builder.use Ably::Rest::Middleware::FailIfUnsupportedMimeType
35
+ end
36
+
37
+ builder.use Ably::Rest::Middleware::ParseJson
38
+ builder.use Ably::Rest::Middleware::ParseMessagePack
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,14 @@
1
+ require 'msgpack'
2
+
3
+ module Ably::Modules
4
+ # MessagePack module adds #to_msgpack to the class on the assumption that the class
5
+ # supports the method #as_json
6
+ #
7
+ module MessagePack
8
+ # Generate a packed MsgPack version of this object based on the JSON representation.
9
+ # Keys thus use mixedCase syntax as expected by the Realtime API
10
+ def to_msgpack(*args)
11
+ as_json(*args).to_msgpack
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,41 @@
1
+ require 'base64'
2
+ require 'ably/modules/conversions'
3
+ require 'ably/modules/message_pack'
4
+
5
+ module Ably::Modules
6
+ # Common model functionality shared across many {Ably::Models}
7
+ module ModelCommon
8
+ include Conversions
9
+ include MessagePack
10
+
11
+ # Provide a normal Hash accessor to the underlying raw message object
12
+ #
13
+ # @return [Object]
14
+ def [](key)
15
+ hash[key]
16
+ end
17
+
18
+ def ==(other)
19
+ other.kind_of?(self.class) &&
20
+ hash == other.hash
21
+ end
22
+
23
+ # Return a JSON ready object from the underlying #hash using Ably naming conventions for keys
24
+ def as_json
25
+ hash.as_json.dup
26
+ end
27
+
28
+ # Stringify the JSON representation of this object from the underlying #hash
29
+ def to_json(*args)
30
+ as_json.to_json(*args)
31
+ end
32
+
33
+ private
34
+ def ensure_utf8_string_for(attribute, value)
35
+ if value
36
+ raise ArgumentError, "#{attribute} must be a String" unless value.kind_of?(String)
37
+ raise ArgumentError, "#{attribute} cannot use ASCII_8BIT encoding, please use UTF_8 encoding" unless value.encoding == Encoding::UTF_8
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,153 @@
1
+ module Ably::Modules
2
+ # StateEmitter module adds a set of generic state related methods to a class on the assumption that
3
+ # the instance variable @state is used exclusively, the {Enum} STATE is defined prior to inclusion of this
4
+ # module, and the class is an {EventEmitter}. It then emits state changes.
5
+ #
6
+ # It also ensures the EventEmitter is configured to retrict permitted events to the
7
+ # the available STATEs and :error.
8
+ #
9
+ # @example
10
+ # class Connection
11
+ # include Ably::Modules::EventEmitter
12
+ # extend Ably::Modules::Enum
13
+ # STATE = ruby_enum('STATE',
14
+ # :initialized,
15
+ # :connecting,
16
+ # :connected
17
+ # )
18
+ # include Ably::Modules::StateEmitter
19
+ # end
20
+ #
21
+ # connection = Connection.new
22
+ # connection.state = :connecting # emits :connecting event via EventEmitter, returns STATE.Connecting
23
+ # connection.state?(:connected) # => false
24
+ # connection.connecting? # => true
25
+ # connection.state # => STATE.Connecting
26
+ # connection.state = :invalid # raises an Exception as only a valid state can be defined
27
+ # connection.trigger :invalid # raises an Exception as only a valid state can be used for EventEmitter
28
+ # connection.change_state :connected # emits :connected event via EventEmitter, returns STATE.Connected
29
+ # connection.once_or_if(:connected) { puts 'block called once when state is connected or becomes connected' }
30
+ #
31
+ module StateEmitter
32
+ # Current state {Ably::Modules::Enum}
33
+ #
34
+ # @return [Symbol] state
35
+ def state
36
+ STATE(@state)
37
+ end
38
+
39
+ # Evaluates if check_state matches current state
40
+ #
41
+ # @return [Boolean]
42
+ def state?(check_state)
43
+ state == check_state
44
+ end
45
+
46
+ # Set the current state {Ably::Modules::Enum}
47
+ #
48
+ # @return [Symbol] new state
49
+ # @api private
50
+ def state=(new_state, *args)
51
+ if state != new_state
52
+ logger.debug("#{self.class}: StateEmitter changed from #{state} => #{new_state}") if respond_to?(:logger, true)
53
+ @state = STATE(new_state)
54
+ trigger @state, *args
55
+ end
56
+ end
57
+ alias_method :change_state, :state=
58
+
59
+ # If the current state matches the target_state argument the block is called immediately.
60
+ # Else the block is called once when the target_state is reached.
61
+ #
62
+ # If the option block :else is provided then if any state other than target_state is reached, the :else block is called,
63
+ # however only one of the blocks will ever be called
64
+ #
65
+ # @param [Symbol,Ably::Modules::Enum,Array] target_states a single state or array of states that once met, will fire the success block only once
66
+ # @param [Hash] options
67
+ # @option options [Proc] :else block called once the state has changed to anything but target_state
68
+ #
69
+ # @yield block is called if the state is matched immediately or once when the state is reached
70
+ #
71
+ # @return [void]
72
+ def once_or_if(target_states, options = {})
73
+ raise ArgumentError, 'Block required' unless block_given?
74
+
75
+ if Array(target_states).any? { |target_state| state == target_state }
76
+ yield
77
+ else
78
+ failure_block = options.fetch(:else, nil)
79
+ failure_wrapper = nil
80
+
81
+ success_wrapper = Proc.new do
82
+ yield
83
+ off &success_wrapper
84
+ off &failure_wrapper if failure_wrapper
85
+ end
86
+
87
+ failure_wrapper = proc do |*args|
88
+ failure_block.call *args
89
+ off &success_wrapper
90
+ off &failure_wrapper
91
+ end if failure_block
92
+
93
+ Array(target_states).each do |target_state|
94
+ once target_state, &success_wrapper
95
+
96
+ once_state_changed do |*args|
97
+ failure_wrapper.call *args unless state == target_state
98
+ end if failure_block
99
+ end
100
+ end
101
+ end
102
+
103
+ # Calls the block once when the state changes
104
+ #
105
+ # @yield block is called once the state changes
106
+ # @return [void]
107
+ #
108
+ # @api private
109
+ def once_state_changed(&block)
110
+ raise ArgumentError, 'Block required' unless block_given?
111
+
112
+ once_block = proc do |*args|
113
+ off *self.class::STATE.map, &once_block
114
+ yield *args
115
+ end
116
+
117
+ once *self.class::STATE.map, &once_block
118
+ end
119
+
120
+ private
121
+
122
+ # Returns an {EventMachine::Deferrable} and once the target state is reached, the
123
+ # success block if provided and {EventMachine::Deferrable#callback} is called.
124
+ # If the state changes to any other state, the {EventMachine::Deferrable#errback} is called.
125
+ #
126
+ def deferrable_for_state_change_to(target_state)
127
+ EventMachine::DefaultDeferrable.new.tap do |deferrable|
128
+ once_or_if(target_state, else: proc { |*args| deferrable.fail self, *args }) do
129
+ yield self if block_given?
130
+ deferrable.succeed self
131
+ end
132
+ end
133
+ end
134
+
135
+ def self.included(klass)
136
+ klass.configure_event_emitter coerce_into: Proc.new { |event|
137
+ if event == :error
138
+ :error
139
+ else
140
+ klass::STATE(event)
141
+ end
142
+ }
143
+
144
+ klass::STATE.each do |state_predicate|
145
+ klass.instance_eval do
146
+ define_method("#{state_predicate.to_sym}?") do
147
+ state?(state_predicate)
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,57 @@
1
+ require 'statesman'
2
+ require 'ably/modules/statesman_monkey_patch'
3
+
4
+ module Ably::Modules
5
+ # Module providing Statesman StateMachine functionality
6
+ #
7
+ # Expects method #logger to be defined
8
+ #
9
+ # @api private
10
+ module StateMachine
11
+ def self.included(klass)
12
+ klass.class_eval do
13
+ include Statesman::Machine
14
+ end
15
+ klass.extend Ably::Modules::StatesmanMonkeyPatch
16
+ klass.extend ClassMethods
17
+ end
18
+
19
+ # Alternative to Statesman's #transition_to that:
20
+ # * log state change failures to {Logger}
21
+ # * raise an exception on the {Ably::Realtime::Channel}
22
+ #
23
+ # @return [void]
24
+ def transition_state(state, *args)
25
+ unless result = transition_to(state, *args)
26
+ exception = exception_for_state_change_to(state)
27
+ object.trigger :error, exception
28
+ logger.fatal "#{self.class}: #{exception.message}"
29
+ end
30
+ result
31
+ end
32
+
33
+ # @return [Statesman History Object]
34
+ def previous_transition
35
+ history[-2]
36
+ end
37
+
38
+ # @return [Symbol]
39
+ def previous_state
40
+ previous_transition.to_state if previous_transition
41
+ end
42
+
43
+ # @return [Ably::Exceptions::StateChangeError]
44
+ def exception_for_state_change_to(state)
45
+ error_message = "#{self.class}: Unable to transition from #{current_state} => #{state}"
46
+ Ably::Exceptions::StateChangeError.new(error_message, nil, 80020)
47
+ end
48
+
49
+ module ClassMethods
50
+ private
51
+
52
+ def is_error_type?(error)
53
+ error.kind_of?(Ably::Models::ErrorInfo) || error.kind_of?(StandardError)
54
+ end
55
+ end
56
+ end
57
+ end