diff --git a/server.cjs b/server.cjs index bacae41..c903f2e 100644 --- a/server.cjs +++ b/server.cjs @@ -129,7 +129,7 @@ const CSP_DIRECTIVES = [ "style-src 'self'", "style-src-elem 'self'", "style-src-attr 'unsafe-inline'", - "img-src 'self' data: blob: https://*.basemaps.cartocdn.com https://basemaps.cartocdn.com", + "img-src 'self' data: blob: https://*.basemaps.cartocdn.com https://basemaps.cartocdn.com https://lastfm.freetls.fastly.net https://*.lastfm.freetls.fastly.net", "font-src 'self' data:", "connect-src 'self' https://challenges.cloudflare.com", "frame-src https://challenges.cloudflare.com", @@ -204,6 +204,22 @@ function todayKey() { return new Date().toISOString().slice(0, 10) } +function lastfmImage(track) { + const images = Array.isArray(track.image) ? track.image : [] + const image = [...images].reverse().find((item) => item?.['#text']) + const url = image?.['#text'] || '' + if (!url) return '' + + try { + const parsed = new URL(url) + if (parsed.protocol !== 'https:') return '' + if (parsed.hostname !== 'lastfm.freetls.fastly.net' && !parsed.hostname.endsWith('.lastfm.freetls.fastly.net')) return '' + return parsed.toString() + } catch { + return '' + } +} + function normalizeLastfmTrack(track) { const artist = track?.artist?.['#text'] || track?.artist?.name || '' const name = track?.name || '' @@ -215,7 +231,7 @@ function normalizeLastfmTrack(track) { name, album: track?.album?.['#text'] || '', url: track?.url || '', - image: '', + image: lastfmImage(track), played_at: track?.date?.uts ? Number(track.date.uts) : null, } } diff --git a/src/App.jsx b/src/App.jsx index e63d488..263f1f1 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1908,18 +1908,7 @@ function SongOfDayCard({ onRetry, songOfDay }) { return (
-
- {track?.image ? ( - - ) : ( - - )} -
+

Song of the day @@ -1960,6 +1949,32 @@ function SongOfDayCard({ onRetry, songOfDay }) { ) } +function SongArtwork({ src }) { + const [failed, setFailed] = useState(false) + const showImage = src && !failed + + useEffect(() => { + setFailed(false) + }, [src]) + + return ( +

+ {showImage ? ( + setFailed(true)} + referrerPolicy="no-referrer" + src={src} + /> + ) : ( + + )} +
+ ) +} + function LandingOverview({ teams, matches, navigate }) { const activeTeams = teams.slice(0, 4) const totalPlayers = matches.reduce((sum, match) => sum + Number(match.player_count || 0), 0) diff --git a/vite.config.js b/vite.config.js index d2c4768..91047f8 100644 --- a/vite.config.js +++ b/vite.config.js @@ -70,13 +70,29 @@ function normalizeLastfmTrack(track) { const name = track?.name || '' if (!artist || !name) return null + const images = Array.isArray(track.image) ? track.image : [] + const image = [...images].reverse().find((item) => item?.['#text']) + let imageUrl = '' + try { + const parsed = new URL(image?.['#text'] || '') + if ( + parsed.protocol === 'https:' && + (parsed.hostname === 'lastfm.freetls.fastly.net' || + parsed.hostname.endsWith('.lastfm.freetls.fastly.net')) + ) { + imageUrl = parsed.toString() + } + } catch { + imageUrl = '' + } + return { id: `${artist.toLowerCase()}::${name.toLowerCase()}`, artist, name, album: track?.album?.['#text'] || '', url: track?.url || '', - image: '', + image: imageUrl, played_at: track?.date?.uts ? Number(track.date.uts) : null, } }