// "limit" is bugged in Typescript, use "limiter-es6-compat" instead // See https://github.com/jhurliman/node-rate-limiter/issues/80 import { RateLimiter, RateLimiterOpts } from "limiter-es6-compat"; import { log } from "./log"; export interface KumaRateLimiterOpts extends RateLimiterOpts { errorMessage : string; } export type KumaRateLimiterCallback = (err : object) => void; class KumaRateLimiter { errorMessage : string; rateLimiter : RateLimiter; /** * @param {object} config Rate limiter configuration object */ constructor(config : KumaRateLimiterOpts) { this.errorMessage = config.errorMessage; this.rateLimiter = new RateLimiter(config); } /** * Callback for pass * @callback passCB * @param {object} err Too many requests */ /** * Should the request be passed through * @param callback Callback function to call with decision * @param {number} num Number of tokens to remove * @returns {Promise} Should the request be allowed? */ async pass(callback : KumaRateLimiterCallback, num = 1) { const remainingRequests = await this.removeTokens(num); log.info("rate-limit", "remaining requests: " + remainingRequests); if (remainingRequests < 0) { if (callback) { callback({ ok: false, msg: this.errorMessage, }); } return false; } return true; } /** * Remove a given number of tokens * @param {number} num Number of tokens to remove * @returns {Promise} Number of remaining tokens */ async removeTokens(num = 1) { return await this.rateLimiter.removeTokens(num); } } export const loginRateLimiter = new KumaRateLimiter({ tokensPerInterval: 20, interval: "minute", fireImmediately: true, errorMessage: "Too frequently, try again later." }); export const apiRateLimiter = new KumaRateLimiter({ tokensPerInterval: 60, interval: "minute", fireImmediately: true, errorMessage: "Too frequently, try again later." }); export const twoFaRateLimiter = new KumaRateLimiter({ tokensPerInterval: 30, interval: "minute", fireImmediately: true, errorMessage: "Too frequently, try again later." });