"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RateLimiter = void 0;
const logger_1 = __importDefault(require("./logger"));
class RateLimiter {
constructor(config) {
this.config = config;
this.requestQueue = [];
this.isProcessing = false;
this.requestCount = 0;
this.hourlyRequestCount = 0;
this.lastRequestTime = 0;
this.hourlyResetTime = Date.now() + 3600000; // 1 hour from now
}
async executeWithRateLimit(operation) {
return new Promise((resolve, reject) => {
this.requestQueue.push(async () => {
try {
const result = await this.executeOperation(operation);
resolve(result);
}
catch (error) {
reject(error);
}
});
if (!this.isProcessing) {
this.processQueue();
}
});
}
async processQueue() {
this.isProcessing = true;
while (this.requestQueue.length > 0) {
// Check hourly reset
if (Date.now() > this.hourlyResetTime) {
this.hourlyRequestCount = 0;
this.hourlyResetTime = Date.now() + 3600000;
logger_1.default.debug('Hourly rate limit reset');
}
// Check hourly limit
if (this.hourlyRequestCount >= this.config.maxRequestsPerHour) {
const waitTime = this.hourlyResetTime - Date.now();
logger_1.default.warn(`Hourly rate limit reached. Waiting ${waitTime}ms`);
await this.delay(waitTime);
continue;
}
// Check per-second limit
const timeSinceLastRequest = Date.now() - this.lastRequestTime;
const minDelay = 1000 / this.config.maxRequestsPerSecond;
if (timeSinceLastRequest < minDelay) {
await this.delay(minDelay - timeSinceLastRequest);
}
const operation = this.requestQueue.shift();
if (operation) {
this.lastRequestTime = Date.now();
this.requestCount++;
this.hourlyRequestCount++;
try {
await operation();
}
catch (error) {
logger_1.default.error('Rate limited operation failed', { error });
}
}
}
this.isProcessing = false;
}
async executeOperation(operation) {
let attempts = 0;
let lastError;
while (attempts < this.config.retryAttempts) {
try {
return await operation();
}
catch (error) {
lastError = error;
attempts++;
// Check if it's a rate limit error
if (this.isRateLimitError(error)) {
const backoffDelay = this.calculateBackoffDelay(attempts);
logger_1.default.warn(`Rate limit hit, retrying in ${backoffDelay}ms (attempt ${attempts}/${this.config.retryAttempts})`);
await this.delay(backoffDelay);
continue;
}
// For non-rate-limit errors, throw immediately
throw error;
}
}
throw lastError;
}
isRateLimitError(error) {
const errorCode = error?.errorCode || error?.code;
const errorMessage = error?.message || '';
return (errorCode === 'REQUEST_LIMIT_EXCEEDED' ||
errorCode === 'INVALID_SESSION_ID' ||
errorMessage.includes('rate limit') ||
errorMessage.includes('exceeded'));
}
calculateBackoffDelay(attempt) {
// Exponential backoff with jitter
const exponentialDelay = this.config.baseDelay * Math.pow(2, attempt - 1);
const jitter = Math.random() * 1000;
return Math.min(exponentialDelay + jitter, 60000); // Cap at 1 minute
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
getStats() {
return {
totalRequests: this.requestCount,
hourlyRequests: this.hourlyRequestCount,
queueLength: this.requestQueue.length,
isProcessing: this.isProcessing
};
}
}
exports.RateLimiter = RateLimiter;