swaf/src/components/RedisComponent.ts

123 lines
4.1 KiB
TypeScript

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<void> {
const redisUrl = `redis://${config.get('redis.host')}:${config.get('redis.port')}`;
this.redisClient = createClient({
url: redisUrl,
password: config.has('redis.password') ? config.get<string>('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<void> {
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<T extends string | undefined>(key: string, defaultValue?: T): Promise<T> {
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<boolean> {
return await this.get(key) !== undefined;
}
public async forget(key: string): Promise<void> {
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<void> {
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<void> {
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<number>('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<number>('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);
}
}