faye-rails icon indicating copy to clipboard operation
faye-rails copied to clipboard

Private channel functionality.

Open jimsynz opened this issue 11 years ago • 4 comments

I'd like to add an API for private channel functionality, tied to a specific session. I'm really open to suggestions from people for how to implement this, and as such am inviting all previous collaborators (@RLovelett @endel @tardate @nilbus @gmanley @mareczek @linrock @jhicks @RyanScottLewis @aaronjensen) to pipe in with ideas and opinions.

So, I'm thinking of something like this:

class SomePageController < ApplicationController
  before_filter do
    authorize_private_channel for: current_user, as: :current_user, using_controller: PrivateChannelController
  end
end

Where the authorize_private_channel will create a unique token to be inserted into the page much like the CSRF token is now. We'll probably have to write a simple JS client library to handle automatic subscription to this private channel when the token is present.

The for: argument can be an arbitrary Ruby object that is passed into scope of the channel and observer DSLs as a way to uniquely identify which particular private channel we're talking about.

The as: argument is used to name the context object in the DSL, so in the example above it will be available as current_user inside the DSL methods.

The using_controller: argument maps the private channel to a particular controller. The controller is the same as any other FayeRails::Controller subclass, except that it is given a special filter which only allows a user with the correct single-use token to subscribe and publish.

This is about the best API I can come up with off the top of my head - has anyone else any ideas?

Thanks!

jimsynz avatar Feb 28 '13 02:02 jimsynz

One clarification will make this a little easier to evaluate. You said the for: option uniquely identifies which particular private channel we're referring to. The current_user variable and the way your description reads make it look obvious that it you are providing authorization for that user. Does this imply that you plan for each user in this case to have their own particular private channel?

nilbus avatar Mar 04 '13 22:03 nilbus

That's right. The idea is that every user or session or whatever has it's own private channel to talk to the server without the messages going to any other clients.

jimsynz avatar Mar 04 '13 22:03 jimsynz

That makes sense. Then group private channels could be authorized with a common group object.

Before I comment further, let's flesh this out the rest of the way.

You started with the Rails controller:

class SomePageController < ApplicationController
  before_filter do
    authorize_private_channel for: current_user, as: :current_user, using_controller: PrivateChannelController
  end
end

In a view you might have something like this to send the token via a private_channel_token:

<%= javascript_tag 
      "var client = new Faye.Client(...);
       client.setHeader('Authorization', '#{private_channel_token}')" %>

Then in our FayeRails::Controller, you said we'd need a filter to make the a channel private:

class PrivateController < FayeRails::Controller
  channel '/privates' do
    private_channel for: :current_user
    monitor :subscribe do
      PrivateController.publish('/privates', "Hello #{current_user.name}, your ID is #{current_user.id}.")
    end
  end
  observe User, :after_update do |user|
    if user == current_user
      PrivateController.publish('/privates', "Hi #{current_user.name}, you got updated with #{user.attributes.inspect}.")
    end
  end
end

However you also said that each for: object (in this case, each User) would have its own channel. So maybe it would be clearer written like:

class PrivateController < FayeRails::Controller
  channel for: :current_user do
    monitor :subscribe do
      PrivateController.publish({for: :current_user}, "Hello #{current_user.name}, your ID is #{current_user.id}.")
    end
  end
  observe User, :after_update do |user|
    if user == current_user
      PrivateController.publish({for: user}, "Hi #{current_user.name}, you got updated with #{user.attributes.inspect}.")
    end
  end
end

I am not suggesting these as final solutions, but I wanted to write out something more concrete that we can iterate off of. Is this close to what you were thinking?

nilbus avatar Mar 16 '13 14:03 nilbus

Here's another really simple idea, it doesn't actually secure the channel. This isn't necessarily good, it was just the first thing that came to my mind.

When a client connects, have them fire a guid generated on the client to /user/init

client = new Faye.Client('/faye')
client.guid = guid()
$(document).ready ->
  client.publish '/user/init', 
    guid: client.guid

and then on the server you can...

channel '/user/init' do
  subscribe do
    # Notice here we send to a /user/:guid
    SomeController.publish("/user/#{message['guid']}", someResponseObject)
  end
end

and then the client will be able to get a private message.

client.subscribe "/user/#{client.guid}", (data)->
  console.log data

Like I said, it's not super nice, but it works.

film42 avatar Apr 25 '13 05:04 film42