mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 06:12:10 +03:00
fix(providers): prevent error boundary when 'Test All' times out or returns bad JSON
- Add AbortController (90s timeout) to handleBatchTest fetch - Add inner try/catch for res.json() — handles truncated/non-JSON responses - Guard ProviderTestResultsView against null/undefined results (was crashing → error boundary) - Improve error check: error path now also guards results.results.length === 0 - Add 'providerTestTimeout' i18n key for friendly timeout message
This commit is contained in:
@@ -189,23 +189,35 @@ export default function ProvidersPage() {
|
||||
if (testingMode) return;
|
||||
setTestingMode(mode === "provider" ? providerId : mode);
|
||||
setTestResults(null);
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 90_000); // 90s max
|
||||
try {
|
||||
const res = await fetch("/api/providers/test-batch", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ mode, providerId }),
|
||||
signal: controller.signal,
|
||||
});
|
||||
const data = await res.json();
|
||||
let data: any;
|
||||
try {
|
||||
data = await res.json();
|
||||
} catch {
|
||||
// Response body is not valid JSON (e.g. truncated due to timeout)
|
||||
data = { error: t("providerTestFailed"), results: [], summary: null };
|
||||
}
|
||||
setTestResults(data);
|
||||
if (data.summary) {
|
||||
if (data?.summary) {
|
||||
const { passed, failed, total } = data.summary;
|
||||
if (failed === 0) notify.success(t("allTestsPassed", { total }));
|
||||
else notify.warning(t("testSummary", { passed, failed, total }));
|
||||
}
|
||||
} catch (error) {
|
||||
setTestResults({ error: t("providerTestFailed") });
|
||||
notify.error(t("providerTestFailed"));
|
||||
} catch (error: any) {
|
||||
const isAbort = error?.name === "AbortError";
|
||||
const msg = isAbort ? t("providerTestTimeout") : t("providerTestFailed");
|
||||
setTestResults({ error: msg, results: [], summary: null });
|
||||
notify.error(msg);
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
setTestingMode(null);
|
||||
}
|
||||
};
|
||||
@@ -1041,17 +1053,23 @@ function ProviderTestResultsView({ results }) {
|
||||
const t = useTranslations("providers");
|
||||
const tc = useTranslations("common");
|
||||
|
||||
if (results.error && !results.results) {
|
||||
// Guard: never crash on malformed/null results (would trigger error boundary)
|
||||
if (!results || typeof results !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (results.error && (!results.results || results.results.length === 0)) {
|
||||
return (
|
||||
<div className="text-center py-6">
|
||||
<span className="material-symbols-outlined text-red-500 text-[32px] mb-2 block">error</span>
|
||||
<p className="text-sm text-red-400">{results.error}</p>
|
||||
<p className="text-sm text-red-400">{String(results.error)}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const { summary, mode } = results;
|
||||
const items = results.results || [];
|
||||
const summary = results.summary ?? null;
|
||||
const mode = results.mode ?? "";
|
||||
const items = Array.isArray(results.results) ? results.results : [];
|
||||
|
||||
const modeLabel =
|
||||
{
|
||||
|
||||
@@ -1184,6 +1184,7 @@
|
||||
"clearing": "Clearing...",
|
||||
"until": "Until {time}",
|
||||
"providerTestFailed": "Provider test failed",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"modeTest": "{mode} Test",
|
||||
"passedCount": "{count} passed",
|
||||
"failedCount": "{count} failed",
|
||||
|
||||
Reference in New Issue
Block a user