berrypod/lib/berrypod_web/plugs/analytics.ex

113 lines
3.2 KiB
Elixir
Raw Normal View History

defmodule BerrypodWeb.Plugs.Analytics do
@moduledoc """
Plug that records a pageview event on every shop HTTP request.
This is Layer 1 of the progressive analytics pipeline it fires on every
GET request regardless of whether JS is enabled. Computes a privacy-friendly
visitor hash, parses the user agent, extracts referrer and UTM params, and
buffers the event for batch writing to SQLite.
Also stores analytics data in the session so the LiveView hook (Layer 2)
can read it for subsequent SPA navigations.
"""
import Plug.Conn
alias Berrypod.Analytics
alias Berrypod.Analytics.{Salt, UAParser, Referrer}
def init(opts), do: opts
def call(%{method: "GET"} = conn, _opts) do
ua = get_req_header(conn, "user-agent") |> List.first("")
{browser, os} = UAParser.parse(ua)
# Skip bots
if browser == "Bot" do
conn
else
# Skip if the logged-in admin is browsing
if admin?(conn) do
prepare_session(conn, ua, browser, os)
else
record_and_prepare(conn, ua, browser, os)
end
end
end
def call(conn, _opts), do: conn
defp record_and_prepare(conn, ua, browser, os) do
visitor_hash = Salt.hash_visitor(conn.remote_ip, ua)
{referrer, referrer_source} = extract_referrer(conn)
utm = extract_utms(conn)
country_code = get_session(conn, "country_code")
screen_size = get_session(conn, "analytics_screen_size")
Analytics.track_pageview(%{
pathname: conn.request_path,
visitor_hash: visitor_hash,
referrer: referrer,
referrer_source: referrer_source,
utm_source: utm.source,
utm_medium: utm.medium,
utm_campaign: utm.campaign,
country_code: country_code,
screen_size: screen_size,
browser: browser,
os: os
})
conn
|> put_session("analytics_visitor_hash", visitor_hash)
|> put_session("analytics_browser", browser)
|> put_session("analytics_os", os)
|> put_session("analytics_referrer", referrer)
|> put_session("analytics_referrer_source", referrer_source)
|> put_session("analytics_utm_source", utm.source)
|> put_session("analytics_utm_medium", utm.medium)
|> put_session("analytics_utm_campaign", utm.campaign)
end
# Store session data for the hook even when we skip recording for admins
defp prepare_session(conn, ua, browser, os) do
visitor_hash = Salt.hash_visitor(conn.remote_ip, ua)
conn
|> put_session("analytics_visitor_hash", visitor_hash)
|> put_session("analytics_browser", browser)
|> put_session("analytics_os", os)
end
defp admin?(conn) do
case conn.assigns[:current_scope] do
%{user: %{}} -> true
_ -> false
end
end
defp extract_referrer(conn) do
referrer_url = get_req_header(conn, "referer") |> List.first()
{referrer, source} = Referrer.parse(referrer_url)
# Don't count self-referrals
host = conn.host
if referrer && referrer == host do
{nil, nil}
else
{referrer, source}
end
end
defp extract_utms(conn) do
params = conn.query_params || %{}
%{
source: Map.get(params, "utm_source"),
medium: Map.get(params, "utm_medium"),
campaign: Map.get(params, "utm_campaign")
}
end
end