Skip to content

analytics: report referrer and traffic source in both analytics versions - #348

Open
MO-Thibault wants to merge 1 commit into
masterfrom
analytics-traffic-source
Open

MO-Thibault wants to merge 1 commit into
masterfrom
analytics-traffic-source

Conversation

@MO-Thibault

Copy link
Copy Markdown
Contributor

Summary

Adds two fields to the auction payload built by both analytics implementations, so we can tell where a session's traffic came from alongside the bid data we already collect.

  • referrer — the referring hostname only.
  • trafficSource — one of direct, internal, paid, campaign, email, organic_search, social, referral.

Touched in both places:

File Change
lib/core/traffic-source.ts New. getTrafficSource() returns { referrer, trafficSource }
lib/addons/prebid/analytics.ts Imports the helper, spreads both fields into witnessData
lib/addons/prototypes/analytics.js Same two fields, helper inlined
lib/addons/prebid/analytics.md Payload field list updated

Motivation and design notes

Three decisions worth a reviewer's attention:

First-touch per tab. document.referrer becomes the site's own hostname after the first internal click. Classifying on every event would report internal for every auction past the first pageview, which is useless. The first non-internal result is cached in sessionStorage under optableTrafficSource and reused for the rest of the session.

Hostname, not full URL. Only the referring hostname is reported. Full referring URLs can carry query strings with personal data. This also costs nothing in practice, since browsers default to strict-origin-when-cross-origin and usually only give us the origin anyway.

Named trafficSource, not source. The same payload already carries optableSources, meaning Optable data sources. Two fields called source and optableSources in one payload would be read as related when they are not.

The logic is duplicated rather than shared between the two files: lib/addons/prototypes/analytics.js is self-contained and imports no modules, so it gets an inlined copy with a comment pointing back at lib/core/traffic-source.ts.

Known limits

  • siteName() approximates the registrable domain with a short list of two-part suffixes rather than the public suffix list. Good enough to separate google.co.uk from co, not exact.
  • In-app browsers (Meta, TikTok) and sites sending Referrer-Policy: no-referrer produce an empty referrer, so some real social traffic lands in direct unless the link carries a click id. Expected, not a bug, but it matters when reading the numbers.
  • No dedicated unit test for traffic-source.ts. It is exercised at 57% through the analytics path. Several lib/core modules ship without tests, but the classification rules are the kind of thing worth pinning down if reviewers want it.

Test plan

  • pnpm build-lib (tsc) clean.
  • pnpm exec jest — 469 tests across 28 suites pass, including the 82 existing prebid analytics tests.
  • pnpm exec prettier --check clean on all four files.
  • ESLint is not installed in this repo, so it was not run.

🤖 Generated with Claude Code

Adds two fields to the auction payload built by lib/addons/prebid/analytics.ts
and lib/addons/prototypes/analytics.js:

- referrer: the referring hostname only, never the full referring URL, which
  can carry query strings with personal data.
- trafficSource: direct, internal, paid, campaign, email, organic_search,
  social or referral, derived from the referrer plus the campaign and click-id
  parameters on the landing URL.

Both are first-touch per tab. document.referrer becomes the site's own hostname
after the first internal click, so the first non-internal result is cached in
sessionStorage under optableTrafficSource and reused for the rest of the
session. Without that, every auction after the first pageview would report
internal.

The logic lives in lib/core/traffic-source.ts for the typed path and is
inlined in the prototype file, which is self-contained and imports no modules.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MO-Thibault
MO-Thibault requested review from a team as code owners September 19, 2026 18:48
// self-contained and pulls in no modules.
const TRAFFIC_SOURCE_KEY = "optableTrafficSource";
const SEARCH_SITES = ["google", "bing", "yahoo", "duckduckgo", "ecosia", "baidu", "yandex", "qwant", "startpage"];
const SOCIAL_SITES = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why duplicated? Can't we use the logic in one place traffic-source.ts?

@@ -0,0 +1,105 @@
const TRAFFIC_SOURCE_KEY = "optableTrafficSource";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add test suite for this file

const referrer = referrerHost();
const properties: TrafficSourceProperties = { referrer, trafficSource: classify(referrer) };

if (properties.trafficSource !== "internal") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this blokc on direct too?

if (medium === "email") return "email";
if (params.has("utm_source")) return "campaign";
if (!host) return "direct";
if (host === window.location.hostname) return "internal";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer this I think. Host exact match will lead to a lot of referal false positives

if (siteName(host) === siteName(window.location.hostname)) return "internal";

if (medium === "email") return "email";
if (params.has("utm_source")) return "campaign";
if (!host) return "direct";
if (host === window.location.hostname) return "internal";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flagged by claude I think relevant. We do utm check before internal so internal referal will get marked as campaign. Should move internal check to top

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants