A Plug.Conn turned into a pipeline context.
This is where pixelex's server-first stance pays off: the IP, user agent, referrer, language and privacy headers are all already here, on every request, before any JavaScript has run. Nothing needs to be asked of the browser to produce a complete analytics record — the tracker adds screen size and engagement, and nothing else that matters.
Client IP behind a proxy
conn.remote_ip is the load balancer's address on Fly, Heroku, Cloudflare
and behind nginx — so every visitor hashes to the same id and a site sees
one visitor forever. Configure the header the proxy actually sets:
config :pixelex, client_ip_header: "fly-client-ip"x-forwarded-for is supported and its left-most entry is taken. That
entry is client-controlled, so it is only trusted when explicitly
configured; the default reads conn.remote_ip and is wrong-but-safe rather
than forgeable.
Country
Taken from a CDN header when one is present — cf-ipcountry,
fly-client-country, x-vercel-ip-country are checked by default, and
:country_header overrides. No GeoIP database is bundled: the hosts these
apps run on already do the lookup, and shipping a 70MB database plus its
licence to duplicate that would be a poor trade.
Summary
Functions
Build a context from a connection.
The visitor's real IP, honouring a configured proxy header.
Consent signals: the privacy headers, plus whatever the host's banner recorded.
Functions
@spec build( Plug.Conn.t(), keyword() ) :: map()
Build a context from a connection.
Options
:site_id— required unless resolvable from the host:render— defaults to:dead:url,:referrer— override what the connection says, for the ingest endpoint where the real page is in the payload rather than the request
@spec client_ip(Plug.Conn.t()) :: String.t() | nil
The visitor's real IP, honouring a configured proxy header.
@spec consent( Plug.Conn.t(), keyword() ) :: Pixelex.Consent.signals()
Consent signals: the privacy headers, plus whatever the host's banner recorded.