From 25b7cbd45364048c1be27805ee771249195f3f4d Mon Sep 17 00:00:00 2001 From: James Russo Date: Mon, 26 Oct 2026 19:39:07 +0000 Subject: [PATCH] feat(docs): download links carry the docs visitor's PostHog id (#6468) * feat(docs): download links carry the docs visitor's PostHog id hyperframes.dev counts each Studio download in PostHog, but it cannot read the docs' posthog cookie (another domain), so a download clicked here lands under an id with none of this visit's pageviews. A docs script now adds the id as ph_id when a download link is clicked, and puts the link back afterwards so a copied link never carries it. Co-Authored-By: Claude Opus 5.5 (1M context) * fix(docs): forward the PostHog id only to the download URL, and only with consent The id now goes only to https://hyperframes.dev/studio/download, the one address the docs link to, never another subdomain or plain http. It is not forwarded when PostHog is not running on the page, when the visitor opted out of PostHog capture, or when the browser sends Do Not Track or Global Privacy Control. Co-Authored-By: Claude Opus 5.5 (1M context) --------- Co-authored-by: Claude Opus 5.5 (1M context) --- docs/download-attribution.js | 90 ++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 docs/download-attribution.js diff --git a/docs/download-attribution.js b/docs/download-attribution.js new file mode 100644 index 0000000000..1e895eb8c3 --- /dev/null +++ b/docs/download-attribution.js @@ -0,0 +1,90 @@ +// Studio download links name this visitor's PostHog id (`ph_id`), so hyperframes.dev counts the download under the +// same person as these docs' pageviews. Both sites send to one PostHog project, but each domain has its own cookie, +// so the download site cannot read this one. Mintlify runs every .js file in docs/ on every page +// (mintlify.com/docs/customize/custom-scripts). The id is added at click time only, so a copied link never carries it, +// and only when PostHog is running here and the visitor has not opted out of it. +(function () { + var TOKEN = "phc_zjjbX0PnWxERXrMHhkEJWj9A9BhGVLRReICgsfTMmpx"; + var STATE = "ph_" + TOKEN + "_posthog"; + // posthog-js keeps a visitor's opt-out under this key: "0" opted out, "1" opted in. It lives in localStorage by + // default, in a cookie when a site configures that, so both are read. + var OPT_OUT = "__ph_opt_in_out_" + TOKEN; + // The one download address the docs link to (docs.json navbar and footer, quickstart.mdx). + var DOWNLOAD = { protocol: "https:", hostname: "hyperframes.dev", pathname: "/studio/download" }; + var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + + function cookie(name) { + var prefix = name + "="; + var raw = document.cookie.split("; ").find(function (c) { + return c.indexOf(prefix) === 0; + }); + return raw ? decodeURIComponent(raw.slice(prefix.length)) : null; + } + + function stored(name) { + try { + return window.localStorage.getItem(name); + } catch { + return null; + } + } + + // Mintlify loads posthog-js for this token from its own proxy (ph.mintlify.com/array//...). Without that + // script on the page PostHog is not running here, so nothing is forwarded. + function posthogLoaded() { + return Array.prototype.some.call(document.scripts, function (s) { + return s.src.indexOf("/array/" + TOKEN + "/") !== -1; + }); + } + + function optedOut() { + var choice = stored(OPT_OUT) || cookie(OPT_OUT); + if (choice !== null && String(choice).replace(/"/g, "") !== "1") return true; + // A browser-wide "do not track" or Global Privacy Control signal counts as no consent. + return navigator.globalPrivacyControl === true || navigator.doNotTrack === "1"; + } + + function posthogId() { + var raw = cookie(STATE) || stored(STATE); + if (!raw) return null; + try { + var id = JSON.parse(raw).distinct_id; + return typeof id === "string" && UUID.test(id) ? id : null; + } catch { + return null; + } + } + + function tag(event) { + var link = event.target instanceof Element && event.target.closest("a[href]"); + if (!link) return; + var url; + try { + url = new URL(link.href); + } catch { + return; + } + if ( + url.protocol !== DOWNLOAD.protocol || + url.hostname !== DOWNLOAD.hostname || + url.port !== "" || + url.pathname !== DOWNLOAD.pathname + ) { + return; + } + if (!posthogLoaded() || optedOut()) return; + var id = posthogId(); + if (!id) return; + var original = link.getAttribute("href"); + url.searchParams.set("ph_id", id); + link.href = url.toString(); + // Put the link back once the browser has followed it, so copying it later still leaves the id out. + setTimeout(function () { + link.setAttribute("href", original); + }, 0); + } + + // Capture phase, before the browser follows the link; auxclick covers a middle-click into a new tab. + document.addEventListener("click", tag, true); + document.addEventListener("auxclick", tag, true); +})();