feat: bootstrap commercial AI drama platform
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
const WINDOW_MS = 60 * 1000;
|
||||
const buckets = new Map();
|
||||
|
||||
function prune(now) {
|
||||
if (buckets.size < 2048) return;
|
||||
for (const [key, bucket] of buckets) {
|
||||
if (bucket.resetAt <= now) buckets.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
export function rateLimitIdentity({ token = "", ipAddress = "" } = {}) {
|
||||
const normalizedToken = String(token || "").trim();
|
||||
if (normalizedToken) return `token:${createHash("sha256").update(normalizedToken).digest("hex")}`;
|
||||
return `ip:${String(ipAddress || "unknown").trim() || "unknown"}`;
|
||||
}
|
||||
|
||||
export function consumeRateLimit({ key, limit, now = Date.now() } = {}) {
|
||||
const normalizedLimit = Math.max(0, Math.floor(Number(limit || 0)));
|
||||
if (!normalizedLimit) return null;
|
||||
const windowStart = Math.floor(now / WINDOW_MS) * WINDOW_MS;
|
||||
const bucketKey = `${String(key || "unknown")}:${windowStart}`;
|
||||
const resetAt = windowStart + WINDOW_MS;
|
||||
const current = buckets.get(bucketKey) || { count: 0, resetAt };
|
||||
current.count += 1;
|
||||
buckets.set(bucketKey, current);
|
||||
prune(now);
|
||||
return {
|
||||
allowed: current.count <= normalizedLimit,
|
||||
count: current.count,
|
||||
limit: normalizedLimit,
|
||||
remaining: Math.max(0, normalizedLimit - current.count),
|
||||
resetAt,
|
||||
retryAfter: Math.max(1, Math.ceil((resetAt - now) / 1000))
|
||||
};
|
||||
}
|
||||
|
||||
export function rateLimitHeaders(result) {
|
||||
if (!result) return {};
|
||||
return {
|
||||
"x-ratelimit-limit": String(result.limit),
|
||||
"x-ratelimit-remaining": String(result.remaining),
|
||||
"x-ratelimit-reset": String(Math.ceil(result.resetAt / 1000)),
|
||||
...(result.allowed ? {} : { "retry-after": String(result.retryAfter) })
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user