Fix analytics data-loss bugs and clean up the admin dashboard; v2.2.4
Publish Container / build (push) Successful in 16m3s
Publish Container / build (push) Successful in 16m3s
- Self-heal consent desync: if the server's consent cookie and the client's localStorage flag fall out of sync, the client now notices and stops silently sending events into a void. - Stop double-counting hard page loads in hit/visitor stats. - Flush pending disk writes on SIGTERM/SIGINT so redeploys don't drop the last few events. - Log rate-limiter drops once per window (diagnoses proxy misconfig). - Widen the bot-detection allowlist to avoid flagging real browsers (Chrome-iOS, Samsung Browser, etc.) as bots. - Stop merging distinct visitors who share an IP when a real session cookie is already present — only fall back to IP correlation when no cookie was sent at all. - Include analyticsEvents/episodePlays in backup snapshots and restore them, so a restore no longer wipes engagement analytics. - Regroup admin dashboard KPI cards by time scope (range vs all-time) and remove a duplicated Top Countries/States block. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8496e227b3
commit
d1a68754a1
9 files changed
+183
-45
No files matched your search
+14
-1
@@ -24,6 +24,19 @@ function hasConsent(): boolean {
|
||||
} catch { /* sessionStorage unavailable */ }
|
||||
})()
|
||||
|
||||
// The client gates on a localStorage flag; the server gates on a cookie.
|
||||
// If the cookie is lost (cleared separately, dropped by the browser on a
|
||||
// plain-HTTP deployment, etc.) while localStorage still says "accepted",
|
||||
// the client would otherwise keep believing tracking is on and silently
|
||||
// lose every event forever. Self-heal by clearing the stale flag so the
|
||||
// consent banner reappears instead of leaking data indefinitely.
|
||||
export async function checkAnalyticsConsentResponse(res: Response): Promise<void> {
|
||||
try {
|
||||
const data = await res.json()
|
||||
if (data?.reason === 'no-consent') localStorage.removeItem(CONSENT_KEY)
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
export function sendEvent(type: string, data: Record<string, unknown>): void {
|
||||
if (!hasConsent()) return
|
||||
fetch('/api/analytics/event', {
|
||||
@@ -31,7 +44,7 @@ export function sendEvent(type: string, data: Record<string, unknown>): void {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type, ...data }),
|
||||
keepalive: true,
|
||||
}).catch(() => {})
|
||||
}).then(checkAnalyticsConsentResponse).catch(() => {})
|
||||
}
|
||||
|
||||
// --- Scroll depth ---
|
||||
|
||||
Reference in new issue
Block a user