UNPKG

@executeautomation/playwright-mcp-server

Version:
209 lines (208 loc) 6.2 kB
import { RateLimitCategory } from './types'; /** * Rate Limiter Class * Implements request throttling and resource protection */ export class RateLimiter { constructor(config) { this.store = new Map(); this.categoryConfigs = new Map(); this.config = config; this.stats = { totalRequests: 0, blockedRequests: 0, activeEntries: 0, byCategory: {}, timestamp: Date.now() }; // Start cleanup interval to remove expired entries this.startCleanup(); } /** * Set rate limit configuration for a specific category * @param category Rate limit category * @param config Category-specific configuration */ setCategoryConfig(category, config) { this.categoryConfigs.set(category, config); } /** * Check if request is within rate limits * @param key Rate limit key (usually IP or user ID) * @param category Request category * @returns Rate limit check result */ checkLimit(key, category = RateLimitCategory.GENERAL) { const config = this.categoryConfigs.get(category) || this.config; const now = Date.now(); const windowStart = now - config.windowMs; // Get or create entry for this key let entry = this.store.get(key); if (!entry || entry.windowStart < windowStart) { // Create new window entry = { count: 0, windowStart: now, firstRequest: now }; this.store.set(key, entry); } // Update statistics this.stats.totalRequests++; if (!this.stats.byCategory[category]) { this.stats.byCategory[category] = { requests: 0, blocked: 0 }; } this.stats.byCategory[category].requests++; // Check if limit exceeded const allowed = entry.count < config.maxRequests; if (allowed) { entry.count++; } else { this.stats.blockedRequests++; this.stats.byCategory[category].blocked++; } const resetTime = entry.windowStart + config.windowMs; const retryAfter = allowed ? undefined : Math.ceil((resetTime - now) / 1000); return { allowed, remaining: Math.max(0, config.maxRequests - entry.count), resetTime, retryAfter, limit: config.maxRequests, current: entry.count }; } /** * Reset rate limits for a specific key * @param key Rate limit key to reset */ resetLimits(key) { this.store.delete(key); } /** * Reset all rate limits */ resetAllLimits() { this.store.clear(); } /** * Get rate limiter statistics * @returns Current statistics */ getStats() { this.stats.activeEntries = this.store.size; this.stats.timestamp = Date.now(); return { ...this.stats }; } /** * Get current entries count * @returns Number of active rate limit entries */ getActiveEntries() { return this.store.size; } /** * Check if a key is currently rate limited * @param key Rate limit key * @param category Request category * @returns Whether the key is rate limited */ isRateLimited(key, category = RateLimitCategory.GENERAL) { const result = this.checkLimit(key, category); return !result.allowed; } /** * Get remaining requests for a key * @param key Rate limit key * @param category Request category * @returns Number of remaining requests */ getRemainingRequests(key, category = RateLimitCategory.GENERAL) { const result = this.checkLimit(key, category); return result.remaining; } /** * Start cleanup interval to remove expired entries */ startCleanup() { this.cleanupInterval = setInterval(() => { this.cleanup(); }, 60000); // Cleanup every minute } /** * Stop cleanup interval */ stopCleanup() { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = undefined; } } /** * Remove expired rate limit entries */ cleanup() { const now = Date.now(); const expiredKeys = []; for (const [key, entry] of this.store.entries()) { // Remove entries older than the longest window const maxWindow = Math.max(this.config.windowMs, ...Array.from(this.categoryConfigs.values()).map(c => c.windowMs)); if (entry.windowStart < now - maxWindow) { expiredKeys.push(key); } } expiredKeys.forEach(key => this.store.delete(key)); } } /** * Resource Limiter Class * Manages system resource limits */ export class ResourceLimiter { constructor(config) { this.activeBrowsers = 0; this.config = config; } /** * Check if browser instance can be created * @returns Whether browser creation is allowed */ canCreateBrowser() { return this.activeBrowsers < this.config.maxBrowsers; } /** * Register a new browser instance */ registerBrowser() { this.activeBrowsers++; } /** * Unregister a browser instance */ unregisterBrowser() { this.activeBrowsers = Math.max(0, this.activeBrowsers - 1); } /** * Get current browser count */ getActiveBrowserCount() { return this.activeBrowsers; } /** * Check current memory usage * @returns Whether memory usage is within limits */ checkMemoryUsage() { const memoryUsage = process.memoryUsage(); return memoryUsage.heapUsed < this.config.maxMemoryUsage; } /** * Get current memory usage percentage * @returns Memory usage as percentage of limit */ getMemoryUsagePercent() { const memoryUsage = process.memoryUsage(); return (memoryUsage.heapUsed / this.config.maxMemoryUsage) * 100; } }