drab v0.10.5 Drab.Presence View Source

Conveniences for Phoenix.Presence.

Provides Phoenix Presence module for Drab, along with some helper functions.

Installation

It is disabled by default, to enable it, add :presence to config.exs.

config :drab, :presence, true

Next, add Drab.Presence to your supervision tree in lib/my_app_web.ex:

children = [
  ...
  Drab.Presence,
]

Please also ensure that there otp_app and endpoint for this app are configured correctly:

config :drab, MyAppWeb.Endpoint,
  otp_app: :my_app_web

In multiple endpoint configuration, you need to specify which endpoint to use with Drab.Presence:

config :drab, :presence, endpoint: MyAppWeb.Endpoint

Usage

When installed, system tracks the presence over every Drab topic, both static topics configured by Drab.Commander.broadcasting/1 and runtime topics run by Drab.Commander.subscribe/2. The default ID of the presence list is a browser UUID (Drab.Browser.id/1):

iex> Drab.Presence.list socket
%{
  "2bd34ffc-b365-46a9-9479-474b628364ed" => %{
    metas: [%{online_at: 1520417565, phx_ref: ...}]
  }
}

The ID may also be taken from Plug Session or from the Drab Store. For example, you have a :current_user_id stored in the session, you may want to use it as an id with id config option:

config :drab, :presence, id: [session: :current_user_id]

So this ID will become the key of the presence map:

iex> Drab.Presence.list socket
%{
  "42" => %{
    metas: [%{online_at: 1520417565, phx_ref: ...}]
  }
}

Notice that system transforms the key value to the binary string.

The similar would be with the Drab Store:

config :drab, :presence, id: [store: :current_user_id]

There is also a possibility to specify both store and session. In this case the order matters: if you put store first, it will take a value from the store, and it is not found, from session.

config :drab, :presence, id: [store: :current_user_id, session: :current_user_id]
config :drab, :presence, id: [session: :current_user_id, store: :current_user_id]

Example

Here we are going to show how to display number of connected users online. The solution is to broadcast every connect and disconnect using Commander’s callbacks. Thus, in the commander:

defmodule MyAppWeb.MyCommander
  use Drab.Commander
  import Drab.Presence

  broadcasting "global"
  onconnect :connected
  ondisconnect :disconnected

  def connected(socket) do
    broadcast_html socket, "#number_of_users", count_users(socket)
  end

  def disconnected(_store, _session) do
    topic = same_topic("global")
    broadcast_html topic, "#number_of_users", count_users(topic)
  end
end

Notice the difference between connected and disconnected callbacks. In the first case we could use the default topic derived from the socket, but after disconnect socket does not longer exists.

Own Presence module

You may also want to provide your own presence module, for example to override Phoenix.Presence.fetch/2 function. In this case, add your module to children list and configure Drab to run it:

config :drab, :presence, module: MyAppWeb.MyPresence

You module must provide start/2 function, which will be launched by Drab on the client connect, along with stop/2, which runs when user unsubscribe from the topic.

defmodule MyAppWeb.MyPresence do
  use Phoenix.Presence, otp_app: :my_app, pubsub_server: MyApp.PubSub

  def start(socket, topic) do
    client_id = Drab.Browser.id!(socket)
    track(socket.channel_pid, topic, client_id, %{online_at: System.system_time(:seconds)})
  end

  def stop(socket, topic) do
    client_id = Drab.Browser.id!(socket)
    untrack(socket.channel_pid, topic, client_id)
  end
end

Link to this section Summary

Functions

Returns the number of total connections to the topic

Counts the number of connected unique users or browsers

Extend presence information with additional data

Callback implementation for Phoenix.Presence.init/1

Returns presences for a channel

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 Functions

Link to this function count_connections(topic) View Source
count_connections(Phoenix.Socket.t() | String.t()) :: integer()

Returns the number of total connections to the topic.

Link to this function count_users(topic) View Source
count_users(Phoenix.Socket.t() | String.t()) :: integer()

Counts the number of connected unique users or browsers.

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

Callback implementation for Phoenix.Presence.fetch/2.

Link to this function handle_diff(diff, state) View Source

Callback implementation for Phoenix.Presence.handle_diff/2.

Callback implementation for Phoenix.Presence.init/1.

Returns presences for a channel.

Calls list/2 with presence module.

Callback implementation for Phoenix.Presence.list/1.

Callback implementation for Phoenix.Presence.start_link/1.

Link to this function track(socket, key, meta) View Source

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

Callback implementation for Phoenix.Presence.track/3.

Link to this function track(pid, topic, key, meta) View Source

Track an arbitary process as a presence.

Same with track/3, except track any process by topic and key.

Callback implementation for Phoenix.Presence.track/4.

Stop tracking a channel’s process.

Callback implementation for Phoenix.Presence.untrack/2.

Link to this function untrack(pid, topic, key) View Source

Stop tracking a process.

Callback implementation for Phoenix.Presence.untrack/3.

Link to this function update(socket, key, meta) View Source

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.

Callback implementation for Phoenix.Presence.update/3.

Link to this function update(pid, topic, key, meta) View Source

Update a process presence’s metadata.

Same as update/3, but with an arbitary process.

Callback implementation for Phoenix.Presence.update/4.