42 lines
1006 B
TypeScript
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);
|
|
}
|
|
}
|