# `PhoenixKit.HeaderWidget`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.64.1/lib/phoenix_kit/header_widget.ex#L1)

A sticky LiveView a module puts into the admin header, before the notifications bell
(`c:PhoenixKit.Module.header_widgets/0`).

A module returns tuples; core normalises them into this struct
(`PhoenixKit.ModuleRegistry.all_header_widgets/0`) and the admin layout renders each one
with `Phoenix.Component.live_render/3` (`sticky: true`, `id: id`).

## Fields

  * `:live_view` - the LiveView module to render.
  * `:id` - the DOM id of the nested LiveView (not one the admin layout uses itself, see
    `reserved_ids/0`). Stable: a sticky LiveView keeps its state
    (and its PubSub subscriptions) across admin navigation only while the id is the same.
    Two widgets with one id cannot both be rendered; the first one registered wins.
  * `:session_fun` - `(layout_assigns -> session)`, called on every render of the
    layout with the layout's assigns. They include `:socket` (the parent LiveView's
    socket), `:phoenix_kit_current_scope` and `:phoenix_kit_current_user` (the user of
    the scope, filled in when the caller passed only the scope), `:current_locale`,
    `:current_locale_base` and `:impersonating?` (see `impersonating?/1`). It returns the
    session of the nested LiveView: a map with **string** keys and values that survive
    being signed into the page, so ids and strings, not structs. Or `nil`: no widget for
    this user, nothing is rendered (and no dock) and nothing is logged. Check what the
    user may see here, where the scope is at hand, rather than after the widget's process
    has started.
  * `:dock` - `true` when the widget uses the dock column (`#pk-dock`, see
    `PhoenixKitWeb.Components.AdminDock`). The column exists only while a rendered widget
    declares it.
  * `:module` - the module that declared the widget. Filled in by the registry.
  * `:session` - the result of `:session_fun`, filled in by
    `PhoenixKit.ModuleRegistry.header_widgets_for/1`.

## Authority

The session is signed, not encrypted: whoever has the page can read it. Ids and strings the
widget needs, never a secret or a token.

The session is all the widget gets: a nested sticky LiveView is mounted without the
`live_session`'s `on_mount` hooks and without `current_scope`, and for every signed-in user
of an admin-layout page. The widget has to check the permission itself from the session
(see `c:PhoenixKit.Module.header_widgets/0`).

The keys `user_token`, `pk_session_accounts`, `live_socket_id` and `phoenix_kit_session_uuid`
are reserved (`reserved_session_keys/0`): they belong to the cookie session, which LiveView
merges under the signed session of the nested LiveView, so a value here would hide the real
one from the session and impersonation checks. They are dropped from the session (one
warning per module and widget, without the values).

`session_fun` receives the layout's assigns, **not** the cookie session and not the session
token. A widget that needs the session reference or an impersonation check does it itself
in its connected `mount/3`: LiveView merges the cookie session from `connect_info` into the
session of every LiveView, so `PhoenixKit.Users.Auth.SessionRef.from_session/1` and
`PhoenixKitWeb.Users.MultiSession.impersonated?/1` can be called there on the `session`
argument (both arrive with the agent-execute PR; this branch does not depend on them).
`:impersonating?` in the assigns is a hint for rendering: it is read from
`scope.multi_session_accounts` and is always `false` while multi-session is off, so it is
not a guarantee. Keep secrets out of the session map: a nested LiveView's session is
signed, not encrypted.

# `entry`

```elixir
@type entry() ::
  {live_view :: module(), id :: String.t(), session_fun()}
  | {live_view :: module(), id :: String.t(), session_fun(),
     opts :: [{:dock, boolean()}]}
```

What `c:PhoenixKit.Module.header_widgets/0` returns, one element per widget.

# `session_fun`

```elixir
@type session_fun() :: (map() -&gt; map() | nil)
```

# `t`

```elixir
@type t() :: %PhoenixKit.HeaderWidget{
  dock: boolean(),
  id: String.t(),
  live_view: module(),
  module: module() | nil,
  session: map() | nil,
  session_fun: session_fun()
}
```

# `impersonating?`

```elixir
@spec impersonating?(PhoenixKit.Users.Auth.Scope.t() | nil) :: boolean()
```

True while the account the session acts as is not the root account of the browser's
multi-session stack, which is what an impersonation ("log in as this user") looks like.

The layout passes this as `:impersonating?` to the session functions. It is an
over-approximation: the stack does not tell an impersonated account from one the user
added with a password, so both read as `true`. False without a stack (the
`multi_session_enabled` setting off, or a single account). A widget that must not run for
an operator borrowing a user's session returns `nil` from its session function when it is
`true`.

# `new`

```elixir
@spec new(term()) :: {:ok, t()} | {:error, String.t()}
```

Normalise one entry of `c:PhoenixKit.Module.header_widgets/0`.

`{live_view, id, session_fun}` or `{live_view, id, session_fun, opts}` with
`opts = [dock: boolean]`. Returns `{:error, reason}` (a string) for anything else.

# `reserved_ids`

```elixir
@spec reserved_ids() :: [String.t()]
```

The DOM ids of the admin layout that a widget may not use.

# `reserved_session_keys`

```elixir
@spec reserved_session_keys() :: [String.t()]
```

The session keys a widget's session function cannot set: they are dropped.

# `resolve_session`

```elixir
@spec resolve_session(t(), map()) ::
  {:ok, t(), dropped_keys :: [String.t()]} | :skip | {:error, String.t()}
```

Run the widget's session function against the layout's assigns.

Returns `{:ok, widget}` with `:session` filled in, `:skip` when the function returned
`nil` (no widget for this user), or `{:error, reason}` when the function raises or returns
something `live_render/3` cannot take (not a map, or a key that is not a string).

---

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