use PATs evenly

This commit is contained in:
martin-mfg
2025-12-30 20:33:50 +01:00
parent e66c1cc07a
commit 4d05faa6cb
+58 -62
View File
@@ -3,6 +3,10 @@
import { CustomError } from "./error.js";
import { logger } from "./log.js";
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
// Script variables.
// Count the number of GitHub API tokens available.
@@ -21,76 +25,68 @@ const RETRIES = process.env.NODE_ENV === "test" ? 7 : PATs;
*
* @param {FetcherFunction} fetcher The fetcher function.
* @param {any} variables Object with arguments to pass to the fetcher function.
* @param {number} retries How many times to retry.
* @returns {Promise<any>} The response from the fetcher function.
*/
const retryer = async (fetcher, variables, retries = 0) => {
const retryer = async (fetcher, variables) => {
if (!RETRIES) {
throw new CustomError("No GitHub API tokens found", CustomError.NO_TOKENS);
}
const startPAT = getRandomInt(PATs);
if (retries > RETRIES) {
throw new CustomError(
"Downtime due to GitHub API rate limiting",
CustomError.MAX_RETRY,
);
for (let retries = 0; retries < RETRIES; retries++) {
const currentPAT = ((startPAT + retries) % PATs) + 1;
try {
let response = await fetcher(
variables,
// @ts-ignore
process.env[`PAT_${currentPAT}`],
// used in tests for faking rate limit
retries,
);
// react on both type and message-based rate-limit signals.
// https://github.com/anuraghazra/github-readme-stats/issues/4425
const errors = response?.data?.errors;
const errorType = errors?.[0]?.type;
const errorMsg = errors?.[0]?.message || "";
const isRateLimited =
(errors && errorType === "RATE_LIMITED") ||
/rate limit/i.test(errorMsg);
if (isRateLimited) {
logger.log(`PAT_${currentPAT} Failed due to rate limiting`);
} else {
return response;
}
} catch (err) {
/** @type {any} */
const e = err;
// network/unexpected error → let caller treat as failure
if (!e?.response) {
throw e;
}
// prettier-ignore
// also checking for bad credentials if any tokens gets invalidated
const isBadCredential =
e?.response?.data?.message === "Bad credentials";
const isAccountSuspended =
e?.response?.data?.message === "Sorry. Your account was suspended.";
if (isBadCredential || isAccountSuspended) {
logger.log(`PAT_${currentPAT} Failed due to bad credentials`);
} else {
// HTTP error with a response → return it for caller-side handling
return e.response;
}
}
}
try {
// try to fetch with the first token since RETRIES is 0 index i'm adding +1
let response = await fetcher(
variables,
// @ts-ignore
process.env[`PAT_${retries + 1}`],
// used in tests for faking rate limit
retries,
);
// react on both type and message-based rate-limit signals.
// https://github.com/anuraghazra/github-readme-stats/issues/4425
const errors = response?.data?.errors;
const errorType = errors?.[0]?.type;
const errorMsg = errors?.[0]?.message || "";
const isRateLimited =
(errors && errorType === "RATE_LIMITED") || /rate limit/i.test(errorMsg);
// if rate limit is hit increase the RETRIES and recursively call the retryer
// with username, and current RETRIES
if (isRateLimited) {
logger.log(`PAT_${retries + 1} Failed due to rate limiting`);
retries++;
// directly return from the function
return retryer(fetcher, variables, retries);
}
// finally return the response
return response;
} catch (err) {
/** @type {any} */
const e = err;
// network/unexpected error → let caller treat as failure
if (!e?.response) {
throw e;
}
// prettier-ignore
// also checking for bad credentials if any tokens gets invalidated
const isBadCredential =
e?.response?.data?.message === "Bad credentials";
const isAccountSuspended =
e?.response?.data?.message === "Sorry. Your account was suspended.";
if (isBadCredential || isAccountSuspended) {
logger.log(`PAT_${retries + 1} Failed due to bad credentials`);
retries++;
// directly return from the function
return retryer(fetcher, variables, retries);
}
// HTTP error with a response → return it for caller-side handling
return e.response;
}
throw new CustomError(
"Downtime due to GitHub API rate limiting",
CustomError.MAX_RETRY,
);
};
export { retryer, RETRIES };