/** * Server-side eTerra session store (global singleton). * * Holds one shared session for the whole app. Multiple browser clients * see the same connection state. The EterraClient already caches sessions * by credential hash, so we just store which credentials are active + * metadata (who connected, when, running jobs). */ import { getProgress } from "./progress-store"; /* ------------------------------------------------------------------ */ /* Types */ /* ------------------------------------------------------------------ */ export type EterraSession = { /** Username used to connect */ username: string; /** Password (kept in memory only, never sent to clients) */ password: string; /** ISO timestamp of connection */ connectedAt: string; /** Running job IDs — tracked so we can warn before disconnect */ activeJobs: Set; }; export type SessionStatus = { connected: boolean; username?: string; connectedAt?: string; /** How many jobs are currently running */ activeJobCount: number; /** First running job's phase (for UI hint) */ activeJobPhase?: string; /** eTerra platform health */ eterraAvailable?: boolean; /** True if eTerra is detected as being in maintenance */ eterraMaintenance?: boolean; /** Human-readable health message */ eterraHealthMessage?: string; }; /* ------------------------------------------------------------------ */ /* Global store */ /* ------------------------------------------------------------------ */ const g = globalThis as { __eterraSessionStore?: EterraSession | null }; function getSession(): EterraSession | null { return g.__eterraSessionStore ?? null; } function setSession(session: EterraSession | null) { g.__eterraSessionStore = session; } /* ------------------------------------------------------------------ */ /* Public API */ /* ------------------------------------------------------------------ */ export function createSession(username: string, password: string): void { setSession({ username, password, connectedAt: new Date().toISOString(), activeJobs: new Set(), }); } export function destroySession(): { destroyed: boolean; reason?: string } { const session = getSession(); if (!session) return { destroyed: true }; // Check for running jobs const running = getRunningJobs(session); if (running.length > 0) { return { destroyed: false, reason: `Există ${running.length} job(uri) active. Așteaptă finalizarea lor înainte de deconectare.`, }; } setSession(null); return { destroyed: true }; } export function forceDestroySession(): void { setSession(null); } export function getSessionCredentials(): { username: string; password: string; } | null { const session = getSession(); if (!session) return null; return { username: session.username, password: session.password }; } export function registerJob(jobId: string): void { const session = getSession(); if (session) session.activeJobs.add(jobId); } export function unregisterJob(jobId: string): void { const session = getSession(); if (session) session.activeJobs.delete(jobId); } export function getSessionStatus(): SessionStatus { const session = getSession(); if (!session) { return { connected: false, activeJobCount: 0 }; } const running = getRunningJobs(session); // Find one running job's phase for a UI hint let activeJobPhase: string | undefined; for (const jid of running) { const p = getProgress(jid); if (p?.phase) { activeJobPhase = p.phase; break; } } return { connected: true, username: session.username, connectedAt: session.connectedAt, activeJobCount: running.length, activeJobPhase, }; } /* ------------------------------------------------------------------ */ /* Helpers */ /* ------------------------------------------------------------------ */ function getRunningJobs(session: EterraSession): string[] { // Guard: ensure activeJobs is iterable (Set). Containers may restart // with stale globalThis where the Set was serialized as plain object. if (!(session.activeJobs instanceof Set)) { session.activeJobs = new Set(); return []; } const running: string[] = []; for (const jid of session.activeJobs) { const p = getProgress(jid); if (p && p.status === "running") { running.push(jid); } else { session.activeJobs.delete(jid); } } return running; }