# `PhoenixKit.Admin.SimplePresence`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.37.1/lib/phoenix_kit/admin/simple_presence.ex#L1)

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.

# `child_spec`

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`

Starts the simple presence system.

# `subscribe`

Subscribes to presence events.

# `track_anonymous`

Tracks an anonymous session.

# `track_user`

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`

Updates metadata for an existing presence.

# `user_key`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
