43 lines
1.1 KiB
TypeScript
43 lines
1.1 KiB
TypeScript
interface RateLimitEntry {
|
|
count: number;
|
|
resetAt: number;
|
|
}
|
|
|
|
const store = new Map<string, RateLimitEntry>();
|
|
|
|
const DEFAULT_WINDOW_MS = 60 * 60 * 1000; // 1 hour
|
|
const DEFAULT_MAX_REQUESTS = 60;
|
|
|
|
// Clean up expired entries periodically
|
|
setInterval(() => {
|
|
const now = Date.now();
|
|
for (const [key, entry] of store) {
|
|
if (now > entry.resetAt) {
|
|
store.delete(key);
|
|
}
|
|
}
|
|
}, 60 * 1000);
|
|
|
|
export function checkRateLimit(
|
|
key: string,
|
|
options?: { windowMs?: number; maxRequests?: number },
|
|
): { allowed: boolean; remaining: number; retryAfterSeconds?: number } {
|
|
const windowMs = options?.windowMs ?? DEFAULT_WINDOW_MS;
|
|
const maxRequests = options?.maxRequests ?? DEFAULT_MAX_REQUESTS;
|
|
const now = Date.now();
|
|
|
|
let entry = store.get(key);
|
|
if (!entry || now > entry.resetAt) {
|
|
entry = { count: 0, resetAt: now + windowMs };
|
|
store.set(key, entry);
|
|
}
|
|
|
|
entry.count++;
|
|
|
|
if (entry.count > maxRequests) {
|
|
const retryAfterSeconds = Math.ceil((entry.resetAt - now) / 1000);
|
|
return { allowed: false, remaining: 0, retryAfterSeconds };
|
|
}
|
|
|
|
return { allowed: true, remaining: maxRequests - entry.count };
|
|
}
|