Skip to content

Commit 695ca50

Browse files
wppNullVoxPopuli
authored andcommitted
Consider a client connected only after receiving a welcome message. (#24)
* Fix a typo in action_cable_client (callaback vs callback). * Consider a client connected only after receiving a welcome message. Given an `action_cable_client` which: 1. connects to an action cable server 2. subscribes to the ChatChannel. 3. receives a message from that channel, then disconnect and starts the process from 1. at a certain point the client connects, but doesn't receive the subscription confirmation and therefore misses broadcast messages. A full example repository is available here: https://github.com/wpp/action-cable-debugging Turns out ActionCable expects a client not to send any messages (including subscription messages) before receiving a "welcome". Quoting from an issue I mistakenly opened on the rails project: > client must consider a connection to be ready/open only after > receiving "welcome" message (and not WebSocket OPEN event). Thus you > should not send any message before that. Sending messages after > receiving "welcome" from server guarantees that the socket is OPEN > (from the server point of view). > Hence the client doesn't implement Action Cable protocol correctly. > Take a look, for example, at Action Cable js client to see how it > should be implemented. If you have a look at the ActionCable JS client mentioned: https://github.com/rails/rails/blob/master/actioncable/app/assets/javascripts/action_cable/connection.coffee#L90 you can confirm that's what they are doing.
1 parent 0c53748 commit 695ca50

File tree

3 files changed

+24
-7
lines changed

3 files changed

+24
-7
lines changed

lib/action_cable_client.rb

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Commands
2222
attr_reader :_message_factory
2323
# The queue should store entries in the format:
2424
# [ action, data ]
25-
attr_accessor :message_queue, :_subscribed, :_subscribed_callaback, :_pinged_callback
25+
attr_accessor :message_queue, :_subscribed, :_subscribed_callback, :_pinged_callback, :_connected_callback
2626

2727
def_delegator :_websocket_client, :onerror, :errored
2828
def_delegator :_websocket_client, :send, :send_msg
@@ -91,7 +91,7 @@ def received
9191
# # do things after the client is connected to the server
9292
# end
9393
def connected
94-
_websocket_client.onopen do
94+
self._connected_callback = Proc.new do
9595
subscribe
9696
yield
9797
end
@@ -111,7 +111,7 @@ def connected
111111
# # do things after successful subscription confirmation
112112
# end
113113
def subscribed(&block)
114-
self._subscribed_callaback = block
114+
self._subscribed_callback = block
115115
end
116116

117117
# @return [Boolean] is the client subscribed to the channel?
@@ -147,6 +147,8 @@ def handle_received_message(message)
147147

148148
if is_ping?(json)
149149
_pinged_callback&.call(json)
150+
elsif is_welcome?(json)
151+
_connected_callback&.call(json)
150152
elsif !subscribed?
151153
check_for_subscribe_confirmation(json)
152154
else
@@ -162,7 +164,7 @@ def check_for_subscribe_confirmation(message)
162164
return unless Message::TYPE_CONFIRM_SUBSCRIPTION == message_type
163165

164166
self._subscribed = true
165-
_subscribed_callaback&.call
167+
_subscribed_callback&.call
166168
end
167169

168170
# {"identifier" => "_ping","message" => 1460201942}
@@ -172,6 +174,12 @@ def is_ping?(message)
172174
Message::IDENTIFIER_PING == message_identifier
173175
end
174176

177+
# {"type" => "welcome"}
178+
def is_welcome?(message)
179+
message_identifier = message[Message::TYPE_KEY]
180+
Message::IDENTIFIER_WELCOME == message_identifier
181+
end
182+
175183
def subscribe
176184
msg = _message_factory.create(Commands::SUBSCRIBE)
177185
send_msg(msg.to_json)

lib/action_cable_client/message.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ class ActionCableClient
44
class Message
55
IDENTIFIER_KEY = 'identifier'
66
IDENTIFIER_PING = 'ping'
7+
IDENTIFIER_WELCOME = 'welcome'
78
# Type is never sent, but is received
89
# TODO: find a better place for this constant
910
TYPE_KEY = 'type'

spec/unit/action_cable_client_spec.rb

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,9 @@
9999

100100
context '#subscribed' do
101101
it 'sets the callback' do
102-
expect(@client._subscribed_callaback).to eq nil
102+
expect(@client._subscribed_callback).to eq nil
103103
@client.subscribed {}
104-
expect(@client._subscribed_callaback).to_not eq nil
104+
expect(@client._subscribed_callback).to_not eq nil
105105
end
106106

107107
it 'once the callback is set, receiving a subscription confirmation invokes the callback' do
@@ -110,14 +110,22 @@
110110
callback_called = true
111111
end
112112

113-
expect(@client).to receive(:_subscribed_callaback).and_call_original
113+
expect(@client).to receive(:_subscribed_callback).and_call_original
114114
message = { 'identifier' => 'ping', 'type' => 'confirm_subscription' }
115115
@client.send(:check_for_subscribe_confirmation, message)
116116
expect(callback_called).to eq true
117117
end
118118
end
119119

120120
context '#connected' do
121+
it 'sets the callback' do
122+
expect(@client._connected_callback).to eq(nil)
123+
124+
@client.connected {}
125+
126+
expect(@client._connected_callback).to_not eq(nil)
127+
end
128+
121129
it 'subscribes' do
122130
# TODO: how do I stub a method chain that takes a block?
123131
# allow{ |b| @client._websocket_client.callback }.to yield_with_no_args

0 commit comments

Comments
 (0)