cleanup
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
const useAuthManager = ({
|
||||
apiClient,
|
||||
token,
|
||||
appStatus,
|
||||
appDispatch,
|
||||
notifyApiError,
|
||||
setStatusMessage,
|
||||
setLoading,
|
||||
}) => {
|
||||
const tokenRef = useRef(token);
|
||||
const refreshPromiseRef = useRef(null);
|
||||
const initialRefreshAttemptedRef = useRef(Boolean(token));
|
||||
|
||||
const refreshAccessToken = useCallback(async () => {
|
||||
console.log('[Auth] Attempting to refresh access token…');
|
||||
appDispatch({ type: 'TOKEN_REFRESH_START' });
|
||||
try {
|
||||
const { data } = await apiClient.post('/auth/refresh');
|
||||
if (data?.access_token) {
|
||||
appDispatch({
|
||||
type: 'TOKEN_REFRESH_SUCCESS',
|
||||
token: data.access_token,
|
||||
tenant: data.tenant || null,
|
||||
});
|
||||
console.log('[Auth] Access token refreshed at', new Date().toISOString());
|
||||
return data.access_token;
|
||||
}
|
||||
throw new Error('Missing access token in refresh response');
|
||||
} catch (error) {
|
||||
console.warn('[Auth] Failed to refresh access token', error);
|
||||
appDispatch({ type: 'TOKEN_REFRESH_FAILURE', error: error?.message || null });
|
||||
throw error;
|
||||
}
|
||||
}, [apiClient, appDispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
tokenRef.current = token;
|
||||
}, [token]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!token && !initialRefreshAttemptedRef.current && appStatus === 'logged-out') {
|
||||
initialRefreshAttemptedRef.current = true;
|
||||
console.log('[Auth] Attempting refresh at startup');
|
||||
refreshAccessToken().catch(() => {});
|
||||
}
|
||||
}, [token, appStatus, refreshAccessToken]);
|
||||
|
||||
useEffect(() => {
|
||||
const requestInterceptor = apiClient.interceptors.request.use((config) => {
|
||||
const currentToken = tokenRef.current;
|
||||
if (currentToken) {
|
||||
config.headers = config.headers || {};
|
||||
if (!config.headers.Authorization) {
|
||||
config.headers.Authorization = `Bearer ${currentToken}`;
|
||||
}
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
const responseInterceptor = apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
const { response, config } = error;
|
||||
if (!response || !config) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
const status = response.status;
|
||||
const url = typeof config.url === 'string' ? config.url : '';
|
||||
const isAuthRoute = url.includes('/auth/login') || url.includes('/auth/refresh');
|
||||
|
||||
if (status === 401 && !config._retry && !isAuthRoute) {
|
||||
console.warn('[Auth] 401 received for', url, '- attempting token refresh');
|
||||
|
||||
if (!refreshPromiseRef.current) {
|
||||
refreshPromiseRef.current = (async () => {
|
||||
try {
|
||||
return await refreshAccessToken();
|
||||
} finally {
|
||||
refreshPromiseRef.current = null;
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
try {
|
||||
const newToken = await refreshPromiseRef.current;
|
||||
if (!newToken) {
|
||||
throw new Error('No token returned from refresh');
|
||||
}
|
||||
config._retry = true;
|
||||
config.headers = config.headers || {};
|
||||
config.headers.Authorization = `Bearer ${newToken}`;
|
||||
console.log('[Auth] Retrying original request', url);
|
||||
try {
|
||||
return await apiClient(config);
|
||||
} catch (retryError) {
|
||||
if (retryError?.response?.status === 401) {
|
||||
notifyApiError(retryError, 'Session expired. Please log in again.');
|
||||
}
|
||||
throw retryError;
|
||||
}
|
||||
} catch (refreshError) {
|
||||
console.warn('[Auth] Refresh failed, clearing session');
|
||||
notifyApiError(refreshError, 'Session expired. Please log in again.');
|
||||
return Promise.reject(refreshError);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
apiClient.interceptors.request.eject(requestInterceptor);
|
||||
apiClient.interceptors.response.eject(responseInterceptor);
|
||||
};
|
||||
}, [apiClient, notifyApiError, refreshAccessToken]);
|
||||
|
||||
const handleLogout = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await apiClient.post('/auth/logout');
|
||||
} catch (error) {
|
||||
console.warn('[Auth] Failed to revoke refresh token during logout', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
appDispatch({ type: 'LOGOUT' });
|
||||
setStatusMessage('Logged out.', 'info');
|
||||
}
|
||||
}, [apiClient, appDispatch, setLoading, setStatusMessage]);
|
||||
|
||||
return { tokenRef, refreshAccessToken, handleLogout };
|
||||
};
|
||||
|
||||
export default useAuthManager;
|
||||
Reference in New Issue
Block a user