UNPKG

express-rate-limiter-ts

Version:

A powerful, flexible and modern rate limiter middleware for Express.js written in TypeScript.

96 lines (95 loc) 3.64 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RedisStore = void 0; const ioredis_1 = __importDefault(require("ioredis")); // RedisStore: Fully working, async, TTL and ban supported store for production. class RedisStore { constructor(options) { this.statsKey = 'rateLimiter:stats'; this.banPrefix = 'rateLimiter:ban:'; this.countPrefix = 'rateLimiter:count:'; this.windowMs = options.windowMs; this.client = new ioredis_1.default(options.url); } // Increment counter and check limit (async) async incr(key) { const redisKey = this.countPrefix + key; // Increment counter, set TTL if key does not exist const current = await this.client.incr(redisKey); if (current === 1) { await this.client.pexpire(redisKey, this.windowMs); } const ttl = await this.client.pttl(redisKey); return { current, remaining: 0, // Remaining limit will be calculated in middleware resetTime: new Date(Date.now() + (ttl > 0 ? ttl : this.windowMs)), exceeded: false, }; } // Read counter (async) async get(key) { const redisKey = this.countPrefix + key; const count = parseInt(await this.client.get(redisKey) || '0', 10); const ttl = await this.client.pttl(redisKey); return { count, resetTime: Date.now() + (ttl > 0 ? ttl : this.windowMs) }; } // Reset counter async reset(key) { await this.client.del(this.countPrefix + key); } // Record statistic (optional) async recordStat(route) { await this.client.hincrby(this.statsKey, route, 1); await this.client.hset(this.statsKey + ':last', route, Date.now()); } // Get statistics async getStats() { const counts = await this.client.hgetall(this.statsKey); const lasts = await this.client.hgetall(this.statsKey + ':last'); const stats = {}; for (const route in counts) { stats[route] = { count: parseInt(counts[route], 10), lastTriggered: new Date(Number(lasts[route]) || Date.now()), }; } return stats; } // Ban async ban(key, seconds, reason) { await this.client.set(this.banPrefix + key, reason, 'EX', seconds); } // Check ban async isBanned(key) { const reason = await this.client.get(this.banPrefix + key); if (reason) { const ttl = await this.client.ttl(this.banPrefix + key); return { banned: true, banExpiresAt: new Date(Date.now() + ttl * 1000), reason }; } return { banned: false }; } // Get banned keys (scan all, should be optimized for large data) async getBanned() { const keys = await this.client.keys(this.banPrefix + '*'); const result = {}; for (const fullKey of keys) { const key = fullKey.replace(this.banPrefix, ''); const reason = await this.client.get(fullKey); const ttl = await this.client.ttl(fullKey); if (reason && ttl > 0) { result[key] = { banExpiresAt: new Date(Date.now() + ttl * 1000), reason }; } } return result; } // Unban async unban(key) { await this.client.del(this.banPrefix + key); await this.client.del(this.countPrefix + key); } } exports.RedisStore = RedisStore;