Skip to content

Commit 02ffe83

Browse files
committed
fix(frontend): limit feed fallback retries to transient failures
1 parent d2f8300 commit 02ffe83

1 file changed

Lines changed: 32 additions & 5 deletions

File tree

frontend/src/hooks/useFeedConversion.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,23 @@ function buildConversionError(message: string, metadata: Partial<ConversionError
239239

240240
const toErrorMessage = (error: unknown): string => {
241241
const details = extractErrorDetails(error);
242+
const detailsMessage = details?.message?.toLowerCase();
243+
if (
244+
detailsMessage &&
245+
(detailsMessage.includes('not valid json') || detailsMessage.includes('unexpected token'))
246+
) {
247+
return 'Invalid response format from feed creation API';
248+
}
242249
if (details?.message) return details.message;
243250
if (error instanceof SyntaxError) return 'Invalid response format from feed creation API';
244-
if (error instanceof Error) return error.message;
251+
if (error instanceof Error) {
252+
const normalizedMessage = error.message.toLowerCase();
253+
if (normalizedMessage.includes('not valid json') || normalizedMessage.includes('unexpected token')) {
254+
return 'Invalid response format from feed creation API';
255+
}
256+
257+
return error.message;
258+
}
245259
if (typeof error === 'string' && error.trim()) return error;
246260
return 'An unexpected error occurred';
247261
};
@@ -291,33 +305,42 @@ function failConversion(
291305
throw buildConversionError(message, metadata);
292306
}
293307

294-
const extractErrorDetails = (error: unknown): { message?: string; code?: string } | null => {
308+
const extractErrorDetails = (error: unknown): { message?: string; code?: string; status?: number } | null => {
295309
if (!error || typeof error !== 'object') return null;
296310

297311
const candidate = error as {
298-
error?: { message?: unknown; code?: unknown };
312+
error?: { message?: unknown; code?: unknown; status?: unknown };
299313
message?: unknown;
300314
code?: unknown;
315+
status?: unknown;
301316
};
302317

303318
const message = normalizeString(candidate.error?.message ?? candidate.message);
304319
const code = normalizeString(candidate.error?.code ?? candidate.code);
305-
return { message, code };
320+
const status = normalizeStatus(candidate.error?.status ?? candidate.status);
321+
return { message, code, status };
306322
};
307323

308324
function retryableForFallback(error: unknown): boolean {
309325
const details = extractErrorDetails(error);
310326
const errorCode = details?.code?.toUpperCase();
327+
const status = details?.status;
311328
if (errorCode && NON_RETRYABLE_ERROR_CODES.has(errorCode)) return false;
329+
if (status && status < 500) return false;
312330

313331
const message = (details?.message ?? toErrorMessage(error)).toLowerCase();
314332
if (!details?.code && (message.includes('unauthorized') || message.includes('forbidden'))) return false;
315333
if (!details?.code && message.includes('bad request')) return false;
316334
if (message.includes('access token') || message.includes('authentication')) return false;
317335
if (message.includes('unsupported strategy')) return false;
318336
if (message.includes('invalid response format')) return false;
337+
if (message.includes('not valid json') || message.includes('unexpected token')) return false;
338+
if (message === 'network error') return false;
339+
if (error instanceof SyntaxError) return false;
319340

320-
return !networkFailure(error, message);
341+
if (status && status >= 500) return true;
342+
if (message.includes('failed to fetch http')) return true;
343+
return message.includes('internal server error') || message.includes('upstream timeout');
321344
}
322345

323346
function networkFailure(error: unknown, normalizedMessage: string): boolean {
@@ -329,6 +352,10 @@ function normalizeString(value: unknown): string | undefined {
329352
return typeof value === 'string' && value.trim() ? value : undefined;
330353
}
331354

355+
function normalizeStatus(value: unknown): number | undefined {
356+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
357+
}
358+
332359
function normalizePreviewText(value?: string): string | null {
333360
if (!value) return null;
334361

0 commit comments

Comments
 (0)