/home/techb158/balavpn.abdallabala.com/src/lib
Edit: /home/techb158/balavpn.abdallabala.com/src/lib/rate-limit.js (2662B)
const { redisIncr, redisTtl } = require('./redis');
const WINDOW_MS = 60_000;
const MAX_PER_WINDOW = 100;
const REDIS_ENABLED = process.env.REDIS_URL && process.env.COSMIC_REDIS_ENABLED !== 'false';
const localHits = new Map();
function getClientIp(request) {
const forwarded = request.headers?.get?.('x-forwarded-for');
if (forwarded) return forwarded.split(',')[0].trim();
const realIp = request.headers?.get?.('x-real-ip');
if (realIp) return realIp;
return '127.0.0.1';
}
async function checkRateLimitRedis(key, max = MAX_PER_WINDOW, windowMs = WINDOW_MS) {
const now = Date.now();
const windowKey = `rl:${key}:${Math.floor(now / windowMs)}`;
const count = await redisIncr(windowKey, windowMs);
const ttl = await redisTtl(windowKey);
const windowSec = Math.ceil(windowMs / 1000);
const reset = ttl > 0 ? Math.ceil(now / 1000) + ttl : Math.ceil(now / 1000) + windowSec;
return { allowed: count <= max, remaining: Math.max(0, max - count), reset };
}
function checkRateLimitLocal(key, max = MAX_PER_WINDOW, windowMs = WINDOW_MS) {
const now = Date.now();
if (!localHits.has(key)) localHits.set(key, []);
const timestamps = localHits.get(key).filter(t => now - t < windowMs);
timestamps.push(now);
localHits.set(key, timestamps);
return {
allowed: timestamps.length <= max,
remaining: Math.max(0, max - timestamps.length),
reset: Math.ceil(now / 1000) + Math.ceil(windowMs / 1000)
};
}
function checkRateLimit(key, max = MAX_PER_WINDOW, windowMs = WINDOW_MS) {
if (REDIS_ENABLED) return checkRateLimitRedis(key, max, windowMs);
return checkRateLimitLocal(key, max, windowMs);
}
async function withRateLimit(request, handler, max = MAX_PER_WINDOW, windowMs = WINDOW_MS) {
const ip = getClientIp(request);
const result = await checkRateLimit(`handler:${ip}`, max, windowMs);
if (!result.allowed) {
return new Response(JSON.stringify({ error: 'Too many requests. Try again shortly.' }), {
status: 429,
headers: {
'Content-Type': 'application/json',
'X-RateLimit-Limit': String(max),
'X-RateLimit-Remaining': '0',
'X-RateLimit-Reset': String(result.reset),
'Cache-Control': 'no-store'
}
});
}
const response = await handler(request);
if (response?.headers) {
response.headers.set('X-RateLimit-Limit', String(max));
response.headers.set('X-RateLimit-Remaining', String(result.remaining));
response.headers.set('X-RateLimit-Reset', String(result.reset));
}
return response;
}
function resetRateLimits() {
localHits.clear();
}
module.exports = { checkRateLimit, getClientIp, resetRateLimits, withRateLimit };