1 |
|
2 | import BottleneckLight from "bottleneck/light";
|
3 | import { VERSION } from "./version";
|
4 | import { wrapRequest } from "./wrap-request";
|
5 | import triggersNotificationPaths from "./generated/triggers-notification-paths";
|
6 | import { routeMatcher } from "./route-matcher";
|
7 |
|
8 | const regex = routeMatcher(triggersNotificationPaths);
|
9 | const triggersNotification = regex.test.bind(regex);
|
10 | const groups = {};
|
11 |
|
12 | const createGroups = function (Bottleneck, common) {
|
13 |
|
14 | groups.global = new Bottleneck.Group({
|
15 | id: "octokit-global",
|
16 | maxConcurrent: 10,
|
17 | ...common,
|
18 | });
|
19 |
|
20 | groups.search = new Bottleneck.Group({
|
21 | id: "octokit-search",
|
22 | maxConcurrent: 1,
|
23 | minTime: 2000,
|
24 | ...common,
|
25 | });
|
26 |
|
27 | groups.write = new Bottleneck.Group({
|
28 | id: "octokit-write",
|
29 | maxConcurrent: 1,
|
30 | minTime: 1000,
|
31 | ...common,
|
32 | });
|
33 |
|
34 | groups.notifications = new Bottleneck.Group({
|
35 | id: "octokit-notifications",
|
36 | maxConcurrent: 1,
|
37 | minTime: 3000,
|
38 | ...common,
|
39 | });
|
40 | };
|
41 | export function throttling(octokit, octokitOptions = {}) {
|
42 | const { enabled = true, Bottleneck = BottleneckLight, id = "no-id", timeout = 1000 * 60 * 2,
|
43 | connection, } = octokitOptions.throttle || {};
|
44 | if (!enabled) {
|
45 | return;
|
46 | }
|
47 | const common = { connection, timeout };
|
48 |
|
49 | if (groups.global == null) {
|
50 | createGroups(Bottleneck, common);
|
51 | }
|
52 | const state = Object.assign({
|
53 | clustering: connection != null,
|
54 | triggersNotification,
|
55 | minimumAbuseRetryAfter: 5,
|
56 | retryAfterBaseValue: 1000,
|
57 | retryLimiter: new Bottleneck(),
|
58 | id,
|
59 | ...groups,
|
60 | },
|
61 |
|
62 | octokitOptions.throttle);
|
63 | if (typeof state.onAbuseLimit !== "function" ||
|
64 | typeof state.onRateLimit !== "function") {
|
65 | throw new Error(`octokit/plugin-throttling error:
|
66 | You must pass the onAbuseLimit and onRateLimit error handlers.
|
67 | See https://github.com/octokit/rest.js#throttling
|
68 |
|
69 | const octokit = new Octokit({
|
70 | throttle: {
|
71 | onAbuseLimit: (retryAfter, options) => {/* ... */},
|
72 | onRateLimit: (retryAfter, options) => {/* ... */}
|
73 | }
|
74 | })
|
75 | `);
|
76 | }
|
77 | const events = {};
|
78 | const emitter = new Bottleneck.Events(events);
|
79 |
|
80 | events.on("abuse-limit", state.onAbuseLimit);
|
81 |
|
82 | events.on("rate-limit", state.onRateLimit);
|
83 |
|
84 | events.on("error", (e) => console.warn("Error in throttling-plugin limit handler", e));
|
85 |
|
86 | state.retryLimiter.on("failed", async function (error, info) {
|
87 | const options = info.args[info.args.length - 1];
|
88 | const isGraphQL = options.url.startsWith("/graphql");
|
89 | if (!(isGraphQL || error.status === 403)) {
|
90 | return;
|
91 | }
|
92 | const retryCount = ~~options.request.retryCount;
|
93 | options.request.retryCount = retryCount;
|
94 | const { wantRetry, retryAfter } = await (async function () {
|
95 | if (/\babuse\b/i.test(error.message)) {
|
96 |
|
97 |
|
98 |
|
99 |
|
100 | const retryAfter = Math.max(~~error.headers["retry-after"], state.minimumAbuseRetryAfter);
|
101 | const wantRetry = await emitter.trigger("abuse-limit", retryAfter, options, octokit);
|
102 | return { wantRetry, retryAfter };
|
103 | }
|
104 | if (error.headers != null &&
|
105 | error.headers["x-ratelimit-remaining"] === "0") {
|
106 |
|
107 |
|
108 | const rateLimitReset = new Date(~~error.headers["x-ratelimit-reset"] * 1000).getTime();
|
109 | const retryAfter = Math.max(Math.ceil((rateLimitReset - Date.now()) / 1000), 0);
|
110 | const wantRetry = await emitter.trigger("rate-limit", retryAfter, options, octokit);
|
111 | return { wantRetry, retryAfter };
|
112 | }
|
113 | return {};
|
114 | })();
|
115 | if (wantRetry) {
|
116 | options.request.retryCount++;
|
117 |
|
118 | return retryAfter * state.retryAfterBaseValue;
|
119 | }
|
120 | });
|
121 | octokit.hook.wrap("request", wrapRequest.bind(null, state));
|
122 | }
|
123 | throttling.VERSION = VERSION;
|
124 | throttling.triggersNotification = triggersNotification;
|