feat: Remove localStorage for game states and implement cross-domain player ID sync
- Remove localStorage for game states and statistics (backend only) - Add API route to suggest player ID based on recently updated states - Add async player ID lookup that finds existing IDs across domains - When visiting a new domain, automatically find and use existing player ID - Enables cross-domain synchronization between hoerdle.de and hördle.de
This commit is contained in:
104
lib/gameState.ts
104
lib/gameState.ts
@@ -28,9 +28,6 @@ export interface Statistics {
|
||||
failed: number;
|
||||
}
|
||||
|
||||
const STORAGE_KEY_PREFIX = 'hoerdle_game_state';
|
||||
const STATS_KEY_PREFIX = 'hoerdle_statistics';
|
||||
|
||||
const INITIAL_SCORE = 90;
|
||||
|
||||
export function useGameState(
|
||||
@@ -40,9 +37,6 @@ export function useGameState(
|
||||
) {
|
||||
const [gameState, setGameState] = useState<GameState | null>(null);
|
||||
const [statistics, setStatistics] = useState<Statistics | null>(null);
|
||||
|
||||
const getStorageKey = () => genre ? `${STORAGE_KEY_PREFIX}_${genre}` : STORAGE_KEY_PREFIX;
|
||||
const getStatsKey = () => genre ? `${STATS_KEY_PREFIX}_${genre}` : STATS_KEY_PREFIX;
|
||||
|
||||
// Get genre key for backend storage
|
||||
// For specials, genre contains the special name
|
||||
@@ -98,89 +92,31 @@ export function useGameState(
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// No backend state found - check localStorage before creating new state
|
||||
const storageKey = getStorageKey();
|
||||
const stored = localStorage.getItem(storageKey);
|
||||
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored);
|
||||
if (parsed.date === today) {
|
||||
// Migration for existing states without score
|
||||
if (parsed.score === undefined) {
|
||||
parsed.score = INITIAL_SCORE;
|
||||
parsed.replayCount = 0;
|
||||
parsed.skipCount = 0;
|
||||
parsed.scoreBreakdown = [{ value: INITIAL_SCORE, reason: 'Start value' }];
|
||||
parsed.yearGuessed = false;
|
||||
}
|
||||
setGameState(parsed as GameState);
|
||||
// Also save to backend for cross-domain sync
|
||||
const statsKey = getStatsKey();
|
||||
const storedStats = localStorage.getItem(statsKey);
|
||||
const stats = storedStats ? JSON.parse(storedStats) : createNewStatistics();
|
||||
if (!statistics) {
|
||||
setStatistics(stats);
|
||||
}
|
||||
await savePlayerState(genreKey, parsed as GameState, stats);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No state found - create new state
|
||||
// No backend state found - create new state
|
||||
// This is the normal case for first-time players or new genre
|
||||
const newState = createNewState(today);
|
||||
setGameState(newState);
|
||||
localStorage.setItem(getStorageKey(), JSON.stringify(newState));
|
||||
const newStats = createNewStatistics();
|
||||
setStatistics(newStats);
|
||||
localStorage.setItem(getStatsKey(), JSON.stringify(newStats));
|
||||
// Save to backend for cross-domain sync
|
||||
await savePlayerState(genreKey, newState, newStats);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[gameState] Failed to load from backend, falling back to localStorage:', error);
|
||||
console.error('[gameState] Failed to load from backend:', error);
|
||||
|
||||
// Fallback to localStorage only on error
|
||||
const storageKey = getStorageKey();
|
||||
const stored = localStorage.getItem(storageKey);
|
||||
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored);
|
||||
if (parsed.date === today) {
|
||||
// Migration for existing states without score
|
||||
if (parsed.score === undefined) {
|
||||
parsed.score = INITIAL_SCORE;
|
||||
parsed.replayCount = 0;
|
||||
parsed.skipCount = 0;
|
||||
parsed.scoreBreakdown = [{ value: INITIAL_SCORE, reason: 'Start value' }];
|
||||
parsed.yearGuessed = false;
|
||||
}
|
||||
setGameState(parsed as GameState);
|
||||
|
||||
// Load statistics from localStorage
|
||||
const statsKey = getStatsKey();
|
||||
const storedStats = localStorage.getItem(statsKey);
|
||||
if (storedStats) {
|
||||
const parsedStats = JSON.parse(storedStats);
|
||||
if (parsedStats.solvedIn7 === undefined) {
|
||||
parsedStats.solvedIn7 = 0;
|
||||
}
|
||||
setStatistics(parsedStats);
|
||||
} else {
|
||||
const newStats = createNewStatistics();
|
||||
setStatistics(newStats);
|
||||
localStorage.setItem(statsKey, JSON.stringify(newStats));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No state found - create new state
|
||||
// On error, create new state and try to save to backend
|
||||
// This handles network errors gracefully
|
||||
const newState = createNewState(today);
|
||||
setGameState(newState);
|
||||
localStorage.setItem(storageKey, JSON.stringify(newState));
|
||||
const newStats = createNewStatistics();
|
||||
setStatistics(newStats);
|
||||
localStorage.setItem(getStatsKey(), JSON.stringify(newStats));
|
||||
// Try to save to backend (may fail, but we try)
|
||||
try {
|
||||
await savePlayerState(genreKey, newState, newStats);
|
||||
} catch (saveError) {
|
||||
console.error('[gameState] Failed to save new state to backend:', saveError);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -190,17 +126,15 @@ export function useGameState(
|
||||
const saveState = async (newState: GameState) => {
|
||||
setGameState(newState);
|
||||
|
||||
// Save to backend (primary)
|
||||
// Save to backend only
|
||||
if (statistics) {
|
||||
try {
|
||||
await savePlayerState(genreKey, newState, statistics);
|
||||
} catch (error) {
|
||||
console.warn('[gameState] Failed to save to backend, using localStorage fallback:', error);
|
||||
console.error('[gameState] Failed to save to backend:', error);
|
||||
// No fallback - backend is required for cross-domain sync
|
||||
}
|
||||
}
|
||||
|
||||
// Also save to localStorage as backup
|
||||
localStorage.setItem(getStorageKey(), JSON.stringify(newState));
|
||||
};
|
||||
|
||||
const updateStatistics = async (attempts: number, solved: boolean) => {
|
||||
@@ -226,17 +160,15 @@ export function useGameState(
|
||||
|
||||
setStatistics(newStats);
|
||||
|
||||
// Save to backend (primary)
|
||||
// Save to backend only
|
||||
if (gameState) {
|
||||
try {
|
||||
await savePlayerState(genreKey, gameState, newStats);
|
||||
} catch (error) {
|
||||
console.warn('[gameState] Failed to save statistics to backend, using localStorage fallback:', error);
|
||||
console.error('[gameState] Failed to save statistics to backend:', error);
|
||||
// No fallback - backend is required for cross-domain sync
|
||||
}
|
||||
}
|
||||
|
||||
// Also save to localStorage as backup
|
||||
localStorage.setItem(getStatsKey(), JSON.stringify(newStats));
|
||||
};
|
||||
|
||||
const addGuess = (guess: string, correct: boolean) => {
|
||||
|
||||
Reference in New Issue
Block a user