Hey, I’m writing a WebSocket app in Phoenix.
In one socket
I have defined 3 channels.
As I can see I can access socket.assigns
in same channel in different &handle_in/3
implementations.
I want to be able to do same with multiple channels in same socket (&join/3
implementations).
What I’m trying to do is to separate API per model, so I have a channel to managing users (register, login, logout …) and other channels. In other channels I want to have access to: socket.assigns.current_user
that is set in first channel.
Is it even possible?
I’m… unsure what you are asking, so let me explain how it works to see if it helps.
When a socket is connected the socket setup module is called, any assigns there stay there (immutable).
When a channel (and topic for that channel) is established it copies the socket object from the socket, assigns and all, and it becomes its own here.
If you register any other topics to that channel, it is still all the same socket objects as that channel, because it is the spawned process that copies the socket, not the topic registrations, they all use the same process.
I ‘think’ you are asking how to see the same assigns from different channels, to do so you set the assigns on the socket connect/2
function.
If anything else is needed, it might be better to in more detail describe what you are trying to accomplish.
@OvermindDL1: thanks for fast response
I updated my question.
socket &connect/2
function is called when I execute this:
let socket = new Phoenix.Socket("/ws");
right?
Is so then it does not help.
What I’m doing is:
- Connect with
Phoenix.Socket
- Join users channel
- Register in users channel
- Logged in users channel - here I have current user (I mean not at first step)
Yes.
If you want to share that logged in user with other channels then you cannot directly (you could send a message though) because all channels are peers, none can touch anything of the others. You instead need to log in the user in the socket then share that auth token with all the channels that are then later created via assigns.
Or you can pass messages externally, though that would be fighting the design a bit more than you would really want. ^.^
@OvermindDL1: I have a question related to Phoenix.Token API. As I can see first argument could be my endpoint, so this API reads secret key from secret_key_base
configuration of my endpoint. I don’t know what should I use as a 2nd argument that’s named salt
. Is it a second secret key that should be also complex and long?
The salt is a second secret key but it is per-token rather than system-wide, and it is just to add a bit more randomization so it can be simple, I usually just use something related to the use, so when I stuff in the :account_id
I will usually just make it "account_id_salt"
or something. ^.^
If you are really paranoid then you could store it in the database or so…
Hi !
You are saying that the socket data is copied. So if I want to assign data for a topic only, say “room:123”, I just have to do it in the channel code (like the join or handle_in functions).
And then, my assigns[:stuff] will be different in my room:123 and room:456. So no need to setup an Agent process like I’ve found on internet.
Of course, this works whith the classic JS code : socket.channel().join(), not if I subscribe the token to another topic from the channel code.
Am I right ?
Or does channel "room:*", HelloWeb.RoomChannel
mean that every topic matching “room:*” will be in the same process ?
Thank you
Personally I recommend to assign data in Socket
, so all socket channels would share its data. For example you could create MyAppWeb.GuestSocket
and MyAppWeb.UserSocket
. First on connect would require credentials or session token and second would not require anything. In such way you can easily work with multiple Channel
and Socket
setup.
If I remember correctly:
-
If you would assign something in
MyAppWeb.UserSocket.connect/2
then you would share all data across allChannel
s in specificSocket
.
You can read more details inPhoenix
documentation: Socket params and assigns -
If you would assign something in
MyAppWeb.UserChannel.join/2
then you would share all data across allhandle_*
events in specificChannel
. -
If you would assign something in specific
handle_in*
event then such data would be available to all other events, but of course only afterhandle_*
event which assigned it has finished successfully. This means that you need to secure other topics by checking if you have assigned required data already and return something like:{"error": 400, "reason": "some event was not yet received"}
.
You have one process for every Socket
connect and one process for Channel
join. It’s why you can’t share data across multiple Channel
unless you are assigning it in Socket.connect/2
i.e. before you join Channel
. Of course if you are using multiple topics for specific Channel.join/3
then you can’t share data across all of them. Imagine being in lobby with all game players in whole game (i.e. not only in specified lobby with let’s say 10 players limit. That would be really terrible to manage list of users in lobby UI
Please correct me if I made any mistake as in last time I’m sick, so my brain is not working with highest effective.
Well as I said, I’m trying to figure out how things work “if I want to assign data for a topic only”.
But I thing we have the same understanding of how things work here.
Just do something like it:
def join("rooms:" <> room_id, _payload, socket) do
{:ok, assign(socket, :example, :sample)}
end
Look that for every called (i.e. not declared in code) join
you have new process. If you are joining to for example RoomsChannel
then you would have something like connection for many-to-many relation between Room
(existing lobby or new one) and User
(assigned for example on Sockect.connect/2
or in same RoomsChannel.join/3
or simple guest if you support it).