@evanp/activitypub-bot 0.45.19 → 0.45.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/lib/requestthrottler.js +60 -16
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/lib/requestthrottler.js
CHANGED
|
@@ -2,7 +2,8 @@ import { setTimeout as sleep } from 'node:timers/promises'
|
|
|
2
2
|
import assert from 'node:assert'
|
|
3
3
|
|
|
4
4
|
const BETA = 0.75
|
|
5
|
-
const
|
|
5
|
+
const OFFSET_THRESHOLD = 30 * 24 * 60 * 60
|
|
6
|
+
const DEFAULT_RESET = 30 * 1000
|
|
6
7
|
|
|
7
8
|
export class ThrottleError extends Error {
|
|
8
9
|
constructor (message, waitTime) {
|
|
@@ -43,25 +44,33 @@ export class RequestThrottler {
|
|
|
43
44
|
assert.strictEqual(typeof host, 'string')
|
|
44
45
|
assert.strictEqual(typeof headers, 'object')
|
|
45
46
|
|
|
47
|
+
const retryAfterHeader = headers.get('retry-after')
|
|
46
48
|
const resetHeader = headers.get('x-ratelimit-reset')
|
|
47
49
|
const remainingHeader = headers.get('x-ratelimit-remaining')
|
|
48
50
|
|
|
49
|
-
if (
|
|
51
|
+
if (retryAfterHeader) {
|
|
52
|
+
const remaining = 0
|
|
53
|
+
const reset = this.#headerToReset(retryAfterHeader)
|
|
54
|
+
this.#logger.debug(
|
|
55
|
+
{ reset, remaining, host, retryAfterHeader },
|
|
56
|
+
'updating'
|
|
57
|
+
)
|
|
58
|
+
await this.#connection.query(
|
|
59
|
+
`INSERT INTO rate_limit (host, remaining, reset)
|
|
60
|
+
VALUES (?, ?, ?)
|
|
61
|
+
ON CONFLICT (host) DO UPDATE
|
|
62
|
+
SET remaining = EXCLUDED.remaining,
|
|
63
|
+
reset = EXCLUDED.reset,
|
|
64
|
+
updated_at = CURRENT_TIMESTAMP`,
|
|
65
|
+
{ replacements: [host, remaining, reset] }
|
|
66
|
+
)
|
|
67
|
+
} else if (resetHeader && remainingHeader) {
|
|
50
68
|
const remaining = parseInt(remainingHeader)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
reset = new Date(Date.now() + (resetSeconds * 1000))
|
|
57
|
-
} else {
|
|
58
|
-
reset = new Date(resetSeconds * 1000)
|
|
59
|
-
}
|
|
60
|
-
} else {
|
|
61
|
-
reset = new Date(resetHeader)
|
|
62
|
-
resetSeconds = reset - Date.now()
|
|
63
|
-
}
|
|
64
|
-
this.#logger.debug({ reset, remaining, host }, 'updating')
|
|
69
|
+
const reset = this.#headerToReset(resetHeader)
|
|
70
|
+
this.#logger.debug(
|
|
71
|
+
{ reset, remaining, host, resetHeader, remainingHeader },
|
|
72
|
+
'updating'
|
|
73
|
+
)
|
|
65
74
|
await this.#connection.query(
|
|
66
75
|
`INSERT INTO rate_limit (host, remaining, reset)
|
|
67
76
|
VALUES (?, ?, ?)
|
|
@@ -165,4 +174,39 @@ export class RequestThrottler {
|
|
|
165
174
|
{ replacements: [host] }
|
|
166
175
|
)
|
|
167
176
|
}
|
|
177
|
+
|
|
178
|
+
#headerToReset (header) {
|
|
179
|
+
let reset
|
|
180
|
+
const now = Date.now()
|
|
181
|
+
if (header.match(/^\d+$/)) {
|
|
182
|
+
const num = parseInt(header)
|
|
183
|
+
|
|
184
|
+
if (num > now - (3600 * 1000)) {
|
|
185
|
+
// epoch in ms
|
|
186
|
+
reset = new Date(num)
|
|
187
|
+
} else if (num > (now / 1000) - 3600) {
|
|
188
|
+
// epoch in s
|
|
189
|
+
reset = new Date(num * 1000)
|
|
190
|
+
} else if (num < OFFSET_THRESHOLD) {
|
|
191
|
+
// offset in s
|
|
192
|
+
reset = new Date(now + (num * 1000))
|
|
193
|
+
} else if (num < OFFSET_THRESHOLD * 1000) {
|
|
194
|
+
// offset in ms
|
|
195
|
+
reset = new Date(now + num)
|
|
196
|
+
} else {
|
|
197
|
+
// no good guesses; default
|
|
198
|
+
reset = new Date(now + DEFAULT_RESET)
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
reset = new Date(header)
|
|
202
|
+
if (Number.isNaN(reset.getTime())) {
|
|
203
|
+
this.#logger.warn(
|
|
204
|
+
{ header },
|
|
205
|
+
'Error parsing header for request throttling'
|
|
206
|
+
)
|
|
207
|
+
reset = new Date(now + DEFAULT_RESET)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return reset
|
|
211
|
+
}
|
|
168
212
|
}
|