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
+12
-3
@@ -15,7 +15,7 @@ import { EpisodeAudioPlayer } from './components/EpisodeAudioPlayer'
|
||||
import { NuggetsHostPortal } from './components/NuggetsHostPortal'
|
||||
import { BIBLE_BOOKS } from '../shared/bible-books.js'
|
||||
import './App.css'
|
||||
import { sendEvent, useScrollDepthTracking, useTimeOnPage, useUTMCapture, useLinkClickTracking } from './analytics'
|
||||
import { sendEvent, useScrollDepthTracking, useTimeOnPage, useUTMCapture, useLinkClickTracking, checkAnalyticsConsentResponse } from './analytics'
|
||||
|
||||
const CONSENT_KEY = 'vbn_analytics_consent_choice'
|
||||
const HEADLINER_WIDGET_ID = 'WI_cmou3b4q7000701p0o9qmmcfj'
|
||||
@@ -28,7 +28,16 @@ function ScrollToTop() {
|
||||
|
||||
function usePageTracking() {
|
||||
const location = useLocation()
|
||||
const isFirstRun = useRef(true)
|
||||
useEffect(() => {
|
||||
// The initial hard page load is already counted server-side by the
|
||||
// hit-counting middleware in server.js (it sees the raw HTML request).
|
||||
// Only client-side route changes (no new HTTP request) need reporting
|
||||
// here — otherwise every fresh visit/refresh gets double-counted.
|
||||
if (isFirstRun.current) {
|
||||
isFirstRun.current = false
|
||||
return
|
||||
}
|
||||
if (localStorage.getItem(CONSENT_KEY) !== 'accepted') return
|
||||
const referrer = document.referrer || ''
|
||||
const controller = new AbortController()
|
||||
@@ -39,7 +48,7 @@ function usePageTracking() {
|
||||
body: JSON.stringify({ path: location.pathname, referrer }),
|
||||
keepalive: true,
|
||||
signal: controller.signal,
|
||||
}).catch(() => {}).finally(() => clearTimeout(timeout))
|
||||
}).then(checkAnalyticsConsentResponse).catch(() => {}).finally(() => clearTimeout(timeout))
|
||||
}, [location.pathname])
|
||||
}
|
||||
|
||||
@@ -1794,7 +1803,7 @@ function LatestEpisodePlayer({ content }: { content: SiteContent }) {
|
||||
listenStartRef.current = Date.now()
|
||||
if (!playTrackedRef.current && title) {
|
||||
playTrackedRef.current = true
|
||||
fetch('/api/analytics/play', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title }) }).catch(() => {})
|
||||
fetch('/api/analytics/play', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title }) }).then(checkAnalyticsConsentResponse).catch(() => {})
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user