- 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
121 lines
3.5 KiB
TypeScript
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);
|
|
}
|
|
|