ably 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|