docfast/dist/middleware/pdfRateLimit.js
openclawd 1ef8f5743c
Some checks failed
Deploy to Production / Deploy to Server (push) Failing after 20s
feat: Add built dist files with EU compliance routes
- Include compiled TypeScript with new /impressum, /privacy, /terms routes
- Temporary commit of dist files for Docker deployment
2026-02-16 13:09:25 +00:00

91 lines
2.5 KiB
JavaScript

import { isProKey } from "../services/keys.js";
// Per-key rate limits (requests per minute)
const FREE_RATE_LIMIT = 10;
const PRO_RATE_LIMIT = 30;
const RATE_WINDOW_MS = 60_000; // 1 minute
// Concurrency limits
const MAX_CONCURRENT_PDFS = 3;
const MAX_QUEUE_SIZE = 10;
const rateLimitStore = new Map();
let activePdfCount = 0;
const pdfQueue = [];
function cleanupExpiredEntries() {
const now = Date.now();
for (const [key, entry] of rateLimitStore.entries()) {
if (now >= entry.resetTime) {
rateLimitStore.delete(key);
}
}
}
function getRateLimit(apiKey) {
return isProKey(apiKey) ? PRO_RATE_LIMIT : FREE_RATE_LIMIT;
}
function checkRateLimit(apiKey) {
cleanupExpiredEntries();
const now = Date.now();
const limit = getRateLimit(apiKey);
const entry = rateLimitStore.get(apiKey);
if (!entry || now >= entry.resetTime) {
// Create new window
rateLimitStore.set(apiKey, {
count: 1,
resetTime: now + RATE_WINDOW_MS
});
return true;
}
if (entry.count >= limit) {
return false;
}
entry.count++;
return true;
}
async function acquireConcurrencySlot() {
if (activePdfCount < MAX_CONCURRENT_PDFS) {
activePdfCount++;
return;
}
if (pdfQueue.length >= MAX_QUEUE_SIZE) {
throw new Error("QUEUE_FULL");
}
return new Promise((resolve, reject) => {
pdfQueue.push({ resolve, reject });
});
}
function releaseConcurrencySlot() {
activePdfCount--;
const waiter = pdfQueue.shift();
if (waiter) {
activePdfCount++;
waiter.resolve();
}
}
export function pdfRateLimitMiddleware(req, res, next) {
const keyInfo = req.apiKeyInfo;
const apiKey = keyInfo?.key || "unknown";
// Check rate limit first
if (!checkRateLimit(apiKey)) {
const limit = getRateLimit(apiKey);
const tier = isProKey(apiKey) ? "pro" : "free";
res.status(429).json({
error: "Rate limit exceeded",
limit: `${limit} PDFs per minute`,
tier,
retryAfter: "60 seconds"
});
return;
}
// Add concurrency control to the request
req.acquirePdfSlot = acquireConcurrencySlot;
req.releasePdfSlot = releaseConcurrencySlot;
next();
}
export function getConcurrencyStats() {
return {
activePdfCount,
queueSize: pdfQueue.length,
maxConcurrent: MAX_CONCURRENT_PDFS,
maxQueue: MAX_QUEUE_SIZE
};
}
// Proactive cleanup every 60s
setInterval(cleanupExpiredEntries, 60_000);