Spec-Zone.ru › Phoenix

Presence

Исходный код

Требование: предполагается, что вы ознакомились с вводными руководствами и запустили приложение Phoenix для работы.

Требование: предполагается, что вы ознакомились с руководством по каналам.

Phoenix Presence — это функция, позволяющая регистрировать информацию о процессах в топике и прозрачно реплицировать её по кластеру. Она сочетает в себе серверную и клиентскую библиотеки, что упрощает её внедрение. Простой пример использования — отображение пользователей, которые в данный момент находятся онлайн в приложении.

Phoenix Presence уникальна по ряду причин. У неё нет единой точки отказа и единого источника истины, она полностью полагается на стандартную библиотеку, не имеет эксплуатационных зависимостей и способна к самовосстановлению.

Настройка

Мы будем использовать Presence, чтобы отслеживать, какие пользователи подключены к серверу, и отправлять обновления клиенту при входе и выходе пользователей. Мы будем передавать эти обновления через Phoenix Channels. Для начала создадим RoomChannel, как мы делали в руководствах по каналам:

$ mix phx.gen.channel Room

Выполните шаги, предложенные генератором, и можно приступать к отслеживанию присутствия.

Генератор Presence

Чтобы начать работу с Presence, сначала нужно сгенерировать модуль Presence. Для этого можно использовать задачу mix phx.gen.presence:

$ mix phx.gen.presence
* creating lib/hello_web/channels/presence.ex

Add your new module to your supervision tree,
in lib/hello/application.ex:

    children = [
      ...
      HelloWeb.Presence,
    ]

You're all set! See the Phoenix.Presence docs for more details:
https://phoenix.hexdocs.pm/Phoenix.Presence.html

Если открыть файл lib/hello_web/channels/presence.ex, мы увидим следующую строку:

use Phoenix.Presence,
  otp_app: :hello,
  pubsub_server: Hello.PubSub

Эта строка настраивает модуль Presence, определяя функции, необходимые для отслеживания присутствия. Как упоминалось в описании задачи генератора, следует добавить этот модуль в дерево супервизии в application.ex:

children = [
  ...
  HelloWeb.Presence,
]

Использование с Channels и JavaScript

Затем создадим канал, через который будем передавать данные о присутствии. После подключения пользователя мы можем отправить по каналу список присутствующих, а затем отслеживать подключение. Также можно указать карту с дополнительной информацией для отслеживания.

defmodule HelloWeb.RoomChannel do
  use Phoenix.Channel
  alias HelloWeb.Presence

  def join("room:lobby", %{"name" => name}, socket) do
    send(self(), :after_join)
    {:ok, assign(socket, :name, name)}
  end

  def handle_info(:after_join, socket) do
    {:ok, _} =
      Presence.track(socket, socket.assigns.name, %{
        online_at: inspect(System.system_time(:second))
      })

    push(socket, "presence_state", Presence.list(socket))
    {:noreply, socket}
  end
end

Наконец, можно использовать клиентскую библиотеку Presence, включённую в phoenix.js, для управления состоянием и изменениями присутствия, поступающими через сокет. Она прослушивает события "presence_state" и "presence_diff" и предоставляет простой callback onSync, с помощью которого можно обрабатывать события по мере их возникновения.

Callback onSync позволяет легко реагировать на изменения состояния присутствия, что чаще всего приводит к повторному отображению обновлённого списка активных пользователей. Метод list можно использовать для форматирования и возврата каждого отдельного объекта присутствия в соответствии с потребностями приложения.

Для перебора пользователей мы используем функцию presences.list(), которая принимает callback. Callback будет вызван для каждого элемента присутствия с двумя аргументами: идентификатором присутствия и списком метаданных (по одному для каждого объекта присутствия с этим идентификатором). Это позволяет отображать пользователей и количество устройств, с которых они находятся онлайн.

Проверить работу Presence можно, добавив следующий код в assets/js/app.js:

import {Socket, Presence} from "phoenix"

let socket = new Socket("/socket", {authToken: window.userToken})
let channel = socket.channel("room:lobby", {name: window.location.search.split("=")[1]})
let presence = new Presence(channel)

function renderOnlineUsers(presence) {
  let response = ""

  presence.list((id, {metas: [first, ...rest]}) => {
    let count = rest.length + 1
    response += `<br>${id} (count: ${count})</br>`
  })

  document.querySelector("main").innerHTML = response
}

socket.connect()

presence.onSync(() => renderOnlineUsers(presence))

channel.join()

Проверить результат можно, открыв три вкладки браузера. Если в двух вкладках перейти по адресу http://localhost:4000/?name=Alice, а в третьей — по адресу http://localhost:4000/?name=Bob, мы увидим:

Alice (count: 2)
Bob (count: 1)

Если закрыть одну из вкладок Alice, счётчик должен уменьшиться до 1. Если закрыть ещё одну вкладку, пользователь должен полностью исчезнуть из списка.

Обеспечение безопасности

В первоначальной реализации мы передаём имя пользователя в URL. Однако во многих системах доступ к функциональности Presence должен быть разрешён только вошедшим в систему пользователям. Для этого настройте аутентификацию по токену, как описано в разделе об аутентификации по токену в руководстве по каналам.

При использовании аутентификации по токену следует обращаться к socket.assigns.user_id, заданному в UserSocket, а не к socket.assigns.name, полученному из параметров.

Использование с LiveView

Хотя Phoenix предоставляет JavaScript API для работы с присутствием, модуль HelloWeb.Presence также можно расширить для поддержки LiveView.

При работе с LiveView важно учитывать, что каждый LiveView является процессом с состоянием. Поэтому, если хранить состояние Presence в LiveView, каждый процесс LiveView будет содержать в памяти полный список пользователей онлайн. Вместо этого можно отслеживать пользователей онлайн в процессе Presence и передавать отдельные события в LiveView, который сможет обновлять список онлайн-пользователей с помощью потока.

Для начала нужно обновить файл lib/hello_web/channels/presence.ex и добавить несколько необязательных callback-функций в модуль HelloWeb.Presence.

Сначала добавим callback init/1. Он позволяет отслеживать состояние Presence внутри процесса.

  def init(_opts) do
    {:ok, %{}}
  end

В модуле Presence также предусмотрен callback fetch/2, позволяющий изменять данные, полученные из Presence, и задавать форму ответа. В данном случае мы добавляем карту id и карту user.

  def fetch(_topic, presences) do
    for {key, %{metas: [meta | metas]}} <- presences, into: %{} do
      # user can be populated here from the database here we populate
      # the name for demonstration purposes
      {key, %{metas: [meta | metas], id: meta.id, user: %{name: meta.id}}}
    end
  end

Осталось добавить callback handle_metas/4. Он обновляет состояние, отслеживаемое в HelloWeb.Presence, при входе и выходе пользователей.

  def handle_metas(topic, %{joins: joins, leaves: leaves}, presences, state) do
    for {user_id, presence} <- joins do
      user_data = %{id: user_id, user: presence.user, metas: Map.fetch!(presences, user_id)}
      msg = {__MODULE__, {:join, user_data}}
      Phoenix.PubSub.local_broadcast(Hello.PubSub, "proxy:#{topic}", msg)
    end

    for {user_id, presence} <- leaves do
      metas =
        case Map.fetch(presences, user_id) do
          {:ok, presence_metas} -> presence_metas
          :error -> []
        end

      user_data = %{id: user_id, user: presence.user, metas: metas}
      msg = {__MODULE__, {:leave, user_data}}
      Phoenix.PubSub.local_broadcast(Hello.PubSub, "proxy:#{topic}", msg)
    end

    {:ok, state}
  end

Как видно, мы рассылаем события о входе и выходе пользователей. Их будет обрабатывать процесс LiveView. Также обратите внимание, что при рассылке событий входа и выхода мы используем канал «proxy». Это нужно, чтобы процесс LiveView не получал события Presence напрямую. Можно добавить несколько вспомогательных функций, чтобы скрыть эту особенность реализации от модуля LiveView.

  def list_online_users(), do: list("online_users") |> Enum.map(fn {_id, presence} -> presence end)

  def track_user(name, params), do: track(self(), "online_users", name, params)

  def subscribe(), do: Phoenix.PubSub.subscribe(Hello.PubSub, "proxy:online_users")

Теперь, когда модуль Presence настроен и рассылает события, можно создать LiveView. Создайте новый файл lib/hello_web/live/online/index.ex со следующим содержимым:

defmodule HelloWeb.OnlineLive do
  use HelloWeb, :live_view

  def mount(params, _session, socket) do
    socket = stream(socket, :presences, [])
    socket =
    if connected?(socket) do
      HelloWeb.Presence.track_user(params["name"], %{id: params["name"]})
      HelloWeb.Presence.subscribe()
      stream(socket, :presences, HelloWeb.Presence.list_online_users())
    else
       socket
    end

    {:ok, socket}
  end

  def render(assigns) do
    ~H"""
    <ul id="online_users" phx-update="stream">
      <li :for={{dom_id, %{id: id, metas: metas}} <- @streams.presences} id={dom_id}>{id} ({length(metas)})</li>
    </ul>
    """
  end

  def handle_info({HelloWeb.Presence, {:join, presence}}, socket) do
    {:noreply, stream_insert(socket, :presences, presence)}
  end

  def handle_info({HelloWeb.Presence, {:leave, presence}}, socket) do
    if presence.metas == [] do
      {:noreply, stream_delete(socket, :presences, presence)}
    else
      {:noreply, stream_insert(socket, :presences, presence)}
    end
  end
end

Если добавить этот маршрут в lib/hello_web/router.ex:

    live "/online/:name", OnlineLive, :index

Затем перейдите по адресу http://localhost:4000/online/Alice в одной вкладке и по адресу http://localhost:4000/online/Bob в другой — вы увидите, что присутствие отслеживается, а также отображается количество подключений для каждого пользователя. Открытие и закрытие вкладок с разными пользователями будет обновлять список присутствующих в реальном времени.

← Предыдущая страница Каналы
Следующая страница → Введение в тестирование

Пакет Hex Предпросмотр Hex (текущий файл) Скачать версию ePub

Создано с помощью ExDoc (v0.40.3) для языка программирования Elixir

© 2014 Chris McCord
Licensed under the MIT License.
https://phoenix.hexdocs.pm/presence.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API