feat(auth): tell the user when the session expired

Every 401 from Auth or Core now goes through useSessionExpired: the login page
says the session expired and returns to the last page after login. App no
longer removes a token that only the browser clock calls expired, so a backend
401 is the one signal. A 403 on the profile now shows an error instead.
This commit is contained in:
lobo committed 2026-09-24 13:29:09 +02:00
1 parent 03259d0515
commit d4c0f8d8f3
19 files changed
+351 -35

No files matched your search

+26 -8
View File
@@ -16,6 +16,7 @@ import SyncStatusBadge from '../components/SyncStatusBadge.jsx';
import useLibrarySync from '../hooks/useLibrarySync.js';
import useForceSync from '../hooks/useForceSync.js';
import {formatPlaytime} from '../utils/games.js';
import useSessionExpired from '../hooks/useSessionExpired.js';
const TOP_GAMES = 5;
// The games endpoint accepts only the page sizes 20, 40 and 80.
@@ -31,6 +32,7 @@ const authHeaders = () => ({
const Dashboard = () => {
const {t} = useTranslation();
const navigate = useNavigate();
const expireSession = useSessionExpired();
const [userData, setUserData] = useState(null);
const [steamData, setSteamData] = useState({count: 0, hasSteamId: false});
const [topGames, setTopGames] = useState([]);
@@ -44,17 +46,25 @@ const Dashboard = () => {
const fetchSteamCount = useCallback(async () => {
try {
const r = await fetch(`${import.meta.env.VITE_CORE_API_URL}/api/library/games-count`, {headers: authHeaders()});
if (r.status === 401) {
expireSession();
return;
}
if (r.ok) {
setSteamData(await r.json());
}
} catch (err) {
console.error('Error fetching steam data:', err);
}
}, []);
}, [expireSession]);
const fetchTopGames = useCallback(async () => {
try {
const r = await fetch(`${import.meta.env.VITE_CORE_API_URL}${TOP_GAMES_URL}`, {headers: authHeaders()});
if (r.status === 401) {
expireSession();
return;
}
if (r.ok) {
const page = await r.json();
setTopGames((page.content ?? []).slice(0, TOP_GAMES));
@@ -62,11 +72,15 @@ const Dashboard = () => {
} catch (err) {
console.error('Error fetching top games:', err);
}
}, []);
}, [expireSession]);
const fetchPlaying = useCallback(async () => {
try {
const r = await fetch(`${import.meta.env.VITE_CORE_API_URL}${PLAYING_URL}`, {headers: authHeaders()});
if (r.status === 401) {
expireSession();
return;
}
if (r.ok) {
const page = await r.json();
setPlaying((page.content ?? []).slice(0, PLAYING_GAMES));
@@ -74,7 +88,7 @@ const Dashboard = () => {
} catch (err) {
console.error('Error fetching playing games:', err);
}
}, []);
}, [expireSession]);
/**
* @param excludeAppid the current suggestion, so the new pick differs from it
@@ -84,6 +98,10 @@ const Dashboard = () => {
const query = excludeAppid ? `?exclude=${excludeAppid}` : '';
try {
const r = await fetch(`${import.meta.env.VITE_CORE_API_URL}/api/library/summary${query}`, {headers: authHeaders()});
if (r.status === 401) {
expireSession();
return true;
}
if (r.ok) {
setSummary(await r.json());
setSummaryFailed(false);
@@ -94,7 +112,7 @@ const Dashboard = () => {
}
setSummaryFailed(true);
return false;
}, []);
}, [expireSession]);
const shuffleSuggestion = async () => {
setShuffling(true);
@@ -131,9 +149,8 @@ const Dashboard = () => {
if (data.steamId) {
await Promise.all([fetchSteamCount(), fetchTopGames(), fetchPlaying(), fetchSummary()]);
}
} else if (profileResponse.status === 401 || profileResponse.status === 403) {
localStorage.removeItem('tbp_jwt');
navigate('/login');
} else if (profileResponse.status === 401) {
expireSession();
} else {
setError(t('INFO.ERROR.LOAD_PROFILE'));
}
@@ -146,13 +163,14 @@ const Dashboard = () => {
};
fetchUserData();
}, [navigate, t, fetchSteamCount, fetchTopGames, fetchPlaying, fetchSummary]);
}, [navigate, expireSession, t, fetchSteamCount, fetchTopGames, fetchPlaying, fetchSummary]);
const hasSteamId = !!userData?.steamId;
const sync = useLibrarySync({
enabled: hasSteamId,
expectInitialSync: hasSteamId && steamData.count === 0,
onComplete: reloadLibrary,
onUnauthorized: expireSession,
});
const forceSync = useForceSync(reloadLibrary);