From 72f024df64cc2c737f86811171cfc621342449d0 Mon Sep 17 00:00:00 2001 From: Pedro Paulo Vezza Campos Date: Fri, 29 May 2026 13:09:36 -0700 Subject: [PATCH] Stop client telemetry from sending cookies to App Insights MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit navigator.sendBeacon forces credentials mode "include" with no opt-out, so it replayed the third-party cookies the Azure ingestion endpoint sets (ai_user, browserId) on every /v2.1/track POST. Our envelopes carry their own operationId/pageviewId, so a cookie is never needed for correlation. Send via a keepalive fetch with credentials:"omit" instead — the modern beacon equivalent that survives page unload (keepalive) and stays off the critical request chain (priority:"low", the reason beacon was used before), while being the only transport that lets us drop credentials. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/telemetry.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/lib/telemetry.ts b/src/lib/telemetry.ts index c92e3fa..2dbe633 100644 --- a/src/lib/telemetry.ts +++ b/src/lib/telemetry.ts @@ -71,17 +71,17 @@ function send(envelopes: Envelope[]): void { if (!config || envelopes.length === 0) return; const url = `${config.ingestionEndpoint}/v2.1/track`; const payload = envelopes.map((e) => JSON.stringify(e)).join("\n"); - // sendBeacon is a background-priority transport, so the ingestion POST stays - // off the page's critical request chain. A plain fetch defaults to High - // priority, which Lighthouse counts as critical (this slow westus2 round-trip - // was dominating the chain). Beacon also reliably survives page unload, which - // is exactly what the visibilitychange/pagehide flush relies on. - if (typeof navigator.sendBeacon === "function") { - const blob = new Blob([payload], { type: "text/plain" }); - if (navigator.sendBeacon(url, blob)) return; - } - // Fallback for the rare browser without sendBeacon: an explicitly - // low-priority keepalive fetch so the request still stays off the chain. + // Deliberately NOT navigator.sendBeacon: beacon forces credentials mode + // "include" with no opt-out, so it replays the third-party cookies the Azure + // ingestion endpoint sets (ai_user, browserId) on every /v2.1/track POST. Our + // envelopes carry their own operationId/pageviewId, so we never need a cookie + // and shouldn't be leaking one to a third party. + // + // A keepalive fetch is the modern beacon equivalent and the only transport + // that lets us drop credentials: `credentials: "omit"` strips the cookies, + // `keepalive: true` survives page unload (what the visibilitychange/pagehide + // flush relies on), and `priority: "low"` keeps the slow westus2 round-trip + // off the page's critical request chain (the reason beacon was used before). fetch(url, { method: "POST", body: payload,