Spaces:
Running
Running
| 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); | |
| } | |
| } | |
| } |