fix: add User-Agent, dual timeout, done-guard, and response logging to _fetchTimeout

- User-Agent header (Spotify may throttle anonymous requests)
- req.setTimeout + socket.setTimeout for belt-and-suspenders
- done() guard prevents double-resolve/reject
- [sp-http] log shows status code of each Spotify response
  so logs reveal whether the hang is before or after HTTP headers arrive

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-10 17:50:18 +02:00
1 parent f4766acc74
commit d2cb1bed68
1 file changed
+16 -7
+16 -7
View File
@@ -72,28 +72,37 @@ function parseSpotifyUrl(url) {
function _fetchTimeout(url, opts = {}, ms = 10000) { function _fetchTimeout(url, opts = {}, ms = 10000) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const u = new URL(url); 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({ const req = https.request({
hostname: u.hostname, port: 443, hostname: u.hostname, port: 443,
path: u.pathname + u.search, path: u.pathname + u.search,
method: opts.method || 'GET', method: opts.method || 'GET',
headers: opts.headers || {}, headers: {
'User-Agent': 'NovaSonic/1.0',
'Accept': 'application/json',
...opts.headers,
},
}, res => { }, res => {
console.log(`[sp-http] ${res.statusCode} ${label}`);
const chunks = []; const chunks = [];
res.on('data', c => chunks.push(c)); res.on('data', c => chunks.push(c));
res.on('end', () => { res.on('end', () => {
const body = Buffer.concat(chunks).toString(); const body = Buffer.concat(chunks).toString('utf8');
resolve({ done(() => resolve({
ok: res.statusCode >= 200 && res.statusCode < 300, ok: res.statusCode >= 200 && res.statusCode < 300,
status: res.statusCode, status: res.statusCode,
headers: { get: h => res.headers[h.toLowerCase()] ?? null }, headers: { get: h => res.headers[h.toLowerCase()] ?? null },
json: () => { try { return Promise.resolve(JSON.parse(body)); } catch(e) { return Promise.reject(e); } }, json: () => { try { return Promise.resolve(JSON.parse(body)); } catch(e) { return Promise.reject(e); } },
text: () => Promise.resolve(body), 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?`)))); // Two-layer timeout: req-level (fires after ms of total inactivity) + socket-level
req.on('error', reject); 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); if (opts.body) req.write(opts.body);
req.end(); req.end();
}); });