2ab097ec8a
Frontend now checks /health before starting auth flow. Shows a clear "Cannot Connect to Server" screen with retry button when backend is unreachable. Stale non-JWT tokens in localStorage are detected and cleared automatically. All API calls have a 10s timeout via AbortController. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
48 lines
1.3 KiB
TypeScript
48 lines
1.3 KiB
TypeScript
import { useEffect, useRef } from 'react';
|
|
import { useGameStore } from '@/store';
|
|
import { api, getAuthToken, setAuthToken, clearAuthToken, decodeTokenPayload } from '@/lib/api';
|
|
import { AUTO_SAVE_INTERVAL_TICKS } from '@ai-tycoon/shared';
|
|
|
|
export function useCloudSave() {
|
|
const tickCount = useGameStore((s) => s.meta.tickCount);
|
|
const companyName = useGameStore((s) => s.meta.companyName);
|
|
const lastSaveTick = useRef(0);
|
|
|
|
useEffect(() => {
|
|
if (!companyName) return;
|
|
if (tickCount - lastSaveTick.current < AUTO_SAVE_INTERVAL_TICKS * 5) return;
|
|
|
|
const token = getAuthToken();
|
|
if (!token) return;
|
|
|
|
lastSaveTick.current = tickCount;
|
|
|
|
const state = useGameStore.getState();
|
|
const { activePage, notifications, ...gameState } = state;
|
|
|
|
api.saves.put({
|
|
companyName: state.meta.companyName,
|
|
saveVersion: state.meta.saveVersion,
|
|
gameData: gameState,
|
|
tickCount: state.meta.tickCount,
|
|
era: state.meta.currentEra,
|
|
}).catch(() => {});
|
|
}, [tickCount, companyName]);
|
|
}
|
|
|
|
export async function ensureAuth(): Promise<string | null> {
|
|
const token = getAuthToken();
|
|
if (token) {
|
|
if (decodeTokenPayload(token)) return token;
|
|
clearAuthToken();
|
|
}
|
|
|
|
try {
|
|
const result = await api.auth.anonymous();
|
|
setAuthToken(result.token);
|
|
return result.token;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|