Rails 动作电缆特定消费者

Rails action cable specific consumer

我正在为 动作线缆 苦苦挣扎。就我而言,我有几个用户(通过 Devise)可以彼此共享任务。

现在,当 user#1user#2 共享任务(通过表单)时,所有经过身份验证的用户都会收到通知。

我应该如何以及在何处识别我的 user#2 只向他广播?

到目前为止,这是我的代码:

connection.rb

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
      logger.add_tags 'ActionCable', current_user.id
    end

    protected

    def find_verified_user # this checks whether a user is authenticated with devise
      if verified_user = env['warden'].user
        verified_user
      else
        reject_unauthorized_connection
      end
    end
  end
end

cable.js

(function() {
  this.App || (this.App = {});

  App.cable = ActionCable.createConsumer();

}).call(this);

todo_channel.rb

class TodoChannel < ApplicationCable::Channel
  def subscribed
    stream_from "todo_channel_#{current_user.id}"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end


  def notify
    ActionCable.server.broadcast "todo_channel_#{current_user.id}", message: 'some message'(not implemented yet)
  end
end

todo.coffee

App.todo = App.cable.subscriptions.create "TodoChannel",
  connected: ->
    # Called when the subscription is ready for use on the server

  disconnected: ->
    # Called when the subscription has been terminated by the server

  received: (data) ->
    console.log(data['message'])

  notify: ->
    @perform 'notify'

我以前遇到过类似的事情,直到我意识到您实际上可以在频道中多次调用 stream_from,并且该用户将在同一频道连接中订阅多个不同的 "rooms"。这意味着你基本上可以做到这一点

class TodoChannel < ApplicationCable::Channel
  def subscribed
    stream_from "todo_channel_all"                            
    stream_from "todo_channel_#{current_user.id}"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def notify(data)
    # depending on data given from the user, send it to only one specific user or everyone listening to the "todo_channel_all"
    if data['message']['other_user_id']
      ActionCable.server.broadcast "todo_channel_#{data['message']['other_user_id']}", message: 'some message'
    else
      ActionCable.server.broadcast "todo_channel_all", message: 'some message'
    end

  end
end

该代码假设用户已经知道其他用户的 ID 并将其发送到频道,您可能必须用一些安全措施或其他方式包装它,我承认我对 [=23 的经验不是很好=]因为我还在学习。

将来可能对您有益的另一件事是,您还可以在同一频道功能中广播多个 messages/times。这意味着您可以潜在地支持将您的任务发送给单个特定用户、特定用户列表或每个人。只需迭代 list/array/whatever 用户并在他们的个人 "todo_channel_#{user.id}"

上向他们每个人广播 task/message/notification/whatever

我最终采用了不同的方法。我会写在这里,以防有人觉得有用。

Notification 有一个必须被通知的用户的 id。所以在模型中我有:

after_commit :broadcast_notification, on: :create

def broadcast_notification
  ActionCable.server.broadcast "todo_channel_#{self.user_id}", message: 'some message'
end

当我将广播放入模型时,我的 todo_channel.rb 看起来像这样:

class TodoChannel < ApplicationCable::Channel
  def subscribed
    stream_from "todo_channel_#{current_user.id}"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end
end

第 1 步:让每个用户都有唯一的 session 令牌。订阅时,每个用户将在 header 秒内发送 session 令牌,并且 header 可在连接 class 中访问。使用 session 令牌查找用户。

第 2 步:使用用户 ID 流式传输用户。

步骤#3:共享任务时,在请求中也带上用户 ID,并在给定的用户 ID 上广播。

这叫做"private chat"。如果你真的想获得 current_user.id,你可以通过 3 种方式实现:

  1. 一些 AJAX onloadtodo.coffee 调用到服务器。

  2. current_user.id渲染为Rails HTML视图中的属性,然后通过todo.coffee中的jQuery获取它(如https://www.sitepoint.com/create-a-chat-app-with-rails-5-actioncable-and-devise/ )

  3. 在用户登录时创建一个普通 cookie,然后在里面检查它 todo.coffee

但是您不应该使用 current_user.id,因为它不安全。如果您使用它,那么某人可能会在同一站点上注册并通过简单地提供随机 user_id.

轻松收听其他用户的私人聊天

而是使用签名(例如 Rails-加密)cookie 作为用户的唯一广播标识符。这样,如果您在同一个站点注册,您将永远不会知道其他用户的签名 cookie - 因此您无法闯入外星人的私人聊天。

app/config/initializers/warden_hooks.rb

https://rubytutorial.io/actioncable-devise-authentication/

# Be sure to restart your server when you modify this file.

Warden::Manager.after_set_user do |user,auth,opts|
  scope = opts[:scope]
  auth.cookies.signed["#{scope}.id"] = user.id
end

Warden::Manager.before_logout do |user, auth, opts|
  scope = opts[:scope]
  auth.cookies.delete("#{scope}.id")
end

todo_channel.rb

class TodoChannel < ApplicationCable::Channel
  def subscribed
    stream_from "todo_channel_#{params['user_signed_cookie']}"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end


  def notify(param_message)
    ActionCable.server.broadcast "todo_channel_#{param_message['user_signed_cookie']}", message: 'some message'(not implemented yet)
  end
end

todo.coffee

user_signed_cookie = document.cookie.replace(/(?:(?:^|.*;\s*)user.id\s*\=\s*([^;]*).*$)|^.*$/, "");

user_logged_in = if user_signed_cookie then true else false

if user_logged_in #avoid repetitive HTTP->WebSockets upgrade pointless browser attempts when no user logged in.
 App.todo = App.cable.subscriptions.create {
          channel:"TodoChannel"
          user_signed_cookie: user_signed_cookie
        },
  connected: ->
    # Called when the subscription is ready for use on the server

  disconnected: ->
    # Called when the subscription has been terminated by the server

  received: (data) ->
    console.log(data['message'])

  notify: (name, content, _) -> #name, content - message fields
    @perform 'notify', name: name, content: content, user_signed_cookie: user_signed_cookie