Phoenix v1.4.0 Phoenix.Presence behaviour View Source
Provides Presence tracking to processes and channels.
This behaviour provides presence features such as fetching
presences for a given topic, as well as handling diffs of
join and leave events as they occur in real-time. Using this
module defines a supervisor and a module that implements the
Phoenix.Tracker
behaviour that uses Phoenix.PubSub
to
broadcast presence updates.
In case you want to use only a subset of the functionality
provided by Phoenix.Presence
, such as tracking processes
but without broadcasting updates, we recommend that you look
at the Phoenix.Tracker
functionality from the phoenix_pubsub
project.
Example Usage
Start by defining a presence module within your application
which uses Phoenix.Presence
and provide the :otp_app
which
holds your configuration, as well as the :pubsub_server
.
defmodule MyApp.Presence do
use Phoenix.Presence, otp_app: :my_app,
pubsub_server: MyApp.PubSub
end
The :pubsub_server
must point to an existing pubsub server
running in your application, which is included by default as
MyApp.PubSub
for new applications.
Next, add the new supervisor to your supervision tree in lib/my_app.ex
:
children = [
...
MyApp.Presence,
]
Once added, presences can be tracked in your channel after joining:
defmodule MyApp.MyChannel do
use MyAppWeb, :channel
alias MyApp.Presence
def join("some:topic", _params, socket) do
send(self(), :after_join)
{:ok, assign(socket, :user_id, ...)}
end
def handle_info(:after_join, socket) do
push(socket, "presence_state", Presence.list(socket))
{:ok, _} = Presence.track(socket, socket.assigns.user_id, %{
online_at: inspect(System.system_time(:second))
})
{:noreply, socket}
end
end
In the example above, the current presence information for
the socket’s topic is pushed to the client as a "presence_state"
event.
Next, Presence.track
is used to register this
channel’s process as a presence for the socket’s user ID, with
a map of metadata.
Finally, a diff of presence join and leave events will be sent to the
client as they happen in real-time with the “presence_diff” event.
The diff structure will be a map of :joins
and :leaves
of the form:
%{joins: %{"123" => %{metas: [%{status: "away", phx_ref: ...}]},
leaves: %{"456" => %{metas: [%{status: "online", phx_ref: ...}]},
See Phoenix.Presence.list/2
for more information on the presence
data structure.
Fetching Presence Information
Presence metadata should be minimized and used to store small,
ephemeral state, such as a user’s “online” or “away” status.
More detailed information, such as user details that need to
be fetched from the database, can be achieved by overriding the fetch/2
function. The fetch/2
callback is triggered when using list/1
and serves as a mechanism to fetch presence information a single time,
before broadcasting the information to all channel subscribers.
This prevents N query problems and gives you a single place to group
isolated data fetching to extend presence metadata. The function must
return a map of data matching the outlined Presence data structure,
including the :metas
key, but can extend the map of information
to include any additional information. For example:
def fetch(_topic, presences) do
query =
from u in User,
where: u.id in ^Map.keys(presences),
select: {u.id, u}
users = query |> Repo.all() |> Enum.into(%{})
for {key, %{metas: metas}} <- presences, into: %{} do
{key, %{metas: metas, user: users[key]}}
end
end
The function above fetches all users from the database who
have registered presences for the given topic. The fetched
information is then extended with a :user
key of the user’s
information, while maintaining the required :metas
field from the
original presence data.
Link to this section Summary
Callbacks
Extend presence information with additional data
Returns presences for a topic
Returns presences for a topic
Track a channel’s process as a presence
Track an arbitary process as a presence
Stop tracking a channel’s process
Stop tracking a process
Update a channel presence’s metadata
Update a process presence’s metadata
Link to this section Types
Link to this section Functions
Returns the map of presence metadata for a topic-key pair.
Examples
Uses the same data format as Phoenix.Presence.list/2
, but only
returns metadata for the presences under a topic and key pair. For example,
a user with key "user1"
, connected to the same chat room "room:1"
from two
devices, could return:
iex> MyPresence.get_by_key("room:1", "user1")
%{name: "User 1", metas: [%{device: "Desktop"}, %{device: "Mobile"}]}
Like Phoenix.Presence.list/2
, the presence metadata is passed to the fetch
callback of your presence module to fetch any additional information.
Returns presences for a topic.
Presence data structure
The presence information is returned as a map with presences grouped by key, cast as a string, and accumulated metadata, with the following form:
%{key => %{metas: [%{phx_ref: ..., ...}, ...]}}
For example, imagine a user with id 123
online from two
different devices, as well as a user with id 456
online from
just one device. The following presence information might be returned:
%{"123" => %{metas: [%{status: "away", phx_ref: ...},
%{status: "online", phx_ref: ...}]},
"456" => %{metas: [%{status: "online", phx_ref: ...}]}}
The keys of the map will usually point to a resource ID. The value
will contain a map with a :metas
key containing a list of metadata
for each resource. Additionally, every metadata entry will contain a
:phx_ref
key which can be used to uniquely identify metadata for a
given key. In the event that the metadata was previously updated,
a :phx_ref_prev
key will be present containing the previous
:phx_ref
value.
Link to this section Callbacks
Extend presence information with additional data.
When list/1
is used to list all presences of the given topic
, this
callback is triggered once to modify the result before it is broadcasted to
all channel subscribers. This avoids N query problems and provides a single
place to extend presence metadata. You must return a map of data matching the
original result, including the :metas
key, but can extend the map to include
any additional information.
The default implementation simply passes presences
through unchanged.
Example
def fetch(_topic, presences) do
query =
from u in User,
where: u.id in ^Map.keys(presences),
select: {u.id, u}
users = query |> Repo.all() |> Enum.into(%{})
for {key, %{metas: metas}} <- presences, into: %{} do
{key, %{metas: metas, user: users[key]}}
end
end
list(socket :: Phoenix.Socket.t()) :: presences()
list(topic()) :: presences()
Returns presences for a topic.
Calls list/2
with presence module.
list(socket :: Phoenix.Socket.t()) :: presences()
list(topic()) :: presences()
Returns presences for a topic.
Calls list/2
with presence module.
track(socket :: Phoenix.Socket.t(), key :: String.t(), meta :: map()) :: {:ok, ref :: binary()} | {:error, reason :: term()}
Track a channel’s process as a presence.
Tracked presences are grouped by key
, cast as a string. For example, to
group each user’s channels together, use user IDs as keys. Each presence can
be associated with a map of metadata to store small, emphemeral state, such as
a user’s online status. To store detailed information, see fetch/2
.
Example
alias MyApp.Presence
def handle_info(:after_join, socket) do
{:ok, _} = Presence.track(socket, socket.assigns.user_id, %{
online_at: inspect(System.system_time(:second))
})
{:noreply, socket}
end
Track an arbitary process as a presence.
Same with track/3
, except track any process by topic
and key
.
untrack(socket :: Phoenix.Socket.t(), key :: String.t()) :: :ok
Stop tracking a channel’s process.
Stop tracking a process.
Update a channel presence’s metadata.
Replace a presence’s metadata by passing a new map or a function that takes the current map and returns a new one.
Update a process presence’s metadata.
Same as update/3
, but with an arbitary process.