ably 0.1.5 → 0.1.6
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 +4 -4
- data/README.md +11 -1
- data/ably.gemspec +4 -3
- data/lib/ably.rb +6 -2
- data/lib/ably/auth.rb +24 -16
- data/lib/ably/exceptions.rb +16 -5
- data/lib/ably/{realtime/models → models}/error_info.rb +9 -11
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +57 -26
- data/lib/ably/{realtime/models → models}/message.rb +45 -38
- data/lib/ably/{realtime/models → models}/nil_channel.rb +4 -4
- data/lib/ably/{rest/models/paged_resource.rb → models/paginated_resource.rb} +21 -10
- data/lib/ably/models/presence_message.rb +126 -0
- data/lib/ably/{realtime/models → models}/protocol_message.rb +76 -38
- data/lib/ably/models/token.rb +74 -0
- data/lib/ably/modules/channels_collection.rb +49 -0
- data/lib/ably/modules/conversions.rb +2 -0
- data/lib/ably/modules/event_emitter.rb +43 -8
- data/lib/ably/modules/event_machine_helpers.rb +1 -0
- data/lib/ably/modules/http_helpers.rb +9 -2
- data/lib/ably/modules/message_pack.rb +14 -0
- data/lib/ably/modules/model_common.rb +29 -0
- data/lib/ably/modules/{state.rb → state_emitter.rb} +8 -7
- data/lib/ably/realtime.rb +37 -7
- data/lib/ably/realtime/channel.rb +154 -31
- data/lib/ably/realtime/channels.rb +47 -0
- data/lib/ably/realtime/client.rb +39 -33
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +50 -21
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +9 -11
- data/lib/ably/realtime/connection.rb +148 -79
- data/lib/ably/realtime/connection/connection_state_machine.rb +111 -0
- data/lib/ably/realtime/connection/websocket_transport.rb +161 -0
- data/lib/ably/realtime/presence.rb +270 -0
- data/lib/ably/rest.rb +14 -3
- data/lib/ably/rest/channel.rb +3 -3
- data/lib/ably/rest/channels.rb +26 -12
- data/lib/ably/rest/client.rb +42 -25
- data/lib/ably/rest/middleware/exceptions.rb +21 -23
- data/lib/ably/rest/middleware/external_exceptions.rb +8 -10
- data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
- data/lib/ably/rest/middleware/parse_json.rb +9 -2
- data/lib/ably/rest/middleware/parse_message_pack.rb +6 -2
- data/lib/ably/rest/presence.rb +4 -4
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/channel_history_spec.rb +125 -0
- data/spec/acceptance/realtime/channel_spec.rb +135 -63
- data/spec/acceptance/realtime/connection_spec.rb +86 -0
- data/spec/acceptance/realtime/message_spec.rb +116 -94
- data/spec/acceptance/realtime/presence_history_spec.rb +0 -0
- data/spec/acceptance/realtime/presence_spec.rb +277 -0
- data/spec/acceptance/rest/auth_spec.rb +351 -347
- data/spec/acceptance/rest/base_spec.rb +43 -26
- data/spec/acceptance/rest/channel_spec.rb +88 -83
- data/spec/acceptance/rest/channels_spec.rb +32 -28
- data/spec/acceptance/rest/presence_spec.rb +83 -63
- data/spec/acceptance/rest/stats_spec.rb +38 -37
- data/spec/acceptance/rest/time_spec.rb +10 -6
- data/spec/integration/modules/{state_spec.rb → state_emitter_spec.rb} +16 -2
- data/spec/spec_helper.rb +14 -0
- data/spec/support/api_helper.rb +4 -0
- data/spec/support/model_helper.rb +28 -9
- data/spec/support/protocol_msgbus_helper.rb +8 -1
- data/spec/support/test_app.rb +24 -14
- data/spec/unit/{realtime → models}/error_info_spec.rb +4 -4
- data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +46 -9
- data/spec/unit/models/message_spec.rb +229 -0
- data/spec/unit/{rest/paged_resource_spec.rb → models/paginated_resource_spec.rb} +19 -11
- data/spec/unit/models/presence_message_spec.rb +230 -0
- data/spec/unit/models/protocol_message_spec.rb +280 -0
- data/spec/unit/{token_spec.rb → models/token_spec.rb} +18 -22
- data/spec/unit/modules/conversions_spec.rb +1 -1
- data/spec/unit/modules/event_emitter_spec.rb +36 -4
- data/spec/unit/realtime/channel_spec.rb +76 -2
- data/spec/unit/realtime/channels_spec.rb +50 -0
- data/spec/unit/realtime/client_spec.rb +31 -1
- data/spec/unit/realtime/connection_spec.rb +8 -15
- data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +6 -6
- data/spec/unit/realtime/presence_spec.rb +100 -0
- data/spec/unit/rest/channels_spec.rb +48 -0
- metadata +72 -38
- data/lib/ably/realtime/models/shared.rb +0 -17
- data/lib/ably/rest/models/message.rb +0 -64
- data/lib/ably/rest/models/presence_message.rb +0 -21
- data/lib/ably/token.rb +0 -80
- data/spec/unit/realtime/message_spec.rb +0 -117
- data/spec/unit/realtime/protocol_message_spec.rb +0 -172
- data/spec/unit/rest/message_spec.rb +0 -75
@@ -0,0 +1,47 @@
|
|
1
|
+
module Ably
|
2
|
+
module Realtime
|
3
|
+
# Class that maintains a map of Channels ensuring Channels are reused
|
4
|
+
class Channels
|
5
|
+
include Ably::Modules::ChannelsCollection
|
6
|
+
|
7
|
+
# @return [Ably::Realtime::Channels]
|
8
|
+
def initialize(client)
|
9
|
+
super client, Ably::Realtime::Channel
|
10
|
+
end
|
11
|
+
|
12
|
+
# @!method get(name, channel_options = {})
|
13
|
+
# Return a {Ably::Realtime::Channel} for the given name
|
14
|
+
#
|
15
|
+
# @param name [String] The name of the channel
|
16
|
+
# @param channel_options [Hash] Channel options, currently reserved for Encryption options
|
17
|
+
# @return [Ably::Realtime::Channel}
|
18
|
+
def get(*args)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
# @!method fetch(name, &missing_block)
|
23
|
+
# Return a {Ably::Realtime::Channel} for the given name if it exists, else the block will be called.
|
24
|
+
# This method is intentionally similar to {http://ruby-doc.org/core-2.1.3/Hash.html#method-i-fetch Hash#fetch} providing a simple way to check if a channel exists or not without creating one
|
25
|
+
#
|
26
|
+
# @param name [String] The name of the channel
|
27
|
+
# @yield [options] (optional) if a missing_block is passed to this method and no channel exists matching the name, this block is called
|
28
|
+
# @yieldparam [String] name of the missing channel
|
29
|
+
# @return [Ably::Realtime::Channel]
|
30
|
+
def fetch(*args)
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
# Detaches the {Ably::Realtime::Channel Realtime Channel} and releases all associated resources.
|
35
|
+
#
|
36
|
+
# Releasing a Realtime Channel is not typically necessary as a channel, once detached, consumes no resources other than
|
37
|
+
# the memory footprint of the {Ably::Realtime::Channel Realtime Channel object}. Release channels to free up resources if required
|
38
|
+
#
|
39
|
+
# @return [void]
|
40
|
+
def release(channel)
|
41
|
+
get(channel).detach do
|
42
|
+
@channels.delete(channel)
|
43
|
+
end if @channels.has_key?(channel)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/ably/realtime/client.rb
CHANGED
@@ -8,18 +8,24 @@ module Ably
|
|
8
8
|
# (see Ably::Rest::Client#client_id)
|
9
9
|
# @!attribute [r] auth_options
|
10
10
|
# (see Ably::Rest::Client#auth_options)
|
11
|
-
# @!attribute [r] tls
|
12
|
-
# (see Ably::Rest::Client#tls)
|
13
11
|
# @!attribute [r] environment
|
14
12
|
# (see Ably::Rest::Client#environment)
|
13
|
+
# @!attribute [r] channels
|
14
|
+
# @return [Aby::Realtime::Channels] The collection of {Ably::Realtime::Channel}s that have been created
|
15
|
+
# @!attribute [r] rest_client
|
16
|
+
# @return [Ably::Rest::Client] The {Ably::Rest::Client REST client} instantiated with the same credentials and configuration that is used for all REST operations such as authentication
|
17
|
+
# @!attribute [r] echo_messages
|
18
|
+
# @return [Boolean] If false, suppresses messages originating from this connection being echoed back on the same connection. Defaults to true
|
15
19
|
class Client
|
16
20
|
extend Forwardable
|
17
21
|
|
18
22
|
DOMAIN = 'realtime.ably.io'
|
19
23
|
|
20
|
-
attr_reader :channels, :auth
|
24
|
+
attr_reader :channels, :auth, :rest_client, :echo_messages
|
21
25
|
def_delegators :auth, :client_id, :auth_options
|
22
|
-
def_delegators :@rest_client, :
|
26
|
+
def_delegators :@rest_client, :environment, :use_tls?, :protocol
|
27
|
+
def_delegators :@rest_client, :logger, :log_level
|
28
|
+
def_delegators :@rest_client, :time, :stats
|
23
29
|
|
24
30
|
# Creates a {Ably::Realtime::Client Realtime Client} and configures the {Ably::Auth} object for the connection.
|
25
31
|
#
|
@@ -42,28 +48,39 @@ module Ably
|
|
42
48
|
# # create a new client and configure a client ID used for presence
|
43
49
|
# client = Ably::Realtime::Client.new(api_key: 'key.id:secret', client_id: 'john')
|
44
50
|
#
|
45
|
-
def initialize(options)
|
46
|
-
@rest_client = Ably::Rest::Client.new(options)
|
51
|
+
def initialize(options, &auth_block)
|
52
|
+
@rest_client = Ably::Rest::Client.new(options, &auth_block)
|
47
53
|
@auth = @rest_client.auth
|
48
|
-
@
|
54
|
+
@channels = Ably::Realtime::Channels.new(self)
|
55
|
+
@echo_messages = @rest_client.options.fetch(:echo_messages, true) == false ? false : true
|
49
56
|
end
|
50
57
|
|
51
|
-
|
52
|
-
|
58
|
+
# Return a {Ably::Realtime::Channel Realtime Channel} for the given name
|
59
|
+
#
|
60
|
+
# @param (see Ably::Realtime::Channels#get)
|
61
|
+
#
|
62
|
+
# @return (see Ably::Realtime::Channels#get)
|
63
|
+
def channel(name, channel_options = {})
|
64
|
+
channels.get(name, channel_options)
|
53
65
|
end
|
54
66
|
|
55
|
-
#
|
56
|
-
|
57
|
-
|
58
|
-
# @return [Ably::Realtime::Channel]
|
59
|
-
def channel(name)
|
60
|
-
@channels ||= {}
|
61
|
-
@channels[name] ||= Ably::Realtime::Channel.new(self, name)
|
67
|
+
# (see Ably::Rest::Client#time)
|
68
|
+
def time
|
69
|
+
rest_client.time
|
62
70
|
end
|
63
71
|
|
64
|
-
#
|
65
|
-
|
66
|
-
|
72
|
+
# (see Ably::Rest::Client#stats)
|
73
|
+
def stats(params = {})
|
74
|
+
rest_client.stats(params)
|
75
|
+
end
|
76
|
+
|
77
|
+
# (see Ably::Realtime::Connection#close)
|
78
|
+
def close(&block)
|
79
|
+
connection.close(&block)
|
80
|
+
end
|
81
|
+
|
82
|
+
# @!attribute [r] endpoint
|
83
|
+
# @return [URI::Generic] Default Ably Realtime endpoint used for all requests
|
67
84
|
def endpoint
|
68
85
|
URI::Generic.build(
|
69
86
|
scheme: use_tls? ? "wss" : "ws",
|
@@ -71,22 +88,11 @@ module Ably
|
|
71
88
|
)
|
72
89
|
end
|
73
90
|
|
91
|
+
# @!attribute [r] connection
|
92
|
+
# @return [Aby::Realtime::Connection] The underlying connection for this client
|
74
93
|
def connection
|
75
|
-
@connection ||=
|
76
|
-
host = endpoint.host
|
77
|
-
port = use_tls? ? 443 : 80
|
78
|
-
|
79
|
-
EventMachine.connect(host, port, Connection, self).tap do |connection|
|
80
|
-
connection.on(:connected) do
|
81
|
-
IncomingMessageDispatcher.new(self)
|
82
|
-
OutgoingMessageDispatcher.new(self)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
94
|
+
@connection ||= Connection.new(self)
|
86
95
|
end
|
87
|
-
|
88
|
-
private
|
89
|
-
attr_reader :rest_client
|
90
96
|
end
|
91
97
|
end
|
92
98
|
end
|
@@ -1,21 +1,18 @@
|
|
1
1
|
module Ably::Realtime
|
2
2
|
class Client
|
3
|
-
# IncomingMessageDispatcher is a (private) class that is used to dispatch {Ably::
|
3
|
+
# IncomingMessageDispatcher is a (private) class that is used to dispatch {Ably::Models::ProtocolMessage} that are
|
4
4
|
# received from Ably via the {Ably::Realtime::Connection}
|
5
5
|
class IncomingMessageDispatcher
|
6
|
-
ACTION = Models::ProtocolMessage::ACTION
|
6
|
+
ACTION = Ably::Models::ProtocolMessage::ACTION
|
7
7
|
|
8
|
-
def initialize(client)
|
9
|
-
@client
|
8
|
+
def initialize(client, connection)
|
9
|
+
@client = client
|
10
|
+
@connection = connection
|
10
11
|
subscribe_to_incoming_protocol_messages
|
11
12
|
end
|
12
13
|
|
13
14
|
private
|
14
|
-
attr_reader :client
|
15
|
-
|
16
|
-
def connection
|
17
|
-
client.connection
|
18
|
-
end
|
15
|
+
attr_reader :client, :connection
|
19
16
|
|
20
17
|
def channels
|
21
18
|
client.channels
|
@@ -24,7 +21,7 @@ module Ably::Realtime
|
|
24
21
|
def get_channel(channel_name)
|
25
22
|
channels.fetch(channel_name) do
|
26
23
|
logger.warn "Received channel message for non-existent channel"
|
27
|
-
Models::NilChannel.new
|
24
|
+
Ably::Models::NilChannel.new
|
28
25
|
end
|
29
26
|
end
|
30
27
|
|
@@ -35,7 +32,7 @@ module Ably::Realtime
|
|
35
32
|
def dispatch_protocol_message(*args)
|
36
33
|
protocol_message = args.first
|
37
34
|
|
38
|
-
unless protocol_message.kind_of?(Models::ProtocolMessage)
|
35
|
+
unless protocol_message.kind_of?(Ably::Models::ProtocolMessage)
|
39
36
|
raise ArgumentError, "Expected a ProtocolMessage. Received #{protocol_message}"
|
40
37
|
end
|
41
38
|
|
@@ -43,6 +40,8 @@ module Ably::Realtime
|
|
43
40
|
logger.debug "#{protocol_message.action} received: #{protocol_message}"
|
44
41
|
end
|
45
42
|
|
43
|
+
update_connection_id protocol_message
|
44
|
+
|
46
45
|
case protocol_message.action
|
47
46
|
when ACTION.Heartbeat
|
48
47
|
when ACTION.Ack
|
@@ -52,12 +51,21 @@ module Ably::Realtime
|
|
52
51
|
logger.warn "NACK received: #{protocol_message}"
|
53
52
|
nack_pending_queue_for_message_serial(protocol_message) if protocol_message.has_message_serial?
|
54
53
|
|
55
|
-
when ACTION.Connect
|
54
|
+
when ACTION.Connect
|
55
|
+
when ACTION.Connected
|
56
|
+
connection.transition_state_machine :connected
|
57
|
+
|
56
58
|
when ACTION.Disconnect, ACTION.Disconnected
|
59
|
+
|
57
60
|
when ACTION.Close
|
58
61
|
when ACTION.Closed
|
62
|
+
connection.transition_state_machine :closed
|
63
|
+
|
59
64
|
when ACTION.Error
|
60
65
|
logger.error "Error received: #{protocol_message.error}"
|
66
|
+
if protocol_message.channel && !protocol_message.has_message_serial?
|
67
|
+
get_channel(protocol_message.channel).change_state Ably::Realtime::Channel::STATE.Failed, protocol_message.error
|
68
|
+
end
|
61
69
|
|
62
70
|
when ACTION.Attach
|
63
71
|
when ACTION.Attached
|
@@ -68,9 +76,13 @@ module Ably::Realtime
|
|
68
76
|
get_channel(protocol_message.channel).change_state Ably::Realtime::Channel::STATE.Detached
|
69
77
|
|
70
78
|
when ACTION.Presence
|
79
|
+
protocol_message.presence.each do |presence|
|
80
|
+
get_channel(protocol_message.channel).presence.__incoming_msgbus__.publish :presence, presence
|
81
|
+
end
|
82
|
+
|
71
83
|
when ACTION.Message
|
72
84
|
protocol_message.messages.each do |message|
|
73
|
-
get_channel(protocol_message.channel).
|
85
|
+
get_channel(protocol_message.channel).__incoming_msgbus__.publish :message, message
|
74
86
|
end
|
75
87
|
|
76
88
|
else
|
@@ -78,21 +90,38 @@ module Ably::Realtime
|
|
78
90
|
end
|
79
91
|
end
|
80
92
|
|
93
|
+
def update_connection_id(protocol_message)
|
94
|
+
if protocol_message.connection_id && (protocol_message.connection_id != connection.id)
|
95
|
+
logger.debug "New connection ID set to #{protocol_message.connection_id}"
|
96
|
+
connection.update_connection_id protocol_message.connection_id
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
81
100
|
def ack_pending_queue_for_message_serial(ack_protocol_message)
|
82
101
|
drop_pending_queue_from_ack(ack_protocol_message) do |protocol_message|
|
83
|
-
protocol_message.messages
|
84
|
-
|
85
|
-
message.succeed message
|
86
|
-
end
|
102
|
+
ack_messages protocol_message.messages
|
103
|
+
ack_messages protocol_message.presence
|
87
104
|
end
|
88
105
|
end
|
89
106
|
|
90
107
|
def nack_pending_queue_for_message_serial(nack_protocol_message)
|
91
108
|
drop_pending_queue_from_ack(nack_protocol_message) do |protocol_message|
|
92
|
-
protocol_message.messages
|
93
|
-
|
94
|
-
|
95
|
-
|
109
|
+
nack_messages protocol_message.messages, nack_protocol_message
|
110
|
+
nack_messages protocol_message.presence, nack_protocol_message
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def ack_messages(messages)
|
115
|
+
messages.each do |message|
|
116
|
+
logger.debug "Calling ACK success callbacks for #{message.class.name} - #{message.to_json}"
|
117
|
+
message.succeed message
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def nack_messages(messages, protocol_message)
|
122
|
+
messages.each do |message|
|
123
|
+
logger.debug "Calling NACK failure callbacks for #{message.class.name} - #{message.to_json}, protocol message: #{protocol_message}"
|
124
|
+
message.fail message, protocol_message.error
|
96
125
|
end
|
97
126
|
end
|
98
127
|
|
@@ -1,25 +1,22 @@
|
|
1
1
|
module Ably::Realtime
|
2
2
|
class Client
|
3
3
|
# OutgoingMessageDispatcher is a (private) class that is used to deliver
|
4
|
-
# outgoing {Ably::
|
4
|
+
# outgoing {Ably::Models::ProtocolMessage}s using the {Ably::Realtime::Connection}
|
5
5
|
# when the connection state is capable of delivering messages
|
6
6
|
class OutgoingMessageDispatcher
|
7
7
|
include Ably::Modules::EventMachineHelpers
|
8
8
|
|
9
|
-
ACTION = Models::ProtocolMessage::ACTION
|
9
|
+
ACTION = Ably::Models::ProtocolMessage::ACTION
|
10
10
|
|
11
|
-
def initialize(client)
|
12
|
-
@client
|
11
|
+
def initialize(client, connection)
|
12
|
+
@client = client
|
13
|
+
@connection = connection
|
13
14
|
subscribe_to_outgoing_protocol_message_queue
|
14
15
|
setup_event_handlers
|
15
16
|
end
|
16
17
|
|
17
18
|
private
|
18
|
-
attr_reader :client
|
19
|
-
|
20
|
-
def connection
|
21
|
-
client.connection
|
22
|
-
end
|
19
|
+
attr_reader :client, :connection
|
23
20
|
|
24
21
|
def can_send_messages?
|
25
22
|
connection.connected?
|
@@ -39,11 +36,12 @@ module Ably::Realtime
|
|
39
36
|
|
40
37
|
def deliver_queued_protocol_messages
|
41
38
|
condition = -> { can_send_messages? && messages_in_outgoing_queue? }
|
39
|
+
|
42
40
|
non_blocking_loop_while(condition) do
|
43
41
|
protocol_message = outgoing_queue.shift
|
44
42
|
pending_queue << protocol_message if protocol_message.ack_required?
|
45
|
-
connection.
|
46
|
-
client.logger.debug
|
43
|
+
connection.transport.send_object protocol_message
|
44
|
+
client.logger.debug "Prot msg sent =>: #{protocol_message.action} #{protocol_message}"
|
47
45
|
end
|
48
46
|
end
|
49
47
|
|
@@ -25,18 +25,16 @@ module Ably
|
|
25
25
|
#
|
26
26
|
# @!attribute [r] state
|
27
27
|
# @return {Ably::Realtime::Connection::STATE} connection state
|
28
|
-
# @!attribute [r]
|
29
|
-
# @return
|
30
|
-
# @!attribute [r]
|
31
|
-
# @return
|
32
|
-
|
33
|
-
class Connection < EventMachine::Connection
|
34
|
-
include Ably::Modules::Conversions
|
28
|
+
# @!attribute [r] id
|
29
|
+
# @return {String} the assigned connection ID
|
30
|
+
# @!attribute [r] error_reason
|
31
|
+
# @return {Ably::Models::ErrorInfo} error information associated with a connection failure
|
32
|
+
class Connection
|
35
33
|
include Ably::Modules::EventEmitter
|
36
34
|
extend Ably::Modules::Enum
|
37
35
|
|
36
|
+
# Valid Connection states
|
38
37
|
STATE = ruby_enum('STATE',
|
39
|
-
:initializing,
|
40
38
|
:initialized,
|
41
39
|
:connecting,
|
42
40
|
:connected,
|
@@ -45,83 +43,178 @@ module Ably
|
|
45
43
|
:closed,
|
46
44
|
:failed
|
47
45
|
)
|
48
|
-
include Ably::Modules::
|
46
|
+
include Ably::Modules::StateEmitter
|
47
|
+
|
48
|
+
attr_reader :id, :error_reason, :client
|
49
|
+
|
50
|
+
# @api private
|
51
|
+
# Underlying socket transport used for this connection, for internal use by the client library
|
52
|
+
# @return {Ably::Realtime::Connection::WebsocketTransport}
|
53
|
+
attr_reader :transport
|
54
|
+
|
55
|
+
# @api private
|
56
|
+
# An internal queue used to manage unsent outgoing messages. You should never interface with this array directly
|
57
|
+
# @return [Array]
|
58
|
+
attr_reader :__outgoing_message_queue__
|
49
59
|
|
50
|
-
|
60
|
+
# @api private
|
61
|
+
# An internal queue used to manage sent messages. You should never interface with this array directly
|
62
|
+
# @return [Array]
|
63
|
+
attr_reader :__pending_message_queue__
|
51
64
|
|
65
|
+
# @api private
|
66
|
+
# Timers used to manage connection state, for internal use by the client library
|
67
|
+
# @return [Hash]
|
68
|
+
attr_reader :timers
|
69
|
+
|
70
|
+
# @api public
|
52
71
|
def initialize(client)
|
53
72
|
@client = client
|
54
|
-
|
73
|
+
|
74
|
+
@serial = -1
|
55
75
|
@__outgoing_message_queue__ = []
|
56
76
|
@__pending_message_queue__ = []
|
57
|
-
@state = STATE.Initializing
|
58
|
-
end
|
59
77
|
|
60
|
-
|
61
|
-
|
78
|
+
@timers = Hash.new { |hash, key| hash[key] = [] }
|
79
|
+
@timers[:initializer] << EventMachine::Timer.new(0.001) { connect }
|
62
80
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
__outgoing_message_queue__ << protocol_message
|
69
|
-
logger.debug("Prot msg queued =>: #{protocol_message.action} #{protocol_message}")
|
70
|
-
__outgoing_protocol_msgbus__.publish :message, protocol_message
|
81
|
+
Client::IncomingMessageDispatcher.new client, self
|
82
|
+
Client::OutgoingMessageDispatcher.new client, self
|
83
|
+
|
84
|
+
EventMachine.next_tick do
|
85
|
+
trigger STATE.Initialized
|
71
86
|
end
|
87
|
+
|
88
|
+
@state_machine = ConnectionStateMachine.new(self)
|
89
|
+
@state = STATE(state_machine.current_state)
|
72
90
|
end
|
73
91
|
|
74
|
-
#
|
75
|
-
|
76
|
-
|
92
|
+
# Causes the connection to close, entering the closed state, from any state except
|
93
|
+
# the failed state. Once closed, the library will not attempt to re-establish the
|
94
|
+
# connection without a call to {Connection#connect}.
|
95
|
+
#
|
96
|
+
# @yield [Ably::Realtime::Connection] block is called as soon as this connection is in the Closed state
|
97
|
+
#
|
98
|
+
# @return <void>
|
99
|
+
def close(&block)
|
100
|
+
if closed?
|
101
|
+
block.call self
|
102
|
+
else
|
103
|
+
EventMachine.next_tick do
|
104
|
+
state_machine.transition_to(:closed)
|
105
|
+
end
|
106
|
+
once(STATE.Closed) { block.call self } if block_given?
|
107
|
+
end
|
108
|
+
end
|
77
109
|
|
78
|
-
|
110
|
+
# Causes the library to re-attempt connection, if it was previously explicitly
|
111
|
+
# closed by the user, or was closed as a result of an unrecoverable error.
|
112
|
+
#
|
113
|
+
# @yield [Ably::Realtime::Connection] block is called as soon as this connection is in the Connected state
|
114
|
+
#
|
115
|
+
# @return <void>
|
116
|
+
def connect(&block)
|
117
|
+
if connected?
|
118
|
+
block.call self
|
119
|
+
else
|
120
|
+
state_machine.transition_to(:connecting)
|
121
|
+
once(STATE.Connected) { block.call self } if block_given?
|
122
|
+
end
|
79
123
|
end
|
80
124
|
|
81
|
-
|
82
|
-
|
125
|
+
# Reconfigure the current connection ID
|
126
|
+
# @return <void>
|
127
|
+
# @api private
|
128
|
+
def update_connection_id(connection_id)
|
129
|
+
@id = connection_id
|
130
|
+
end
|
83
131
|
|
84
|
-
|
85
|
-
|
132
|
+
# Send #transition_to to connection state machine
|
133
|
+
# @return [Boolean] true if new_state can be transitioned_to by state machine
|
134
|
+
# @api private
|
135
|
+
def transition_state_machine(new_state)
|
136
|
+
state_machine.transition_to(new_state)
|
86
137
|
end
|
87
138
|
|
88
|
-
|
89
|
-
|
139
|
+
# @!attribute [r] __outgoing_protocol_msgbus__
|
140
|
+
# @return [Ably::Util::PubSub] Client library internal outgoing message bus
|
141
|
+
# @api private
|
142
|
+
def __outgoing_protocol_msgbus__
|
143
|
+
@__outgoing_protocol_msgbus__ ||= create_pub_sub_message_bus
|
90
144
|
end
|
91
145
|
|
92
|
-
|
93
|
-
|
146
|
+
# @!attribute [r] __incoming_protocol_msgbus__
|
147
|
+
# @return [Ably::Util::PubSub] Client library internal incoming message bus
|
148
|
+
# @api private
|
149
|
+
def __incoming_protocol_msgbus__
|
150
|
+
@__incoming_protocol_msgbus__ ||= create_pub_sub_message_bus
|
94
151
|
end
|
95
152
|
|
96
|
-
#
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
153
|
+
# @!attribute [r] logger
|
154
|
+
# @return [Logger] The Logger configured for this client when the client was instantiated.
|
155
|
+
# Configure the log_level with the `:log_level` option, refer to {Ably::Realtime::Client#initialize}
|
156
|
+
def logger
|
157
|
+
client.logger
|
101
158
|
end
|
102
159
|
|
103
|
-
|
104
|
-
|
160
|
+
# Add protocol message to the outgoing message queue and notify the dispatcher that a message is
|
161
|
+
# ready to be sent
|
162
|
+
#
|
163
|
+
# @param [Ably::Models::ProtocolMessage] protocol_message
|
164
|
+
# @return <void>
|
165
|
+
# @api private
|
166
|
+
def send_protocol_message(protocol_message)
|
167
|
+
add_message_serial_if_ack_required_to(protocol_message) do
|
168
|
+
Models::ProtocolMessage.new(protocol_message).tap do |protocol_message|
|
169
|
+
add_message_to_outgoing_queue protocol_message
|
170
|
+
notify_message_dispatcher_of_new_message protocol_message
|
171
|
+
logger.debug("Prot msg queued =>: #{protocol_message.action} #{protocol_message}")
|
172
|
+
end
|
173
|
+
end
|
105
174
|
end
|
106
175
|
|
107
|
-
def
|
108
|
-
|
176
|
+
def add_message_to_outgoing_queue(protocol_message)
|
177
|
+
__outgoing_message_queue__ << protocol_message
|
109
178
|
end
|
110
179
|
|
111
|
-
|
112
|
-
|
113
|
-
@__outgoing_protocol_msgbus__ ||= pub_sub_message_bus
|
180
|
+
def notify_message_dispatcher_of_new_message(protocol_message)
|
181
|
+
__outgoing_protocol_msgbus__.publish :message, protocol_message
|
114
182
|
end
|
115
183
|
|
116
|
-
#
|
117
|
-
|
118
|
-
|
184
|
+
# Creates and sets up a new {WebSocketTransport} available on attribute #transport
|
185
|
+
# @yield [Ably::Realtime::Connection::WebsocketTransport] block is called with new websocket transport
|
186
|
+
# @api private
|
187
|
+
def setup_transport(&block)
|
188
|
+
if transport && !transport.ready_for_release?
|
189
|
+
raise RuntimeError, "Existing WebsocketTransport is connected, and must be closed first"
|
190
|
+
end
|
191
|
+
|
192
|
+
@transport = EventMachine.connect(connection_host, connection_port, WebsocketTransport, self) do |websocket|
|
193
|
+
yield websocket
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Reconnect the {Ably::Realtime::Connection::WebsocketTransport} following a disconnection
|
198
|
+
# @api private
|
199
|
+
def reconnect_transport
|
200
|
+
raise RuntimeError, "WebsocketTransport is not set up" if !transport
|
201
|
+
raise RuntimeError, "WebsocketTransport is not disconnected so cannot be reconnected" if !transport.disconnected?
|
202
|
+
|
203
|
+
transport.reconnect(connection_host, connection_port)
|
119
204
|
end
|
120
205
|
|
121
206
|
private
|
122
|
-
attr_reader :
|
207
|
+
attr_reader :manager, :serial, :state_machine
|
208
|
+
|
209
|
+
def connection_host
|
210
|
+
client.endpoint.host
|
211
|
+
end
|
123
212
|
|
124
|
-
def
|
213
|
+
def connection_port
|
214
|
+
client.use_tls? ? 443 : 80
|
215
|
+
end
|
216
|
+
|
217
|
+
def create_pub_sub_message_bus
|
125
218
|
Ably::Util::PubSub.new(
|
126
219
|
coerce_into: Proc.new { |event| Models::ProtocolMessage::ACTION(event) }
|
127
220
|
)
|
@@ -136,37 +229,13 @@ module Ably
|
|
136
229
|
end
|
137
230
|
|
138
231
|
def add_message_serial_to(protocol_message)
|
139
|
-
@
|
140
|
-
protocol_message[:msgSerial] =
|
232
|
+
@serial += 1
|
233
|
+
protocol_message[:msgSerial] = serial
|
141
234
|
yield
|
142
235
|
rescue StandardError => e
|
143
|
-
@
|
236
|
+
@serial -= 1
|
144
237
|
raise e
|
145
238
|
end
|
146
|
-
|
147
|
-
def setup_driver
|
148
|
-
@driver = WebSocket::Driver.client(self)
|
149
|
-
|
150
|
-
driver.on("open") do
|
151
|
-
logger.debug("WebSocket connection opened to #{url}")
|
152
|
-
change_state STATE.Connected
|
153
|
-
end
|
154
|
-
|
155
|
-
driver.on("message") do |event|
|
156
|
-
begin
|
157
|
-
message = Models::ProtocolMessage.new(JSON.parse(event.data).freeze)
|
158
|
-
logger.debug("Prot msg recv <=: #{message.action} #{event.data}")
|
159
|
-
__incoming_protocol_msgbus__.publish :message, message
|
160
|
-
rescue KeyError
|
161
|
-
client.logger.error("Unsupported Protocol Message received, unrecognised 'action': #{event.data}\nNo action taken")
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
# Used by {Ably::Modules::State} to debug state changes
|
167
|
-
def logger
|
168
|
-
client.logger
|
169
|
-
end
|
170
239
|
end
|
171
240
|
end
|
172
241
|
end
|