What the server already knows about a request, extracted.
A Phoenix app gets the IP, the user agent, the Referer, the language and
the privacy headers for free, on every request, before a single byte of
JavaScript runs. That is most of an analytics record, and it is the reason
pixelex is server-first: the JS tracker adds screen size, scroll depth and
engagement time, and nothing else that matters.
Bot filtering
ua_inspector returns a distinct %UAInspector.Result.Bot{} for crawlers,
which is the whole of bot detection here. Unfiltered, a busy site's "traffic"
is substantially Googlebot, AhrefsBot and a long tail of scrapers, and every
number derived from it is wrong in the same direction.
Degrading without ua_inspector
It is an optional dependency, because it pulls hackney ~> 1.0 and hackney
1.25.0 carries unpatched advisories with no fix in the 1.x line. Without it,
browser/os/device_type are nil and bot detection falls back to a
short substring list that catches the well-behaved crawlers and misses the
rest. mode/0 reports which is running; the installer adds the dependency by
default and warn_once/0 says so at boot when it is absent.
Summary
Functions
Is this user agent something other than a human in a browser?
Browser, OS, device type and whether this is a crawler.
:ua_inspector when the real database is available, :heuristic when degraded.
Split a URL into the parts stored separately.
Logs once, at boot, when an optional enrichment dependency is missing.
Types
Functions
Is this user agent something other than a human in a browser?
Covers crawlers and HTTP libraries alike. Asked by the browser ingest endpoint before it does any other work.
Deliberately not asked by Pixelex.track/3: a mobile SDK sends okhttp or
Dart/3.x, which are libraries by exactly this definition, and a server-side
call was made on purpose by the host application. Filtering there would drop
real events to catch traffic that never arrives that way.
Browser, OS, device type and whether this is a crawler.
Never raises: a user agent is attacker-controlled input, and the correct outcome for an unparseable one is an under-described event, not a failed request.
@spec mode() :: :ua_inspector | :heuristic
:ua_inspector when the real database is available, :heuristic when degraded.
@spec url_parts(String.t() | nil) :: %{ url: String.t() | nil, pathname: String.t() | nil, hostname: String.t() | nil }
Split a URL into the parts stored separately.
pathname is stored without the query string: query strings carry click ids,
session tokens and occasionally personal data, and a "top pages" report that
lists ten thousand variants of /product?utm_content=… is not a report.
The full URL is kept for attribution, which needs the parameters.
@spec warn_once() :: :ok
Logs once, at boot, when an optional enrichment dependency is missing.