UNPKG

ratelimit-header-parser

Version:

Parse RateLimit headers of various forms, including the combined form from draft 7 of the IETF standard, into a normalized format.

133 lines (132 loc) 4 kB
// source/ratelimit-header-parser.ts function parseRateLimit(input, options) { if ("headers" in input && typeof input.headers === "object" && !Array.isArray(input.headers)) { return parseHeadersObject(input.headers, options); } if ("getHeaders" in input && typeof input.getHeaders === "function") { return parseHeadersObject(input.getHeaders(), options); } return parseHeadersObject(input, options); } function parseHeadersObject(input, options) { const combined = getHeader(input, "ratelimit"); if (combined) return parseCombinedRateLimitHeader(combined); let prefix; if (getHeader(input, "ratelimit-remaining")) { prefix = "ratelimit-"; } else if (getHeader(input, "x-ratelimit-remaining")) { prefix = "x-ratelimit-"; } else if (getHeader(input, "x-rate-limit-remaining")) { prefix = "x-rate-limit-"; } else { return; } const limit = toInt(getHeader(input, `${prefix}limit`)); const used = toInt(getHeader(input, `${prefix}used`)) || toInt(getHeader(input, `${prefix}observed`)); const remaining = toInt(getHeader(input, `${prefix}remaining`)); let reset; const resetRaw = getHeader(input, `${prefix}reset`); const resetType = options == null ? void 0 : options.reset; switch (resetType) { case "date": { reset = parseResetDate(resetRaw != null ? resetRaw : ""); break; } case "unix": { reset = parseResetUnix(resetRaw != null ? resetRaw : ""); break; } case "seconds": { reset = parseResetSeconds(resetRaw != null ? resetRaw : ""); break; } case "milliseconds": { reset = parseResetMilliseconds(resetRaw != null ? resetRaw : ""); break; } default: { if (resetRaw) reset = parseResetAuto(resetRaw); else { const retryAfter = getHeader(input, "retry-after"); if (retryAfter) { reset = parseResetUnix(retryAfter); } } } } return { limit: Number.isNaN(limit) ? used + remaining : limit, // Reddit omits used: Number.isNaN(used) ? limit - remaining : used, // Most omit remaining, reset }; } var reLimit = /limit\s*=\s*(\d+)/i; var reRemaining = /remaining\s*=\s*(\d+)/i; var reReset = /reset\s*=\s*(\d+)/i; function parseCombinedRateLimitHeader(input) { var _a, _b, _c; const limit = toInt((_a = reLimit.exec(input)) == null ? void 0 : _a[1]); const remaining = toInt((_b = reRemaining.exec(input)) == null ? void 0 : _b[1]); const resetSeconds = toInt((_c = reReset.exec(input)) == null ? void 0 : _c[1]); const reset = secondsToDate(resetSeconds); return { limit, used: limit - remaining, remaining, reset }; } function secondsToDate(seconds) { const d = /* @__PURE__ */ new Date(); d.setSeconds(d.getSeconds() + seconds); return d; } function toInt(input) { if (typeof input === "number") return input; return Number.parseInt(input != null ? input : "", 10); } function getHeader(headers, name) { var _a; if ("get" in headers && typeof headers.get === "function") { return (_a = headers.get(name)) != null ? _a : void 0; } if (name in headers && typeof headers[name] === "string") { return headers[name]; } return void 0; } function parseResetDate(resetRaw) { return new Date(resetRaw); } function parseResetUnix(resetRaw) { const resetNumber = toInt(resetRaw); return new Date(resetNumber * 1e3); } function parseResetSeconds(resetRaw) { const resetNumber = toInt(resetRaw); return secondsToDate(resetNumber); } function parseResetMilliseconds(resetRaw) { const resetNumber = toInt(resetRaw); return secondsToDate(resetNumber / 1e3); } var reLetters = /[a-z]/i; function parseResetAuto(resetRaw) { if (reLetters.test(resetRaw)) { return parseResetDate(resetRaw); } const resetNumber = toInt(resetRaw); if (resetNumber && resetNumber > 1e9) { return parseResetUnix(resetNumber); } return parseResetSeconds(resetNumber); } export { parseRateLimit };