diff --git a/backend/novasonic-server.js b/backend/novasonic-server.js index 7b11403..410ed14 100644 --- a/backend/novasonic-server.js +++ b/backend/novasonic-server.js @@ -72,28 +72,37 @@ function parseSpotifyUrl(url) { function _fetchTimeout(url, opts = {}, ms = 10000) { return new Promise((resolve, reject) => { const u = new URL(url); + const label = u.hostname + u.pathname.slice(0, 40); + const done = (fn) => { if (!done.called) { done.called = true; fn(); } }; const req = https.request({ hostname: u.hostname, port: 443, path: u.pathname + u.search, method: opts.method || 'GET', - headers: opts.headers || {}, + headers: { + 'User-Agent': 'NovaSonic/1.0', + 'Accept': 'application/json', + ...opts.headers, + }, }, res => { + console.log(`[sp-http] ${res.statusCode} ${label}`); const chunks = []; res.on('data', c => chunks.push(c)); res.on('end', () => { - const body = Buffer.concat(chunks).toString(); - resolve({ + const body = Buffer.concat(chunks).toString('utf8'); + done(() => resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode, headers: { get: h => res.headers[h.toLowerCase()] ?? null }, json: () => { try { return Promise.resolve(JSON.parse(body)); } catch(e) { return Promise.reject(e); } }, text: () => Promise.resolve(body), - }); + })); }); - res.on('error', reject); + res.on('error', e => done(() => reject(e))); }); - req.on('socket', s => s.setTimeout(ms, () => req.destroy(new Error(`Spotify nicht erreichbar (Timeout ${ms}ms) — api.spotify.com blockiert?`)))); - req.on('error', reject); + // Two-layer timeout: req-level (fires after ms of total inactivity) + socket-level + req.setTimeout(ms, () => req.destroy(new Error(`Spotify timeout ${ms}ms: ${label}`))); + req.on('socket', s => s.setTimeout(ms, () => req.destroy(new Error(`Spotify socket timeout ${ms}ms: ${label}`)))); + req.on('error', e => done(() => reject(e))); if (opts.body) req.write(opts.body); req.end(); });