From 76271c1e548e2dec9490f7922079d57c267600fa Mon Sep 17 00:00:00 2001 From: Honey Date: Sat, 9 May 2026 23:39:07 +0200 Subject: [PATCH] fix: 25s backend timeout + 30s frontend timeout on Spotify resolve Extracts resolve logic into _resolveSpotify() and wraps it in Promise.race with a 25s timer so the backend always sends a response even if undici's AbortSignal.timeout silently fails on blocked sockets. Also reduces frontend timeout from 90s to 30s. Co-Authored-By: Claude Sonnet 4.6 --- backend/novasonic-server.js | 84 +++++++++++++++++-------------------- src/spotify.js | 2 +- 2 files changed, 40 insertions(+), 46 deletions(-) diff --git a/backend/novasonic-server.js b/backend/novasonic-server.js index 8371064..6f1a13e 100644 --- a/backend/novasonic-server.js +++ b/backend/novasonic-server.js @@ -223,57 +223,51 @@ app.get('/api/health', (req, res) => { }); }); +async function _resolveSpotify(type, id) { + const token = await fetchSpotifyToken(); + + if (type === 'track') { + const t = await _spApi(`/tracks/${id}`, token); + const artist = _spArtistName(t.artists); + const artUrl = _spArtwork(t); + return { type: 'track', name: t.name || '', artist, artworkUrl: artUrl, + tracks: [_spTrackItem(t, t.album?.name, artUrl, artist)] }; + } + + if (type === 'album') { + const album = await _spApi(`/albums/${id}`, token); + const artist = _spArtistName(album.artists); + const artUrl = _spArtwork(album); + const trackItems = await _spAllAlbumTracks(id, token); + return { type: 'album', name: album.name || '', artist, artworkUrl: artUrl, + tracks: trackItems.map(t => _spTrackItem(t, album.name, artUrl, artist)) }; + } + + if (type === 'playlist') { + const pl = await _spApi(`/playlists/${id}?fields=name,description,owner,images`, token); + const owner = pl.owner?.display_name || ''; + const artUrl = _spArtwork(pl); + const { tracks: trackItems, skippedLocal } = await _spAllPlaylistTracks(id, token); + return { type: 'playlist', name: pl.name || '', artist: owner, artworkUrl: artUrl, + description: pl.description || '', skippedLocal, + tracks: trackItems.map(t => _spTrackItem(t, null, null, null)) }; + } + + throw new Error(`Unbekannter Typ: ${type}`); +} + app.post('/api/spotify/resolve', async (req, res) => { const { url } = req.body; if (!url) return res.status(400).json({ error: 'url required' }); try { const { type, id } = parseSpotifyUrl(url); - const token = await fetchSpotifyToken(); + console.log(`[spotify/resolve] ${type} ${id}`); - if (type === 'track') { - const t = await _spApi(`/tracks/${id}`, token); - const artist = _spArtistName(t.artists); - const artUrl = _spArtwork(t); - return res.json({ - type: 'track', - name: t.name || '', - artist, - artworkUrl: artUrl, - tracks: [_spTrackItem(t, t.album?.name, artUrl, artist)], - }); - } - - if (type === 'album') { - const album = await _spApi(`/albums/${id}`, token); - const artist = _spArtistName(album.artists); - const artUrl = _spArtwork(album); - const trackItems = await _spAllAlbumTracks(id, token); - return res.json({ - type: 'album', - name: album.name || '', - artist, - artworkUrl: artUrl, - tracks: trackItems.map(t => _spTrackItem(t, album.name, artUrl, artist)), - }); - } - - if (type === 'playlist') { - const pl = await _spApi(`/playlists/${id}?fields=name,description,owner,images`, token); - const owner = pl.owner?.display_name || ''; - const artUrl = _spArtwork(pl); - const { tracks: trackItems, skippedLocal } = await _spAllPlaylistTracks(id, token); - return res.json({ - type: 'playlist', - name: pl.name || '', - artist: owner, - artworkUrl: artUrl, - description: pl.description || '', - skippedLocal, - tracks: trackItems.map(t => _spTrackItem(t, null, null, null)), - }); - } - - res.status(400).json({ error: `Unbekannter Typ: ${type}` }); + const timeout = new Promise((_, reject) => + setTimeout(() => reject(new Error('Zeitüberschreitung — Spotify API antwortet nicht (>25s)')), 25000) + ); + const data = await Promise.race([_resolveSpotify(type, id), timeout]); + res.json(data); } catch(e) { console.error('[spotify/resolve]', e.message); if (e.message.includes('403') && e.message.includes('/playlists/')) { diff --git a/src/spotify.js b/src/spotify.js index d408e85..6e7e8d6 100644 --- a/src/spotify.js +++ b/src/spotify.js @@ -63,7 +63,7 @@ export async function resolveSpotifyLink() { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }), - signal: AbortSignal.timeout(90000), + signal: AbortSignal.timeout(30000), }); if (!res.ok) { const err = await res.json().catch(() => ({}));