Fix session expiration handling
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import axios from 'axios';
|
||||
import { API_BASE_URL } from '../config/env';
|
||||
import { loadSession, saveSession, clearSession } from '../storage/session';
|
||||
import { notifySessionExpired } from '../utils/sessionEvents';
|
||||
|
||||
const client = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
@@ -38,6 +39,7 @@ client.interceptors.response.use(
|
||||
const session = await loadSession();
|
||||
if (!session.refreshToken) {
|
||||
await clearSession();
|
||||
notifySessionExpired();
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -68,6 +70,7 @@ client.interceptors.response.use(
|
||||
} catch (refreshError) {
|
||||
resolveQueue(refreshError, null);
|
||||
await clearSession();
|
||||
notifySessionExpired();
|
||||
throw refreshError;
|
||||
} finally {
|
||||
isRefreshing = false;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { Alert } from 'react-native';
|
||||
import { Alert, AppState } from 'react-native';
|
||||
import { changePassword as apiChangePassword, getMe, login as apiLogin, logout as apiLogout, register as apiRegister } from '../api/auth';
|
||||
import { initLocalDb, saveProfileCache } from '../storage/localDb';
|
||||
import { clearSession, loadSession, saveSession } from '../storage/session';
|
||||
import { SESSION_EXPIRED_MESSAGE, setSessionExpiredHandler } from '../utils/sessionEvents';
|
||||
|
||||
const AuthContext = createContext(null);
|
||||
|
||||
@@ -11,6 +12,15 @@ export const AuthProvider = ({ children }) => {
|
||||
const [tokens, setTokens] = useState({ accessToken: null, refreshToken: null });
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const clearLocalAuthState = useCallback(async ({ showAlert = false } = {}) => {
|
||||
await clearSession();
|
||||
setUser(null);
|
||||
setTokens({ accessToken: null, refreshToken: null });
|
||||
if (showAlert) {
|
||||
Alert.alert('Sesión expirada', SESSION_EXPIRED_MESSAGE);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const bootstrap = async () => {
|
||||
initLocalDb();
|
||||
@@ -27,14 +37,56 @@ export const AuthProvider = ({ children }) => {
|
||||
setUser(data.data.user);
|
||||
saveProfileCache(data.data.user);
|
||||
} catch (error) {
|
||||
await clearSession();
|
||||
await clearLocalAuthState();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
bootstrap();
|
||||
}, []);
|
||||
}, [clearLocalAuthState]);
|
||||
|
||||
useEffect(() => {
|
||||
return setSessionExpiredHandler(() => {
|
||||
clearLocalAuthState({ showAlert: true });
|
||||
});
|
||||
}, [clearLocalAuthState]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user || !tokens.accessToken) return undefined;
|
||||
|
||||
let timeoutId = null;
|
||||
let isMounted = true;
|
||||
|
||||
const enforceExpiry = async ({ showAlert = false } = {}) => {
|
||||
const session = await loadSession();
|
||||
if (!session.accessToken || !session.expiresAt || session.expiresAt <= Date.now()) {
|
||||
if (isMounted) {
|
||||
await clearLocalAuthState({ showAlert });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
timeoutId = setTimeout(() => {
|
||||
clearLocalAuthState({ showAlert: true });
|
||||
}, Math.max(session.expiresAt - Date.now(), 0));
|
||||
};
|
||||
|
||||
enforceExpiry();
|
||||
|
||||
const subscription = AppState.addEventListener('change', (state) => {
|
||||
if (state === 'active') {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
enforceExpiry({ showAlert: true });
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
subscription.remove();
|
||||
};
|
||||
}, [clearLocalAuthState, tokens.accessToken, user]);
|
||||
|
||||
const login = useCallback(async (email, password) => {
|
||||
const { data } = await apiLogin({ email, password });
|
||||
@@ -59,11 +111,9 @@ export const AuthProvider = ({ children }) => {
|
||||
} catch (error) {
|
||||
Alert.alert('Cerrar sesión', 'No se pudo notificar el cierre al backend. La sesión local se cerrará igual.');
|
||||
} finally {
|
||||
await clearSession();
|
||||
setUser(null);
|
||||
setTokens({ accessToken: null, refreshToken: null });
|
||||
await clearLocalAuthState();
|
||||
}
|
||||
}, []);
|
||||
}, [clearLocalAuthState]);
|
||||
|
||||
const requestPasswordChange = useCallback(async ({ currentPassword, newPassword }) => {
|
||||
return apiChangePassword({ currentPassword, newPassword });
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
export const SESSION_EXPIRED_MESSAGE = 'Tu sesión expiró. Vuelve a iniciar sesión.';
|
||||
|
||||
let sessionExpiredHandler = null;
|
||||
|
||||
export const setSessionExpiredHandler = (handler) => {
|
||||
sessionExpiredHandler = handler;
|
||||
return () => {
|
||||
if (sessionExpiredHandler === handler) {
|
||||
sessionExpiredHandler = null;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const notifySessionExpired = () => {
|
||||
if (sessionExpiredHandler) {
|
||||
sessionExpiredHandler();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user