How to terminate subscription to an actioncable channel from server?

不羁的心 提交于 2019-12-05 06:27:45

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

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!