// server/routes/games/sessionState.ts import { TransientGameState, TransientSessionState, TRANSIENT_SESSION_KEYS, TRANSIENT_GAME_KEYS } from "./transientSchema"; import { Game, Session } from "./types"; class TransientStateManager { private sessions = new Map(); private games = new Map(); // Session transient state preserveSession(gameId: string, sessionId: string, session: Session): void { const key = `${gameId}:${sessionId}`; const transient: any = {}; // Automatically preserve all transient fields from schema TRANSIENT_SESSION_KEYS.forEach((k) => { if (k in session) { transient[k] = session[k]; } }); this.sessions.set(key, transient); } restoreSession(gameId: string, sessionId: string, session: Session): void { const key = `${gameId}:${sessionId}`; const transient = this.sessions.get(key); if (transient) { Object.assign(session, transient); // Don't delete - keep for future loads } } clearSession(gameId: string, sessionId: string): void { const key = `${gameId}:${sessionId}`; const transient = this.sessions.get(key); if (transient) { // Clean up timers if (transient.keepAlive) clearTimeout(transient.keepAlive); if (transient.pingInterval) clearTimeout(transient.pingInterval); if (transient._getBatch?.timer) clearTimeout(transient._getBatch.timer); if (transient._pendingTimeout) clearTimeout(transient._pendingTimeout); } this.sessions.delete(key); } // Game transient state preserveGame(gameId: string, game: Game): void { const transient: any = {}; // Automatically preserve all transient fields from schema TRANSIENT_GAME_KEYS.forEach((k) => { if (k in game) { transient[k] = game[k]; } }); this.games.set(gameId, transient); } restoreGame(gameId: string, game: Game): void { const transient = this.games.get(gameId); if (transient) { Object.assign(game, transient); } } clearGame(gameId: string): void { const transient = this.games.get(gameId); if (transient?.turnTimer) { clearTimeout(transient.turnTimer); } this.games.delete(gameId); } /** * Remove all transient fields from a session object (for serialization) * Automatically uses all keys from TRANSIENT_SESSION_SCHEMA */ stripSessionTransients(session: any): void { // Remove all transient fields automatically TRANSIENT_SESSION_KEYS.forEach((key) => delete session[key]); // Remove player reference (runtime only) delete session.player; // Catch-all: remove any underscore-prefixed fields and functions Object.keys(session).forEach((k) => { if (k.startsWith("_")) delete session[k]; else if (typeof session[k] === "function") delete session[k]; }); } /** * Remove all transient fields from a game object (for serialization) * Automatically uses all keys from TRANSIENT_GAME_SCHEMA */ stripGameTransients(game: any): void { TRANSIENT_GAME_KEYS.forEach((key) => delete game[key]); } } export const transientState = new TransientStateManager();