0

I am new to ActionCable and sockets and trying to implement some real time features.

I successfully implemented real time notifications functionality (basic one) into my app, however there are a couple of things on which i spent some time to understand.

My Real time notifications code:

The authentification process:

# Connection.rb (using Devise)
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = env['warden'].user
      reject_unauthorized_connection unless self.current_user

      logger.add_tags 'ActionCable', current_user.email
    end

  end
end

Notification Channel:

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

  def unsubscribed
  end
end

Notifications.coffee

App.notifications = App.cable.subscriptions.create "NotificationsChannel",
  connected: ->

  disconnected: ->

  received: (data) ->
    this.update_counter(data.counter)

    # if use_sound is "true" play a mp3 sound
    # use_sound is sent ("true" or "false") because when a notification is destroyed we don't want to use sound only update counter  

    if data.use_sound == "true"
      $("#sound-play").get(0).play()

  # Update Notifications Counter and change color

  update_counter: (counter) ->
    $("#notifications-counter").html("#{counter}")

    if counter > 0
      $("#notifications-counter").css('color','#FFA5A5')
    else
      $("#notifications-counter").css('color','white')

And there is the Job file where we broadcast to the channel with Notifications Counter

Note: I only send notifications count because in the nav-bar when the user clicks the (ex. 2 Notifications) - button, a dropdown is toggled and populated via AJax call.

My questions are:

  1. Is it normal to have all the time 'GET /cable' requests with the response 'An unauthorized connection attempt was rejected' when a user is not logged in ( I understand that the current_user in the Connect.rb file is set from the cookie, but when a user is on ex Home page, Logg-in page, etc.. basically not authenticated.

    In the server logs I can see every 1,2 seconds the GET /cable enter image description here

    Is this normal? I'm thinking that somehow I have to stop this if the user logs out or something.

  2. When I start the server I get the following error for like 10 seconds till the server starts in the browsers console. Since the notifications work, I don't know if this is a problem of not, but an error it is.

    WebSocket connection to 'ws://localhost:3000/cable' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED

    I even tried setting config.action_cable.mount_path = "/cable" or "ws://localhost:3000/cable" and did not fix it.

  3. Next I want to implement a chat, where 2 users can have a private conversation, is the "current_user" defined in the Connection.rb 'secure', how can I add authorization to ActionCable or something like. In the rest of my app I'm using CanCan Gem and works perfectly.

Thank you very much for the time invested in reading the question. If you have answers for my questions I will really appreciate them. And if you have some directions to give or tips for building a complex and secure chat with ActionCable I would love to hear them. Thank you!

Rares R
  • 209
  • 1
  • 13

1 Answers1

1

I think what you want to be looking at is the cable.js file where the createConsumer function is called.

Creating a consumer is what subscribers a user to the channel. I think you need to focus on when this code gets called.

You are using user authentication's presence to reject unauthorised use.

This is very good, however not so great when the JS consumer is created for any visitor (i.e. cable.js is loaded into application.js for all pages).

What you can do is use content_for into a seperate layout for authenticated users that calls the cable.js to create a consumer and ensure that cable.js does not get included into unauthorised pages.

fbelanger
  • 3,522
  • 1
  • 17
  • 32
  • This is a great idea! My app has multiple layouts, we know exactly in which layout the user is logged it. Before reading your answer I thought of the following solution, note I don't really know if its a solution. What if we set a timeOut and call perform 'unfollow' somewhere in js if for example $('notification-counter-id') is not present and in the unfollow method server side we do stop_all_streams. Im not that sure if we can call perform 'unfollow' if we reject current_user because he's not authenticated. And not that sure when to call 'unfollow' or when to do stop_all_streams in code. – Rares R Apr 22 '17 at 18:24
  • Doing a unfollow approach is like doing blacklisting. Everyone is opted-in then opted-out through the process. I think the other approach is less complicated and robust because only signed-in users would get the consumer and if done manually would still reject them. If you were to use the unfollow approach, then part of the rejection would become to broadcast the unfollow, create the channel action and code for the consumer. – fbelanger Apr 22 '17 at 18:27
  • You are definitely right. One more thing, so I can understand ActionCable better. The scenario for my app when user is not logged in is: User is on localhost, because we include cable in application.rb, he tries to subscribe via createCustomer, and here is what I don't understand, createCustomer calls 'follow' function in NotificationsChannel to subscribe to it, right? I checked DHH ActionCable Example and we called @performe on each 'chat room', that is logic because we want to stop all the streams if we change the room and then stream to the new room. Sorry if I expressed myself poorly. :) – Rares R Apr 22 '17 at 18:37
  • I believe the `createConsumer` calls `ApplicationCable::Connection` to setup and persist a websocket. Once the websocket is established, then the JS for `NotficationsChannel` will sub to a specific stream. The channel will know about the sockets that are subscribed and will push to these websockets. – fbelanger Apr 22 '17 at 18:49
  • I read a bit more and I actually understood. Consumers ('user') require an instance of the connection on client side. This is established by `createConsumer`. Then we'll have to tell the client to subscribe to any content broadcasts and we establish that by subscribing `App.notifications = App.cable.subscriptions.create "NotificationsChannel"` – Rares R Apr 22 '17 at 19:14