cookie-banner: attempt sendBeacon even when async flush is in-flight

The early return from flushSync() when this.flushing was true skipped
the synchronous sendBeacon path, dropping pending reports during page
unload. Move the flushing guard after the sendBeacon attempt so the
unload-safe transport is always tried; only the keepalive-fetch
fallback is gated on the mutex.

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-05-11 14:32:47 +04:00
parent 733b793991
commit b81795e62e

View File

@@ -239,6 +239,12 @@ export class ReportQueue {
// unload time -- still strictly better than the previous behaviour,
// which lost the entire last debounce window.
//
// sendBeacon is always attempted even when an async `flush()` is
// in-flight (`this.flushing === true`). The server handles
// duplicates idempotently. The keepalive-fetch fallback is only
// used when the async flush mutex is free to avoid concurrent
// fetch lifecycles conflicting over the `flushing` flag.
//
// Items are only removed from `pending` once the transport
// confirms delivery: sendBeacon synchronously returns true when
// the browser has accepted ownership of the request, and the
@@ -248,7 +254,6 @@ export class ReportQueue {
// `visibilitychange:hidden` fires but the page is restored from
// bfcache rather than truly unloading.
private flushSync(): void {
if (this.flushing) return;
if (this.pending.size === 0) return;
const { keys, body } = this.takeBatch();
@@ -268,6 +273,10 @@ export class ReportQueue {
}
}
// Fall back to keepalive fetch only when no async flush owns the
// mutex — sendBeacon above is the primary unload-safe path.
if (this.flushing) return;
if (typeof fetch === "function") {
this.flushing = true;
try {