229 lines
8.1 KiB
JavaScript
229 lines
8.1 KiB
JavaScript
// pkg/dist-src/index.js
|
|
import BottleneckLight from "bottleneck/light.js";
|
|
|
|
// pkg/dist-src/version.js
|
|
var VERSION = "0.0.0-development";
|
|
|
|
// pkg/dist-src/wrap-request.js
|
|
var noop = () => Promise.resolve();
|
|
function wrapRequest(state, request, options) {
|
|
return state.retryLimiter.schedule(doRequest, state, request, options);
|
|
}
|
|
async function doRequest(state, request, options) {
|
|
const { pathname } = new URL(options.url, "http://github.test");
|
|
const isAuth = isAuthRequest(options.method, pathname);
|
|
const isWrite = !isAuth && options.method !== "GET" && options.method !== "HEAD";
|
|
const isSearch = options.method === "GET" && pathname.startsWith("/search/");
|
|
const isGraphQL = pathname.startsWith("/graphql");
|
|
const retryCount = ~~request.retryCount;
|
|
const jobOptions = retryCount > 0 ? { priority: 0, weight: 0 } : {};
|
|
if (state.clustering) {
|
|
jobOptions.expiration = 1e3 * 60;
|
|
}
|
|
if (isWrite || isGraphQL) {
|
|
await state.write.key(state.id).schedule(jobOptions, noop);
|
|
}
|
|
if (isWrite && state.triggersNotification(pathname)) {
|
|
await state.notifications.key(state.id).schedule(jobOptions, noop);
|
|
}
|
|
if (isSearch) {
|
|
await state.search.key(state.id).schedule(jobOptions, noop);
|
|
}
|
|
const req = (isAuth ? state.auth : state.global).key(state.id).schedule(jobOptions, request, options);
|
|
if (isGraphQL) {
|
|
const res = await req;
|
|
if (res.data.errors != null && res.data.errors.some((error) => error.type === "RATE_LIMITED")) {
|
|
const error = Object.assign(new Error("GraphQL Rate Limit Exceeded"), {
|
|
response: res,
|
|
data: res.data
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
return req;
|
|
}
|
|
function isAuthRequest(method, pathname) {
|
|
return method === "PATCH" && // https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#create-a-scoped-access-token
|
|
/^\/applications\/[^/]+\/token\/scoped$/.test(pathname) || method === "POST" && // https://docs.github.com/en/rest/apps/oauth-applications?apiVersion=2022-11-28#reset-a-token
|
|
(/^\/applications\/[^/]+\/token$/.test(pathname) || // https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#create-an-installation-access-token-for-an-app
|
|
/^\/app\/installations\/[^/]+\/access_tokens$/.test(pathname) || // https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
|
|
pathname === "/login/oauth/access_token");
|
|
}
|
|
|
|
// pkg/dist-src/generated/triggers-notification-paths.js
|
|
var triggers_notification_paths_default = [
|
|
"/orgs/{org}/invitations",
|
|
"/orgs/{org}/invitations/{invitation_id}",
|
|
"/orgs/{org}/teams/{team_slug}/discussions",
|
|
"/orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments",
|
|
"/repos/{owner}/{repo}/collaborators/{username}",
|
|
"/repos/{owner}/{repo}/commits/{commit_sha}/comments",
|
|
"/repos/{owner}/{repo}/issues",
|
|
"/repos/{owner}/{repo}/issues/{issue_number}/comments",
|
|
"/repos/{owner}/{repo}/issues/{issue_number}/sub_issue",
|
|
"/repos/{owner}/{repo}/issues/{issue_number}/sub_issues/priority",
|
|
"/repos/{owner}/{repo}/pulls",
|
|
"/repos/{owner}/{repo}/pulls/{pull_number}/comments",
|
|
"/repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies",
|
|
"/repos/{owner}/{repo}/pulls/{pull_number}/merge",
|
|
"/repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers",
|
|
"/repos/{owner}/{repo}/pulls/{pull_number}/reviews",
|
|
"/repos/{owner}/{repo}/releases",
|
|
"/teams/{team_id}/discussions",
|
|
"/teams/{team_id}/discussions/{discussion_number}/comments"
|
|
];
|
|
|
|
// pkg/dist-src/route-matcher.js
|
|
function routeMatcher(paths) {
|
|
const regexes = paths.map(
|
|
(path) => path.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
|
|
);
|
|
const regex2 = `^(?:${regexes.map((r) => `(?:${r})`).join("|")})[^/]*$`;
|
|
return new RegExp(regex2, "i");
|
|
}
|
|
|
|
// pkg/dist-src/index.js
|
|
var regex = routeMatcher(triggers_notification_paths_default);
|
|
var triggersNotification = regex.test.bind(regex);
|
|
var groups = {};
|
|
var createGroups = function(Bottleneck, common) {
|
|
groups.global = new Bottleneck.Group({
|
|
id: "octokit-global",
|
|
maxConcurrent: 10,
|
|
...common
|
|
});
|
|
groups.auth = new Bottleneck.Group({
|
|
id: "octokit-auth",
|
|
maxConcurrent: 1,
|
|
...common
|
|
});
|
|
groups.search = new Bottleneck.Group({
|
|
id: "octokit-search",
|
|
maxConcurrent: 1,
|
|
minTime: 2e3,
|
|
...common
|
|
});
|
|
groups.write = new Bottleneck.Group({
|
|
id: "octokit-write",
|
|
maxConcurrent: 1,
|
|
minTime: 1e3,
|
|
...common
|
|
});
|
|
groups.notifications = new Bottleneck.Group({
|
|
id: "octokit-notifications",
|
|
maxConcurrent: 1,
|
|
minTime: 3e3,
|
|
...common
|
|
});
|
|
};
|
|
function throttling(octokit, octokitOptions) {
|
|
const {
|
|
enabled = true,
|
|
Bottleneck = BottleneckLight,
|
|
id = "no-id",
|
|
timeout = 1e3 * 60 * 2,
|
|
// Redis TTL: 2 minutes
|
|
connection
|
|
} = octokitOptions.throttle || {};
|
|
if (!enabled) {
|
|
return {};
|
|
}
|
|
const common = { timeout };
|
|
if (typeof connection !== "undefined") {
|
|
common.connection = connection;
|
|
}
|
|
if (groups.global == null) {
|
|
createGroups(Bottleneck, common);
|
|
}
|
|
const state = Object.assign(
|
|
{
|
|
clustering: connection != null,
|
|
triggersNotification,
|
|
fallbackSecondaryRateRetryAfter: 60,
|
|
retryAfterBaseValue: 1e3,
|
|
retryLimiter: new Bottleneck(),
|
|
id,
|
|
...groups
|
|
},
|
|
octokitOptions.throttle
|
|
);
|
|
if (typeof state.onSecondaryRateLimit !== "function" || typeof state.onRateLimit !== "function") {
|
|
throw new Error(`octokit/plugin-throttling error:
|
|
You must pass the onSecondaryRateLimit and onRateLimit error handlers.
|
|
See https://octokit.github.io/rest.js/#throttling
|
|
|
|
const octokit = new Octokit({
|
|
throttle: {
|
|
onSecondaryRateLimit: (retryAfter, options) => {/* ... */},
|
|
onRateLimit: (retryAfter, options) => {/* ... */}
|
|
}
|
|
})
|
|
`);
|
|
}
|
|
const events = {};
|
|
const emitter = new Bottleneck.Events(events);
|
|
events.on("secondary-limit", state.onSecondaryRateLimit);
|
|
events.on("rate-limit", state.onRateLimit);
|
|
events.on(
|
|
"error",
|
|
(e) => octokit.log.warn("Error in throttling-plugin limit handler", e)
|
|
);
|
|
state.retryLimiter.on("failed", async function(error, info) {
|
|
const [state2, request, options] = info.args;
|
|
const { pathname } = new URL(options.url, "http://github.test");
|
|
const shouldRetryGraphQL = pathname.startsWith("/graphql") && error.status !== 401;
|
|
if (!(shouldRetryGraphQL || error.status === 403 || error.status === 429)) {
|
|
return;
|
|
}
|
|
const retryCount = ~~request.retryCount;
|
|
request.retryCount = retryCount;
|
|
options.request.retryCount = retryCount;
|
|
const { wantRetry, retryAfter = 0 } = await (async function() {
|
|
if (/\bsecondary rate\b/i.test(error.message)) {
|
|
const retryAfter2 = Number(error.response.headers["retry-after"]) || state2.fallbackSecondaryRateRetryAfter;
|
|
const wantRetry2 = await emitter.trigger(
|
|
"secondary-limit",
|
|
retryAfter2,
|
|
options,
|
|
octokit,
|
|
retryCount
|
|
);
|
|
return { wantRetry: wantRetry2, retryAfter: retryAfter2 };
|
|
}
|
|
if (error.response.headers != null && error.response.headers["x-ratelimit-remaining"] === "0" || (error.response.data?.errors ?? []).some(
|
|
(error2) => error2.type === "RATE_LIMITED"
|
|
)) {
|
|
const rateLimitReset = new Date(
|
|
~~error.response.headers["x-ratelimit-reset"] * 1e3
|
|
).getTime();
|
|
const retryAfter2 = Math.max(
|
|
// Add one second so we retry _after_ the reset time
|
|
// https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#exceeding-the-rate-limit
|
|
Math.ceil((rateLimitReset - Date.now()) / 1e3) + 1,
|
|
0
|
|
);
|
|
const wantRetry2 = await emitter.trigger(
|
|
"rate-limit",
|
|
retryAfter2,
|
|
options,
|
|
octokit,
|
|
retryCount
|
|
);
|
|
return { wantRetry: wantRetry2, retryAfter: retryAfter2 };
|
|
}
|
|
return {};
|
|
})();
|
|
if (wantRetry) {
|
|
request.retryCount++;
|
|
return retryAfter * state2.retryAfterBaseValue;
|
|
}
|
|
});
|
|
octokit.hook.wrap("request", wrapRequest.bind(null, state));
|
|
return {};
|
|
}
|
|
throttling.VERSION = VERSION;
|
|
throttling.triggersNotification = triggersNotification;
|
|
export {
|
|
throttling
|
|
};
|