refactor(tts): ensure timeout cleanup and validate remote voice payloads

Make voice-fetching more robust by always clearing the request timeout and stricter type-checking of remote data.

- move clearTimeout into finally blocks for Deepinfra and custom OpenAI voice fetches to guarantee the timeout is cleared on success, failure, or abort
- validate that custom endpoint returns an array of strings before accepting voices; otherwise fall back to defaults

This reduces potential timer leaks and avoids accepting malformed voice lists from custom endpoints.
This commit is contained in:
Richard R 2026-04-06 11:41:42 -06:00
parent 09944ec4e4
commit bf139d9a67

View file

@ -181,8 +181,6 @@ async function fetchDeepinfraVoices(apiKey: string): Promise<string[]> {
},
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error('Failed to fetch Deepinfra voices');
}
@ -195,12 +193,13 @@ async function fetchDeepinfraVoices(apiKey: string): Promise<string[]> {
}
return [];
} catch (error) {
clearTimeout(timeoutId);
if (error instanceof DOMException && error.name === 'AbortError') {
return [];
}
console.error('Error fetching Deepinfra voices:', error);
return [];
} finally {
clearTimeout(timeoutId);
}
}
@ -220,18 +219,19 @@ async function fetchCustomOpenAiVoices(baseUrl: string, apiKey: string): Promise
},
});
clearTimeout(timeoutId);
if (!response.ok) {
return null;
}
const data = await response.json();
return Array.isArray(data.voices) ? data.voices : null;
return Array.isArray(data.voices) && data.voices.every((voice: unknown) => typeof voice === 'string')
? data.voices
: null;
} catch {
clearTimeout(timeoutId);
console.log('Custom endpoint does not support voices, using defaults');
return null;
} finally {
clearTimeout(timeoutId);
}
}