1
0
2025-10-08 17:01:50 -07:00

108 lines
3.1 KiB
TypeScript

// 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<string, TransientSessionState>();
private games = new Map<string, TransientGameState>();
// 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();