/* eslint-disable max-classes-per-file */ import { RedisConnectionContract } from '@ioc:Adonis/Addons/Redis'; type SessionDataType = { userId: string; userName: string; connected: boolean }; const SESSION_TTL = 24 * 60 * 60; // 1 day class RedisSessionStore { public redisClient: RedisConnectionContract; constructor(redisClient: RedisConnectionContract) { this.redisClient = redisClient; } // eslint-disable-next-line class-methods-use-this private transform(values: (string | null)[]) { const [userId, userName, connected] = values; if (!userId || !userName || !connected) return undefined; return { userId, userName, connected: connected === 'true', }; } private async scanSessionKeys(): Promise> { const keys = new Set(); let nextIndex = 0; do { // eslint-disable-next-line no-await-in-loop const [nextIndexAsStr, result] = await this.redisClient.scan(nextIndex, 'MATCH', '*', 'COUNT', 100); nextIndex = parseInt(nextIndexAsStr, 10); result.forEach(s => keys.add(s)); } while (nextIndex !== 0); return keys; } /** * Return session data by session id * * @param id Session id * @returns Session data */ public async findSession(id: string): Promise { const values = await this.redisClient.hmget(id, 'userId', 'userName', 'connected'); return this.transform(values); } /** * Returns the all sessions from store */ public async getSessions(): Promise> { const keys = await this.scanSessionKeys(); const commands = Array.from(keys).map(key => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const [prefix, ...id] = key.split('-'); return ['hmget', id.join('-'), 'userId', 'userName', 'connected']; }); return this.redisClient .multi(commands) .exec() .then(results => results.map(([err, session]) => (err ? undefined : this.transform(session)))); } /** * Returns the unique sessions with connected flag `true` */ public async getOnlineSessionsCount() { const sessions = await this.getSessions(); const online = new Set(sessions.filter(s => s?.connected === true).map(s => s?.userId)); return online.size; } /** * Set session data to redis store * * @param id Session id * @param data Session data */ public async saveSession(id: string, data: SessionDataType): Promise { await this.redisClient .multi() .hmset(id, { userId: data.userId, userName: data.userName, connected: data.connected, }) .expire(id, SESSION_TTL) .exec(); } /** * Remove session from redis store * * @param id Session id */ public async destroySession(id: string): Promise { const session = await this.findSession(id); if (session) { return this.redisClient.del(id); } return 0; } /** * Remove all sessions from redis store * * @returns Count of deleted sessions */ public async destroyAllSessions() { const keys = await this.scanSessionKeys().then(values => Array.from(values).map(v => { const [, ...key] = v.split('-'); return key.join('-'); }) ); if (keys.length > 0) { const deleted = await this.redisClient.del(keys); return deleted; } return 0; } } export default RedisSessionStore;