Phoenix Channels are basically sockets. Senders broadcast messages about topics. Receivers subscribe to topics so that they can get those messages.
See Presence to extend functionality
mixphx.gen.channel [module_name] #user
Server
Already setup by default in endpoint.ex and user_socket.ex, most of the work is client side
To add current user to socket
lib/ss_web/channels/user_socket.ex
defmoduleSsWeb.UserSocketdousePhoenix.Socket## Channels channel "room:*",SsWeb.RoomChanneldefconnect(_params, socket,_connect_info) doIO.puts"CONNTECTING BABY" {:ok,assign(socket, :current_user, user)} #assign lets you store state(key-value) to this socketendend
Whenever a client sends a message whose topic starts with "room:", it will be routed to our RoomChannel.
Channels
Like controllers, but have incoming and outgoing events and connections persist
Have join/3, terminate/2, handle_in/3, and handle_out/3
lib/ss_web/channels/room_channel.ex
defmoduleSsWeb.RoomChanneldousePhoenix.Channeldefjoin("room:lobby",_message, socket) do {:ok, socket}#{:ok, :payload, socket} #paylaod can be objectenddefjoin("subevent:*", payload, socket) do assignedSocket =assign(socket, :event_id, event_id)#broadcast can only be called after socket finishes joiningsend(self, :after_join) {:ok, assignedSocket}enddefhandle_info(:after_join, socket) dopush(socket,"feed", %{list: feed_items(socket)}) {:noreply, socket}end#eChannel.push("new_msg", {body: "hi"});defhandle_in("new_msg", %{"body"=> body}, socket) do user_id = socket.assigns.user_idbroadcast!(socket,"new_msg", %{body: body})# Must use Endpoint to broadcast to external channelReactPhoenixWeb.Endpoint.broadcast("user:#{user_id}","new_msg", %{body: body}) {:reply, :ok, socket} {:reply, {:error, %{errors: changeset}}, socket)#best practice to respond with error or ok so client can properly deal end#when you broadcast user_joined this intercepts intercept ["user_joined"]defhandle_out("user_joined", msg, socket) doifAccounts.ignoring_user?(socket.assigns[:user], msg.user_id) do {:noreply, socket}elsepush(socket,"user_joined", msg) {:noreply, socket}endenddefterminate(reason, socket) do#can't do send self here broadcast socket,"user_left", payload#will not send to socket broadcast_from socket,"user_left", payload {:noreply, socket}endend
broadcast will notify all clients on socket's topic and invoke their handle_out callback if defined for filtering/customization
When a client successfully establishes this connection, Phoenix Channels initializes an instance of %Phoenix.Socket{}. The channel server then retains awareness of this socket instance and maintains state within that instance via calls to socket.assign
import { Socket } from'phoenix';constsocket=newSocket("/socket", {params: {token: <yourauthtoken>}})socket.connect({body: "params passed to connect"});const channel = socket.channel(`challenges:${challengeId}`);channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) })channel.on("new_msg", payload => {//payload is exactly as passed, no wrapper})channel.push(event, payload) .receive('ok', data => debug(data)) .receive('error', reasons => debug("er", reasons)) .receive('timeout', () => debug("timeout"))function onClick() {channel.push("new_msg", {body:"test"})}
Other Client
socket.onError( () => )socket.onClose( () => )channel.onError( () =>console.log("error!") )channel.onClose( () =>console.log("gone gracefully") )// Unsubscribe the do_stuff handlerconstref1=channel.on("event", do_stuff)channel.off("event", ref1)// Unsubscribe all handlers from eventchannel.off("event")channel.leave().receive("ok", () =>alert("left!") )