import { REDIS_HTTP_URL } from "@/lib/constants"; import { LRUCache } from "lru-cache"; import { logger } from "@formbricks/logger"; interface Options { interval: number; allowedPerInterval: number; } const inMemoryRateLimiter = (options: Options) => { const tokenCache = new LRUCache({ max: 1000, ttl: options.interval * 1000, // converts to expected input of milliseconds }); return async (token: string) => { const currentUsage = tokenCache.get(token) ?? 0; if (currentUsage >= options.allowedPerInterval) { throw new Error("Rate limit exceeded"); } tokenCache.set(token, currentUsage + 1); }; }; const redisRateLimiter = (options: Options) => async (token: string) => { try { if (!REDIS_HTTP_URL) { throw new Error("Redis HTTP URL is not set"); } const tokenCountResponse = await fetch(`${REDIS_HTTP_URL}/INCR/${token}`); if (!tokenCountResponse.ok) { logger.error({ tokenCountResponse }, "Failed to increment token count in Redis"); return; } const { INCR } = await tokenCountResponse.json(); if (INCR === 1) { await fetch(`${REDIS_HTTP_URL}/EXPIRE/${token}/${options.interval.toString()}`); } else if (INCR > options.allowedPerInterval) { throw new Error(); } } catch (e) { logger.error({ error: e }, "Rate limit exceeded"); throw new Error("Rate limit exceeded for IP: " + token); } }; export const rateLimit = (options: Options) => { if (REDIS_HTTP_URL) { return redisRateLimiter(options); } else { return inMemoryRateLimiter(options); } };