import config from "config"; import {Express} from "express"; import session, {Store} from "express-session"; import {createClient, RedisClientType} from "redis"; import ApplicationComponent from "../ApplicationComponent.js"; import CacheProvider from "../CacheProvider.js"; import {logger} from "../Logger.js"; export default class RedisComponent extends ApplicationComponent implements CacheProvider { private readonly prefix: string = config.get('redis.prefix'); private redisClient?: RedisClientType; private store: Store = new RedisStore(this); public async start(_app: Express): Promise { const redisUrl = `redis://${config.get('redis.host')}:${config.get('redis.port')}`; this.redisClient = createClient({ url: redisUrl, password: config.has('redis.password') ? config.get('redis.password') : undefined, }); this.redisClient.on('error', (err: Error) => { logger.error(err, 'An error occurred with redis.'); }); await this.redisClient.connect(); } public async stop(): Promise { const redisClient = this.redisClient; if (redisClient) { await this.close('Redis connection', callback => { redisClient.quit() .then(() => callback()) .catch(callback); }); } } public getStore(): Store { return this.store; } public isReady(): boolean { return this.redisClient !== undefined && this.redisClient.isOpen; } public async get(key: string, defaultValue?: T): Promise { if (!this.redisClient) { throw new Error(`Redis client was not initialized.`); } return (await this.redisClient.get(this.prefix + key)|| defaultValue || undefined) as T; } public async has(key: string): Promise { return await this.get(key) !== undefined; } public async forget(key: string): Promise { if (!this.redisClient) { throw new Error(`Redis client was not initialized.`); } await this.redisClient.del(this.prefix + key); } public async remember(key: string, value: string, ttl: number): Promise { if (!this.redisClient) { throw new Error(`Redis client was not initialized.`); } await this.redisClient.pSetEx(this.prefix + key, ttl, value); } public async persist(key: string, ttl: number): Promise { if (!this.redisClient) { throw new Error(`Redis client was not initialized.`); } await this.redisClient.pExpire(this.prefix + key, ttl); } } class RedisStore extends Store { private readonly redisComponent: RedisComponent; public constructor(redisComponent: RedisComponent) { super(); this.redisComponent = redisComponent; } public get(sid: string, callback: (err?: Error, session?: (session.SessionData | null)) => void): void { this.redisComponent.get(`-session:${sid}`) .then(value => { if (value) { this.redisComponent.persist(`-session:${sid}`, config.get('session.cookie.maxAge')) .then(() => { callback(undefined, JSON.parse(value)); }) .catch(callback); } else { callback(undefined, null); } }) .catch(callback); } public set(sid: string, session: session.SessionData, callback?: (err?: Error) => void): void { this.redisComponent.remember(`-session:${sid}`, JSON.stringify(session), config.get('session.cookie.maxAge')) .then(() => { if (callback) callback(); }) .catch(callback); } public destroy(sid: string, callback?: (err?: Error) => void): void { this.redisComponent.forget(`-session:${sid}`) .then(() => { if (callback) callback(); }) .catch(callback); } }