How to terminate subscription to an actioncable channel from server?

做~自己de王妃 提交于 2019-12-07 03:44:55

问题


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?


回答1:


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



回答2:


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.




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




回答4:


You can do something like this.

class YourChannel < ApplicationCable::Channel

  #your code

  def your_custom_action
    if something
      reject_subscription
    end
  end
end


来源:https://stackoverflow.com/questions/39815216/how-to-terminate-subscription-to-an-actioncable-channel-from-server

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