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