UNPKG

dressed

Version:

A sleek, serverless-ready Discord bot framework.

81 lines 3.69 kB
import { setTimeout } from "node:timers"; export const buckets = new Map(); export const bucketIds = new Map(); let globalReset = -1; function ensureBucket(id) { var _a; const bucketId = (_a = bucketIds.get(id)) !== null && _a !== void 0 ? _a : id; if (!buckets.has(bucketId)) { buckets.set(bucketId, { limit: 1, remaining: 1, refresh: -1, promise: Promise.resolve(), }); } const bucket = buckets.get(bucketId); // Runtimes like CF workers persist global state between requests except promises -> null if (!bucket.promise) bucket.promise = Promise.resolve(); return bucket; } function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } export function checkLimit(req, bucketTTL) { return new Promise((resolveChecker) => { const bucketIdKey = `${req.method}:${req.url}`; const bucket = ensureBucket(bucketIdKey); bucket.promise = bucket.promise.then(async () => { const deltaG = globalReset - Date.now(); if (deltaG > 0) { await delay(deltaG); } const bucket = ensureBucket(bucketIdKey); const deltaB = bucket.refresh - Date.now(); if (bucket.remaining-- === 0) { await delay(Math.max(0, deltaB)); bucket.remaining = bucket.limit - 1; } let resolveRequest; resolveChecker((res) => { var _a, _b, _c, _d; const bucketId = res.headers.get("x-ratelimit-bucket"); const limit = parseInt((_a = res.headers.get("x-ratelimit-limit")) !== null && _a !== void 0 ? _a : "", 10); const remaining = parseInt((_b = res.headers.get("x-ratelimit-remaining")) !== null && _b !== void 0 ? _b : "", 10); const resetAfter = parseFloat((_c = res.headers.get("x-ratelimit-reset-after")) !== null && _c !== void 0 ? _c : ""); const retryAfter = parseFloat((_d = res.headers.get("retry-after")) !== null && _d !== void 0 ? _d : ""); const scope = res.headers.get("x-ratelimit-scope"); const refreshAfter = (Number.isNaN(retryAfter) ? resetAfter : retryAfter) * 1000; const tmpBucket = buckets.get(bucketIdKey); buckets.delete(bucketIdKey); if (scope === "global" && !Number.isNaN(retryAfter)) { globalReset = Date.now() + retryAfter * 1000; return resolveRequest(); } if ([limit, remaining, resetAfter].some(Number.isNaN) || !bucketId) return resolveRequest(); bucketIds.set(bucketIdKey, bucketId); const bucket = ensureBucket(bucketIdKey); bucket.limit = limit; bucket.remaining = remaining; bucket.refresh = Date.now() + refreshAfter; bucket.promise = tmpBucket ? bucket.promise.then(() => tmpBucket.promise) : bucket.promise; clearTimeout(bucket.cleaner); if (bucketTTL !== -1) { bucket.cleaner = setTimeout(() => { buckets.delete(bucketId); bucketIds.forEach((v, k) => { v === bucketId && bucketIds.delete(k); }); }, bucketTTL * 1000).unref(); } resolveRequest(); }); return new Promise((r) => { resolveRequest = r; }); }); }); } //# sourceMappingURL=ratelimit.js.map