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
|
44 |
|
45 | } = octokitOptions.throttle || {};
|
46 | if (!enabled) {
|
47 | return;
|
48 | }
|
49 | const common = { connection, timeout };
|
50 |
|
51 | if (groups.global == null) {
|
52 | createGroups(Bottleneck, common);
|
53 | }
|
54 | const state = Object.assign({
|
55 | clustering: connection != null,
|
56 | triggersNotification,
|
57 | minimumAbuseRetryAfter: 5,
|
58 | retryAfterBaseValue: 1000,
|
59 | retryLimiter: new Bottleneck(),
|
60 | id,
|
61 | ...groups
|
62 | },
|
63 |
|
64 | octokitOptions.throttle);
|
65 | if (typeof state.onAbuseLimit !== "function" ||
|
66 | typeof state.onRateLimit !== "function") {
|
67 | throw new Error(`octokit/plugin-throttling error:
|
68 | You must pass the onAbuseLimit and onRateLimit error handlers.
|
69 | See https://github.com/octokit/rest.js#throttling
|
70 |
|
71 | const octokit = new Octokit({
|
72 | throttle: {
|
73 | onAbuseLimit: (retryAfter, options) => {/* ... */},
|
74 | onRateLimit: (retryAfter, options) => {/* ... */}
|
75 | }
|
76 | })
|
77 | `);
|
78 | }
|
79 | const events = {};
|
80 | const emitter = new Bottleneck.Events(events);
|
81 |
|
82 | events.on("abuse-limit", state.onAbuseLimit);
|
83 |
|
84 | events.on("rate-limit", state.onRateLimit);
|
85 |
|
86 | events.on("error", e => console.warn("Error in throttling-plugin limit handler", e));
|
87 |
|
88 | state.retryLimiter.on("failed", async function (error, info) {
|
89 | const options = info.args[info.args.length - 1];
|
90 | const isGraphQL = options.url.startsWith("/graphql");
|
91 | if (!(isGraphQL || error.status === 403)) {
|
92 | return;
|
93 | }
|
94 | const retryCount = ~~options.request.retryCount;
|
95 | options.request.retryCount = retryCount;
|
96 | const { wantRetry, retryAfter } = await (async function () {
|
97 | if (/\babuse\b/i.test(error.message)) {
|
98 |
|
99 |
|
100 |
|
101 |
|
102 | const retryAfter = Math.max(~~error.headers["retry-after"], state.minimumAbuseRetryAfter);
|
103 | const wantRetry = await emitter.trigger("abuse-limit", retryAfter, options);
|
104 | return { wantRetry, retryAfter };
|
105 | }
|
106 | if (error.headers != null &&
|
107 | error.headers["x-ratelimit-remaining"] === "0") {
|
108 |
|
109 |
|
110 | const rateLimitReset = new Date(~~error.headers["x-ratelimit-reset"] * 1000).getTime();
|
111 | const retryAfter = Math.max(Math.ceil((rateLimitReset - Date.now()) / 1000), 0);
|
112 | const wantRetry = await emitter.trigger("rate-limit", retryAfter, options);
|
113 | return { wantRetry, retryAfter };
|
114 | }
|
115 | return {};
|
116 | })();
|
117 | if (wantRetry) {
|
118 | options.request.retryCount++;
|
119 |
|
120 | return retryAfter * state.retryAfterBaseValue;
|
121 | }
|
122 | });
|
123 | octokit.hook.wrap("request", wrapRequest.bind(null, state));
|
124 | }
|
125 | throttling.VERSION = VERSION;
|
126 | throttling.triggersNotification = triggersNotification;
|