Files
hoerdle/lib/playerId.ts
Hördle Bot 2846afb6f7 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
2025-12-01 20:37:47 +01:00

121 lines
3.5 KiB
TypeScript

/**
* Player Identifier Management
*
* Generates and manages a unique player identifier (UUID) that is stored
* in localStorage. This identifier is used to sync game states across
* different domains (hoerdle.de and hördle.de).
*/
const STORAGE_KEY = 'hoerdle_player_id';
/**
* Generate a UUID v4
*/
function generatePlayerId(): string {
// UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
/**
* Try to find an existing player ID from the backend
*
* @param genreKey - Genre key to search for
* @returns Player ID if found, null otherwise
*/
async function findExistingPlayerId(genreKey: string): Promise<string | null> {
try {
const response = await fetch('/api/player-id/suggest', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ genreKey }),
});
if (response.ok) {
const data = await response.json();
if (data.playerId) {
return data.playerId;
}
}
} catch (error) {
console.warn('[playerId] Failed to find existing player ID:', error);
}
return null;
}
/**
* Get or create a player identifier
*
* If no identifier exists in localStorage, tries to find an existing one from the backend
* (based on recently updated states). If none found, generates a new UUID.
* This enables cross-domain synchronization between hoerdle.de and hördle.de.
*
* @param genreKey - Optional genre key to search for existing player ID
* @returns Player identifier (UUID v4)
*/
export async function getOrCreatePlayerIdAsync(genreKey?: string): Promise<string> {
if (typeof window === 'undefined') {
// Server-side: return empty string (not used on server)
return '';
}
let playerId = localStorage.getItem(STORAGE_KEY);
if (!playerId) {
// Try to find an existing player ID from backend if genreKey is provided
if (genreKey) {
const existingId = await findExistingPlayerId(genreKey);
if (existingId) {
playerId = existingId;
localStorage.setItem(STORAGE_KEY, playerId);
return playerId;
}
}
// Generate new UUID if no existing ID found
playerId = generatePlayerId();
localStorage.setItem(STORAGE_KEY, playerId);
}
return playerId;
}
/**
* Get or create a player identifier (synchronous version)
*
* This is the legacy synchronous version. For cross-domain sync, use getOrCreatePlayerIdAsync instead.
*
* @returns Player identifier (UUID v4)
*/
export function getOrCreatePlayerId(): string {
if (typeof window === 'undefined') {
// Server-side: return empty string (not used on server)
return '';
}
let playerId = localStorage.getItem(STORAGE_KEY);
if (!playerId) {
playerId = generatePlayerId();
localStorage.setItem(STORAGE_KEY, playerId);
}
return playerId;
}
/**
* Get the current player identifier without creating a new one
*
* @returns Player identifier or null if not set
*/
export function getPlayerId(): string | null {
if (typeof window === 'undefined') {
return null;
}
return localStorage.getItem(STORAGE_KEY);
}