Files
erika/lib/share/rate-limit.ts
T

45 lines
1.1 KiB
TypeScript

import "server-only";
import { getRedisClient } from "@/lib/redis";
import { ShareHttpError } from "@/lib/share/http-error";
const FIXED_WINDOW_SCRIPT = `
local count = redis.call("INCR", KEYS[1])
if count == 1 then
redis.call("EXPIRE", KEYS[1], ARGV[1])
end
return count
`;
export async function enforceShareRateLimit({
key,
limit,
windowSeconds,
}: {
key: string;
limit: number;
windowSeconds: number;
}) {
const nowSeconds = Math.floor(Date.now() / 1000);
const window = Math.floor(nowSeconds / windowSeconds);
const retryAfter = windowSeconds - (nowSeconds % windowSeconds);
try {
const redis = await getRedisClient();
const count = Number(
await redis.send("EVAL", [
FIXED_WINDOW_SCRIPT,
"1",
`erika:share:${key}:${window}`,
String(windowSeconds + 1),
])
);
if (count > limit) {
throw new ShareHttpError("Rate limit exceeded", 429, retryAfter);
}
} catch (error) {
if (error instanceof ShareHttpError) throw error;
throw new ShareHttpError("Rate limiting is temporarily unavailable", 503);
}
}