108 lines
3.1 KiB
TypeScript
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(); |