Skip to content

Commit da05e24

Browse files
committed
fix: add retry logic and content-type guards for auth endpoints on HF Space 503s
1 parent 2ea8c7d commit da05e24

1 file changed

Lines changed: 45 additions & 5 deletions

File tree

frontend/src/pages/LandingPage.tsx

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,23 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
308308
}
309309
};
310310

311+
// Fetch with automatic retry on 503/5xx (HF Space cold-start / rate-limit)
312+
const fetchWithRetry = async (url: string, options: RequestInit, retries = 3): Promise<Response> => {
313+
for (let attempt = 1; attempt <= retries; attempt++) {
314+
const response = await fetch(url, options);
315+
// If 5xx and not last attempt, wait and retry
316+
if (response.status >= 500 && attempt < retries) {
317+
const delay = attempt * 1200; // 1.2s, 2.4s
318+
setErrorMsg(`Server is warming up… retrying (${attempt}/${retries - 1})`);
319+
await new Promise(res => setTimeout(res, delay));
320+
continue;
321+
}
322+
return response;
323+
}
324+
// Should never reach here, but TypeScript needs it
325+
return fetch(url, options);
326+
};
327+
311328
const handleTraditionalSubmit = async (e: React.FormEvent) => {
312329
e.preventDefault();
313330
setErrorMsg(null);
@@ -338,7 +355,7 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
338355

339356
try {
340357
if (authMode === 'signup') {
341-
const response = await fetch(`${apiUrl}/api/v1/auth/signup`, {
358+
const response = await fetchWithRetry(`${apiUrl}/api/v1/auth/signup`, {
342359
method: 'POST',
343360
headers: { 'Content-Type': 'application/json' },
344361
body: JSON.stringify({
@@ -349,6 +366,12 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
349366
})
350367
});
351368

369+
// Guard against non-JSON 503 HTML responses from HF
370+
const contentType = response.headers.get('content-type') || '';
371+
if (!contentType.includes('application/json')) {
372+
throw new Error("The server is temporarily unavailable. Please try again in a few seconds.");
373+
}
374+
352375
const data = await response.json();
353376
if (!response.ok) {
354377
throw new Error(data.detail || "Sign up failed.");
@@ -370,8 +393,8 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
370393
}, 1000);
371394
}
372395
} else {
373-
// Sign In
374-
const response = await fetch(`${apiUrl}/api/v1/auth/login`, {
396+
// Sign In — with retry on 503
397+
const response = await fetchWithRetry(`${apiUrl}/api/v1/auth/login`, {
375398
method: 'POST',
376399
headers: { 'Content-Type': 'application/json' },
377400
body: JSON.stringify({
@@ -380,6 +403,12 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
380403
})
381404
});
382405

406+
// Guard against non-JSON 503 HTML responses from HF
407+
const contentType = response.headers.get('content-type') || '';
408+
if (!contentType.includes('application/json')) {
409+
throw new Error("The server is temporarily unavailable. Please wait a moment and try again.");
410+
}
411+
383412
const data = await response.json();
384413
if (!response.ok) {
385414
throw new Error(data.detail || "Invalid email or password.");
@@ -393,6 +422,7 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
393422
setSuccessMsg("Your account is not verified yet. A new verification code has been sent to your email.");
394423
setPassword('');
395424
} else if (data.access_token) {
425+
setErrorMsg(null);
396426
onAuthSuccess(data.access_token);
397427
setShowAuthModal(false);
398428
}
@@ -417,7 +447,7 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
417447
setLoading(true);
418448

419449
try {
420-
const response = await fetch(`${apiUrl}/api/v1/auth/verify`, {
450+
const response = await fetchWithRetry(`${apiUrl}/api/v1/auth/verify`, {
421451
method: 'POST',
422452
headers: { 'Content-Type': 'application/json' },
423453
body: JSON.stringify({
@@ -426,6 +456,11 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
426456
})
427457
});
428458

459+
const contentType = response.headers.get('content-type') || '';
460+
if (!contentType.includes('application/json')) {
461+
throw new Error("The server is temporarily unavailable. Please try again in a few seconds.");
462+
}
463+
429464
const data = await response.json();
430465
if (!response.ok) {
431466
throw new Error(data.detail || "Verification failed.");
@@ -451,14 +486,19 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
451486
setSuccessMsg(null);
452487

453488
try {
454-
const response = await fetch(`${apiUrl}/api/v1/auth/resend-code`, {
489+
const response = await fetchWithRetry(`${apiUrl}/api/v1/auth/resend-code`, {
455490
method: 'POST',
456491
headers: { 'Content-Type': 'application/json' },
457492
body: JSON.stringify({
458493
email: emailToVerify
459494
})
460495
});
461496

497+
const contentType = response.headers.get('content-type') || '';
498+
if (!contentType.includes('application/json')) {
499+
throw new Error("The server is temporarily unavailable. Please try again in a few seconds.");
500+
}
501+
462502
const data = await response.json();
463503
if (!response.ok) {
464504
throw new Error(data.detail || "Failed to resend code.");

0 commit comments

Comments
 (0)