/** * Session Cache Service * * Redis-based caching for MPC session data. */ import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import Redis from 'ioredis'; export interface CachedSessionInfo { sessionId: string; sessionType: string; participants: string[]; thresholdN: number; thresholdT: number; status: string; createdAt: string; } @Injectable() export class SessionCacheService implements OnModuleInit, OnModuleDestroy { private readonly logger = new Logger(SessionCacheService.name); private redis: Redis; private readonly keyPrefix = 'mpc:session:'; private readonly defaultTTL = 3600; // 1 hour constructor(private readonly configService: ConfigService) {} async onModuleInit() { const host = this.configService.get('REDIS_HOST') || 'localhost'; const port = this.configService.get('REDIS_PORT') || 6379; const password = this.configService.get('REDIS_PASSWORD'); const db = this.configService.get('REDIS_DB') || 5; this.redis = new Redis({ host, port, password, db, retryStrategy: (times) => { const delay = Math.min(times * 50, 2000); return delay; }, }); this.redis.on('connect', () => { this.logger.log('Redis connected'); }); this.redis.on('error', (err) => { this.logger.error('Redis error', err); }); } async onModuleDestroy() { await this.redis.quit(); this.logger.log('Redis disconnected'); } /** * Cache session info */ async cacheSession(sessionId: string, info: CachedSessionInfo, ttl?: number): Promise { const key = this.keyPrefix + sessionId; await this.redis.setex(key, ttl || this.defaultTTL, JSON.stringify(info)); this.logger.debug(`Cached session: ${sessionId}`); } /** * Get cached session info */ async getSession(sessionId: string): Promise { const key = this.keyPrefix + sessionId; const data = await this.redis.get(key); if (!data) { return null; } return JSON.parse(data); } /** * Update session status in cache */ async updateSessionStatus(sessionId: string, status: string): Promise { const session = await this.getSession(sessionId); if (session) { session.status = status; await this.cacheSession(sessionId, session); } } /** * Remove session from cache */ async removeSession(sessionId: string): Promise { const key = this.keyPrefix + sessionId; await this.redis.del(key); this.logger.debug(`Removed session from cache: ${sessionId}`); } /** * Check if session exists in cache */ async hasSession(sessionId: string): Promise { const key = this.keyPrefix + sessionId; return (await this.redis.exists(key)) === 1; } /** * Cache session message for relay */ async cacheMessage(sessionId: string, messageId: string, message: any, ttl?: number): Promise { const key = `${this.keyPrefix}${sessionId}:msg:${messageId}`; await this.redis.setex(key, ttl || 300, JSON.stringify(message)); } /** * Get pending messages for a session */ async getPendingMessages(sessionId: string, partyId: string): Promise { const pattern = `${this.keyPrefix}${sessionId}:msg:*`; const keys = await this.redis.keys(pattern); const messages: any[] = []; for (const key of keys) { const data = await this.redis.get(key); if (data) { const message = JSON.parse(data); // Filter messages for this party if (!message.toParties || message.toParties.includes(partyId)) { messages.push(message); } } } return messages; } /** * Generic key-value operations */ async set(key: string, value: any, ttl?: number): Promise { const fullKey = this.keyPrefix + key; if (ttl) { await this.redis.setex(fullKey, ttl, JSON.stringify(value)); } else { await this.redis.set(fullKey, JSON.stringify(value)); } } async get(key: string): Promise { const fullKey = this.keyPrefix + key; const data = await this.redis.get(fullKey); return data ? JSON.parse(data) : null; } async delete(key: string): Promise { const fullKey = this.keyPrefix + key; await this.redis.del(fullKey); } }