Add JWT auto-refresh: store refresh_token at login, retry on 401
Some checks failed
Beta CI/CD Pipeline / build-and-test (push) Failing after 43s
Beta CI/CD Pipeline / deploy-beta (push) Has been skipped

This commit is contained in:
vickytechkey 2026-08-21 11:59:04 +05:30
parent 91971379c5
commit f071e72678
2 changed files with 77 additions and 12 deletions

View file

@ -120,15 +120,76 @@ export const CONFIG = {
}
}
export function apiFetch(url: string, options: RequestInit = {}): Promise<Response> {
let isRefreshing = false;
let refreshSubscribers: ((token: string) => void)[] = [];
function onTokenRefreshed(token: string) {
refreshSubscribers.forEach(cb => cb(token));
refreshSubscribers = [];
}
async function refreshAccessToken(): Promise<string | null> {
const refreshToken = localStorage.getItem('refresh_token');
if (!refreshToken) return null;
try {
const res = await fetch(`${CONFIG.apiBaseUrl}/api/auth/token/refresh/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh: refreshToken }),
});
if (!res.ok) return null;
const data = await res.json();
const newToken = data.access;
if (newToken) {
localStorage.setItem('access_token', newToken);
return newToken;
}
return null;
} catch {
return null;
}
}
export async function apiFetch(url: string, options: RequestInit = {}): Promise<Response> {
const token = localStorage.getItem('access_token');
const headers = {
...options.headers,
...(token ? { 'Authorization': `Bearer ${token}` } : {})
};
return fetch(url, {
const makeRequest = (authToken: string | null) =>
fetch(url, {
...options,
headers,
credentials: 'include'
headers: {
...options.headers,
...(authToken ? { 'Authorization': `Bearer ${authToken}` } : {}),
},
credentials: 'include',
});
const response = await makeRequest(token);
// Auto-refresh on 401
if (response.status === 401) {
if (!isRefreshing) {
isRefreshing = true;
const newToken = await refreshAccessToken();
isRefreshing = false;
if (newToken) {
onTokenRefreshed(newToken);
return makeRequest(newToken);
} else {
// Refresh failed — clear tokens
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
refreshSubscribers = [];
}
} else {
// Queue the request until refresh completes
return new Promise(resolve => {
refreshSubscribers.push((newToken: string) => {
resolve(makeRequest(newToken));
});
});
}
}
return response;
}

View file

@ -229,7 +229,8 @@ export const SellerProvider: React.FC<{children: React.ReactNode}> = ({ children
})
.catch(err => {
console.error('Session restore failed:', err);
localStorage.removeItem('access_token');
localStorage.removeItem('access_token')
localStorage.removeItem('refresh_token');
});
}
}, []);
@ -389,6 +390,7 @@ export const SellerProvider: React.FC<{children: React.ReactNode}> = ({ children
.catch(err => console.error('Error logging out:', err))
.finally(() => {
localStorage.removeItem('access_token')
localStorage.removeItem('refresh_token')
// Reset all onboarding & profile states to their defaults
setIsProfileComplete(false)
setProfileStep(1)
@ -491,6 +493,7 @@ export const SellerProvider: React.FC<{children: React.ReactNode}> = ({ children
})
.then(data => {
localStorage.setItem('access_token', data.access_token);
if (data.refresh_token) localStorage.setItem('refresh_token', data.refresh_token);
navigateTo('profile-completion')
})
})
@ -518,6 +521,7 @@ export const SellerProvider: React.FC<{children: React.ReactNode}> = ({ children
})
.then(data => {
localStorage.setItem('access_token', data.access_token);
if (data.refresh_token) localStorage.setItem('refresh_token', data.refresh_token);
if (data.user && data.user.profile) {
const profile = data.user.profile;
setPhone(profile.phone || '');