ably-pubsub-core 0.0.1.pre
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 +7 -0
- data/lib/ably/agent.rb +8 -0
- data/lib/ably/auth.rb +791 -0
- data/lib/ably/exceptions.rb +169 -0
- data/lib/ably/logger.rb +119 -0
- data/lib/ably/models/auth_details.rb +45 -0
- data/lib/ably/models/channel_details.rb +69 -0
- data/lib/ably/models/channel_metrics.rb +107 -0
- data/lib/ably/models/channel_occupancy.rb +50 -0
- data/lib/ably/models/channel_options.rb +120 -0
- data/lib/ably/models/channel_state_change.rb +84 -0
- data/lib/ably/models/channel_status.rb +63 -0
- data/lib/ably/models/cipher_params.rb +137 -0
- data/lib/ably/models/connection_details.rb +149 -0
- data/lib/ably/models/connection_state_change.rb +83 -0
- data/lib/ably/models/delta_extras.rb +28 -0
- data/lib/ably/models/device_details.rb +126 -0
- data/lib/ably/models/device_push_details.rb +94 -0
- data/lib/ably/models/error_info.rb +104 -0
- data/lib/ably/models/http_paginated_response.rb +112 -0
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +235 -0
- data/lib/ably/models/message.rb +223 -0
- data/lib/ably/models/message_encoders/base.rb +135 -0
- data/lib/ably/models/message_encoders/base64.rb +40 -0
- data/lib/ably/models/message_encoders/cipher.rb +84 -0
- data/lib/ably/models/message_encoders/json.rb +34 -0
- data/lib/ably/models/message_encoders/utf8.rb +26 -0
- data/lib/ably/models/nil_logger.rb +20 -0
- data/lib/ably/models/paginated_result.rb +201 -0
- data/lib/ably/models/presence_message.rb +244 -0
- data/lib/ably/models/protocol_message.rb +288 -0
- data/lib/ably/models/push_channel_subscription.rb +114 -0
- data/lib/ably/models/stats.rb +242 -0
- data/lib/ably/models/stats_types.rb +107 -0
- data/lib/ably/models/token_details.rb +148 -0
- data/lib/ably/models/token_request.rb +180 -0
- data/lib/ably/modules/ably.rb +26 -0
- data/lib/ably/modules/async_wrapper.rb +69 -0
- data/lib/ably/modules/channels_collection.rb +105 -0
- data/lib/ably/modules/conversions.rb +153 -0
- data/lib/ably/modules/encodeable.rb +111 -0
- data/lib/ably/modules/enum.rb +230 -0
- data/lib/ably/modules/event_emitter.rb +200 -0
- data/lib/ably/modules/event_machine_helpers.rb +26 -0
- data/lib/ably/modules/exception_codes.rb +128 -0
- data/lib/ably/modules/http_helpers.rb +41 -0
- data/lib/ably/modules/message_emitter.rb +78 -0
- data/lib/ably/modules/message_pack.rb +14 -0
- data/lib/ably/modules/model_common.rb +84 -0
- data/lib/ably/modules/safe_deferrable.rb +83 -0
- data/lib/ably/modules/safe_yield.rb +41 -0
- data/lib/ably/modules/state_emitter.rb +181 -0
- data/lib/ably/modules/state_machine.rb +59 -0
- data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/ably/modules/uses_state_machine.rb +111 -0
- data/lib/ably/realtime/auth.rb +283 -0
- data/lib/ably/realtime/channel/channel_manager.rb +272 -0
- data/lib/ably/realtime/channel/channel_properties.rb +40 -0
- data/lib/ably/realtime/channel/channel_state_machine.rb +104 -0
- data/lib/ably/realtime/channel/publisher.rb +82 -0
- data/lib/ably/realtime/channel/push_channel.rb +69 -0
- data/lib/ably/realtime/channel.rb +440 -0
- data/lib/ably/realtime/channels.rb +71 -0
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +225 -0
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +87 -0
- data/lib/ably/realtime/client.rb +363 -0
- data/lib/ably/realtime/connection/connection_manager.rb +594 -0
- data/lib/ably/realtime/connection/connection_state_machine.rb +144 -0
- data/lib/ably/realtime/connection/websocket_transport.rb +287 -0
- data/lib/ably/realtime/connection.rb +738 -0
- data/lib/ably/realtime/models/nil_channel.rb +30 -0
- data/lib/ably/realtime/presence/members_map.rb +395 -0
- data/lib/ably/realtime/presence/presence_manager.rb +78 -0
- data/lib/ably/realtime/presence/presence_state_machine.rb +57 -0
- data/lib/ably/realtime/presence.rb +502 -0
- data/lib/ably/realtime/push/admin.rb +75 -0
- data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
- data/lib/ably/realtime/push/device_registrations.rb +105 -0
- data/lib/ably/realtime/push.rb +25 -0
- data/lib/ably/realtime/recovery_key_context.rb +36 -0
- data/lib/ably/realtime.rb +60 -0
- data/lib/ably/rest/channel/push_channel.rb +59 -0
- data/lib/ably/rest/channel.rb +194 -0
- data/lib/ably/rest/channels.rb +47 -0
- data/lib/ably/rest/client.rb +727 -0
- data/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/ably/rest/middleware/exceptions.rb +53 -0
- data/lib/ably/rest/middleware/external_exceptions.rb +24 -0
- data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +22 -0
- data/lib/ably/rest/middleware/logger.rb +58 -0
- data/lib/ably/rest/middleware/parse_json.rb +27 -0
- data/lib/ably/rest/middleware/parse_message_pack.rb +43 -0
- data/lib/ably/rest/presence.rb +113 -0
- data/lib/ably/rest/push/admin.rb +58 -0
- data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
- data/lib/ably/rest/push/device_registrations.rb +114 -0
- data/lib/ably/rest/push.rb +25 -0
- data/lib/ably/rest.rb +40 -0
- data/lib/ably/util/ably_extensions.rb +29 -0
- data/lib/ably/util/crypto.rb +134 -0
- data/lib/ably/util/pub_sub.rb +45 -0
- data/lib/ably/util/safe_deferrable.rb +44 -0
- data/lib/ably/version.rb +7 -0
- data/lib/ably.rb +16 -0
- metadata +497 -0
|
@@ -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,128 @@
|
|
|
1
|
+
# This file is generated by running `rake :generate_error_codes`
|
|
2
|
+
# Do not manually modify this file
|
|
3
|
+
# Generated at: 2018-09-18 18:28:58 UTC
|
|
4
|
+
#
|
|
5
|
+
module Ably
|
|
6
|
+
module Exceptions
|
|
7
|
+
module Codes
|
|
8
|
+
NO_ERROR = 10000
|
|
9
|
+
BAD_REQUEST = 40000
|
|
10
|
+
INVALID_REQUEST_BODY = 40001
|
|
11
|
+
INVALID_PARAMETER_NAME = 40002
|
|
12
|
+
INVALID_PARAMETER_VALUE = 40003
|
|
13
|
+
INVALID_HEADER = 40004
|
|
14
|
+
INVALID_CREDENTIAL = 40005
|
|
15
|
+
INVALID_CONNECTION_ID = 40006
|
|
16
|
+
INVALID_MESSAGE_ID = 40007
|
|
17
|
+
INVALID_CONTENT_LENGTH = 40008
|
|
18
|
+
MAXIMUM_MESSAGE_LENGTH_EXCEEDED = 40009
|
|
19
|
+
INVALID_CHANNEL_NAME = 40010
|
|
20
|
+
STALE_RING_STATE = 40011
|
|
21
|
+
INVALID_CLIENT_ID = 40012
|
|
22
|
+
INVALID_MESSAGE_DATA_OR_ENCODING = 40013
|
|
23
|
+
RESOURCE_DISPOSED = 40014
|
|
24
|
+
INVALID_DEVICE_ID = 40015
|
|
25
|
+
BATCH_ERROR = 40020
|
|
26
|
+
INVALID_PUBLISH_REQUEST_UNSPECIFIED = 40030
|
|
27
|
+
INVALID_PUBLISH_REQUEST_INVALID_CLIENTSPECIFIED_ID = 40031
|
|
28
|
+
UNAUTHORIZED = 40100
|
|
29
|
+
INVALID_CREDENTIALS = 40101
|
|
30
|
+
INCOMPATIBLE_CREDENTIALS = 40102
|
|
31
|
+
INVALID_USE_OF_BASIC_AUTH_OVER_NONTLS_TRANSPORT = 40103
|
|
32
|
+
TIMESTAMP_NOT_CURRENT = 40104
|
|
33
|
+
NONCE_VALUE_REPLAYED = 40105
|
|
34
|
+
UNABLE_TO_OBTAIN_CREDENTIALS_FROM_GIVEN_PARAMETERS = 40106
|
|
35
|
+
ACCOUNT_DISABLED = 40110
|
|
36
|
+
ACCOUNT_RESTRICTED_CONNECTION_LIMITS_EXCEEDED = 40111
|
|
37
|
+
ACCOUNT_BLOCKED_MESSAGE_LIMITS_EXCEEDED = 40112
|
|
38
|
+
ACCOUNT_BLOCKED = 40113
|
|
39
|
+
ACCOUNT_RESTRICTED_CHANNEL_LIMITS_EXCEEDED = 40114
|
|
40
|
+
APPLICATION_DISABLED = 40120
|
|
41
|
+
KEY_ERROR_UNSPECIFIED = 40130
|
|
42
|
+
KEY_REVOKED = 40131
|
|
43
|
+
KEY_EXPIRED = 40132
|
|
44
|
+
KEY_DISABLED = 40133
|
|
45
|
+
TOKEN_ERROR_UNSPECIFIED = 40140
|
|
46
|
+
TOKEN_REVOKED = 40141
|
|
47
|
+
TOKEN_EXPIRED = 40142
|
|
48
|
+
TOKEN_UNRECOGNISED = 40143
|
|
49
|
+
INVALID_JWT_FORMAT = 40144
|
|
50
|
+
INVALID_TOKEN_FORMAT = 40145
|
|
51
|
+
CONNECTION_BLOCKED_LIMITS_EXCEEDED = 40150
|
|
52
|
+
OPERATION_NOT_PERMITTED_WITH_PROVIDED_CAPABILITY = 40160
|
|
53
|
+
ERROR_FROM_CLIENT_TOKEN_CALLBACK = 40170
|
|
54
|
+
FORBIDDEN = 40300
|
|
55
|
+
ACCOUNT_DOES_NOT_PERMIT_TLS_CONNECTION = 40310
|
|
56
|
+
OPERATION_REQUIRES_TLS_CONNECTION = 40311
|
|
57
|
+
APPLICATION_REQUIRES_AUTHENTICATION = 40320
|
|
58
|
+
UNABLE_TO_ACTIVATE_ACCOUNT_DUE_TO_PLACEMENT_CONSTRAINT_UNSPECIFIED = 40330
|
|
59
|
+
UNABLE_TO_ACTIVATE_ACCOUNT_DUE_TO_PLACEMENT_CONSTRAINT_INCOMPATIBLE_ENVIRONMENT = 40331
|
|
60
|
+
UNABLE_TO_ACTIVATE_ACCOUNT_DUE_TO_PLACEMENT_CONSTRAINT_INCOMPATIBLE_SITE = 40332
|
|
61
|
+
NOT_FOUND = 40400
|
|
62
|
+
METHOD_NOT_ALLOWED = 40500
|
|
63
|
+
RATE_LIMIT_EXCEEDED_NONFATAL_REQUEST_REJECTED_UNSPECIFIED = 42910
|
|
64
|
+
MAX_PERCONNECTION_PUBLISH_RATE_LIMIT_EXCEEDED_NONFATAL_UNABLE_TO_PUBLISH_MESSAGE = 42911
|
|
65
|
+
RATE_LIMIT_EXCEEDED_FATAL = 42920
|
|
66
|
+
MAX_PERCONNECTION_PUBLISH_RATE_LIMIT_EXCEEDED_FATAL_CLOSING_CONNECTION = 42921
|
|
67
|
+
INTERNAL_ERROR = 50000
|
|
68
|
+
INTERNAL_CHANNEL_ERROR = 50001
|
|
69
|
+
INTERNAL_CONNECTION_ERROR = 50002
|
|
70
|
+
TIMEOUT_ERROR = 50003
|
|
71
|
+
REQUEST_FAILED_DUE_TO_OVERLOADED_INSTANCE = 50004
|
|
72
|
+
REACTOR_OPERATION_FAILED = 70000
|
|
73
|
+
REACTOR_OPERATION_FAILED_POST_OPERATION_FAILED = 70001
|
|
74
|
+
REACTOR_OPERATION_FAILED_POST_OPERATION_RETURNED_UNEXPECTED_CODE = 70002
|
|
75
|
+
REACTOR_OPERATION_FAILED_MAXIMUM_NUMBER_OF_CONCURRENT_INFLIGHT_REQUESTS_EXCEEDED = 70003
|
|
76
|
+
EXCHANGE_ERROR_UNSPECIFIED = 71000
|
|
77
|
+
FORCED_REATTACHMENT_DUE_TO_PERMISSIONS_CHANGE = 71001
|
|
78
|
+
EXCHANGE_PUBLISHER_ERROR_UNSPECIFIED = 71100
|
|
79
|
+
NO_SUCH_PUBLISHER = 71101
|
|
80
|
+
PUBLISHER_NOT_ENABLED_AS_AN_EXCHANGE_PUBLISHER = 71102
|
|
81
|
+
EXCHANGE_PRODUCT_ERROR_UNSPECIFIED = 71200
|
|
82
|
+
NO_SUCH_PRODUCT = 71201
|
|
83
|
+
PRODUCT_DISABLED = 71202
|
|
84
|
+
NO_SUCH_CHANNEL_IN_THIS_PRODUCT = 71203
|
|
85
|
+
EXCHANGE_SUBSCRIPTION_ERROR_UNSPECIFIED = 71300
|
|
86
|
+
SUBSCRIPTION_DISABLED = 71301
|
|
87
|
+
REQUESTER_HAS_NO_SUBSCRIPTION_TO_THIS_PRODUCT = 71302
|
|
88
|
+
CONNECTION_FAILED = 80000
|
|
89
|
+
CONNECTION_FAILED_NO_COMPATIBLE_TRANSPORT = 80001
|
|
90
|
+
CONNECTION_SUSPENDED = 80002
|
|
91
|
+
DISCONNECTED = 80003
|
|
92
|
+
ALREADY_CONNECTED = 80004
|
|
93
|
+
INVALID_CONNECTION_ID_REMOTE_NOT_FOUND = 80005
|
|
94
|
+
UNABLE_TO_RECOVER_CONNECTION_MESSAGES_EXPIRED = 80006
|
|
95
|
+
UNABLE_TO_RECOVER_CONNECTION_MESSAGE_LIMIT_EXCEEDED = 80007
|
|
96
|
+
UNABLE_TO_RECOVER_CONNECTION_CONNECTION_EXPIRED = 80008
|
|
97
|
+
CONNECTION_NOT_ESTABLISHED_NO_TRANSPORT_HANDLE = 80009
|
|
98
|
+
INVALID_OPERATION_INVALID_TRANSPORT_HANDLE = 80010
|
|
99
|
+
UNABLE_TO_RECOVER_CONNECTION_INCOMPATIBLE_AUTH_PARAMS = 80011
|
|
100
|
+
UNABLE_TO_RECOVER_CONNECTION_INVALID_OR_UNSPECIFIED_CONNECTION_SERIAL = 80012
|
|
101
|
+
PROTOCOL_ERROR = 80013
|
|
102
|
+
CONNECTION_TIMED_OUT = 80014
|
|
103
|
+
INCOMPATIBLE_CONNECTION_PARAMETERS = 80015
|
|
104
|
+
OPERATION_ON_SUPERSEDED_TRANSPORT = 80016
|
|
105
|
+
CONNECTION_CLOSED = 80017
|
|
106
|
+
INVALID_CONNECTION_ID_INVALID_FORMAT = 80018
|
|
107
|
+
CLIENT_CONFIGURED_AUTHENTICATION_PROVIDER_REQUEST_FAILED = 80019
|
|
108
|
+
CONTINUITY_LOSS_DUE_TO_MAXIMUM_SUBSCRIBE_MESSAGE_RATE_EXCEEDED = 80020
|
|
109
|
+
CLIENT_RESTRICTION_NOT_SATISFIED = 80030
|
|
110
|
+
CHANNEL_OPERATION_FAILED = 90000
|
|
111
|
+
CHANNEL_OPERATION_FAILED_INVALID_CHANNEL_STATE = 90001
|
|
112
|
+
CHANNEL_OPERATION_FAILED_EPOCH_EXPIRED_OR_NEVER_EXISTED = 90002
|
|
113
|
+
UNABLE_TO_RECOVER_CHANNEL_MESSAGES_EXPIRED = 90003
|
|
114
|
+
UNABLE_TO_RECOVER_CHANNEL_MESSAGE_LIMIT_EXCEEDED = 90004
|
|
115
|
+
UNABLE_TO_RECOVER_CHANNEL_NO_MATCHING_EPOCH = 90005
|
|
116
|
+
UNABLE_TO_RECOVER_CHANNEL_UNBOUNDED_REQUEST = 90006
|
|
117
|
+
CHANNEL_OPERATION_FAILED_NO_RESPONSE_FROM_SERVER = 90007
|
|
118
|
+
MAXIMUM_NUMBER_OF_CHANNELS_PER_CONNECTION_EXCEEDED = 90010
|
|
119
|
+
UNABLE_TO_ENTER_PRESENCE_CHANNEL_NO_CLIENTID = 91000
|
|
120
|
+
UNABLE_TO_ENTER_PRESENCE_CHANNEL_INVALID_CHANNEL_STATE = 91001
|
|
121
|
+
UNABLE_TO_LEAVE_PRESENCE_CHANNEL_THAT_IS_NOT_ENTERED = 91002
|
|
122
|
+
UNABLE_TO_ENTER_PRESENCE_CHANNEL_MAXIMUM_MEMBER_LIMIT_EXCEEDED = 91003
|
|
123
|
+
UNABLE_TO_AUTOMATICALLY_REENTER_PRESENCE_CHANNEL = 91004
|
|
124
|
+
PRESENCE_STATE_IS_OUT_OF_SYNC = 91005
|
|
125
|
+
MEMBER_IMPLICITLY_LEFT_PRESENCE_CHANNEL_CONNECTION_CLOSED = 91100
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
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://www.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,78 @@
|
|
|
1
|
+
require 'ably/util/pub_sub'
|
|
2
|
+
|
|
3
|
+
module Ably::Modules
|
|
4
|
+
# Message emitter, subscriber and unsubscriber (Pub/Sub) functionality common to Channels and Presence
|
|
5
|
+
# In addition to standard Pub/Sub functionality, it allows subscribers to subscribe to :all.
|
|
6
|
+
module MessageEmitter
|
|
7
|
+
|
|
8
|
+
include Ably::Modules::SafeYield
|
|
9
|
+
|
|
10
|
+
# Subscribe to events on this object
|
|
11
|
+
#
|
|
12
|
+
# @param names [String,Symbol] Optional, the event name(s) to subscribe to. Defaults to `:all` events
|
|
13
|
+
# @yield [Object] For each event, the provided block is called with the event payload object
|
|
14
|
+
#
|
|
15
|
+
# @return [void]
|
|
16
|
+
#
|
|
17
|
+
def subscribe(*names, &callback)
|
|
18
|
+
raise ArgumentError, 'Block required to subscribe to events' unless block_given?
|
|
19
|
+
names = :all unless names && !names.empty?
|
|
20
|
+
Array(names).uniq.each do |name|
|
|
21
|
+
message_emitter_subscriptions[message_emitter_subscriptions_message_name_key(name)] << callback
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Unsubscribe the matching block for events on the this object.
|
|
26
|
+
# If a block is not provided, all subscriptions will be unsubscribed
|
|
27
|
+
#
|
|
28
|
+
# @param names [String,Symbol] Optional, the event name(s) to unsubscribe from. Defaults to `:all` events
|
|
29
|
+
#
|
|
30
|
+
# @return [void]
|
|
31
|
+
#
|
|
32
|
+
def unsubscribe(*names, &callback)
|
|
33
|
+
names = :all unless names && !names.empty?
|
|
34
|
+
Array(names).each do |name|
|
|
35
|
+
if name == :all
|
|
36
|
+
message_emitter_subscriptions.keys
|
|
37
|
+
else
|
|
38
|
+
Array(message_emitter_subscriptions_message_name_key(name))
|
|
39
|
+
end.each do |key|
|
|
40
|
+
message_emitter_subscriptions[key].delete_if do |block|
|
|
41
|
+
!block_given? || callback == block
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Emit a message to message subscribers
|
|
48
|
+
#
|
|
49
|
+
# param name [String,Symbol] the event name
|
|
50
|
+
# param payload [Object] the event object to emit
|
|
51
|
+
#
|
|
52
|
+
# @return [void]
|
|
53
|
+
#
|
|
54
|
+
# @api private
|
|
55
|
+
def emit_message(name, payload)
|
|
56
|
+
message_emitter_subscriptions[:all].each { |cb| safe_yield(cb, payload) }
|
|
57
|
+
message_emitter_subscriptions[name].each { |cb| safe_yield(cb, payload) } if name
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
def message_emitter_subscriptions
|
|
62
|
+
@message_emitter_subscriptions ||= Hash.new { |hash, key| hash[key] = [] }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def message_emitter_subscriptions_message_name_key(name)
|
|
66
|
+
if name == :all
|
|
67
|
+
:all
|
|
68
|
+
else
|
|
69
|
+
message_emitter_subscriptions_coerce_message_key(name)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# this method can be overwritten easily to enforce use of set key types§
|
|
74
|
+
def message_emitter_subscriptions_coerce_message_key(name)
|
|
75
|
+
name.to_s
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
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(pk = nil)
|
|
11
|
+
as_json.to_msgpack(pk)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
def self.included(base)
|
|
12
|
+
base.extend(ClassMethods)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Provide a normal Hash accessor to the underlying raw message object
|
|
16
|
+
#
|
|
17
|
+
# @return [Object]
|
|
18
|
+
#
|
|
19
|
+
def [](key)
|
|
20
|
+
attributes[key]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def ==(other)
|
|
24
|
+
other.kind_of?(self.class) &&
|
|
25
|
+
attributes == other.attributes
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Return a JSON ready object from the underlying #attributes using Ably naming conventions for keys
|
|
29
|
+
#
|
|
30
|
+
# @return [Hash]
|
|
31
|
+
#
|
|
32
|
+
def as_json(*args)
|
|
33
|
+
attributes.as_json.reject { |key, val| val.nil? }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Stringify the JSON representation of this object from the underlying #attributes
|
|
37
|
+
#
|
|
38
|
+
# @return [String]
|
|
39
|
+
#
|
|
40
|
+
def to_json(*args)
|
|
41
|
+
as_json.to_json(*args)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @!attribute [r] hash
|
|
45
|
+
# @return [Integer] Compute a hash-code for this hash. Two hashes with the same content will have the same hash code
|
|
46
|
+
def hash
|
|
47
|
+
attributes.hash
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def to_s
|
|
51
|
+
representation = attributes.map do |key, val|
|
|
52
|
+
if val.nil?
|
|
53
|
+
nil
|
|
54
|
+
else
|
|
55
|
+
val_str = val.to_s
|
|
56
|
+
val_str = "#{val_str[0...80]}..." if val_str.length > 80
|
|
57
|
+
"#{key}=#{val_str}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
"<#{self.class.name}: #{representation.compact.join(', ')}>"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
module ClassMethods
|
|
64
|
+
# Return a new instance of this object using the provided JSON-like object or JSON string
|
|
65
|
+
# @param json_like_object [Hash, String] JSON-like object or JSON string
|
|
66
|
+
# @return a new instance to this object
|
|
67
|
+
def from_json(json_like_object)
|
|
68
|
+
if json_like_object.kind_of?(String)
|
|
69
|
+
new(JSON.parse(json_like_object))
|
|
70
|
+
else
|
|
71
|
+
new(json_like_object)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
def ensure_utf8_string_for(attribute, value)
|
|
78
|
+
if value
|
|
79
|
+
raise ArgumentError, "#{attribute} must be a String" unless value.kind_of?(String)
|
|
80
|
+
raise ArgumentError, "#{attribute} cannot use ASCII_8BIT encoding, please use UTF_8 encoding" unless value.encoding == Encoding::UTF_8
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require 'eventmachine'
|
|
2
|
+
|
|
3
|
+
module Ably::Modules
|
|
4
|
+
# SafeDeferrable module provides an EventMachine::Deferrable interface to the object it is included in
|
|
5
|
+
# and is safe to use for for public interfaces of this client library.
|
|
6
|
+
# Any exceptions raised in the success or failure callbacks are caught and logged to #logger
|
|
7
|
+
#
|
|
8
|
+
# An exception in a callback provided by a developer should not break this client library
|
|
9
|
+
# and stop further execution of code.
|
|
10
|
+
#
|
|
11
|
+
# @note this Module requires that the method #logger is available
|
|
12
|
+
#
|
|
13
|
+
# See http://www.rubydoc.info/gems/eventmachine/1.0.7/EventMachine/Deferrable
|
|
14
|
+
#
|
|
15
|
+
module SafeDeferrable
|
|
16
|
+
include EventMachine::Deferrable
|
|
17
|
+
|
|
18
|
+
# Specify a block to be executed if and when the Deferrable object receives
|
|
19
|
+
# a status of :succeeded.
|
|
20
|
+
# See http://www.rubydoc.info/gems/eventmachine/1.0.7/EventMachine/Deferrable#callback-instance_method
|
|
21
|
+
#
|
|
22
|
+
# @return [void]
|
|
23
|
+
#
|
|
24
|
+
def callback(&block)
|
|
25
|
+
super do |*args|
|
|
26
|
+
safe_deferrable_block(*args, &block)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Specify a block to be executed if and when the Deferrable object receives
|
|
31
|
+
# a status of :failed.
|
|
32
|
+
# See http://www.rubydoc.info/gems/eventmachine/1.0.7/EventMachine/Deferrable#errback-instance_method
|
|
33
|
+
#
|
|
34
|
+
# @return [void]
|
|
35
|
+
#
|
|
36
|
+
def errback(&block)
|
|
37
|
+
super do |*args|
|
|
38
|
+
safe_deferrable_block(*args, &block)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Mark the Deferrable as succeeded and trigger all success callbacks.
|
|
43
|
+
# See http://www.rubydoc.info/gems/eventmachine/1.0.7/EventMachine/Deferrable#succeed-instance_method
|
|
44
|
+
#
|
|
45
|
+
# @return [void]
|
|
46
|
+
#
|
|
47
|
+
def succeed(*args)
|
|
48
|
+
super(*args)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Mark the Deferrable as failed and trigger all error callbacks.
|
|
52
|
+
# See http://www.rubydoc.info/gems/eventmachine/1.0.7/EventMachine/Deferrable#fail-instance_method
|
|
53
|
+
#
|
|
54
|
+
# @return [void]
|
|
55
|
+
#
|
|
56
|
+
def fail(*args)
|
|
57
|
+
super(*args)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
def safe_deferrable_block(*args)
|
|
62
|
+
yield(*args)
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
message = "An exception in a Deferrable callback was caught. #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
|
|
65
|
+
if defined?(:logger) && logger.respond_to?(:error)
|
|
66
|
+
logger.error message
|
|
67
|
+
else
|
|
68
|
+
fallback_logger.error message
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def fallback_logger
|
|
73
|
+
@fallback_logger ||= ::Logger.new(STDOUT).tap do |logger|
|
|
74
|
+
logger.formatter = lambda do |severity, datetime, progname, msg|
|
|
75
|
+
[
|
|
76
|
+
"#{datetime.strftime("%Y-%m-%d %H:%M:%S.%L")} #{::Logger::SEV_LABEL[severity]} #{msg}",
|
|
77
|
+
"Warning: SafeDeferrable expects the method #logger to be defined in the class it is included in, the method was not found in #{self.class}"
|
|
78
|
+
].join("\n")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Ably::Modules
|
|
2
|
+
# SafeYield provides the method safe_yield that will yield to the consumer
|
|
3
|
+
# who provided a block, however any exceptions will be caught, logged, and
|
|
4
|
+
# operation of the client library will continue.
|
|
5
|
+
#
|
|
6
|
+
# An exception in a callback provided by a developer should not break this client library
|
|
7
|
+
# and stop further execution of code.
|
|
8
|
+
#
|
|
9
|
+
# @note this Module requires that the method #logger is available
|
|
10
|
+
#
|
|
11
|
+
# @api private
|
|
12
|
+
module SafeYield
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def safe_yield(block, *args)
|
|
16
|
+
block.call(*args)
|
|
17
|
+
rescue StandardError => e
|
|
18
|
+
message = "An exception in an external block was caught. #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
|
|
19
|
+
safe_yield_log_error message
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def safe_yield_log_error(message)
|
|
23
|
+
if defined?(:logger) && logger.respond_to?(:error)
|
|
24
|
+
return logger.error message
|
|
25
|
+
end
|
|
26
|
+
rescue StandardError
|
|
27
|
+
fallback_logger.error message
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def fallback_logger
|
|
31
|
+
@fallback_logger ||= ::Logger.new(STDOUT).tap do |logger|
|
|
32
|
+
logger.formatter = lambda do |severity, datetime, progname, msg|
|
|
33
|
+
[
|
|
34
|
+
"#{datetime.strftime("%Y-%m-%d %H:%M:%S.%L")} #{::Logger::SEV_LABEL[severity]} #{msg}",
|
|
35
|
+
"Warning: SafeYield expects the method #logger to be defined in the class it is included in, the method was not found in #{self.class}"
|
|
36
|
+
].join("\n")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,181 @@
|
|
|
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 restrict permitted events to the
|
|
7
|
+
# the available STATEs or EVENTs if defined i.e. if EVENTs includes an additional type such as
|
|
8
|
+
# :update, then it will support all EVENTs being emitted. EVENTs must be a superset of STATEs
|
|
9
|
+
#
|
|
10
|
+
# @note This module requires that the method #logger is defined.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# class Connection
|
|
14
|
+
# include Ably::Modules::EventEmitter
|
|
15
|
+
# extend Ably::Modules::Enum
|
|
16
|
+
# STATE = ruby_enum('STATE',
|
|
17
|
+
# :initialized,
|
|
18
|
+
# :connecting,
|
|
19
|
+
# :connected
|
|
20
|
+
# )
|
|
21
|
+
# include Ably::Modules::StateEmitter
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# connection = Connection.new
|
|
25
|
+
# connection.state = :connecting # emits :connecting event via EventEmitter, returns STATE.Connecting
|
|
26
|
+
# connection.state?(:connected) # => false
|
|
27
|
+
# connection.connecting? # => true
|
|
28
|
+
# connection.state # => STATE.Connecting
|
|
29
|
+
# connection.state = :invalid # raises an Exception as only a valid state can be defined
|
|
30
|
+
# connection.emit :invalid # raises an Exception as only a valid state can be used for EventEmitter
|
|
31
|
+
# connection.change_state :connected # emits :connected event via EventEmitter, returns STATE.Connected
|
|
32
|
+
# connection.once_or_if(:connected) { puts 'block called once when state is connected or becomes connected' }
|
|
33
|
+
#
|
|
34
|
+
module StateEmitter
|
|
35
|
+
# Current state {Ably::Modules::Enum}
|
|
36
|
+
#
|
|
37
|
+
# @return [Symbol] state
|
|
38
|
+
#
|
|
39
|
+
def state
|
|
40
|
+
STATE(@state)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Evaluates if check_state matches current state
|
|
44
|
+
#
|
|
45
|
+
# @return [Boolean]
|
|
46
|
+
#
|
|
47
|
+
def state?(check_state)
|
|
48
|
+
state == check_state
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Set the current state {Ably::Modules::Enum}
|
|
52
|
+
#
|
|
53
|
+
# @return [Symbol] new state
|
|
54
|
+
# @api private
|
|
55
|
+
def state=(new_state, *args)
|
|
56
|
+
if state != new_state
|
|
57
|
+
logger.debug { "#{self.class}: StateEmitter changed from #{state} => #{new_state}" } if respond_to?(:logger, true)
|
|
58
|
+
@state = STATE(new_state)
|
|
59
|
+
emit @state, *args
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
alias_method :change_state, :state=
|
|
63
|
+
|
|
64
|
+
# If the current state matches the target_state argument the block is called immediately.
|
|
65
|
+
# Else the block is called once when the target_state is reached.
|
|
66
|
+
#
|
|
67
|
+
# If the option block :else is provided then if any state other than target_state is reached, the :else block is called,
|
|
68
|
+
# however only one of the blocks will ever be called
|
|
69
|
+
#
|
|
70
|
+
# @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
|
|
71
|
+
# @param [Hash] options
|
|
72
|
+
# @option options [Proc] :else block called once the state has changed to anything but target_state
|
|
73
|
+
#
|
|
74
|
+
# @yield block is called if the state is matched immediately or once when the state is reached
|
|
75
|
+
#
|
|
76
|
+
# @return [void]
|
|
77
|
+
def once_or_if(target_states, options = {}, &block)
|
|
78
|
+
raise ArgumentError, 'Block required' unless block_given?
|
|
79
|
+
|
|
80
|
+
if Array(target_states).any? { |target_state| state == target_state }
|
|
81
|
+
safe_yield block
|
|
82
|
+
else
|
|
83
|
+
failure_block = options.fetch(:else, nil)
|
|
84
|
+
failure_wrapper = nil
|
|
85
|
+
|
|
86
|
+
success_wrapper = lambda do |*args|
|
|
87
|
+
yield
|
|
88
|
+
off(&success_wrapper)
|
|
89
|
+
off(&failure_wrapper) if failure_wrapper
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
failure_wrapper = lambda do |*args|
|
|
93
|
+
failure_block.call(*args)
|
|
94
|
+
off(&success_wrapper)
|
|
95
|
+
off(&failure_wrapper)
|
|
96
|
+
end if failure_block
|
|
97
|
+
|
|
98
|
+
Array(target_states).each do |target_state|
|
|
99
|
+
safe_unsafe_method options[:unsafe], :once, target_state, &success_wrapper
|
|
100
|
+
|
|
101
|
+
safe_unsafe_method options[:unsafe], :once_state_changed do |*args|
|
|
102
|
+
failure_wrapper.call(*args) unless state == target_state
|
|
103
|
+
end if failure_block
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Equivalent of {#once_or_if} but any exception raised in a block will bubble up and cause this client library to fail.
|
|
109
|
+
# This method should only be used internally by the client library.
|
|
110
|
+
# @api private
|
|
111
|
+
def unsafe_once_or_if(target_states, options = {}, &block)
|
|
112
|
+
once_or_if(target_states, options.merge(unsafe: true), &block)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Calls the block once when the state changes
|
|
116
|
+
#
|
|
117
|
+
# @yield block is called once the state changes
|
|
118
|
+
# @return [void]
|
|
119
|
+
#
|
|
120
|
+
# @api private
|
|
121
|
+
def once_state_changed(options = {}, &block)
|
|
122
|
+
raise ArgumentError, 'Block required' unless block_given?
|
|
123
|
+
|
|
124
|
+
once_block = lambda do |*args|
|
|
125
|
+
off(*self.class::STATE.map, &once_block)
|
|
126
|
+
yield(*args)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
safe_unsafe_method options[:unsafe], :once, *self.class::STATE.map, &once_block
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Equivalent of {#once_state_changed} but any exception raised in a block will bubble up and cause this client library to fail.
|
|
133
|
+
# This method should only be used internally by the client library.
|
|
134
|
+
# @api private
|
|
135
|
+
def unsafe_once_state_changed(&block)
|
|
136
|
+
once_state_changed(unsafe: true, &block)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
# Returns an {Ably::Util::SafeDeferrable} and once the target state is reached, the
|
|
142
|
+
# success block if provided and {Ably::Util::SafeDeferrable#callback} is called.
|
|
143
|
+
# If the state changes to any other state, the {Ably::Util::SafeDeferrable#errback} is called.
|
|
144
|
+
#
|
|
145
|
+
def deferrable_for_state_change_to(target_state)
|
|
146
|
+
Ably::Util::SafeDeferrable.new(logger).tap do |deferrable|
|
|
147
|
+
fail_proc = lambda do |state_change|
|
|
148
|
+
deferrable.fail state_change.reason
|
|
149
|
+
end
|
|
150
|
+
once_or_if(target_state, else: fail_proc) do
|
|
151
|
+
yield self if block_given?
|
|
152
|
+
deferrable.succeed self
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def self.included(klass)
|
|
158
|
+
klass.configure_event_emitter coerce_into: lambda { |event|
|
|
159
|
+
# Special case allows EVENT instead of STATE to be emitted
|
|
160
|
+
# Relies on the assumption that EVENT is a superset of STATE
|
|
161
|
+
if klass.const_defined?(:EVENT)
|
|
162
|
+
klass::EVENT(event)
|
|
163
|
+
else
|
|
164
|
+
klass::STATE(event)
|
|
165
|
+
end
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
klass::STATE.each do |state_predicate|
|
|
169
|
+
klass.instance_eval do
|
|
170
|
+
define_method("#{state_predicate.to_sym}?") do
|
|
171
|
+
state?(state_predicate)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def safe_unsafe_method(unsafe, method_name, *args, &block)
|
|
178
|
+
public_send("#{'unsafe_' if unsafe}#{method_name}", *args, &block)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|