"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.rateLimitService = exports.BurstLimitError = exports.RateLimitError = exports.RateLimitService = exports.ANONYMOUS_TIER = exports.RATE_LIMIT_TIERS = void 0; const rate_limiter_flexible_1 = require("rate-limiter-flexible"); const logger_1 = require("../lib/logger"); exports.RATE_LIMIT_TIERS = { free: { name: 'Free', requestsPerHour: 100, requestsPerMinute: 10, bulkJobsPerDay: 2, maxUrls: 50, exportLimit: 5, }, pro: { name: 'Pro', requestsPerHour: 1000, requestsPerMinute: 50, bulkJobsPerDay: 20, maxUrls: 1000, exportLimit: 100, }, enterprise: { name: 'Enterprise', requestsPerHour: 10000, requestsPerMinute: 200, bulkJobsPerDay: 100, maxUrls: 10000, exportLimit: 1000, }, }; exports.ANONYMOUS_TIER = { name: 'Anonymous', requestsPerHour: 50, requestsPerMinute: 5, bulkJobsPerDay: 0, maxUrls: 10, exportLimit: 0, }; class RateLimitService { rateLimiters; constructor() { this.rateLimiters = new Map(); this.initializeRateLimiters(); } initializeRateLimiters() { this.rateLimiters.set('legacy', new rate_limiter_flexible_1.RateLimiterMemory({ keyPrefix: 'rl_legacy', points: 100, duration: 3600, blockDuration: 3600, execEvenly: true, })); this.rateLimiters.set('anonymous', new rate_limiter_flexible_1.RateLimiterMemory({ keyPrefix: 'rl_anon', points: exports.ANONYMOUS_TIER.requestsPerHour, duration: 3600, blockDuration: 3600, execEvenly: true, })); Object.keys(exports.RATE_LIMIT_TIERS).forEach(tier => { const config = exports.RATE_LIMIT_TIERS[tier]; this.rateLimiters.set(`user_${tier}_hour`, new rate_limiter_flexible_1.RateLimiterMemory({ keyPrefix: `rl_user_${tier}_h`, points: config.requestsPerHour, duration: 3600, blockDuration: 900, execEvenly: true, })); this.rateLimiters.set(`user_${tier}_minute`, new rate_limiter_flexible_1.RateLimiterMemory({ keyPrefix: `rl_user_${tier}_m`, points: config.requestsPerMinute, duration: 60, blockDuration: 60, execEvenly: true, })); this.rateLimiters.set(`bulk_${tier}_day`, new rate_limiter_flexible_1.RateLimiterMemory({ keyPrefix: `rl_bulk_${tier}_d`, points: config.bulkJobsPerDay, duration: 86400, blockDuration: 86400, execEvenly: false, })); this.rateLimiters.set(`export_${tier}_day`, new rate_limiter_flexible_1.RateLimiterMemory({ keyPrefix: `rl_export_${tier}_d`, points: config.exportLimit, duration: 86400, blockDuration: 86400, execEvenly: false, })); }); } async getUserTier(userId) { if (!userId || userId === 'anonymous-user') { return exports.ANONYMOUS_TIER; } return exports.RATE_LIMIT_TIERS.free; } async checkRateLimit(type, key, userId) { try { const tier = await this.getUserTier(userId); let limiterKey; let limit; if (type === 'legacy') { limiterKey = 'legacy'; limit = 100; } else if (!userId || userId === 'anonymous-user') { limiterKey = 'anonymous'; limit = exports.ANONYMOUS_TIER.requestsPerHour; } else { const tierName = tier.name.toLowerCase(); switch (type) { case 'tracking': limiterKey = `user_${tierName}_hour`; limit = tier.requestsPerHour; break; case 'bulk': limiterKey = `bulk_${tierName}_day`; limit = tier.bulkJobsPerDay; break; case 'export': limiterKey = `export_${tierName}_day`; limit = tier.exportLimit; break; default: throw new Error(`Unknown rate limit type: ${type}`); } } const rateLimiter = this.rateLimiters.get(limiterKey); if (!rateLimiter) { throw new Error(`Rate limiter not found: ${limiterKey}`); } const result = await rateLimiter.consume(key, 1); return { limit, remaining: result.remainingPoints || 0, reset: new Date(Date.now() + (result.msBeforeNext || 0)), tier: tier.name, }; } catch (error) { if (error instanceof Error && error.message.includes('Rate limit')) { const tier = await this.getUserTier(userId); throw new RateLimitError(tier.name, 0, new Date(Date.now() + 3600000)); } logger_1.logger.error('Rate limit check failed:', error); throw error; } } async checkBurstLimit(userId) { const tier = await this.getUserTier(userId); if (tier === exports.ANONYMOUS_TIER || userId === 'anonymous-user') return; const tierName = tier.name.toLowerCase(); const limiterKey = `user_${tierName}_minute`; const rateLimiter = this.rateLimiters.get(limiterKey); if (!rateLimiter) { logger_1.logger.warn(`Burst rate limiter not found: ${limiterKey}`); return; } try { await rateLimiter.consume(userId, 1); } catch (error) { throw new BurstLimitError(tier.name, tier.requestsPerMinute); } } async getRateLimitStatus(type, key, userId) { const tier = await this.getUserTier(userId); let limiterKey; let limit; if (type === 'legacy') { limiterKey = 'legacy'; limit = 100; } else if (!userId || userId === 'anonymous-user') { limiterKey = 'anonymous'; limit = exports.ANONYMOUS_TIER.requestsPerHour; } else { const tierName = tier.name.toLowerCase(); switch (type) { case 'tracking': limiterKey = `user_${tierName}_hour`; limit = tier.requestsPerHour; break; case 'bulk': limiterKey = `bulk_${tierName}_day`; limit = tier.bulkJobsPerDay; break; case 'export': limiterKey = `export_${tierName}_day`; limit = tier.exportLimit; break; default: throw new Error(`Unknown rate limit type: ${type}`); } } const rateLimiter = this.rateLimiters.get(limiterKey); if (!rateLimiter) { throw new Error(`Rate limiter not found: ${limiterKey}`); } try { const result = await rateLimiter.get(key); return { limit, remaining: result ? result.remainingPoints || 0 : limit, reset: result ? new Date(Date.now() + (result.msBeforeNext || 0)) : new Date(), tier: tier.name, }; } catch (error) { logger_1.logger.error('Failed to get rate limit status:', error); return { limit, remaining: limit, reset: new Date(), tier: tier.name, }; } } async resetRateLimit(key, type) { try { if (type) { const rateLimiter = this.rateLimiters.get(type); if (rateLimiter) { await rateLimiter.delete(key); } } else { for (const rateLimiter of this.rateLimiters.values()) { await rateLimiter.delete(key).catch(() => { }); } } logger_1.logger.info(`Rate limit reset for key: ${key}`, { type }); } catch (error) { logger_1.logger.error('Failed to reset rate limit:', error); throw error; } } async getStatistics() { try { return { totalRequests: 0, activeKeys: this.rateLimiters.size, tierDistribution: { anonymous: 1, free: Object.keys(exports.RATE_LIMIT_TIERS).length, pro: Object.keys(exports.RATE_LIMIT_TIERS).length, enterprise: Object.keys(exports.RATE_LIMIT_TIERS).length, }, }; } catch (error) { logger_1.logger.error('Failed to get rate limit statistics:', error); return { totalRequests: 0, activeKeys: 0, tierDistribution: { anonymous: 0, free: 0, pro: 0, enterprise: 0 }, }; } } } exports.RateLimitService = RateLimitService; class RateLimitError extends Error { tier; remaining; reset; constructor(tier, remaining, reset) { super(`Rate limit exceeded for ${tier} tier`); this.tier = tier; this.remaining = remaining; this.reset = reset; this.name = 'RateLimitError'; } } exports.RateLimitError = RateLimitError; class BurstLimitError extends Error { tier; limit; constructor(tier, limit) { super(`Burst limit exceeded for ${tier} tier (${limit} requests per minute)`); this.tier = tier; this.limit = limit; this.name = 'BurstLimitError'; } } exports.BurstLimitError = BurstLimitError; exports.rateLimitService = new RateLimitService(); //# sourceMappingURL=rate-limit.service.js.map