1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, '__esModule', { value: true });
|
4 |
|
5 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
6 |
|
7 | var BottleneckLight = _interopDefault(require('bottleneck/light'));
|
8 |
|
9 | function _defineProperty(obj, key, value) {
|
10 | if (key in obj) {
|
11 | Object.defineProperty(obj, key, {
|
12 | value: value,
|
13 | enumerable: true,
|
14 | configurable: true,
|
15 | writable: true
|
16 | });
|
17 | } else {
|
18 | obj[key] = value;
|
19 | }
|
20 |
|
21 | return obj;
|
22 | }
|
23 |
|
24 | function ownKeys(object, enumerableOnly) {
|
25 | var keys = Object.keys(object);
|
26 |
|
27 | if (Object.getOwnPropertySymbols) {
|
28 | var symbols = Object.getOwnPropertySymbols(object);
|
29 | if (enumerableOnly) symbols = symbols.filter(function (sym) {
|
30 | return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
31 | });
|
32 | keys.push.apply(keys, symbols);
|
33 | }
|
34 |
|
35 | return keys;
|
36 | }
|
37 |
|
38 | function _objectSpread2(target) {
|
39 | for (var i = 1; i < arguments.length; i++) {
|
40 | var source = arguments[i] != null ? arguments[i] : {};
|
41 |
|
42 | if (i % 2) {
|
43 | ownKeys(Object(source), true).forEach(function (key) {
|
44 | _defineProperty(target, key, source[key]);
|
45 | });
|
46 | } else if (Object.getOwnPropertyDescriptors) {
|
47 | Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
48 | } else {
|
49 | ownKeys(Object(source)).forEach(function (key) {
|
50 | Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
51 | });
|
52 | }
|
53 | }
|
54 |
|
55 | return target;
|
56 | }
|
57 |
|
58 | const VERSION = "3.3.4";
|
59 |
|
60 | const noop = () => Promise.resolve();
|
61 |
|
62 |
|
63 | function wrapRequest(state, request, options) {
|
64 | return state.retryLimiter.schedule(doRequest, state, request, options);
|
65 | }
|
66 |
|
67 | async function doRequest(state, request, options) {
|
68 | const isWrite = options.method !== "GET" && options.method !== "HEAD";
|
69 | const isSearch = options.method === "GET" && options.url.startsWith("/search/");
|
70 | const isGraphQL = options.url.startsWith("/graphql");
|
71 | const retryCount = ~~options.request.retryCount;
|
72 | const jobOptions = retryCount > 0 ? {
|
73 | priority: 0,
|
74 | weight: 0
|
75 | } : {};
|
76 |
|
77 | if (state.clustering) {
|
78 |
|
79 |
|
80 |
|
81 | jobOptions.expiration = 1000 * 60;
|
82 | }
|
83 |
|
84 |
|
85 |
|
86 | if (isWrite || isGraphQL) {
|
87 | await state.write.key(state.id).schedule(jobOptions, noop);
|
88 | }
|
89 |
|
90 |
|
91 | if (isWrite && state.triggersNotification(options.url)) {
|
92 | await state.notifications.key(state.id).schedule(jobOptions, noop);
|
93 | }
|
94 |
|
95 |
|
96 | if (isSearch) {
|
97 | await state.search.key(state.id).schedule(jobOptions, noop);
|
98 | }
|
99 |
|
100 | const req = state.global.key(state.id).schedule(jobOptions, request, options);
|
101 |
|
102 | if (isGraphQL) {
|
103 | const res = await req;
|
104 |
|
105 | if (res.data.errors != null &&
|
106 | res.data.errors.some(error => error.type === "RATE_LIMITED")) {
|
107 | const error = Object.assign(new Error("GraphQL Rate Limit Exceeded"), {
|
108 | headers: res.headers,
|
109 | data: res.data
|
110 | });
|
111 | throw error;
|
112 | }
|
113 | }
|
114 |
|
115 | return req;
|
116 | }
|
117 |
|
118 | var triggersNotificationPaths = ["/orgs/{org}/invitations", "/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}/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"];
|
119 |
|
120 |
|
121 | function routeMatcher(paths) {
|
122 |
|
123 |
|
124 | |
125 |
|
126 |
|
127 |
|
128 |
|
129 | const regexes = paths.map(path => path.split("/")
|
130 | .map(c => c.startsWith("{") ? "(?:.+?)" : c).join("/"));
|
131 |
|
132 | |
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 | const regex = `^(?:${regexes.map(r => `(?:${r})`).join("|")})[^/]*$`;
|
139 |
|
140 | |
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 | return new RegExp(regex, "i");
|
147 | }
|
148 |
|
149 | const regex = routeMatcher(triggersNotificationPaths);
|
150 | const triggersNotification = regex.test.bind(regex);
|
151 | const groups = {};
|
152 |
|
153 | const createGroups = function (Bottleneck, common) {
|
154 |
|
155 | groups.global = new Bottleneck.Group(_objectSpread2({
|
156 | id: "octokit-global",
|
157 | maxConcurrent: 10
|
158 | }, common));
|
159 |
|
160 | groups.search = new Bottleneck.Group(_objectSpread2({
|
161 | id: "octokit-search",
|
162 | maxConcurrent: 1,
|
163 | minTime: 2000
|
164 | }, common));
|
165 |
|
166 | groups.write = new Bottleneck.Group(_objectSpread2({
|
167 | id: "octokit-write",
|
168 | maxConcurrent: 1,
|
169 | minTime: 1000
|
170 | }, common));
|
171 |
|
172 | groups.notifications = new Bottleneck.Group(_objectSpread2({
|
173 | id: "octokit-notifications",
|
174 | maxConcurrent: 1,
|
175 | minTime: 3000
|
176 | }, common));
|
177 | };
|
178 |
|
179 | function throttling(octokit, octokitOptions = {}) {
|
180 | const {
|
181 | enabled = true,
|
182 | Bottleneck = BottleneckLight,
|
183 | id = "no-id",
|
184 | timeout = 1000 * 60 * 2,
|
185 |
|
186 | connection
|
187 | } = octokitOptions.throttle || {};
|
188 |
|
189 | if (!enabled) {
|
190 | return;
|
191 | }
|
192 |
|
193 | const common = {
|
194 | connection,
|
195 | timeout
|
196 | };
|
197 |
|
198 | if (groups.global == null) {
|
199 | createGroups(Bottleneck, common);
|
200 | }
|
201 |
|
202 | const state = Object.assign(_objectSpread2({
|
203 | clustering: connection != null,
|
204 | triggersNotification,
|
205 | minimumAbuseRetryAfter: 5,
|
206 | retryAfterBaseValue: 1000,
|
207 | retryLimiter: new Bottleneck(),
|
208 | id
|
209 | }, groups),
|
210 | octokitOptions.throttle);
|
211 |
|
212 | if (typeof state.onAbuseLimit !== "function" || typeof state.onRateLimit !== "function") {
|
213 | throw new Error(`octokit/plugin-throttling error:
|
214 | You must pass the onAbuseLimit and onRateLimit error handlers.
|
215 | See https://github.com/octokit/rest.js#throttling
|
216 |
|
217 | const octokit = new Octokit({
|
218 | throttle: {
|
219 | onAbuseLimit: (retryAfter, options) => {/* ... */},
|
220 | onRateLimit: (retryAfter, options) => {/* ... */}
|
221 | }
|
222 | })
|
223 | `);
|
224 | }
|
225 |
|
226 | const events = {};
|
227 | const emitter = new Bottleneck.Events(events);
|
228 |
|
229 | events.on("abuse-limit", state.onAbuseLimit);
|
230 |
|
231 | events.on("rate-limit", state.onRateLimit);
|
232 |
|
233 | events.on("error", e => console.warn("Error in throttling-plugin limit handler", e));
|
234 |
|
235 | state.retryLimiter.on("failed", async function (error, info) {
|
236 | const options = info.args[info.args.length - 1];
|
237 | const shouldRetryGraphQL = options.url.startsWith("/graphql") && error.status !== 401;
|
238 |
|
239 | if (!(shouldRetryGraphQL || error.status === 403)) {
|
240 | return;
|
241 | }
|
242 |
|
243 | const retryCount = ~~options.request.retryCount;
|
244 | options.request.retryCount = retryCount;
|
245 | const {
|
246 | wantRetry,
|
247 | retryAfter
|
248 | } = await async function () {
|
249 | if (/\babuse\b/i.test(error.message)) {
|
250 |
|
251 |
|
252 |
|
253 |
|
254 | const retryAfter = Math.max(~~error.headers["retry-after"], state.minimumAbuseRetryAfter);
|
255 | const wantRetry = await emitter.trigger("abuse-limit", retryAfter, options, octokit);
|
256 | return {
|
257 | wantRetry,
|
258 | retryAfter
|
259 | };
|
260 | }
|
261 |
|
262 | if (error.headers != null && error.headers["x-ratelimit-remaining"] === "0") {
|
263 |
|
264 |
|
265 |
|
266 | const rateLimitReset = new Date(~~error.headers["x-ratelimit-reset"] * 1000).getTime();
|
267 | const retryAfter = Math.max(Math.ceil((rateLimitReset - Date.now()) / 1000), 0);
|
268 | const wantRetry = await emitter.trigger("rate-limit", retryAfter, options, octokit);
|
269 | return {
|
270 | wantRetry,
|
271 | retryAfter
|
272 | };
|
273 | }
|
274 |
|
275 | return {};
|
276 | }();
|
277 |
|
278 | if (wantRetry) {
|
279 | options.request.retryCount++;
|
280 |
|
281 | return retryAfter * state.retryAfterBaseValue;
|
282 | }
|
283 | });
|
284 | octokit.hook.wrap("request", wrapRequest.bind(null, state));
|
285 | }
|
286 | throttling.VERSION = VERSION;
|
287 | throttling.triggersNotification = triggersNotification;
|
288 |
|
289 | exports.throttling = throttling;
|
290 |
|