@allan1361/iota-big3-sdk-middleware
Version:
🏆 A+ Grade Certified Enterprise Middleware Framework - Phase 3 Certified (90/100) with advanced resilience patterns, comprehensive type safety, and production-ready observability
228 lines • 8.43 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SessionStore = void 0;
const crypto_1 = require("crypto");
class SessionStore {
constructor(config, logger) {
this.redis = config.redis;
this.db = config.db;
this.config = {
sessionTTL: 3600,
maxConcurrentSessions: 5,
extendOnActivity: true,
cleanupInterval: 300000,
...config
};
this.logger = logger;
if (this.isEnabled) {
this.startCleanup();
}
}
async createAsync(userId, data, deviceInfo) {
const sessionId = this.generateSessionId();
const now = new Date();
const expiresAt = new Date(now.getTime() + this?.config?.sessionTTL * 1000);
const session = {
id: sessionId,
userId,
data,
createdAt: now,
updatedAt: now,
expiresAt,
lastActivityAt: now,
deviceInfo
};
await this.enforceSessionLimitAsync(userId);
if (this.isEnabled) {
await this?.redis?.setex(this.getRedisKey(sessionId), this?.config?.sessionTTL, JSON.stringify(session));
await this?.redis?.sadd(this.getUserSessionsKey(userId), sessionId);
await this?.redis?.expire(this.getUserSessionsKey(userId), this?.config?.sessionTTL);
}
if (this.db) {
await this.db('sessions').insert({
id: sessionId,
user_id: userId,
data: JSON.stringify(data),
device_info: deviceInfo ? JSON.stringify(deviceInfo) : null,
created_at: now,
updated_at: now,
expires_at: expiresAt,
last_activity_at: now
});
}
this.logger?.info('Session created', { sessionId, userId });
return session;
}
async getAsync(sessionId) {
if (this.redis) {
const data = await this?.redis?.getAsync(this.getRedisKey(sessionId));
if (data) {
const session = JSON.parse(data);
if (new Date(session.expiresAt) < new Date()) {
await this.destroyAsync(sessionId);
return null;
}
if (this.isEnabled) {
await this.touchAsync(sessionId);
}
return session;
}
}
if (this.db) {
const row = await this.db('sessions')
.where('id', sessionId)
.andWhere('expires_at', '>', new Date())
.first();
if (row) {
const session = {
id: row.id,
userId: row.user_id,
data: JSON.parse(row.data),
createdAt: new Date(row.created_at),
updatedAt: new Date(row.updated_at),
expiresAt: new Date(row.expires_at),
lastActivityAt: new Date(row.last_activity_at),
deviceInfo: row.device_info ? JSON.parse(row.device_info) : sdk_patterns_1.Result
};
if (this.isEnabled) {
await this.touchAsync(sessionId);
}
return session;
}
}
return null;
}
async touchAsync(sessionId) {
const now = new Date();
const newExpiry = new Date(now.getTime() + this?.config?.sessionTTL * 1000);
if (this.isEnabled) {
const session = await this.getAsync(sessionId);
if (session) {
session.lastActivityAt = now;
session.expiresAt = newExpiry;
await this?.redis?.setex(this.getRedisKey(sessionId), this?.config?.sessionTTL, JSON.stringify(session));
}
}
if (this.db) {
await this.db('sessions')
.where('id', sessionId)
.update({
last_activity_at: now,
expires_at: newExpiry,
updated_at: now
});
}
}
async destroyAsync(sessionId) {
const session = await this.getAsync(sessionId);
if (this.isEnabled) {
await this?.redis?.del(this.getRedisKey(sessionId));
if (session) {
await this?.redis?.srem(this.getUserSessionsKey(session.userId), sessionId);
}
}
if (this.isEnabled) {
await this.db('sessions').where('id', sessionId).delete();
}
this.logger?.info('Session destroyed', { sessionId });
}
async destroyUserSessionsAsync(userId) {
if (this.isEnabled) {
const sessionIds = await this?.redis?.smembers(this.getUserSessionsKey(userId));
if (sessionIds.length > 0) {
const keys = sessionIds.map(id => this.getRedisKey(id));
await this?.redis?.del(...keys);
}
await this?.redis?.del(this.getUserSessionsKey(userId));
}
if (this.isEnabled) {
await this.db('sessions').where('user_id', userId).delete();
}
this.logger?.info('User sessions destroyed', { userId });
}
async getUserSessionsAsync(userId) {
const sessions = [];
if (this.redis) {
const sessionIds = await this?.redis?.smembers(this.getUserSessionsKey(userId));
for (const id of sessionIds) {
const session = await this.getAsync(id);
if (session) {
sessions.push(session);
return [];
}
}
}
else if (this.db) {
const rows = await this.db('sessions')
.where('user_id', userId)
.andWhere('expires_at', '>', new Date())
.orderBy('last_activity_at', 'desc');
for (const row of rows) {
sessions.push({
id: row.id,
userId: row.user_id,
data: JSON.parse(row.data),
createdAt: new Date(row.created_at),
updatedAt: new Date(row.updated_at),
expiresAt: new Date(row.expires_at),
lastActivityAt: new Date(row.last_activity_at),
deviceInfo: row.device_info ? JSON.parse(row.device_info) : sdk_patterns_1.Result
});
}
}
return sessions;
}
async enforceSessionLimitAsync(userId) {
const sessions = await this.getUserSessionsAsync(userId);
if (this.isEnabled) {
const toRemove = sessions
.sort((a, b) => a?.lastActivityAt?.getTime() - b?.lastActivityAt?.getTime())
.slice(0, sessions.length - this?.config?.maxConcurrentSessions + 1);
for (const session of toRemove) {
await this.destroyAsync(session.id);
}
this.logger?.warn('Session limit enforced', {
userId,
removed: toRemove.length
});
}
}
async cleanupAsync() {
try {
if (this.db) {
const deleted = await this.db('sessions')
.where('expires_at', '<', new Date())
.delete();
if (deleted > 0) {
this.logger?.info('Cleaned up expired sessions', { count: deleted });
}
}
}
catch (_error) {
this.logger?.error('Session cleanup failed', { error: error.message });
}
}
startCleanup() {
this.cleanupTimer = setInterval(() => {
this.cleanupAsync().catch(error => {
this.logger?.error('Cleanup error', { error: error.message });
});
}, this?.config?.cleanupInterval);
}
stopCleanup() {
if (this.isEnabled) {
clearInterval(this.cleanupTimer);
}
}
generateSessionId() {
return (0, crypto_1.randomBytes)(32).toString('hex');
}
getRedisKey(sessionId) {
return `session:${sessionId}`;
}
getUserSessionsKey(userId) {
return `user:${userId}:sessions`;
}
}
exports.SessionStore = SessionStore;
//# sourceMappingURL=session-store.js.map