UNPKG

@iota-big3/sdk-gateway

Version:

Universal API Gateway with protocol translation, intelligent routing, rate limiting, health checking, and caching

212 lines 6.04 kB
"use strict"; /** * @iota-big3/sdk-gateway * Rate Limiter - Request rate limiting with configurable strategies * Phase 2g Implementation */ Object.defineProperty(exports, "__esModule", { value: true }); exports.RateLimiter = void 0; const events_1 = require("events"); /** * In-memory store for rate limiting */ class MemoryStore { constructor() { this.store = new Map(); } async increment(key, windowMs) { const now = Date.now(); const record = this.store.get(key); if (!record || now > record.resetTime) { // New window const resetTime = now + windowMs; const newRecord = { count: 1, resetTime }; this.store.set(key, newRecord); return newRecord; } // Increment existing record.count++; return record; } async reset(key) { this.store.delete(key); } async clear() { this.store.clear(); } getSize() { return this.store.size; } // Clean up expired entries cleanup() { const now = Date.now(); for (const [key, record] of this.store.entries()) { if (now > record.resetTime) { this.store.delete(key); } } } } /** * Rate limiter implementation */ class RateLimiter extends events_1.EventEmitter { constructor(config) { super(); this.enabled = true; this.metrics = { totalRequests: 0, allowedRequests: 0, blockedRequests: 0, skippedRequests: 0, activeKeys: 0 }; const defaults = { windowMs: 60000, max: 100, keyGenerator: (req) => req.headers['x-forwarded-for'] || 'anonymous', skip: () => false, handler: (req, res) => { }, onLimitReached: () => { }, store: new MemoryStore() }; this.config = { ...defaults, ...config }; this.store = this.config.store; // Start cleanup interval for memory store if (this.store instanceof MemoryStore) { this.cleanupInterval = setInterval(() => { this.store.cleanup(); this.metrics.activeKeys = this.store.getSize(); }, 60000); // Clean up every minute } } /** * Check if request is within rate limit */ async checkLimitAsync(request) { this.metrics.totalRequests++; // Check if disabled if (!this.enabled) { this.metrics.allowedRequests++; return { allowed: true, remaining: -1, // Indicates no limit skipped: true }; } // Check if should skip if (this.config.skip(request)) { this.metrics.skippedRequests++; return { allowed: true, remaining: Number.MAX_SAFE_INTEGER, skipped: true }; } try { // Generate key for rate limiting const key = this.config.keyGenerator(request); const result = await this.incrementKeyAsync(key); if (result.count <= this.config.max) { // Within limit this.metrics.allowedRequests++; return { allowed: true, remaining: this.config.max - result.count, resetTime: result.resetTime }; } else { // Limit exceeded this.metrics.blockedRequests++; // Notify callback const info = { key, limit: this.config.max, windowMs: this.config.windowMs, hits: result.count, resetTime: result.resetTime }; this.config.onLimitReached(info); this.emit('limit:reached', info); return { allowed: false, remaining: 0, resetTime: result.resetTime, retryAfter: Math.max(0, result.resetTime - Date.now()) }; } } catch (error) { // Fail open - allow request on error this.emit('error', error); this.metrics.allowedRequests++; return { allowed: true, remaining: -1, error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Increment key in store */ async incrementKeyAsync(key) { return this.store.increment(key, this.config.windowMs); } /** * Reset limit for a specific key */ async resetAsync(key) { await this.store.reset(key); this.emit('limit:reset', { key }); } /** * Clear all limits */ async clearAsync() { await this.store.clear(); this.metrics = { totalRequests: 0, allowedRequests: 0, blockedRequests: 0, skippedRequests: 0, activeKeys: 0 }; this.emit('limits:cleared'); } /** * Get current metrics */ getMetrics() { return { ...this.metrics }; } /** * Enable rate limiting */ enable() { this.enabled = true; this.emit('limiter:enabled'); } /** * Disable rate limiting */ disable() { this.enabled = false; this.emit('limiter:disabled'); } /** * Check if rate limiting is enabled */ isEnabled() { return this.enabled; } /** * Destroy the rate limiter */ destroy() { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = undefined; } this.removeAllListeners(); } } exports.RateLimiter = RateLimiter; //# sourceMappingURL=rate-limiter.js.map