fix: switch Spotify HTTP to https module with socket.setTimeout for OS-level timeout

undici cannot abort a TCP connect in progress even with AbortController.
Node's https.request + socket.setTimeout fires at the OS socket level
and aborts within ms even when the TCP SYN is silently dropped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-10 17:45:10 +02:00
1 parent d2620519aa
commit f4766acc74
1 file changed
+30 -6
+30 -6
View File
@@ -67,12 +67,36 @@ function parseSpotifyUrl(url) {
return { type: m[1], id: m[2] };
}
// AbortSignal.timeout() is unreliable in Node/undici when TCP connect is blocked.
// Use AbortController + setTimeout instead — always fires from Node's event loop.
function _fetchTimeout(url, opts, ms = 10000) {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), ms);
return fetch(url, { ...opts, signal: ctrl.signal }).finally(() => clearTimeout(timer));
// undici (native fetch) cannot abort a pending TCP connect even with AbortController.
// Use Node's https module with socket.setTimeout() — fires at OS level during connect.
function _fetchTimeout(url, opts = {}, ms = 10000) {
return new Promise((resolve, reject) => {
const u = new URL(url);
const req = https.request({
hostname: u.hostname, port: 443,
path: u.pathname + u.search,
method: opts.method || 'GET',
headers: opts.headers || {},
}, res => {
const chunks = [];
res.on('data', c => chunks.push(c));
res.on('end', () => {
const body = Buffer.concat(chunks).toString();
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);
});
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);
if (opts.body) req.write(opts.body);
req.end();
});
}
let _spTokenCache = null;