Search code examples
javascriptruby-on-railsactioncable

Rails' ActionCable server-side subscribe with params


I'm building a REST API using Ruby on Rails as a backend. This API is consumed by a VueJS frontend. I am not using Rails' frontend, the frontend is completely independent.

I have established a WebSocket connection using ActionCable but I'm struggling to send params to the subscription.

I have the following ruby channel :

class WorkChannel < ApplicationCable::Channel
    def subscribed
        stream_from "works-#{params[:creator_id]}"
    end
end

And in my JavaScript frontend, I subscribe to the channel using this code :

const msg = {
    command: 'subscribe',
    identifier: JSON.stringify({
        channel: 'WorkChannel',
    }),
};
socket.send(JSON.stringify(msg));

The subscription is successful if I remove the variable in the stream_from part.

On all guides and tutorials that I've read, I only found one explaining how to communicate with ActionCable without using Rails' frontend methods. See here for the complete guide.

However, this guide does not send data to the subscription and I can't find it on the documentation.

What is the JSON syntax of the object I need to send to emulate the behavior of App.cable.subscriptions.create({channel: 'WorkChannel', creator_id: 1 }?

I tried different syntaxes but none of them was working :

const msg = {
    command: 'subscribe',
    identifier: JSON.stringify({
        channel: 'WorkChannel',
    }),
    creator_id: 1,
};

Or

const msg = {
    command: 'subscribe',
    identifier: JSON.stringify({
        channel: 'WorkChannel',
        creator_id: 1,
    }),  
};

And even

const msg = {
    command: 'subscribe',
    identifier: JSON.stringify({
        channel: 'WorkChannel',
    }),
    data: {
        creator_id: 1,
    },
};

Solution

  • You don't want to send the user id as a parameter, it can be exploited. Imagine a user pauses the js excecution, changes the user_id and resumes, he will receive messages that goes to other users.

    I'd recommend you try to get the current user id from the logged user, something like https://guides.rubyonrails.org/action_cable_overview.html#server-side-components

    # app/channels/application_cable/connection.rb
    module ApplicationCable
      class Connection < ActionCable::Connection::Base
        identified_by :current_user
    
        def connect
          self.current_user = find_verified_user
        end
    
        private
          def find_verified_user
            if verified_user = User.find_by(id: cookies.encrypted[:user_id])
              verified_user
            else
              reject_unauthorized_connection
            end
          end
      end
    end
    

    The correct current user detection may change depending on what authentication system you use, if you use devise, you may need to get the user from warden like this: https://stackoverflow.com/a/38702351/1430810