@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 CHANGED
@@ -9,6 +9,13 @@ and this project adheres to
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.45.20] - 2026-05-14
13
+
14
+ ### Fixed
15
+
16
+ - Handle some pathological formats for request throttle resets -- epoch in
17
+ ms and offset in ms.
18
+
12
19
  ## [0.45.19] - 2026-05-14
13
20
 
14
21
  ### 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 EPOCH_THRESHOLD = 30 * 24 * 60 * 60
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 (resetHeader && remainingHeader) {
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
- let resetSeconds
52
- let reset
53
- if (resetHeader.match(/^\d+$/)) {
54
- resetSeconds = parseInt(resetHeader)
55
- if (resetSeconds < EPOCH_THRESHOLD) {
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evanp/activitypub-bot",
3
- "version": "0.45.19",
3
+ "version": "0.45.20",
4
4
  "description": "server-side ActivityPub bot framework",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",