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

42 lines
1006 B
TypeScript

import { ShareHttpError } from "@/lib/share/http-error";
export type FixedWindowIncrement = (
key: string,
expiresInSeconds: number,
) => Promise<number>;
export async function enforceFixedWindowRateLimit({
key,
limit,
windowSeconds,
increment,
now = Date.now(),
}: {
key: string;
limit: number;
windowSeconds: number;
increment: FixedWindowIncrement;
now?: number;
}) {
const nowSeconds = Math.floor(now / 1000);
const window = Math.floor(nowSeconds / windowSeconds);
const retryAfter = windowSeconds - (nowSeconds % windowSeconds);
let count: number;
try {
count = await increment(
`erika:share:${key}:${window}`,
windowSeconds + 1,
);
} catch {
throw new ShareHttpError("Rate limiting is temporarily unavailable", 503);
}
if (!Number.isFinite(count)) {
throw new ShareHttpError("Rate limiting is temporarily unavailable", 503);
}
if (count > limit) {
throw new ShareHttpError("Rate limit exceeded", 429, retryAfter);
}
}