fix: resolve token refresh race condition and pdf viewer empty token rendering
All checks were successful
Automated Container Build / build-and-push (push) Successful in 24s

This commit is contained in:
Elijah 2026-05-31 20:44:55 -07:00
parent f3ea79ab1f
commit e88243f097
2 changed files with 43 additions and 21 deletions

View file

@ -152,7 +152,11 @@ export default function FilePreview({ file, onClose, downloadToken }: FilePrevie
)}
{previewType === 'pdf' && (
<DynamicPdfViewer url={downloadUrl} />
!downloadToken ? (
<div className="flex items-center justify-center p-8"><Loader2 className="w-8 h-8 animate-spin text-accent" /></div>
) : (
<DynamicPdfViewer url={downloadUrl} />
)
)}
{(previewType === 'text' || previewType === 'code') && (

View file

@ -68,7 +68,7 @@ class ApiClient {
const refreshed = await this.refreshAccessToken();
if (refreshed) {
headers['Authorization'] = `Bearer ${this.accessToken}`;
return fetch(`${API_BASE}${endpoint}`, {
const retryResponse = await fetch(`${API_BASE}${endpoint}`, {
method: options.method || 'GET',
headers,
body: options.body instanceof FormData
@ -77,6 +77,13 @@ class ApiClient {
? JSON.stringify(options.body)
: undefined,
});
if (!retryResponse.ok && retryResponse.status === 401) {
if (typeof window !== 'undefined') window.dispatchEvent(new Event('auth_error'));
throw new Error(`HTTP 401`);
}
return retryResponse;
}
}
@ -100,29 +107,40 @@ class ApiClient {
return response;
}
private refreshPromise: Promise<boolean> | null = null;
private async refreshAccessToken(): Promise<boolean> {
if (!this.refreshToken) return false;
try {
const response = await fetch(`${API_BASE}/api/auth/refresh`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.refreshToken}`,
},
});
if (response.ok) {
const data = await response.json();
this.setTokens(data.access_token, data.refresh_token || this.refreshToken!);
return true;
}
// Refresh failed
this.clearTokens();
return false;
} catch (e) {
this.clearTokens();
return false;
if (this.refreshPromise) {
return this.refreshPromise;
}
this.refreshPromise = (async () => {
try {
const response = await fetch(`${API_BASE}/api/auth/refresh`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.refreshToken}`,
},
});
if (response.ok) {
const data = await response.json();
this.setTokens(data.access_token, data.refresh_token || this.refreshToken!);
return true;
}
this.clearTokens();
return false;
} catch {
this.clearTokens();
return false;
} finally {
this.refreshPromise = null;
}
})();
return this.refreshPromise;
}
async ensureTokenFresh(): Promise<boolean> {