Something went wrong. Try again.
FlockOff is a mass-block tool for the Atmosphere flockoff.app
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475import 'server-only'
interface RateLimitEntry { count: number resetAt: number}
declare global { // eslint-disable-next-line no-var var __rateLimitStore: Map<string, RateLimitEntry> | undefined // eslint-disable-next-line no-var var __rateLimitCleanupStarted: boolean | undefined}
const store: Map<string, RateLimitEntry> = (globalThis.__rateLimitStore ??= new Map())const MAX_KEYS = 5_000const CLEANUP_INTERVAL_MS = 60_000
function cleanupExpiredEntries(): void { const now = Date.now() for (const [key, entry] of store) { if (entry.resetAt <= now) store.delete(key) }
// Last-resort cap for spoofed or unusually high-cardinality keys. Map keeps // insertion order, so this drops the oldest entries first. while (store.size > MAX_KEYS) { const oldest = store.keys().next().value as string | undefined if (!oldest) break store.delete(oldest) }}
if (!globalThis.__rateLimitCleanupStarted) { globalThis.__rateLimitCleanupStarted = true setInterval(cleanupExpiredEntries, CLEANUP_INTERVAL_MS).unref?.()}
export function checkRateLimit( key: string, options: { limit: number; windowMs: number }): { allowed: true } | { allowed: false; retryAfterSeconds: number } { if (store.size > MAX_KEYS) cleanupExpiredEntries()
const now = Date.now() const current = store.get(key)
if (!current || current.resetAt <= now) { store.set(key, { count: 1, resetAt: now + options.windowMs }) return { allowed: true } }
if (current.count >= options.limit) { return { allowed: false, retryAfterSeconds: Math.ceil((current.resetAt - now) / 1000), } }
current.count++ return { allowed: true }}
export function getClientIp(request: Request): string { const forwardedFor = request.headers.get('x-forwarded-for') const forwardedParts = forwardedFor?.split(',').map((part) => part.trim()).filter(Boolean)
// In direct-to-app dev the header is absent. Behind a trusted single proxy, // the right-most XFF value is less attacker-controlled than the left-most // value, which clients can often spoof before the proxy appends its hop. return forwardedParts?.at(-1) || request.headers.get('x-real-ip')?.trim() || 'unknown'}