PhoenixKitWebAnalytics.LiveHook (PhoenixKitWebAnalytics v0.5.0)

Copy Markdown View Source

Everything a visitor does on a LiveView page, recorded server-side from the socket — no client-side script.

PhoenixKitWebAnalytics.Plug sees HTTP requests, which covers the first load of a page. Once a LiveView is connected, the visitor's clicks, form submits and navigations travel over the websocket instead, and that's where this hook sits. It records:

  • Page views for live navigation — push_navigate, push_patch, <.link navigate> / <.link patch> change the URL without an HTTP request, so the plug never sees them.
  • Interactions — every phx-click, phx-submit, phx-keydown … event the LiveView handles, by event name ("add_to_cart", "save"). Form contents are never recorded; see "What an interaction stores" below.
  • Leaving — the page is registered with PhoenixKitWebAnalytics.LivePresence, which records a "leave" event with the time spent on the page when the LiveView process ends, and powers the "on the site right now" view.

Attach it in the host's live_session, after whatever mounts the current user, so logged-in visitors are attributed:

live_session :public,
  on_mount: [
    {PhoenixKitWeb.Users.Auth, :phoenix_kit_mount_current_scope},
    {PhoenixKitWebAnalytics.LiveHook, :track_navigation}
  ] do
  live "/", HomeLive
  live "/pricing", PricingLive
end

Requires connect_info on the socket

The hook needs the same two inputs the plug has — client IP and User-Agent — because they feed the daily visitor hash. Without them a LiveView navigation would hash to a different visitor than the page load that preceded it, inflating the visitor count. Rather than record data it knows to be wrong, the hook does nothing unless the endpoint provides them:

socket "/live", Phoenix.LiveView.Socket,
  websocket: [connect_info: [:peer_data, :x_headers, :user_agent, session: @session_options]],
  longpoll: [connect_info: [:peer_data, :x_headers, :user_agent, session: @session_options]]

The keys must be listed on both transports: LiveView falls back to long polling when a websocket can't be opened (a corporate proxy, a flaky network), and a visitor on the fallback transport is invisible to this hook if only websocket: carries them. If :peer_data or :user_agent is missing the hook stays inert and only full page loads are counted — check this first if LiveView activity isn't showing up.

:x_headers is what lets the hook see the visitor behind a reverse proxy: when the socket's peer is a private or loopback address (the proxy), the visitor's address is the forwarded one, read by the same rule as the plug (see "Client IP" in PhoenixKitWebAnalytics.Plug). Without :x_headers such a socket can't name its visitor — a proxy, a container network and localhost in development look the same — so the hook stays inert for it — no page view, no interaction, no "Right now" entry — and counts the skip; Settings then shows a warning. With :x_headers listed and no proxy in front (development, a LAN), the peer is the visitor. The same headers carry an x-accept-language, when a proxy sets one, for the visitor's language.

Not double-counted

A page load is an ordinary HTTP response the plug already recorded, so the handle_params that follows the connected mount is not counted again. A live navigation is told apart by LiveView's _live_referer connect parameter, which the client sends only when it arrived by push_navigate / <.link navigate>. A reconnect (a deploy, a dropped network) remounts with _mounts > 0 and is never counted — the visitor didn't go anywhere.

What an interaction stores

The event name, the page it happened on, and — only for parameter names listed in the web_analytics_event_params setting (tab, view, step … by default) — short scalar values, so "switched to the pricing tab" is visible. Everything else in the params is discarded before it leaves the LiveView process: form fields, free text, uploads.

Form typing (phx-change, recognisable by its "_target" parameter) is not recorded at all, and neither are event names listed in the web_analytics_ignore_events setting. The same event repeated within a second on one page counts once, so a double-click or a key held down is one interaction.

Events handled by a LiveComponent (phx-target={@myself}) run in the component, not the LiveView, and are not seen by this hook.

Do Not Track

The socket can't see request headers, so the plug notes a DNT: 1 / Sec-GPC: 1 visitor in the session and this hook reads it from there; such a visitor's LiveView activity is not recorded either. That needs the plug to run after :fetch_session, as it does in a standard :browser pipeline.

Summary

Functions

on_mount callback. Use :track_navigation.

Functions

on_mount(atom, params, session, socket)

@spec on_mount(
  :track_navigation,
  map() | :not_mounted_at_router,
  map(),
  Phoenix.LiveView.Socket.t()
) ::
  {:cont, Phoenix.LiveView.Socket.t()}

on_mount callback. Use :track_navigation.