*
* It records one thing: which page this visitor opened, and where they came
* from. That is the half of the customer's path we never had — the article
* that sent them, the affiliate link, the product pages they compared before
* the checkout form (which is tracked separately and in far more detail).
*
* Three decisions worth knowing:
*
* THE BROWSER ID IS A FIRST-PARTY COOKIE, not a fingerprint. It is the same
* id the checkout form uses, which is what lets a journey join up with the
* order it ends in; on a browser that clears cookies the journey simply
* starts again, and that is the honest outcome.
*
* IT SENDS THE FULL URL, fragment included. The Ads management campaign uses
* `#via=` links, and a fragment never reaches a server on its own — read
* here or nowhere.
*
* IT NEVER BLOCKS THE PAGE. `sendBeacon` where available, a keepalive fetch
* otherwise, no retries, and every failure is swallowed: a tracker that
* costs a visitor a page load costs more than the data is worth.
*/
(function () {
"use strict";
// A page that loads the script twice — a layout and the view it extends
// both including it, two bundles on one site — would otherwise report every
// page view twice, and the journey would read as if the visitor kept
// reloading. Cheaper to make that impossible than to de-duplicate later.
if (window.__erClickTracker) return;
window.__erClickTracker = true;
// On our own form hosts the beacon stays first-party and goes to a path
// with nothing for a filter list to match — `/track/clicks` is exactly the
// kind of URL EasyPrivacy exists to stop, same-origin or not. The absolute
// address remains for pages that load this file from another origin (the
// buy.* SPA), where a relative path would point at the wrong server.
var ENDPOINT = /^form\./.test(location.hostname)
? "/api/e/c"
: "https://form.e-residence.com/api/track/clicks";
/**
* The address the page was OPENED with.
*
* Set by a one-line inline snippet at the very top of
, before any
* other script runs. It has to be, because the checkout rewrites the URL to
* `?id=` while it boots — and a deferred tracker reading
* `location.href` afterwards sees the rewritten address and loses the
* `?via=` the visitor actually arrived with. That is exactly how a referral
* click straight to the form went unrecorded on 2026-08-24.
*/
var entryUrl = window.__erEntryUrl || location.href;
var COOKIE = "er_bid";
var YEAR = 365 * 24 * 60 * 60;
function readCookie(name) {
var match = document.cookie.match(new RegExp("(^|;\\s*)" + name + "=([^;]*)"));
return match ? decodeURIComponent(match[2]) : null;
}
function writeCookie(name, value) {
// Root domain so form.e-residence.com and e-residence.com are one visitor;
// Lax because nothing here is cross-site, and this is not an auth cookie.
var host = location.hostname.split(".").slice(-2).join(".");
document.cookie =
name + "=" + encodeURIComponent(value) +
";path=/;max-age=" + YEAR + ";domain=." + host + ";SameSite=Lax" +
(location.protocol === "https:" ? ";Secure" : "");
}
function browserId() {
var existing = readCookie(COOKIE);
if (existing) return existing;
var id =
window.crypto && crypto.randomUUID
? crypto.randomUUID()
: String(Date.now()) + "-" + Math.random().toString(16).slice(2);
writeCookie(COOKIE, id);
return id;
}
function device() {
var w = window.innerWidth || 0;
if (w && w < 640) return "mobile";
if (w && w < 1024) return "tablet";
return "desktop";
}
function send(payload) {
var body = JSON.stringify(payload);
try {
if (navigator.sendBeacon) {
// text/plain, not application/json, and this is the whole difference
// between working and silently doing nothing: the marketing site and
// the tracking endpoint are different origins, application/json is not
// a CORS-safelisted content type, and a beacon that needs a preflight
// is dropped by the browser without a word. The server reads the raw
// body, so the label costs us nothing.
navigator.sendBeacon(
ENDPOINT,
new Blob([body], { type: "text/plain;charset=UTF-8" })
);
return;
}
fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "text/plain;charset=UTF-8" },
body: body,
keepalive: true,
credentials: "omit",
}).catch(function () {});
} catch (e) {
/* a tracker never breaks a page */
}
}
/** The last URL we reported, so a double-fired navigation is not a click. */
var reported = null;
/** The page before this one within the journey. */
var previous = null;
function track() {
if (location.href === reported) return;
reported = location.href;
send({
browser_id: browserId(),
clicks: [
{
// The entry address for the first report of this page load; after
// that the tracker is following real navigations and `location`
// is the truth.
url: entryUrl || location.href,
referrer: document.referrer || null,
session_id: sessionId(),
language: (navigator.language || "").slice(0, 8),
device: device(),
// The page before this one WITHIN the journey — `document.referrer`
// is empty on same-tab SPA navigation, and that is exactly where the
// path is most interesting.
previous_url: previous,
ago_ms: 0,
},
],
});
previous = location.href;
entryUrl = null;
}
/** One id per tab, so a journey can also be read session by session. */
function sessionId() {
try {
var existing = sessionStorage.getItem("er_sid");
if (existing) return existing;
var id =
window.crypto && crypto.randomUUID
? crypto.randomUUID()
: String(Date.now()) + "-" + Math.random().toString(16).slice(2);
sessionStorage.setItem("er_sid", id);
return id;
} catch (e) {
return null;
}
}
track();
// Single-page navigations (the Astro site and the React forms both do them)
// never fire another page load, so the journey would stop at the entry page.
var lastUrl = location.href;
["pushState", "replaceState"].forEach(function (method) {
var original = history[method];
history[method] = function () {
var result = original.apply(this, arguments);
if (location.href !== lastUrl) {
lastUrl = location.href;
track();
}
return result;
};
});
window.addEventListener("popstate", function () {
if (location.href !== lastUrl) {
lastUrl = location.href;
track();
}
});
})();