/**
* Session Manager
* Manages multiple thinking sessions for concurrent users
*/
import { SequentialThinkingManager } from '../lib.js';
import { generateSecureSessionId } from '../utils/crypto.js';
interface SessionData {
manager: SequentialThinkingManager;
lastAccess: number;
created: number;
}
export class SessionManager {
private sessions: Map<string, SessionData>;
private cleanupInterval: NodeJS.Timeout | null;
private readonly SESSION_TTL = 3600000; // 1 hour
private readonly CLEANUP_INTERVAL = 600000; // 10 minutes
private readonly MAX_SESSIONS = 10000;
constructor() {
this.sessions = new Map();
this.cleanupInterval = null;
this.startCleanup();
}
private startCleanup(): void {
if (this.cleanupInterval) {
return;
}
this.cleanupInterval = setInterval(() => {
this.cleanupExpiredSessions();
}, this.CLEANUP_INTERVAL);
if (this.cleanupInterval.unref) {
this.cleanupInterval.unref();
}
}
private cleanupExpiredSessions(): void {
const now = Date.now();
let cleaned = 0;
for (const [id, session] of this.sessions.entries()) {
if (now - session.lastAccess > this.SESSION_TTL) {
session.manager.destroy();
this.sessions.delete(id);
cleaned++;
}
}
if (cleaned > 0) {
console.log(`SessionManager: Cleaned up ${cleaned} expired sessions. Active: ${this.sessions.size}`);
}
// Emergency cleanup if over limit
if (this.sessions.size > this.MAX_SESSIONS) {
this.emergencyCleanup();
}
}
private emergencyCleanup(): void {
const sessions = Array.from(this.sessions.entries())
.sort((a, b) => a[1].lastAccess - b[1].lastAccess);
const toRemove = sessions.slice(0, Math.floor(this.MAX_SESSIONS * 0.2));
for (const [id, session] of toRemove) {
session.manager.destroy();
this.sessions.delete(id);
}
console.warn(
`SessionManager: Emergency cleanup removed ${toRemove.length} oldest sessions. Active: ${this.sessions.size}`
);
}
/**
* Get or create a session
*/
public getOrCreate(sessionId?: string): { sessionId: string; manager: SequentialThinkingManager } {
const sid = sessionId || generateSecureSessionId();
let session = this.sessions.get(sid);
if (!session) {
session = {
manager: new SequentialThinkingManager(),
lastAccess: Date.now(),
created: Date.now(),
};
this.sessions.set(sid, session);
}
session.lastAccess = Date.now();
return { sessionId: sid, manager: session.manager };
}
/**
* Get an existing session
*/
public get(sessionId: string): SequentialThinkingManager | null {
const session = this.sessions.get(sessionId);
if (session) {
session.lastAccess = Date.now();
return session.manager;
}
return null;
}
/**
* Delete a session
*/
public delete(sessionId: string): boolean {
const session = this.sessions.get(sessionId);
if (session) {
session.manager.destroy();
return this.sessions.delete(sessionId);
}
return false;
}
/**
* Get statistics
*/
public getStats() {
return {
totalSessions: this.sessions.size,
oldestSession: this.getOldestSessionAge(),
newestSession: this.getNewestSessionAge(),
};
}
private getOldestSessionAge(): number | null {
let oldest: number | null = null;
for (const session of this.sessions.values()) {
if (!oldest || session.created < oldest) {
oldest = session.created;
}
}
return oldest;
}
private getNewestSessionAge(): number | null {
let newest: number | null = null;
for (const session of this.sessions.values()) {
if (!newest || session.created > newest) {
newest = session.created;
}
}
return newest;
}
/**
* Destroy all sessions and stop cleanup
*/
public destroy(): void {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
this.cleanupInterval = null;
}
for (const session of this.sessions.values()) {
session.manager.destroy();
}
this.sessions.clear();
}
}