@iota-big3/sdk-gateway
Version:
Universal API Gateway with protocol translation, intelligent routing, rate limiting, health checking, and caching
212 lines • 6.04 kB
JavaScript
"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