Pixelex.LiveView (Pixelex v0.1.0)

Copy Markdown View Source

Page views from a LiveView, including the ones a page reload never happens for.

live_session :default, on_mount: [Pixelex.LiveView] do
  live "/", HomeLive
  live "/pricing", PricingLive
end

What makes LiveView different

Three navigations that look the same to a user and nothing like each other to the server:

in the templateserverclient
<.link href=>full HTTP request, new mountnormal page load
<.link navigate=>old LiveView dismounted, new one mounted, no HTTP requestphx:navigate
<.link patch=>same LiveView, handle_params/3 onlyphx:navigate

Two of the three never reach a Plug, which is why a request-counting plug alone reports a LiveView app as a one-page site. This hook attaches to handle_params, which is the one callback all three run through.

Counting each visitor once — and why this hook needs Pixelex.Plug

mount/3 and handle_params/3 both run twice on a page load: once for the dead render (plain HTML over HTTP) and again when the socket connects. Count both and every human is two visitors.

The division of labour:

  • Pixelex.Plug records the dead render. It runs inside the HTTP request, so it has the real client IP, user agent and Referer. This is also what counts crawlers and clients whose socket never opens.
  • This hook records connected renders, and the initial one is dropped by Pixelex.Sessions.first_view?/3 as a duplicate of what the plug just wrote — same visitor, same path, within seconds.
  • live_patch and live_navigate change the path, so they pass the duplicate check and are counted. Neither ever reaches a plug.

The hook deliberately ignores dead renders, and this is not an optimisation. get_connect_info/2 returns nothing during a dead render, so there is no peer address to hash — the visitor would come out as a different person from the one the plug recorded a moment earlier, and the duplicate check would never fire. Every page load would count twice.

So plug Pixelex.Plug in your endpoint is required, not optional, for a LiveView app. Without it, initial page views are not counted at all.

Reconnects

A dropped WiFi connection, a closed laptop lid, or a rolling deploy re-runs mount/3 for every connected client. Left alone, a deploy inflates page views by the size of the connected user base — a number that looks like growth. LiveView sends _mounts in the connect params: 0 on a client's first mount and higher on every rejoin after it, so a rejoin is skipped.

The referrer is only there once

Referer arrives on the initial HTTP request and never again — the WebSocket has no such header. Pixelex.Plug stashes it in the session, and this hook reads it back, so attribution survives the socket upgrade.

Options

on_mount: {Pixelex.LiveView, site_id: "shop", skip: &MyApp.admin?/1}

Summary

Functions

Is this mount a client rejoining, rather than arriving?

Record an event from inside a LiveView.

Functions

rejoin?(arg1)

@spec rejoin?(map()) :: boolean()

Is this mount a client rejoining, rather than arriving?

LiveView sends _mounts in the connect params: 0 the first time a client mounts a page and higher on every rejoin after it — a dropped connection, a closed laptop lid, a rolling deploy. Every one of those re-runs mount/3 for every connected client, so without this check a deploy inflates page views by the size of the connected user base, which looks exactly like growth.

Public because Phoenix.LiveViewTest overrides _mounts with its own value and ignores put_connect_params/2 for that key, so this branch cannot be reached through a simulated mount. Untestable code in the path of every page view is not acceptable; a documented function is.

track(socket, name, props)

Record an event from inside a LiveView.

Pixelex.LiveView.track(socket, "booking_completed", %{value: 1499.0})