Reuben_OS / lib /sessionManager.ts
Reubencf's picture
some changes made
c21bca9
import crypto from 'crypto';
import fs from 'fs/promises';
import fsSync from 'fs';
import path from 'path';
export interface Session {
id: string;
key: string;
createdAt: Date;
lastAccessed: Date;
metadata?: Record<string, any>;
}
export class SessionManager {
private static instance: SessionManager;
private sessions: Map<string, Session> = new Map();
private sessionDir: string;
private publicDir: string;
private constructor() {
// Use /data for Hugging Face persistent storage, fallback to local for development
const baseDataDir = process.env.NODE_ENV === 'production' && fsSync.existsSync('/data')
? '/data'
: path.join(process.cwd(), 'data');
this.sessionDir = path.join(baseDataDir, 'files');
this.publicDir = path.join(baseDataDir, 'public');
this.initializeDirectories();
}
static getInstance(): SessionManager {
if (!SessionManager.instance) {
SessionManager.instance = new SessionManager();
}
return SessionManager.instance;
}
private async initializeDirectories() {
try {
await fs.mkdir(this.sessionDir, { recursive: true });
await fs.mkdir(this.publicDir, { recursive: true });
} catch (error) {
console.error('Error initializing directories:', error);
}
}
generateSessionKey(): string {
return crypto.randomBytes(32).toString('hex');
}
async createSession(metadata?: Record<string, any>): Promise<Session> {
const sessionId = `session_${Date.now()}_${crypto.randomBytes(8).toString('hex')}`;
const sessionKey = this.generateSessionKey();
const session: Session = {
id: sessionId,
key: sessionKey,
createdAt: new Date(),
lastAccessed: new Date(),
metadata
};
this.sessions.set(sessionKey, session);
// Create session directory
const sessionPath = path.join(this.sessionDir, sessionId);
await fs.mkdir(sessionPath, { recursive: true });
// Save session metadata
await fs.writeFile(
path.join(sessionPath, 'session.json'),
JSON.stringify(session, null, 2)
);
return session;
}
async validateSession(sessionIdOrKey: string): Promise<boolean> {
// First check if it's a session ID (starts with "session_")
if (sessionIdOrKey.startsWith('session_')) {
return this.validateSessionById(sessionIdOrKey);
}
// Otherwise treat as session key for backward compatibility
if (this.sessions.has(sessionIdOrKey)) {
const session = this.sessions.get(sessionIdOrKey)!;
session.lastAccessed = new Date();
return true;
}
// Check if session exists on disk by key
try {
const sessionsOnDisk = await fs.readdir(this.sessionDir);
for (const sessionId of sessionsOnDisk) {
const sessionPath = path.join(this.sessionDir, sessionId, 'session.json');
try {
const sessionData = await fs.readFile(sessionPath, 'utf-8');
const session = JSON.parse(sessionData) as Session;
if (session.key === sessionIdOrKey) {
session.lastAccessed = new Date();
this.sessions.set(sessionIdOrKey, session);
return true;
}
} catch (error) {
continue;
}
}
} catch (error) {
console.error('Error validating session:', error);
}
return false;
}
async validateSessionById(sessionId: string): Promise<boolean> {
// Check in memory first
for (const [key, session] of this.sessions.entries()) {
if (session.id === sessionId) {
session.lastAccessed = new Date();
return true;
}
}
// Check on disk
try {
const sessionPath = path.join(this.sessionDir, sessionId, 'session.json');
const sessionData = await fs.readFile(sessionPath, 'utf-8');
const session = JSON.parse(sessionData) as Session;
// Load into memory
session.lastAccessed = new Date();
this.sessions.set(session.key, session);
return true;
} catch (error) {
console.log('Session not found:', sessionId);
}
return false;
}
async getSession(sessionIdOrKey: string): Promise<Session | null> {
// If it's a session ID, find by ID
if (sessionIdOrKey.startsWith('session_')) {
for (const [key, session] of this.sessions.entries()) {
if (session.id === sessionIdOrKey) {
return session;
}
}
// Try loading from disk
try {
const sessionPath = path.join(this.sessionDir, sessionIdOrKey, 'session.json');
const sessionData = await fs.readFile(sessionPath, 'utf-8');
const session = JSON.parse(sessionData) as Session;
this.sessions.set(session.key, session);
return session;
} catch (error) {
return null;
}
}
// Otherwise treat as key
if (await this.validateSession(sessionIdOrKey)) {
return this.sessions.get(sessionIdOrKey) || null;
}
return null;
}
async getSessionPath(sessionIdOrKey: string): Promise<string | null> {
// If it's already a session ID, use it directly
if (sessionIdOrKey.startsWith('session_')) {
const sessionPath = path.join(this.sessionDir, sessionIdOrKey);
try {
await fs.access(sessionPath);
return sessionPath;
} catch {
return null;
}
}
// Otherwise get session by key
const session = await this.getSession(sessionIdOrKey);
if (session) {
return path.join(this.sessionDir, session.id);
}
return null;
}
getPublicPath(): string {
return this.publicDir;
}
async listSessionFiles(sessionIdOrKey: string): Promise<string[]> {
const sessionPath = await this.getSessionPath(sessionIdOrKey);
if (!sessionPath) {
throw new Error('Invalid session ID or key');
}
try {
const files = await fs.readdir(sessionPath);
return files.filter(file => file !== 'session.json');
} catch (error) {
console.error('Error listing session files:', error);
return [];
}
}
async saveFileToSession(sessionIdOrKey: string, fileName: string, content: Buffer | string): Promise<string> {
const sessionPath = await this.getSessionPath(sessionIdOrKey);
if (!sessionPath) {
throw new Error('Invalid session ID or key');
}
const filePath = path.join(sessionPath, fileName);
await fs.writeFile(filePath, content);
return filePath;
}
async getFileFromSession(sessionIdOrKey: string, fileName: string): Promise<Buffer> {
const sessionPath = await this.getSessionPath(sessionIdOrKey);
if (!sessionPath) {
throw new Error('Invalid session ID or key');
}
const filePath = path.join(sessionPath, fileName);
return await fs.readFile(filePath);
}
async saveFileToPublic(fileName: string, content: Buffer | string): Promise<string> {
const filePath = path.join(this.publicDir, fileName);
await fs.writeFile(filePath, content);
return filePath;
}
async getFileFromPublic(fileName: string): Promise<Buffer> {
const filePath = path.join(this.publicDir, fileName);
return await fs.readFile(filePath);
}
async deleteSession(sessionIdOrKey: string): Promise<boolean> {
const sessionPath = await this.getSessionPath(sessionIdOrKey);
if (!sessionPath) {
return false;
}
try {
await fs.rm(sessionPath, { recursive: true, force: true });
// Remove from memory by finding the right key
if (sessionIdOrKey.startsWith('session_')) {
for (const [key, session] of this.sessions.entries()) {
if (session.id === sessionIdOrKey) {
this.sessions.delete(key);
break;
}
}
} else {
this.sessions.delete(sessionIdOrKey);
}
return true;
} catch (error) {
console.error('Error deleting session:', error);
return false;
}
}
// Clean up old sessions (older than 24 hours)
async cleanupOldSessions(): Promise<void> {
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
try {
const sessionsOnDisk = await fs.readdir(this.sessionDir);
for (const sessionId of sessionsOnDisk) {
const sessionPath = path.join(this.sessionDir, sessionId, 'session.json');
try {
const sessionData = await fs.readFile(sessionPath, 'utf-8');
const session = JSON.parse(sessionData) as Session;
if (new Date(session.lastAccessed) < oneDayAgo) {
await fs.rm(path.join(this.sessionDir, sessionId), { recursive: true, force: true });
}
} catch (error) {
continue;
}
}
} catch (error) {
console.error('Error cleaning up old sessions:', error);
}
}
}