Fix 5 bugs: analytics range keys, stale visitor rows, NuggetsPage init state, episodes timer leak, unsanitized nuggets object; v2.0.1
Publish Container / docker (push) Successful in 14m54s

- analytics: union byDayReal+byDayBot keys so bot-only days aren't dropped from range totals
- analytics: exclude timestamp-less visitor rows from range filters (was unconditionally including them)
- App.tsx: init `configured` to false so a network failure doesn't show a misleading "no episodes" state; add r.ok guard before r.json()
- episodes.js: wrap fetch in try/catch so clearTimeout is called on network failure, matching the nuggets.js pattern
- helpers.js: add sanitizeNuggets + wire it into sanitizeSiteContent so corrupted hosts/urls can't crash NuggetsPage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-09-08 08:21:02 -04:00
1 parent 31e79ad542
commit 4378fad66f
5 files changed
+50 -14

No files matched your search

+27
View File
@@ -330,6 +330,32 @@ function sanitizeArchivedSeries(value) {
)
}
function sanitizeNuggets(raw) {
const n = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {}
const hosts = Array.isArray(n.hosts)
? n.hosts
.filter(h => h && typeof h === 'object' && typeof h.id === 'string' && h.id.trim())
.map(h => ({
id: h.id.trim(),
name: typeof h.name === 'string' ? h.name.trim().slice(0, 120) : '',
role: typeof h.role === 'string' ? h.role.trim().slice(0, 120) : '',
bio: typeof h.bio === 'string' ? h.bio.trim().slice(0, 600) : '',
imageUrl: sanitizeUrl(h.imageUrl) || '',
archived: Boolean(h.archived),
}))
: []
return {
title: typeof n.title === 'string' ? n.title.trim().slice(0, 120) : '',
tagline: typeof n.tagline === 'string' ? n.tagline.trim().slice(0, 240) : '',
description: typeof n.description === 'string' ? n.description.trim().slice(0, 600) : '',
podcastArtUrl: sanitizeUrl(n.podcastArtUrl) || '',
rssFeedUrl: sanitizeUrl(n.rssFeedUrl) || '',
spotifyUrl: sanitizeUrl(n.spotifyUrl) || '',
appleUrl: sanitizeUrl(n.appleUrl) || '',
hosts,
}
}
export function sanitizeSiteContent(siteContent) {
if (!siteContent || typeof siteContent !== 'object' || Array.isArray(siteContent)) return {}
@@ -345,6 +371,7 @@ export function sanitizeSiteContent(siteContent) {
colossiansStudySections: sanitizeColossiansStudySections(siteContent.colossiansStudySections),
redirects: sanitizeRedirectRules(siteContent.redirects),
podcastFeaturedLinks: sanitizeFeaturedLinks(siteContent.podcastFeaturedLinks ?? DEFAULT_PODCAST_FEATURED_LINKS),
nuggets: sanitizeNuggets(siteContent.nuggets),
episodesSeoIntro: typeof siteContent.episodesSeoIntro === 'string' && siteContent.episodesSeoIntro.trim() ? siteContent.episodesSeoIntro.trim().slice(0, 600) : '',
welcomeEmailSpotifyBtnLabel: typeof siteContent.welcomeEmailSpotifyBtnLabel === 'string' ? siteContent.welcomeEmailSpotifyBtnLabel.trim().slice(0, 80) : '',
welcomeEmailAppleBtnLabel: typeof siteContent.welcomeEmailAppleBtnLabel === 'string' ? siteContent.welcomeEmailAppleBtnLabel.trim().slice(0, 80) : '',