berrypod/lib/berrypod_web/analytics_hook.ex

101 lines
3.7 KiB
Elixir
Raw Normal View History

defmodule BerrypodWeb.AnalyticsHook do
@moduledoc """
LiveView on_mount hook for analytics Layer 2 of the progressive pipeline.
Reads analytics data from the session (set by the Plugs.Analytics plug) and
tracks subsequent SPA navigations via handle_params. Skips the initial mount
since the plug already recorded that pageview.
Also handles the `analytics:screen` event from the JS hook (Layer 3) to
capture screen width for device classification.
"""
import Phoenix.Component, only: [assign: 3]
import Phoenix.LiveView, only: [attach_hook: 4, connected?: 1]
alias Berrypod.Analytics
def on_mount(:track, _params, session, socket) do
socket =
socket
|> assign(:analytics_visitor_hash, session["analytics_visitor_hash"])
|> assign(:analytics_browser, session["analytics_browser"])
|> assign(:analytics_os, session["analytics_os"])
|> assign(:analytics_referrer, session["analytics_referrer"])
|> assign(:analytics_referrer_source, session["analytics_referrer_source"])
|> assign(:analytics_utm_source, session["analytics_utm_source"])
|> assign(:analytics_utm_medium, session["analytics_utm_medium"])
|> assign(:analytics_utm_campaign, session["analytics_utm_campaign"])
|> assign(:analytics_screen_size, session["analytics_screen_size"])
|> assign(:analytics_country_code, session["country_code"])
|> assign(:analytics_initial_mount, true)
socket =
if connected?(socket) and socket.assigns.analytics_visitor_hash do
socket
|> attach_hook(:analytics_params, :handle_params, &handle_analytics_params/3)
|> attach_hook(:analytics_events, :handle_event, &handle_analytics_event/3)
else
socket
end
{:cont, socket}
end
defp handle_analytics_params(_params, uri, socket) do
# Skip the initial mount — the plug already recorded this pageview
if socket.assigns.analytics_initial_mount do
{:cont, assign(socket, :analytics_initial_mount, false)}
else
# Skip if admin user is browsing
if admin?(socket) do
{:cont, socket}
else
pathname = URI.parse(uri).path
Analytics.track_pageview(%{
pathname: pathname,
visitor_hash: socket.assigns.analytics_visitor_hash,
referrer: socket.assigns.analytics_referrer,
referrer_source: socket.assigns.analytics_referrer_source,
utm_source: socket.assigns.analytics_utm_source,
utm_medium: socket.assigns.analytics_utm_medium,
utm_campaign: socket.assigns.analytics_utm_campaign,
country_code: socket.assigns.analytics_country_code,
screen_size: socket.assigns.analytics_screen_size,
browser: socket.assigns.analytics_browser,
os: socket.assigns.analytics_os
})
# Clear referrer/UTMs after first SPA navigation — they only apply to the entry
{:cont,
socket
|> assign(:analytics_referrer, nil)
|> assign(:analytics_referrer_source, nil)
|> assign(:analytics_utm_source, nil)
|> assign(:analytics_utm_medium, nil)
|> assign(:analytics_utm_campaign, nil)}
end
end
end
defp handle_analytics_event("analytics:screen", %{"width" => width}, socket)
when is_integer(width) do
screen_size = classify_screen(width)
{:cont, assign(socket, :analytics_screen_size, screen_size)}
end
defp handle_analytics_event(_event, _params, socket), do: {:cont, socket}
defp classify_screen(width) when width < 768, do: "mobile"
defp classify_screen(width) when width < 1024, do: "tablet"
defp classify_screen(_width), do: "desktop"
defp admin?(socket) do
case socket.assigns[:current_scope] do
%{user: %{}} -> true
_ -> false
end
end
end