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.
- checksums.yaml +13 -5
- data/.gitmodules +1 -1
- data/.rspec +1 -0
- data/.travis.yml +7 -3
- data/SPEC.md +495 -419
- data/ably-rest.gemspec +19 -5
- data/lib/ably-rest.rb +9 -1
- data/lib/submodules/ably-ruby/.gitignore +6 -0
- data/lib/submodules/ably-ruby/.rspec +1 -0
- data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
- data/lib/submodules/ably-ruby/.travis.yml +10 -0
- data/lib/submodules/ably-ruby/Gemfile +4 -0
- data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
- data/lib/submodules/ably-ruby/README.md +122 -0
- data/lib/submodules/ably-ruby/Rakefile +34 -0
- data/lib/submodules/ably-ruby/SPEC.md +1794 -0
- data/lib/submodules/ably-ruby/ably.gemspec +36 -0
- data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
- data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
- data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
- data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
- data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
- data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
- data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
- data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
- data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
- data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
- data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
- data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
- data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
- data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
- data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
- data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
- data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
- data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
- data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
- data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
- 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
|