How to connect to Elixir Channel

Good communication is as stimulating as black coffee, and just as hard.

Some channel - Photo by Sander Mathlener on Unsplash

Establishing frontend connection

socket.js is generated to create a connection to socket, it also exports connection to be used.

// channel creation let channel = socket.channel("upload_state:all", {}) // channel joining channel.join()

.receive("ok", data => {

console.log("Joined topic", "upload_state:all" )

})

.receive("error", resp => {

console.log("Unable to join topic", "upload_state:all")

}) // on channel event do something

channel.on("change", payload => {

channel.js file should contain the logic for connection to channel. After connection to channel is established you call the join method on channel. Event that we will listen for is called change and should be triggered on upload_state:all channel. You need to include this channel.js into your app.js file.

Security

You can use the user id as means to generating token. I am using Pow dependency to handle users, and I am happy with it. I’ve added plugs to handle the token generation. First plug attaches the user id to connection and next one generates token.

# router.ex

pipeline :browser do

...

plug :put_user_id

plug :put_token

...

end



defp put_user_id(conn, _headers) do

%{id: id} = conn

|> Pow.Plug.current_user

|> extract_user_id

assign(conn, :user_id, id)

end



defp put_token(conn, _headers) do

id = conn.assigns.user_id

first_token = Phoenix.Token.sign(SwaggUploadWeb.Endpoint, "salt", id)

assign(conn, :token, first_token)

end

Now we can extract the token and user id from conn and attach it to window object.

<!-- app.html.eex -->

<script>window.userToken = "<%= assigns[:token] %>";</script><script>window.userId = "<%= assigns[:user_id] %>";</script>

After we attached that to window we need to use it in socket.js

// socket.js let socket = new Socket("/socket", {params: {token: window.userToken, user_id: window.userId}})

We use verify method with same salting, after that passes we get tuple with :ok atom.

# user_socket.ex

def connect(%{"token" => token, "user_id" => user_id}, socket, _connect_info) do

case Phoenix.Token.verify(SwaggUploadWeb.Endpoint, "salt", token, max_age: 86400) do

{:ok, _} ->

{:ok, assign(socket, :user_id, user_id) }

_ ->

:error

end

end

Handling Elixir channels

Mix tool has specific task in order to create channels. It gives basic instructions on how to setup your channel. You need to add your channel to socket.

# user_

channel "upload_state:*", SwaggUploadWeb.UploadStateChannel # your_channel.ex

YourWebAppModule.Endpoint.broadcast("upload_state:all", "change", state)

Second method above broadcasts message to channel with change event. I’ve added interception in order to handle filtering. All users will get messages specific to their events. We can extract user id from socket struct and use it to compare with payload id, after they match we can push payload.

intercept ["change"]

def handle_out("change", payload, socket) do

%{id: id} = payload

case (Integer.parse(socket.assigns[:user_id])) do

^id ->

push socket, "change", payload

{:noreply, socket}

_ ->

{:noreply, socket}

end

end

Thank you for reading!