Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/rage/cable/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,11 @@ def __disconnect(connection)
# # Disconnect only the user's specific device
# RageCable::Connection.disconnect(current_user_device: [user, "device-123"])
#
# @example Using protocol options
# RageCable::Connection.disconnect(current_user: User.find(1), protocol_options: { reconnect: false })
#
# @param identified_by [Hash] a single key-value pair where the key is a declared identifier
# @param protocol_options [Hash] options passed to the connection's protocol when disconnecting
# @raise [ArgumentError] if no identifier, multiple identifiers, or an unknown identifier is provided
# @!method self.disconnect(**identified_by)
# @!method self.disconnect(protocol_options: nil, **identified_by)
end
9 changes: 7 additions & 2 deletions lib/rage/cable/protocols/actioncable_v1_json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ module MESSAGES
UNAUTHORIZED = { type: TYPE::DISCONNECT, reason: REASON::UNAUTHORIZED, reconnect: false }.to_json
INVALID = { type: TYPE::DISCONNECT, reason: REASON::INVALID, reconnect: true }.to_json
DISCONNECT = { type: TYPE::DISCONNECT, reason: REASON::REMOTE, reconnect: true }.to_json
DISCONNECT_NO_RECONNECT = { type: TYPE::DISCONNECT, reason: REASON::REMOTE, reconnect: false }.to_json
end

HANDSHAKE_HEADERS = { "Sec-WebSocket-Protocol" => "actioncable-v1-json" }
Expand Down Expand Up @@ -173,7 +174,11 @@ def self.protocol_id
# Process the disconnect initiated by the server.
#
# @param connection [Rage::Cable::WebSocketConnection] the connection object
def self.on_remote_disconnect(connection)
connection.write(MESSAGES::DISCONNECT)
# @param options [Hash, nil] options passed to the protocol when disconnecting
def self.on_remote_disconnect(connection, options)
reconnect = options.nil? || options.fetch(:reconnect, true)
message = reconnect ? MESSAGES::DISCONNECT : MESSAGES::DISCONNECT_NO_RECONNECT

connection.write(message)
end
end
3 changes: 2 additions & 1 deletion lib/rage/cable/protocols/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ def supports_rpc?
# Process the disconnect initiated by the server.
#
# @param connection [Rage::Cable::WebSocketConnection] the connection object
def on_remote_disconnect(connection)
# @param options [Hash, nil] options passed to the protocol when disconnecting
def on_remote_disconnect(connection, options)
end

private
Expand Down
16 changes: 12 additions & 4 deletions lib/rage/cable/remote_disconnect.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ module ClassMethods
# Disconnect all connections matching the specified identifier.
# @example Disconnect all connections for a user
# RageCable::Connection.disconnect(current_user: User.find(1))
def disconnect(**identified_by)
def disconnect(protocol_options: nil, **identified_by)
raise ArgumentError, "An identifier must be provided" if identified_by.empty?
raise ArgumentError, "Only one identifier can be provided" if identified_by.size > 1

Expand All @@ -38,16 +38,24 @@ def disconnect(**identified_by)
raise ArgumentError, "Unknown identifier: #{id_pair[0]}"
end

Rage::PubSub.publish("", topic: ["rage", name, id_pair])
message = if protocol_options
raise ArgumentError, "protocol_options must be a Hash" unless protocol_options.is_a?(Hash)
protocol_options.to_json
else
""
end

Rage::PubSub.publish(message, topic: ["rage", name, id_pair])
end
end

private

def __remote_disconnect_setup(connection, protocol)
__identified_by_map.each do |identified_by|
Rage::PubSub.subscribe(topic: ["rage", self.class.name, identified_by], subscription_id: connection) do
protocol.on_remote_disconnect(connection)
Rage::PubSub.subscribe(topic: ["rage", self.class.name, identified_by], subscription_id: connection) do |message|
protocol_options = JSON.parse(message, symbolize_names: true) unless message.empty?
protocol.on_remote_disconnect(connection, protocol_options)
connection.close
end
end
Expand Down
39 changes: 36 additions & 3 deletions spec/cable/connection_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,29 @@
"TestCableConnection",
[:current_user, user]
]
expect(Rage::PubSub).to receive(:publish).with(anything, topic: expected_topic)
expect(Rage::PubSub).to receive(:publish).with("", topic: expected_topic)

cable_connection_class.disconnect(current_user: user)
end

it "publishes protocol_options as JSON" do
user = double(id: 1234)

expected_topic = [
"rage",
"TestCableConnection",
[:current_user, user]
]
expect(Rage::PubSub).to receive(:publish).with('{"reconnect":false}', topic: expected_topic)

cable_connection_class.disconnect(current_user: user, protocol_options: { reconnect: false })
end

it "raises ArgumentError if protocol_options is not a Hash" do
expect {
cable_connection_class.disconnect(current_user: double, protocol_options: "invalid")
}.to raise_error(ArgumentError, /protocol_options must be a Hash/)
end
end

context "with multiple identifiers" do
Expand Down Expand Up @@ -312,10 +331,24 @@ def connect

subject

expect(protocol).to receive(:on_remote_disconnect).with(connection)
expect(protocol).to receive(:on_remote_disconnect).with(connection, nil)
expect(connection).to receive(:close)

pubsub_callback.call("")
end

it "passes protocol_options to on_remote_disconnect" do
pubsub_callback = nil
allow(Rage::PubSub).to receive(:subscribe) do |&block|
pubsub_callback = block
end

subject

expect(protocol).to receive(:on_remote_disconnect).with(connection, { reconnect: false })
expect(connection).to receive(:close)

pubsub_callback.call
pubsub_callback.call('{"reconnect":false}')
end

context "with multiple identifiers" do
Expand Down
Loading