Spaces:
Running
Running
import { Context, Next } from 'hono'; | |
import { config } from '../config'; | |
interface RateLimitStore { | |
[key: string]: { | |
count: number; | |
resetTime: number; | |
}; | |
} | |
const store: RateLimitStore = {}; | |
// Simple in-memory rate limiter | |
export const rateLimitMiddleware = async (c: Context, next: Next) => { | |
const ip = c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'; | |
const now = Date.now(); | |
const windowStart = now - config.rateLimit.windowMs; | |
// Clean up old entries | |
Object.keys(store).forEach(key => { | |
if (store[key].resetTime < windowStart) { | |
delete store[key]; | |
} | |
}); | |
// Check rate limit | |
if (!store[ip]) { | |
store[ip] = { count: 1, resetTime: now + config.rateLimit.windowMs }; | |
} else if (store[ip].resetTime < now) { | |
store[ip] = { count: 1, resetTime: now + config.rateLimit.windowMs }; | |
} else { | |
store[ip].count++; | |
} | |
if (store[ip].count > config.rateLimit.max) { | |
return c.json( | |
{ error: 'Too many requests', retryAfter: Math.ceil((store[ip].resetTime - now) / 1000) }, | |
429, | |
{ | |
'Retry-After': Math.ceil((store[ip].resetTime - now) / 1000).toString(), | |
'X-RateLimit-Limit': config.rateLimit.max.toString(), | |
'X-RateLimit-Remaining': '0', | |
'X-RateLimit-Reset': new Date(store[ip].resetTime).toISOString(), | |
} | |
); | |
} | |
// Add rate limit headers | |
c.header('X-RateLimit-Limit', config.rateLimit.max.toString()); | |
c.header('X-RateLimit-Remaining', (config.rateLimit.max - store[ip].count).toString()); | |
c.header('X-RateLimit-Reset', new Date(store[ip].resetTime).toISOString()); | |
await next(); | |
}; |