Chapter 19

プレゼンス

koga1020
koga1020
2021.11.23に更新

プレゼンス

前提: このガイドでは、入門ガイドの内容を理解し、Phoenixアプリケーションを起動していることを前提としています

前提: このガイドでは、チャンネルガイドの内容を前提としています

Phoenix Presenceは、トピックのプロセス情報を登録し、クラスター全体に透過的に複製する機能です。サーバーサイドとクライアントサイドの両方のライブラリを組み合わせたもので、実装が簡単です。簡単な使用例としては、アプリケーションで現在オンラインになっているユーザーを表示することが挙げられます。

Phoenix Presenceが特別なのは、いくつかの理由があります。単一障害点がなく、信頼できる唯一の情報源(SSOT)がなく、標準ライブラリに完全に依存しており、運用上の依存性がなく、自己回復できます。

セットアップ

Presenceを使って、どのユーザーがサーバーに接続しているかを追跡し、ユーザーが参加したり離脱したりするとクライアントにアップデートを送信します。更新情報はPhoenix Channelsで配信します。そこで、チャンネルガイドで行ったように、RoomChannel を作成しましょう。

$ mix phx.gen.channel Room

ジェネレーターの後のステップに従えば、プレゼンスの追跡を開始する準備が整います。

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:
http://hexdocs.pm/phoenix/Phoenix.Presence.html

lib/hello_web/channels/presence.ex ファイルを開くと、次のような行があります。

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

これでプレゼンス用のモジュールが設定され、プレゼンスの追跡に必要な関数が定義されました。ジェネレータータスクで述べたように、このモジュールを application.ex にあるスーパーバイザーツリーに追加する必要があります。

children = [
  ...
  HelloWeb.Presence,
]

次に、プレゼンスを伝達するためのチャンネルを作成します。ユーザーが参加した後、プレゼンスのリストをチャネルにプッシュし、コネクションを追跡できます。また、追跡するための追加情報のマップを提供することもできます。

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

最後に、phoenix.js に含まれるクライアントサイドのPresenceライブラリを使用して、ソケットから送られてくるステートとプレゼンスの差分を管理できます。このライブラリは "presence_state""presence_diff" イベントをリッスンし、イベントが発生したときにそれを処理するためのシンプルなコールバック onSync を提供します。

onSync コールバックを使用すると、プレゼンス状態の変更に簡単に対応できます。これはほとんどの場合、アクティブユーザーの更新されたリストを再レンダリングすることになります。list メソッドを使用して、アプリケーションのニーズに基づいて個々のプレゼンスをフォーマットして返すことができます。

ユーザーを反復処理するには、コールバックを受け付ける presences.list() 関数を使います。コールバックは各プレゼンスに対して2つの引数、プレゼンスIDとメタのリスト(そのプレゼンスIDへ対応するプレゼンスごとに1つずつ)を指定して呼び出されます。これを使ってユーザーとオンラインになっているデバイスの数を表示します。

以下を assets/js/app.js に追加することで、プレゼンスが動作していることを確認できます。

import {Socket, Presence} from "phoenix"

let socket = new Socket("/socket", {params: {token: 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[role=main]").innerHTML = response
}

socket.connect()

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

channel.join()

3つのブラウザータブを開くことで、これが動作していることを確認できます。2つのブラウザータブで http://localhost:4000/?name=Alice に移動し、 http://localhost:4000/?name=Bob に移動すると、次のように表示されるはずです。

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

アリスタブの1つを閉じればカウントは1に減り、別のタブを閉じればリストから完全に消えるはずです。

安全性の確保

初期の実装では、ユーザーの名前をURLの一部として渡しています。しかし、多くのシステムでは、ログインしているユーザーのみがプレゼンス機能にアクセスできるようにしたいと考えます。そのためには、トークン認証を設定する必要があります。チャンネルガイドのトークン認証のセクションで詳しく説明されています

トークン認証では、パラメーターから設定した socket.assigns.name ではなく、UserSocket で設定した socket.assigns.user_id にアクセスする必要があります。