// rateLimit.js - Rate Limiting and Resource Management
const config = require('./config');
const audit = require('./audit');
class RateLimiter {
constructor() {
this.requestMap = new Map(); // clientId -> array of timestamps
this.activeCaptureCount = 0;
this.maxConcurrent = config.security.maxConcurrentCaptures;
this.enabled = config.security.rateLimit.enabled;
this.maxRequests = config.security.rateLimit.maxRequests;
this.windowMs = config.security.rateLimit.windowMs;
// Cleanup old entries periodically
setInterval(() => this.cleanup(), 60000); // Every minute
}
/**
* Check if client has exceeded rate limit
*/
checkRateLimit(clientId) {
if (!this.enabled) return true;
const now = Date.now();
const clientRequests = this.requestMap.get(clientId) || [];
// Remove old requests outside the time window
const validRequests = clientRequests.filter(
timestamp => now - timestamp < this.windowMs
);
// Check if limit exceeded
if (validRequests.length >= this.maxRequests) {
audit.logRateLimitViolation(clientId, 'API');
throw new Error(
`Rate limit exceeded: Maximum ${this.maxRequests} requests per ${this.windowMs/1000} seconds`
);
}
// Add current request
validRequests.push(now);
this.requestMap.set(clientId, validRequests);
return true;
}
/**
* Acquire a capture slot (for concurrent capture limiting)
*/
async acquireCaptureSlot(clientId) {
if (this.activeCaptureCount >= this.maxConcurrent) {
audit.logSecurityEvent(
'CAPTURE_LIMIT_REACHED',
{
activeCaptureCount: this.activeCaptureCount,
maxConcurrent: this.maxConcurrent
},
clientId
);
throw new Error(
`Too many concurrent captures: Maximum ${this.maxConcurrent} allowed, ${this.activeCaptureCount} active`
);
}
this.activeCaptureCount++;
console.error(`[RESOURCE] Capture slot acquired (${this.activeCaptureCount}/${this.maxConcurrent})`);
}
/**
* Release a capture slot
*/
releaseCaptureSlot() {
if (this.activeCaptureCount > 0) {
this.activeCaptureCount--;
console.error(`[RESOURCE] Capture slot released (${this.activeCaptureCount}/${this.maxConcurrent})`);
}
}
/**
* Get current rate limit status for a client
*/
getStatus(clientId) {
const now = Date.now();
const clientRequests = this.requestMap.get(clientId) || [];
const validRequests = clientRequests.filter(
timestamp => now - timestamp < this.windowMs
);
return {
requestsInWindow: validRequests.length,
maxRequests: this.maxRequests,
windowMs: this.windowMs,
remaining: Math.max(0, this.maxRequests - validRequests.length),
resetAt: validRequests.length > 0
? new Date(validRequests[0] + this.windowMs).toISOString()
: null,
activeCaptureCount: this.activeCaptureCount,
maxConcurrentCaptures: this.maxConcurrent
};
}
/**
* Cleanup old entries from memory
*/
cleanup() {
const now = Date.now();
let cleaned = 0;
for (const [clientId, requests] of this.requestMap.entries()) {
const validRequests = requests.filter(
timestamp => now - timestamp < this.windowMs
);
if (validRequests.length === 0) {
this.requestMap.delete(clientId);
cleaned++;
} else {
this.requestMap.set(clientId, validRequests);
}
}
if (cleaned > 0) {
console.error(`[RESOURCE] Cleaned ${cleaned} expired rate limit entries`);
}
}
/**
* Reset rate limit for a specific client (admin function)
*/
reset(clientId) {
this.requestMap.delete(clientId);
console.error(`[RESOURCE] Rate limit reset for client: ${clientId}`);
}
/**
* Reset all rate limits (admin function)
*/
resetAll() {
const size = this.requestMap.size;
this.requestMap.clear();
console.error(`[RESOURCE] All rate limits reset (${size} clients)`);
}
}
// Export singleton instance
module.exports = new RateLimiter();