/**
* Developed by eBrook Group.
* Copyright © 2026 eBrook Group (https://www.ebrook.com.tw)
*/
/**
* Simple rate limiter to prevent API abuse
*/
export class RateLimiter {
private requests: Map<string, number[]> = new Map();
private readonly maxRequests: number;
private readonly timeWindow: number;
/**
* Create a rate limiter
* @param maxRequests - Maximum number of requests allowed in time window
* @param timeWindow - Time window in milliseconds (default: 60000 = 1 minute)
*/
constructor(maxRequests: number = 100, timeWindow: number = 60000) {
this.maxRequests = maxRequests;
this.timeWindow = timeWindow;
}
/**
* Check if request is within rate limit
* @param identifier - Unique identifier for the rate limit (e.g., user ID, IP)
* @returns void
* @throws Error if rate limit exceeded
*/
async checkLimit(identifier: string = 'global'): Promise<void> {
const now = Date.now();
const requests = this.requests.get(identifier) ?? [];
// Clean up expired request records
const validRequests = requests.filter(time => now - time < this.timeWindow);
// Check if limit exceeded
if (validRequests.length >= this.maxRequests) {
const oldestRequest = Math.min(...validRequests);
const waitTime = Math.ceil((oldestRequest + this.timeWindow - now) / 1000);
throw new Error(
`Rate limit exceeded. Maximum ${this.maxRequests} requests per ${this.timeWindow / 1000} seconds. ` +
`Please wait ${waitTime} seconds before making more requests.`
);
}
// Record new request
validRequests.push(now);
this.requests.set(identifier, validRequests);
}
/**
* Reset rate limit for an identifier
* @param identifier - Identifier to reset
*/
reset(identifier: string = 'global'): void {
this.requests.delete(identifier);
}
/**
* Get current request count for an identifier
* @param identifier - Identifier to check
* @returns Current request count
*/
getCount(identifier: string = 'global'): number {
const now = Date.now();
const requests = this.requests.get(identifier) ?? [];
return requests.filter(time => now - time < this.timeWindow).length;
}
}