How to terminate subscription to an actioncable channel from server?
Asked Answered
S

4

7

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?

Shoer answered 2/10, 2016 at 8:29 Comment(1)
I'm curious about that too. I decided to send a "disconnect" message to the client and the client does the subscription termination after receiving that message.Jordonjorey
C
6

http://api.rubyonrails.org/classes/ActionCable/Channel/Base.html#class-ActionCable::Channel::Base-label-Rejecting+subscription+requests

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
Casady answered 11/4, 2017 at 7:51 Comment(0)
F
3

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.

Foramen answered 19/2, 2019 at 9:11 Comment(0)
F
3

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).

Feather answered 29/5, 2019 at 22:31 Comment(0)
Q
-1

You can do something like this.

class YourChannel < ApplicationCable::Channel

  #your code

  def your_custom_action
    if something
      reject_subscription
    end
  end
end
Quent answered 30/10, 2016 at 11:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.