2020-04-22 15:52:17 +02:00
|
|
|
import config from "config";
|
2021-05-03 19:29:22 +02:00
|
|
|
import {Express} from "express";
|
2020-04-22 15:52:17 +02:00
|
|
|
import session, {Store} from "express-session";
|
2021-05-03 19:29:22 +02:00
|
|
|
import redis, {RedisClient} from "redis";
|
|
|
|
|
|
|
|
import ApplicationComponent from "../ApplicationComponent.js";
|
|
|
|
import CacheProvider from "../CacheProvider.js";
|
|
|
|
import {logger} from "../Logger.js";
|
2020-04-22 15:52:17 +02:00
|
|
|
|
2020-09-25 22:03:22 +02:00
|
|
|
export default class RedisComponent extends ApplicationComponent implements CacheProvider {
|
2020-04-22 15:52:17 +02:00
|
|
|
private redisClient?: RedisClient;
|
|
|
|
private store?: Store;
|
|
|
|
|
2020-09-25 23:42:15 +02:00
|
|
|
public async start(_app: Express): Promise<void> {
|
2020-04-25 18:31:54 +02:00
|
|
|
this.redisClient = redis.createClient(config.get('redis.port'), config.get('redis.host'), {
|
|
|
|
password: config.has('redis.password') ? config.get<string>('redis.password') : undefined,
|
|
|
|
});
|
2020-09-25 23:42:15 +02:00
|
|
|
this.redisClient.on('error', (err: Error) => {
|
2021-01-22 15:54:26 +01:00
|
|
|
logger.error(err, 'An error occurred with redis.');
|
2020-04-22 15:52:17 +02:00
|
|
|
});
|
2021-01-22 15:55:06 +01:00
|
|
|
|
|
|
|
this.store = new RedisStore(this);
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public async stop(): Promise<void> {
|
2020-09-25 23:42:15 +02:00
|
|
|
const redisClient = this.redisClient;
|
|
|
|
if (redisClient) {
|
|
|
|
await this.close('Redis connection', callback => redisClient.quit(callback));
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public getStore(): Store {
|
|
|
|
if (!this.store) throw `Redis store was not initialized.`;
|
|
|
|
return this.store;
|
|
|
|
}
|
|
|
|
|
|
|
|
public canServe(): boolean {
|
|
|
|
return this.redisClient !== undefined && this.redisClient.connected;
|
|
|
|
}
|
2020-07-19 11:57:47 +02:00
|
|
|
|
2020-10-01 13:58:50 +02:00
|
|
|
public async get<T extends string | undefined>(key: string, defaultValue?: T): Promise<T> {
|
|
|
|
return await new Promise<T>((resolve, reject) => {
|
2020-07-19 11:57:47 +02:00
|
|
|
if (!this.redisClient) {
|
2021-01-22 15:55:06 +01:00
|
|
|
reject(`Redis client was not initialized.`);
|
2020-07-19 11:57:47 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.redisClient.get(key, (err, val) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
return;
|
|
|
|
}
|
2020-10-01 13:58:50 +02:00
|
|
|
resolve((val || defaultValue || undefined) as T);
|
2020-07-19 11:57:47 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public async has(key: string): Promise<boolean> {
|
2020-10-01 13:58:50 +02:00
|
|
|
return await this.get(key) !== undefined;
|
2020-07-19 11:57:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public async forget(key: string): Promise<void> {
|
2020-09-25 23:42:15 +02:00
|
|
|
return await new Promise<void>((resolve, reject) => {
|
2020-07-19 11:57:47 +02:00
|
|
|
if (!this.redisClient) {
|
2021-01-22 15:55:06 +01:00
|
|
|
reject(`Redis client was not initialized.`);
|
2020-07-19 11:57:47 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.redisClient.del(key, (err) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public async remember(key: string, value: string, ttl: number): Promise<void> {
|
2020-09-25 23:42:15 +02:00
|
|
|
return await new Promise<void>((resolve, reject) => {
|
2020-07-19 11:57:47 +02:00
|
|
|
if (!this.redisClient) {
|
2021-01-22 15:55:06 +01:00
|
|
|
reject(`Redis client was not initialized.`);
|
2020-07-19 11:57:47 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.redisClient.psetex(key, ttl, value, (err) => {
|
2021-01-22 15:55:06 +01:00
|
|
|
if (err) return reject(err);
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public async persist(key: string, ttl: number): Promise<void> {
|
|
|
|
return await new Promise<void>((resolve, reject) => {
|
|
|
|
if (!this.redisClient) {
|
|
|
|
reject(`Redis client was not initialized.`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.redisClient.pexpire(key, ttl, (err) => {
|
|
|
|
if (err) return reject(err);
|
2020-07-19 11:57:47 +02:00
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2020-09-25 23:42:15 +02:00
|
|
|
}
|
2021-01-22 15:55:06 +01:00
|
|
|
|
|
|
|
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 {
|
2021-01-24 16:29:23 +01:00
|
|
|
this.redisComponent.get(`-session:${sid}`)
|
2021-01-22 15:55:06 +01:00
|
|
|
.then(value => {
|
|
|
|
if (value) {
|
2021-01-24 16:29:23 +01:00
|
|
|
this.redisComponent.persist(`-session:${sid}`, config.get<number>('session.cookie.maxAge'))
|
2021-01-22 15:55:06 +01:00
|
|
|
.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 {
|
2021-01-24 16:29:23 +01:00
|
|
|
this.redisComponent.remember(`-session:${sid}`, JSON.stringify(session), config.get<number>('session.cookie.maxAge'))
|
2021-01-22 15:55:06 +01:00
|
|
|
.then(() => {
|
|
|
|
if (callback) callback();
|
|
|
|
})
|
|
|
|
.catch(callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
public destroy(sid: string, callback?: (err?: Error) => void): void {
|
2021-01-24 16:29:23 +01:00
|
|
|
this.redisComponent.forget(`-session:${sid}`)
|
2021-01-22 15:55:06 +01:00
|
|
|
.then(() => {
|
|
|
|
if (callback) callback();
|
|
|
|
})
|
|
|
|
.catch(callback);
|
|
|
|
}
|
|
|
|
}
|