import { Redis } from '@ehacke/redis'; import { RateLimiterMemory, RateLimiterRedis } from 'rate-limiter-flexible'; export interface LimitResultInterface { name: string; limit: number; remaining: number; resetMs: number; blockReason: string | null; } export interface CreateLimiterInterface { redis: Redis; points: number; durationSec: number; blockDurationSec: number; name: string; reason: string; getKey(body, ipAddress: string): string; shouldConsume(body, ipAddress: string): Promise<boolean>; shouldReset(body, ipAddress: string): Promise<boolean>; execEvenly?: boolean; execEvenlyMinDelayMs?: number; } interface LimiterInterface { limiter: RateLimiterRedis; name: string; max: number; reason: string; blockDurationSec: number; getKey(body, ipAddress): string; shouldConsume(body, ipAddress: string): Promise<boolean>; shouldReset(body, ipAddress: string): Promise<boolean>; } const CONSTANTS = { WORKER_COUNT: 1, }; /** * @class */ export class Limiter implements LimiterInterface { /** * @param {LimiterInterface} params */ constructor(params: LimiterInterface) { this.limiter = params.limiter; this.name = params.name; this.reason = params.reason; this.max = params.max; this.getKey = params.getKey; this.shouldConsume = params.shouldConsume; this.shouldReset = params.shouldReset; this.blockDurationSec = params.blockDurationSec; } readonly name: string; readonly reason: string; readonly max: number; readonly blockDurationSec: number; // eslint-disable-next-line no-unused-vars,class-methods-use-this,@typescript-eslint/no-unused-vars,require-jsdoc getKey(body: any, ipAddress: string): string { throw new Error('Method not implemented.'); } // eslint-disable-next-line no-unused-vars,class-methods-use-this,@typescript-eslint/no-unused-vars,require-jsdoc shouldConsume(body: any, ipAddress: string): Promise<boolean> { throw new Error('Method not implemented.'); } // eslint-disable-next-line no-unused-vars,class-methods-use-this,@typescript-eslint/no-unused-vars,require-jsdoc shouldReset(body: any, ipAddress: string): Promise<boolean> { throw new Error('Method not implemented.'); } readonly limiter: RateLimiterRedis; /** * Create instance * * @param {CreateLimiterInterface} params * @returns {Limiter} */ static create(params: CreateLimiterInterface): Limiter { const rateLimiterMemory = new RateLimiterMemory({ keyPrefix: params.name, points: Math.round(params.points / CONSTANTS.WORKER_COUNT), duration: params.durationSec, blockDuration: params.blockDurationSec, execEvenly: params.execEvenly, execEvenlyMinDelayMs: params.execEvenlyMinDelayMs, }); return new Limiter({ name: params.name, reason: params.reason, getKey: params.getKey, shouldConsume: params.shouldConsume, shouldReset: params.shouldReset, max: params.points, blockDurationSec: params.blockDurationSec, limiter: new RateLimiterRedis({ storeClient: params.redis, keyPrefix: params.name, points: params.points, duration: params.durationSec, blockDuration: params.blockDurationSec, execEvenly: params.execEvenly, execEvenlyMinDelayMs: params.execEvenlyMinDelayMs, insuranceLimiter: rateLimiterMemory, }), }); } }