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' && ( {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') && ( {(previewType === 'text' || previewType === 'code') && (

View file

@ -68,7 +68,7 @@ class ApiClient {
const refreshed = await this.refreshAccessToken(); const refreshed = await this.refreshAccessToken();
if (refreshed) { if (refreshed) {
headers['Authorization'] = `Bearer ${this.accessToken}`; headers['Authorization'] = `Bearer ${this.accessToken}`;
return fetch(`${API_BASE}${endpoint}`, { const retryResponse = await fetch(`${API_BASE}${endpoint}`, {
method: options.method || 'GET', method: options.method || 'GET',
headers, headers,
body: options.body instanceof FormData body: options.body instanceof FormData
@ -77,6 +77,13 @@ class ApiClient {
? JSON.stringify(options.body) ? JSON.stringify(options.body)
: undefined, : 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; return response;
} }
private refreshPromise: Promise<boolean> | null = null;
private async refreshAccessToken(): Promise<boolean> { private async refreshAccessToken(): Promise<boolean> {
if (!this.refreshToken) return false; 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) { if (this.refreshPromise) {
const data = await response.json(); return this.refreshPromise;
this.setTokens(data.access_token, data.refresh_token || this.refreshToken!);
return true;
}
// Refresh failed
this.clearTokens();
return false;
} catch (e) {
this.clearTokens();
return false;
} }
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> { async ensureTokenFresh(): Promise<boolean> {