PhoenixKit.Admin.SimplePresence (phoenix_kit v2.37.1)

Copy Markdown View Source

Simple presence tracking system for PhoenixKit admin interface.

This is a lightweight alternative to Phoenix.Presence that works without requiring a full OTP application supervision tree.

Keys and multi-tab tracking

An authenticated visitor is tracked under user_key(user_uuid, session_id) ("user:<uuid>:<session_id>"), an anonymous one under "anonymous:<session_id>" — one ETS row per (identity, session), not per process. session_id is a stable per-login identifier that is the same across every LiveView mount sharing one login or one anonymous browser session, so opening a second tab, or mounting a second LiveView under the same session, tracks the SAME row instead of creating a second one: each track_* call adds its calling process's monitor to that row's monitor set rather than replacing the previous tab's. The row is deleted — and a *_session_disconnected event broadcast — only once its LAST monitor goes down; closing one tab while another is still open just shrinks the monitor set.

For an authenticated visitor tracked through PhoenixKitWeb.Users.Auth's on_mount hooks, session_id is a SHA-256 digest of the session token (Base.url_encode64/2, not the raw token or the raw live_socket_id itself) — stable per login for idempotent keying, but not reversible if it ends up somewhere it shouldn't (a log line, a PubSub event payload). A call site that still passes a raw session["live_socket_id"] (PhoenixKitWeb.Live.Dashboard.Overview's own local tracking call, which predates this scheme, and phoenix_kit_entities's own hook, a separate package) produces a DIFFERENT session_id for the same login until it is updated to match — a second, otherwise-idempotent row rather than a merge.

connected_at is pinned to the row's first appearance: a second tab merges its metadata into the existing row without resetting the timestamp.

This table is local to the BEAM node it runs on — presence does not merge across nodes in a multi-node deployment, so a visitor connected to node A is invisible to a "Live sessions" page served from node B.

Summary

Functions

Returns a specification to start this module under a supervisor.

Gets presence statistics.

Gets the presence topic name.

Lists all active sessions.

Lists anonymous sessions only.

Lists authenticated sessions only.

Starts the simple presence system.

Subscribes to presence events.

Tracks an anonymous session.

Tracks an authenticated user session.

Updates metadata for an existing presence.

Builds the idempotent ETS key for a user's presence row.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

get_presence_stats()

Gets presence statistics.

get_topic()

Gets the presence topic name.

list_active_sessions()

Lists all active sessions.

list_anonymous_sessions()

Lists anonymous sessions only.

list_authenticated_sessions()

Lists authenticated sessions only.

start_link(opts \\ [])

Starts the simple presence system.

subscribe()

Subscribes to presence events.

track_anonymous(session_id, metadata \\ %{})

Tracks an anonymous session.

track_user(user, metadata \\ %{})

Tracks an authenticated user session.

Idempotent by (user.uuid, metadata.session_id) — a second track_user/2 call for the same user and session (e.g. a second open tab, or another LiveView mounted under the same login) merges into the existing presence row instead of creating a second one or replacing the first tab's monitor.

update_metadata(key, metadata_updates)

Updates metadata for an existing presence.

user_key(user_uuid, session_id)

@spec user_key(String.t(), String.t() | nil) :: String.t()

Builds the idempotent ETS key for a user's presence row.

session_id: nil falls back to one row per user (pre-session-keying behavior) — a caller that tracks a user without a session_id still collides across tabs on that single row.