fix: guard ALL saves behind ready — never save DEFAULT_STATE to server
Some checks failed
CI/CD — Build & Deploy / Build & Deploy (push) Failing after 17s
Some checks failed
CI/CD — Build & Deploy / Build & Deploy (push) Failing after 17s
Root cause: on refresh, store starts with DEFAULT_STATE (resources: 0). Blur/interval/unload handlers saved this zero state before server load. Fix: every save path checks useGameStore.getState().ready before writing.
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
// useSaveSync.ts — Auto-save game state to backend every 30s
|
||||
// Serveur = autorité. Cookie-based auth — credentials sent automatically.
|
||||
// Serveur = autorité. NEVER save before server state is loaded (ready guard).
|
||||
|
||||
import { useEffect, useRef, useCallback, useState } from "react";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
import { useGameStore } from "../store/useGameStore";
|
||||
import type { GameState } from "../core/economy";
|
||||
|
||||
const SAVE_INTERVAL_MS = 30_000; // 30 seconds
|
||||
@@ -35,6 +36,7 @@ async function apiRequest(path: string, options: RequestInit = {}) {
|
||||
|
||||
export function useSaveSync({ getGameState, onLoad, playTimeSeconds }: SaveSyncOptions) {
|
||||
const { user } = useAuth();
|
||||
const ready = useGameStore((s) => s.ready);
|
||||
const lastSaveRef = useRef<string | null>(null);
|
||||
const loadedRef = useRef(false);
|
||||
const [serverLoaded, setServerLoaded] = useState(false);
|
||||
@@ -42,7 +44,6 @@ export function useSaveSync({ getGameState, onLoad, playTimeSeconds }: SaveSyncO
|
||||
// Load save from server on mount (once, only if logged in)
|
||||
useEffect(() => {
|
||||
if (loadedRef.current || !user) {
|
||||
// Not logged in → signal immediately
|
||||
if (!user) setServerLoaded(true);
|
||||
return;
|
||||
}
|
||||
@@ -63,9 +64,9 @@ export function useSaveSync({ getGameState, onLoad, playTimeSeconds }: SaveSyncO
|
||||
});
|
||||
}, [onLoad, user]);
|
||||
|
||||
// Save function
|
||||
// Save function — GUARDED by ready (never save DEFAULT_STATE)
|
||||
const saveToServer = useCallback(async () => {
|
||||
if (!user) return;
|
||||
if (!user || !useGameStore.getState().ready) return;
|
||||
|
||||
const gameState = getGameState();
|
||||
const result = await apiRequest("/save", {
|
||||
@@ -78,25 +79,24 @@ export function useSaveSync({ getGameState, onLoad, playTimeSeconds }: SaveSyncO
|
||||
}
|
||||
}, [getGameState, playTimeSeconds, user]);
|
||||
|
||||
// Auto-save interval
|
||||
// Auto-save interval — only when ready
|
||||
useEffect(() => {
|
||||
if (!user) return undefined;
|
||||
if (!user || !ready) return undefined;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
saveToServer();
|
||||
}, SAVE_INTERVAL_MS);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [saveToServer, user]);
|
||||
}, [saveToServer, user, ready]);
|
||||
|
||||
// Reload from server on tab focus (multi-tab/multi-browser sync)
|
||||
// Multi-tab sync: save on blur, reload on focus — only when ready
|
||||
useEffect(() => {
|
||||
if (!user) return undefined;
|
||||
|
||||
const handleFocus = () => {
|
||||
apiRequest("/save").then((data) => {
|
||||
if (data?.gameState && data.lastSave) {
|
||||
// Only load if server save is newer than our last known save
|
||||
if (!lastSaveRef.current || new Date(data.lastSave) > new Date(lastSaveRef.current)) {
|
||||
onLoad(data.gameState);
|
||||
lastSaveRef.current = data.lastSave;
|
||||
@@ -107,8 +107,8 @@ export function useSaveSync({ getGameState, onLoad, playTimeSeconds }: SaveSyncO
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
if (!useGameStore.getState().ready) return;
|
||||
saveToServer();
|
||||
console.info("[SaveSync] Saved on blur — other tabs will get latest");
|
||||
};
|
||||
|
||||
window.addEventListener("focus", handleFocus);
|
||||
@@ -117,12 +117,12 @@ export function useSaveSync({ getGameState, onLoad, playTimeSeconds }: SaveSyncO
|
||||
window.removeEventListener("focus", handleFocus);
|
||||
window.removeEventListener("blur", handleBlur);
|
||||
};
|
||||
}, [user, onLoad]);
|
||||
}, [user, onLoad, saveToServer]);
|
||||
|
||||
// Save on page unload
|
||||
// Save on page unload — GUARDED by ready
|
||||
useEffect(() => {
|
||||
const handleUnload = () => {
|
||||
if (!user) return;
|
||||
if (!user || !useGameStore.getState().ready) return;
|
||||
|
||||
const gameState = getGameState();
|
||||
const payload = JSON.stringify({ gameState, playTimeSeconds });
|
||||
|
||||
Reference in New Issue
Block a user