Pixelex.Enrich (Pixelex v0.1.0)

Copy Markdown View Source

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

device()

@type device() :: %{
  browser: String.t() | nil,
  os: String.t() | nil,
  device_type: String.t() | nil,
  client_type: String.t() | nil,
  bot?: boolean()
}

Functions

bot?(user_agent)

@spec bot?(String.t() | nil) :: boolean()

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.

device(user_agent)

@spec device(String.t() | nil) :: device()

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.

mode()

@spec mode() :: :ua_inspector | :heuristic

:ua_inspector when the real database is available, :heuristic when degraded.

url_parts(url)

@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.

warn_once()

@spec warn_once() :: :ok

Logs once, at boot, when an optional enrichment dependency is missing.