gen / lib /rateLimiter.js
AnesKAM's picture
Create lib/rateLimiter.js
4ec5069 verified
Raw
History Blame Contribute Delete
2.01 kB
// محدّد طلبات توليد الصور:
// - طابور عالمي يحترم 40 طلب/دقيقة لمزود NVIDIA
// - حد لكل مستخدم: 3 صور كل 3 ساعات (يُتتبّع عبر معرّف العميل)
const GLOBAL_MAX_PER_MIN = 40;
const USER_MAX = 3;
const USER_WINDOW_MS = 3 * 60 * 60 * 1000; // 3 ساعات
let windowStart = Date.now();
let countThisMinute = 0;
const userHits = new Map(); // clientId -> [timestamps]
// طابور لضمان عدم تجاوز 40/دقيقة
const queue = [];
let processing = false;
function resetWindowIfNeeded() {
const now = Date.now();
if (now - windowStart >= 60_000) {
windowStart = now;
countThisMinute = 0;
}
}
export function checkUserQuota(clientId) {
const now = Date.now();
const hits = (userHits.get(clientId) || []).filter(t => now - t < USER_WINDOW_MS);
userHits.set(clientId, hits);
if (hits.length >= USER_MAX) {
const oldest = hits[0];
const retryMs = USER_WINDOW_MS - (now - oldest);
return { allowed: false, retryMs, remaining: 0 };
}
return { allowed: true, remaining: USER_MAX - hits.length };
}
export function recordUserHit(clientId) {
const hits = userHits.get(clientId) || [];
hits.push(Date.now());
userHits.set(clientId, hits);
}
// نفّذ مهمة عبر الطابور العالمي (يضمن <= 40/دقيقة)
export function enqueue(task) {
return new Promise((resolve, reject) => {
queue.push({ task, resolve, reject });
drain();
});
}
async function drain() {
if (processing) return;
processing = true;
while (queue.length) {
resetWindowIfNeeded();
if (countThisMinute >= GLOBAL_MAX_PER_MIN) {
const wait = 60_000 - (Date.now() - windowStart);
await new Promise(r => setTimeout(r, Math.max(wait, 250)));
continue;
}
const { task, resolve, reject } = queue.shift();
countThisMinute++;
try {
resolve(await task());
} catch (e) {
reject(e);
}
}
processing = false;
}