PhoenixKit.HeaderWidget (phoenix_kit v2.64.1)

Copy Markdown View Source

A sticky LiveView a module puts into the admin header, before the notifications bell (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 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.

Summary

Types

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

t()

Functions

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 DOM ids of the admin layout that a widget may not use.

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

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

Types

entry()

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

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

session_fun()

@type session_fun() :: (map() -> map() | nil)

t()

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

Functions

impersonating?(arg1)

@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(arg1)

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

Normalise one entry of 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()

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

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

reserved_session_keys()

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

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

resolve_session(widget, assigns)

@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).