Phoenix.Channel behaviour

Defines a Phoenix Channel.

Channels provide a means for bidirectional communication from clients that integrates with the Phoenix.PubSub layer for soft-realtime functionality.

Topics & Callbacks

When clients join a channel, they do so by subscribing a topic. Topics are string idenitifiers in the Phoenix.PubSub layer that allow multiple processes to subscribe and broadcast messages about a give topic. Everytime you join a Channel, you need to choose which particular topic you want to listen to. The topic is just an identifier, but by convention it is often made of two parts: "topic:subtopic". Using the "topic:subtopic" approach pairs nicely with the Phoenix.Router.channel/3 macro to match topic patterns in your router to your channel handlers:

socket "/ws", MyApp do
  channel "rooms:*", RoomChannel
end

Any topic coming into the router with the "rooms:" prefix, would dispatch to MyApp.RoomChannel in the above example. Topics can also be pattern matched in your channels’ join/3 callback to pluck out the scoped pattern:

# handles the special `"lobby"` subtopic
def join("rooms:lobby", _auth_message, socket) do
  {:ok, socket}
end

# handles any other subtopic as the room ID, ie `"rooms:12"`, `"rooms:34"`
def join("rooms:" <> room_id, auth_message, socket) do
  {:ok, socket}
end

Authorization

Clients must join a channel to send and receive PubSub events on that channel. Your channels must implement a join/3 callback that authorizes the socket for the given topic. It is common for clients to send up authorization data, such as HMAC’d tokens for this purpose.

To authorize a socket in join/3, return {:ok, socket} To refuse authorization in join/3, return:ignore`

Incoming Events

After a client has successfully joined a channel, incoming events from the client are routed through the channel’s handle_in/3 callbacks. Within these callbacks, you can perform any action. Typically you’ll either foward a message out to all listeners with Phoenix.Channel.broadcast!/3, or reply directly to the socket with Phoenix.Channel.reply/3. Incoming callbacks must return the socket to maintain ephemeral state.

Here’s an example of receiving an incoming "new:msg" event from a one client, and broadcasting the message to all topic subscribers for this socket.

def handle_in("new:msg", %{"uid" => uid, "body" => body}, socket) do
  broadcast! socket, "new:msg", %{uid: uid, body: body}
  {:ok, socket}
end

You can also send a reply directly to the socket:

# client asks for their current rank, reply sent directly as new event
def handle_in("current:rank", socket) do
  reply socket, "current:rank", %{val: Game.get_rank(socket.assigns[:user])}
  {:ok, socket}
end

Outgoing Events

When an event is broadcasted with Phoenix.Channel.broadcast/3, each channel subscribers’ handle_out/3 callback is triggered where the event can be relayed as is, or customized on a socket by socket basis to append extra information, or conditionally filter the message from being delivered. Note: broadcast/3, broadcast!/3 and reply/3 both return {:ok, socket}.

def handle_in("new:msg", %{"uid" => uid, "body" => body}, socket) do
  broadcast! socket, "new:msg", %{uid: uid, body: body}
end

# for every socket subscribing on this topic, append an `is_editable`
# value for client metadata
def handle_out("new:msg", msg, socket) do
  reply socket, "new:msg", Dict.merge(msg,
    is_editable: User.can_edit_message?(socket.assigns[:user], msg)
  )
end

# do not send broadcasted `"user:joined"` events if this socket's user
# is ignoring the user who joined
def handle_out("user:joined", msg, socket) do
  if User.ignoring?(socket.assigns[:user], msg.user_id) do
    {:ok, socket}
  else
    reply socket, "user:joined", msg
  end
end

By default, unhandled outgoing events are forwarded to each client as a reply, but you’ll need to define the catch-all clause yourself once you define an handle_out/3 clause.

Broadcasting to an external topic

In some cases, you will want to broadcast messages without the context of a soceket. This could be for broadcasting from within your channel to an external topic, or broadcasting from elsewhere in your application like a Controller or GenServer. For these cases, you can broadcast from your Endpoint its configured PubSub server will be used:

# within channel
def handle_in("new:msg", %{"uid" => uid, "body" => body}, socket) do
  broadcast! socket, "new:msg", %{uid: uid, body: body}
  MyApp.Endpoint.broadcast! "rooms:superadmin", "new:msg", %{uid: uid, body: body}
  {:ok, socket}
end

# within controller
def create(conn, params) do
  ...
  MyApp.Endpoint.broadcast! "rooms:" <> rid, "new:msg", %{uid: uid, body: body}
  MyApp.Endpoint.broadcast! "rooms:superadmin", "new:msg", %{uid: uid, body: body}
  redirect conn, to: "/"
end
Source

Summary

broadcast!(socket, event, msg)

Same as Phoenix.Channel.broadcast/4, but raises Phoenix.PubSub.BroadcastError if broadcast fails

broadcast!(server, topic, event, message)
broadcast(socket, event, msg)

Broadcast event, serializable as JSON to channel

broadcast(server, topic, event, message)
broadcast_from!(socket, event, msg)

Same as Phoenix.Channel.broadcast_from/4, but raises Phoenix.PubSub.BroadcastError if broadcast fails

broadcast_from!(pubsub_server, socket, event, message)
broadcast_from!(pubsub_server, from, topic, event, message)
broadcast_from(socket, event, msg)

Broadcast event from pid, serializable as JSON to channel The broadcasting socket from, does not receive the published message. The event’s message must be a map serializable as JSON

broadcast_from(pubsub_server, socket, event, message)
broadcast_from(pubsub_server, from, topic, event, message)
reply(socket, event, message)

Sends Dict, JSON serializable message to socket

Functions

broadcast(socket, event, msg)

Broadcast event, serializable as JSON to channel

Examples

iex> Channel.broadcast "rooms:global", "new:message", %{id: 1, content: "hello"}
:ok
iex> Channel.broadcast socket, "new:message", %{id: 1, content: "hello"}
:ok
Source
broadcast(server, topic, event, message)
Source
broadcast!(socket, event, msg)

Same as Phoenix.Channel.broadcast/4, but raises Phoenix.PubSub.BroadcastError if broadcast fails

Source
broadcast!(server, topic, event, message)
Source
broadcast_from(socket, event, msg)

Broadcast event from pid, serializable as JSON to channel The broadcasting socket from, does not receive the published message. The event’s message must be a map serializable as JSON.

Examples

iex> Channel.broadcast_from self, "rooms:global", "new:message", %{id: 1, content: "hello"}
:ok
Source
broadcast_from(pubsub_server, socket, event, message)
Source
broadcast_from(pubsub_server, from, topic, event, message)
Source
broadcast_from!(socket, event, msg)

Same as Phoenix.Channel.broadcast_from/4, but raises Phoenix.PubSub.BroadcastError if broadcast fails

Source
broadcast_from!(pubsub_server, socket, event, message)
Source
broadcast_from!(pubsub_server, from, topic, event, message)
Source
reply(socket, event, message)

Sends Dict, JSON serializable message to socket

Source

Callbacks

handle_in/3

Specs:

Source
handle_out/3

Specs:

Source
join/3

Specs:

Source
leave/2

Specs:

Source