Phoenix.Channel behaviour

Defines a Phoenix Channel.

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

Topics & Callbacks

When clients join a channel, they do so by subscribing to a topic. Topics are string identifiers in the Phoenix.PubSub layer that allow multiple processes to subscribe and broadcast messages about a given 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 forward a message to all listeners with Phoenix.Channel.broadcast!/3, or push a message directly down the socket with Phoenix.Channel.push/3. Incoming callbacks must return the socket to maintain ephemeral state.

Here’s an example of receiving an incoming "new_msg" event from 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}
  {:noreply, socket}
end

You can also push a message directly down the socket:

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

Synchronous Replies

In addition to pushing messages out when you receive a handle_in event, you can also reply directly to a client event for request/response style messaging. This is useful when a client must know the result of an operation or to simply ack messages.

For example, imagine creating a resource and replying with the created record:

def handle_in("create:post", attrs, socket) do
  changeset = Post.changeset(%Post{}, attrs)

  if changeset.valid? do
    Repo.insert(changeset)
    {:reply, {:ok, changeset}, socket}
  else
    {:reply, {:error, changeset.errors}, socket}
  end
end

Alternatively, you may just want to ack the status of the operation:

def handle_in("create:post", attrs, socket) do
  changeset = Post.changeset(%Post{}, attrs)

  if changeset.valid? do
    Repo.insert(changeset)
    {:reply, :ok, socket}
  else
    {:reply, :error, socket}
  end
end

Outgoing Events

When an event is broadcasted with Phoenix.Channel.broadcast/3, each channel subscriber’s 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.

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

# for every socket subscribing to this topic, append an `is_editable`
# value for client metadata.
def handle_out("new_msg", msg, socket) do
  push socket, "new_msg", Dict.merge(msg,
    is_editable: User.can_edit_message?(socket.assigns[:user], msg)
  )
  {:noreply, socket}
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
  unless User.ignoring?(socket.assigns[:user], msg.user_id) do
    push socket, "user:joined", msg
  end
  {:noreply, socket}
end

By default, unhandled outgoing events are forwarded to each client as a push, 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 socket. 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}
  {:noreply, 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 a 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)
push(socket, event, message)

Sends Dict, JSON serializable message to socket

Functions

broadcast(socket, event, msg)

Broadcast event, serializable as JSON to a 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
push(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
terminate/2

Specs:

Source