From 38b3324627a9bd9169c784774abe917b83bdde3b Mon Sep 17 00:00:00 2001 From: Alex Leventer Date: Mon, 26 Oct 2026 19:19:22 -0700 Subject: [PATCH 1/2] Add PostHog for logged-out visitors outside the EEA/UK Restores the PostHog half of #2540, now that ui-ex #6104 and #6126 let docs.fly.io read /api/consent-scope with credentials. GA4 is left out. fly-analytics.js asks fly.io whether the visitor is logged in and whether they came through an EEA/UK edge, and loads PostHog only for logged-out visitors outside the EEA/UK. Anything else, including errors and preview hosts, loads nothing. docs.json gets a Legal footer linking the Privacy Policy. Refs superfly/docs-tracking#304 Co-Authored-By: Claude Opus 5.5 --- docs.json | 13 +++++++ fly-analytics.js | 99 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 fly-analytics.js diff --git a/docs.json b/docs.json index dc786faeb7..4dcdc08ec1 100644 --- a/docs.json +++ b/docs.json @@ -54,6 +54,19 @@ "domain": "docs.fly.io" } }, + "footer": { + "links": [ + { + "header": "Legal", + "items": [ + { + "label": "Privacy Policy", + "href": "https://fly.io/legal/privacy-policy/" + } + ] + } + ] + }, "navigation": { "tabs": [ { diff --git a/fly-analytics.js b/fly-analytics.js new file mode 100644 index 0000000000..0457c16aef --- /dev/null +++ b/fly-analytics.js @@ -0,0 +1,99 @@ +// PostHog for docs.fly.io, for logged-out visitors outside the EEA/UK. +// +// There is no consent banner here. EEA/UK visitors get nothing beyond Plausible (docs.json +// `integrations.plausible`, which sets nothing on the device). The banner lives on fly.io only. +// +// One self-contained file on purpose: Mintlify runs every .js in the content directory +// in no guaranteed order, so this loads its own PostHog bundle (the vendored +// no-external-code build that landing serves from fly.io) instead of relying on another file. +// +// docs.fly.io has no server, so it asks ui-ex's /api/consent-scope on fly.io (cross-origin, +// session cookie included; ui-ex allows exactly https://docs.fly.io) for: +// * eu_scope — did the visitor arrive via an EEA/UK edge? +// * authenticated — is the visitor logged in? +// Anything other than a clear "not EEA/UK, logged out" (including preview hosts, local +// `mint dev` and network errors) loads nothing. +(function () { + if (window.__flyAnalytics) return; + window.__flyAnalytics = true; + + var POSTHOG_KEY = "phc_nuaUijb3iLWut9Kbr56bSHSP4MYdeb8CFd9BgPm3LAVY"; + var POSTHOG_HOST = "https://posthog.fly.io"; + var POSTHOG_SRC = "https://fly.io/static/javascripts/posthog.js"; + var CONSENT_SCOPE_URL = "https://fly.io/api/consent-scope"; + + fetch(CONSENT_SCOPE_URL, { credentials: "include", headers: { Accept: "application/json" } }) + .then(function (r) { + return r.ok ? r.json() : null; + }) + .catch(function () { + return null; + }) + .then(function (data) { + if (!data || data.authenticated || data.eu_scope !== false) return; + loadPosthog(); + }); + + // Hosts we treat as "us" for referrer attribution: fly.io and any *.fly.io subdomain. + function isInternalHost(host, currentHost) { + return host === currentHost || host === "fly.io" || host.endsWith(".fly.io"); + } + + // Rewrite internal referrers to PostHog's "$direct" so moving between fly.io and the + // docs doesn't overwrite the real traffic source. Mirrors landing and ui-ex. + function scrubInternalReferrer(props, currentHost) { + if (!props) return; + var pairs = [ + ["$referring_domain", "$referrer"], + ["$initial_referring_domain", "$initial_referrer"], + ]; + for (var i = 0; i < pairs.length; i++) { + var domain = props[pairs[i][0]]; + if (typeof domain === "string" && domain !== "$direct" && isInternalHost(domain, currentHost)) { + props[pairs[i][0]] = "$direct"; + props[pairs[i][1]] = "$direct"; + } + } + } + + function beforeSend(event) { + if (!event || !event.properties) return event; + var currentHost = window.location.hostname; + scrubInternalReferrer(event.properties, currentHost); + scrubInternalReferrer(event.properties.$set_once, currentHost); + return event; + } + + function loadPosthog() { + loadScript(POSTHOG_SRC, function () { + if (typeof window.posthog === "undefined") return; + window.posthog.init(POSTHOG_KEY, { + api_host: POSTHOG_HOST, + ui_host: "https://eu.posthog.com", + disable_external_dependency_loading: true, + before_send: beforeSend, + autocapture: false, + capture_dead_clicks: false, + rageclick: false, + enable_heatmaps: false, + disable_session_recording: true, + disable_surveys: true, + capture_exceptions: false, + person_profiles: "identified_only", + // Mintlify navigates client-side, so count route changes as pageviews. + capture_pageview: "history_change", + capture_pageleave: true, + persistence: "localStorage+cookie", + advanced_disable_decide: true, + }); + }); + } + + function loadScript(src, onload) { + var s = document.createElement("script"); + s.async = true; + s.src = src; + if (onload) s.onload = onload; + document.head.appendChild(s); + } +})(); From f49bd13f74ce7e4e336e15834f50aef3e976b4c6 Mon Sep 17 00:00:00 2001 From: Alex Leventer Date: Mon, 26 Oct 2026 19:24:20 -0700 Subject: [PATCH 2/2] Use advanced_disable_flags instead of the deprecated advanced_disable_decide Co-Authored-By: Claude Opus 5.5 --- fly-analytics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fly-analytics.js b/fly-analytics.js index 0457c16aef..d147a61893 100644 --- a/fly-analytics.js +++ b/fly-analytics.js @@ -84,7 +84,7 @@ capture_pageview: "history_change", capture_pageleave: true, persistence: "localStorage+cookie", - advanced_disable_decide: true, + advanced_disable_flags: true, }); }); }