Is there a way to terminate the subscription to a particular channel for any particular consumer from the server side (controller) so that disconnected callback in my coffee script file can be invoked?
class ChatChannel < ApplicationCable::Channel
def subscribed
@room = Chat::Room[params[:room_number]]
reject unless current_user.can_access?(@room)
end
end
Before calling reject
you can also inform the subscriber of the reject's reason:
class ChatChannel < ApplicationCable::Channel
def subscribed
if params["answerer"]
answerer = params["answerer"]
answerer_user = User.find_by email: answerer
if answerer_user
stream_from "chat_#{answerer_user}_channel"
else
connection.transmit identifier: params, error: "The user #{answerer} not found."
# http://api.rubyonrails.org/classes/ActionCable/Channel/Base.html#class-ActionCable::Channel::Base-label-Rejecting+subscription+requests
reject
end
else
connection.transmit identifier: params, error: "No params specified."
# http://api.rubyonrails.org/classes/ActionCable/Channel/Base.html#class-ActionCable::Channel::Base-label-Rejecting+subscription+requests
reject
end
end
end
The previous answers allow you to reject an attempt to subscribe to a channel. But they don't let you forcibly unsubscribe a connection after it's subscribed. A user might be ejected from a chatroom, say, so you need to cancel their subscription to the chatroom channel. I came up with this Pull Request to Rails to support this.
Essentially it adds an unsubscribe method to remote_connections
so you can call:
subscription_identifier = "{\"channel\":\"ChatChannel\", \"chat_id\":1}"
remote_connection = ActionCable.server.remote_connections.where(current_user: User.find(1))
remote_connection.unsubscribe(subscription_identifier)
That sends a message on the internal_channel
(which all connections are subscribed to) that the relevant connection responds to by removing its subscription to the specified channel.
Like Ollie's answer correctly pointed out, the other answers here are rejecting the ActionCable connection before it succeeds, but the question asks about disconnecting a subscription after it was already subscribed.
This question is very important because it deals with the scenario of an user being kicked out of a chatroom he was previously in. Unless you disconnect him from that subscription, he will continue to receive messages for that channel through the WebSocket until he closes his window/tab or reloads the page (because then a new subscription will be started and the server will not subscribe him to the chat he doesn't have permission anymore).
Ollie's answer points to a great pull request he made, because it allows to disconnect a specific stream, and not all open WebSockets connections a user has; the problem is it's not merged in Rails yet.
My solution is to use a documented API feature that already exists. Even tough it doesn't let you choose which stream you want to disconnect, you can disconnect all open websocket connects from a User.
In my tests this works fine because as soon as the disconnection happens, all tabs will try to resubscribe in a couple of seconds, and it will trigger the subscribed
method in each ActionCable channel, thereby restarting the connections, but now based on the most up-to-date permissions from the server (which, of course, will not resubscribe him to the chat he was kicked out of).
The solution goes like this, assuming you have a join record ChatroomUser that is used to track if a specific user can read the chat in a specific chatroom:
class ChatroomUser < ApplicationRecord
belongs_to :chatroom
belongs_to :user
after_destroy :disconnect_action_cable_connections
private
def disconnect_action_cable_connections
ActionCable.server.remote_connections.where(current_user: self.user).disconnect
end
end
This uses this API (https://api.rubyonrails.org/classes/ActionCable/RemoteConnections.html), and assumes you have current_user
set up in your ApplicationCable::Connection, as most people do (per tutorials).
You can do something like this.
class YourChannel < ApplicationCable::Channel
#your code
def your_custom_action
if something
reject_subscription
end
end
end
© 2022 - 2024 — McMap. All rights reserved.