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
@@ -1064,6 +1064,8 @@ export async function createBackupSnapshot(reason = 'scheduled') {
|
||||
publishState: state.publishState,
|
||||
hitStats: state.hitStats,
|
||||
visitorStats: state.visitorStats,
|
||||
analyticsEvents: state.analyticsEvents,
|
||||
episodePlays: state.episodePlays,
|
||||
contactSubmissions: state.contactSubmissions,
|
||||
studyCommunityPosts: state.studyCommunityPosts,
|
||||
replyTemplates: state.replyTemplates,
|
||||
@@ -1179,6 +1181,8 @@ export async function restoreFromBackup(filename) {
|
||||
|
||||
state.hitStats = sanitizeLoadedHitStats(parsed?.hitStats)
|
||||
state.visitorStats = sanitizeLoadedVisitorStats(parsed?.visitorStats)
|
||||
state.analyticsEvents = sanitizeLoadedAnalyticsEvents(parsed?.analyticsEvents)
|
||||
state.episodePlays = sanitizeLoadedEpisodePlays(parsed?.episodePlays)
|
||||
state.contactSubmissions = sanitizeLoadedContactSubmissions(parsed?.contactSubmissions)
|
||||
state.replyTemplates = sanitizeReplyTemplates(parsed?.replyTemplates)
|
||||
state.replyHistory = sanitizeReplyHistory(parsed?.replyHistory)
|
||||
@@ -1186,6 +1190,8 @@ export async function restoreFromBackup(filename) {
|
||||
|
||||
queueHitStatsWrite()
|
||||
queueVisitorStatsWrite()
|
||||
queueAnalyticsEventsWrite()
|
||||
queueEpisodePlaysWrite()
|
||||
queueContactSubmissionsWrite()
|
||||
queueReplyTemplatesWrite()
|
||||
queueReplyHistoryWrite()
|
||||
@@ -1194,6 +1200,8 @@ export async function restoreFromBackup(filename) {
|
||||
await Promise.all([
|
||||
state.hitStatsWritePromise,
|
||||
state.visitorStatsWritePromise,
|
||||
state.analyticsEventsWritePromise,
|
||||
state.episodePlaysWritePromise,
|
||||
state.contactSubmissionsWritePromise,
|
||||
state.replyTemplatesWritePromise,
|
||||
state.replyHistoryWritePromise,
|
||||
@@ -1298,6 +1306,23 @@ function sanitizeLoadedHitStats(value) {
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeLoadedAnalyticsEvents(value) {
|
||||
return {
|
||||
scrollDepth: value?.scrollDepth && typeof value.scrollDepth === 'object' ? value.scrollDepth : {},
|
||||
timeOnPage: value?.timeOnPage && typeof value.timeOnPage === 'object' ? value.timeOnPage : {},
|
||||
outboundClicks: value?.outboundClicks && typeof value.outboundClicks === 'object' ? value.outboundClicks : {},
|
||||
linkClicks: value?.linkClicks && typeof value.linkClicks === 'object' ? value.linkClicks : {},
|
||||
utmSources: value?.utmSources && typeof value.utmSources === 'object' ? value.utmSources : {},
|
||||
searchQueries: value?.searchQueries && typeof value.searchQueries === 'object' ? value.searchQueries : {},
|
||||
notFound: value?.notFound && typeof value.notFound === 'object' ? value.notFound : {},
|
||||
audioEvents: value?.audioEvents && typeof value.audioEvents === 'object' ? value.audioEvents : {},
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeLoadedEpisodePlays(value) {
|
||||
return (value && typeof value === 'object' && !Array.isArray(value)) ? value : {}
|
||||
}
|
||||
|
||||
function sanitizeLoadedVisitorStats(value) {
|
||||
const loadedVisitors = value?.visitors && typeof value.visitors === 'object' ? value.visitors : {}
|
||||
|
||||
|
||||
Reference in new issue
Block a user