@@ -3,6 +3,15 @@ import type { FetchResponse, RetryConfig, RetryFunction } from './types';
33import { delayInvocation , timeNow } from './utils' ;
44import { generateCacheKey } from './cache-manager' ;
55
6+ function getMsFromHttpDate ( dateString : string ) : number | null {
7+ const ms = Date . parse ( dateString ) - timeNow ( ) ;
8+
9+ if ( ! isNaN ( ms ) ) {
10+ return Math . max ( 0 , Math . floor ( ms ) ) ;
11+ }
12+ return null ;
13+ }
14+
615/**
716 * Calculates the number of milliseconds to wait before retrying a request,
817 * based on the `Retry-After` HTTP header in the provided response.
@@ -17,26 +26,52 @@ import { generateCacheKey } from './cache-manager';
1726export function getRetryAfterMs (
1827 extendedResponse : FetchResponse | null ,
1928) : number | null {
20- const retryAfter = extendedResponse ?. headers ?. [ 'retry-after' ] ;
21-
22- if ( ! retryAfter ) {
29+ if ( ! extendedResponse ) {
2330 return null ;
2431 }
2532
26- // Try parsing as seconds
27- const seconds = Number ( retryAfter ) ;
33+ const headers = extendedResponse . headers || { } ;
34+ const retryAfter = headers [ 'retry-after' ] ;
35+
36+ if ( retryAfter ) {
37+ // Try parsing as seconds
38+ const seconds = Number ( retryAfter ) ;
39+
40+ if ( ! isNaN ( seconds ) && seconds >= 0 ) {
41+ return seconds * 1000 ;
42+ }
43+
44+ const ms = getMsFromHttpDate ( retryAfter ) ;
2845
29- if ( ! isNaN ( seconds ) && seconds >= 0 ) {
30- return seconds * 1000 ;
46+ if ( ms !== null ) {
47+ return ms ;
48+ }
3149 }
3250
33- // Try parsing as HTTP-date
34- const date = new Date ( retryAfter ) ;
51+ // Headers are already in lowercase
52+ const RATELIMIT_RESET = 'ratelimit-reset' ;
53+
54+ // Unix timestamp when the rate limit window resets (relative to current time)
55+ // Fallback to checking 'ratelimit-reset-after' OR 'x-ratelimit-reset-after' headers
56+ const rateLimitResetAfter =
57+ headers [ RATELIMIT_RESET + '-after' ] ||
58+ headers [ 'x-' + RATELIMIT_RESET + '-after' ] ;
59+
60+ if ( rateLimitResetAfter ) {
61+ const seconds = Number ( rateLimitResetAfter ) ;
62+
63+ if ( ! isNaN ( seconds ) ) {
64+ return seconds * 1000 ;
65+ }
66+ }
3567
36- if ( ! isNaN ( date . getTime ( ) ) ) {
37- const ms = date . getTime ( ) - timeNow ( ) ;
68+ // ISO 8601 datetime when the rate limit resets
69+ // Fallback to checking 'ratelimit-reset-at' 'x-ratelimit-reset-at' headers
70+ const rateLimitResetAt =
71+ headers [ RATELIMIT_RESET + '-at' ] || headers [ 'x-' + RATELIMIT_RESET + '-at' ] ;
3872
39- return ms > 0 ? ms : 0 ;
73+ if ( rateLimitResetAt ) {
74+ return getMsFromHttpDate ( rateLimitResetAt ) ;
4075 }
4176
4277 return null ;
@@ -139,8 +174,8 @@ export async function withRetry<
139174 }
140175
141176 // If we should not stop retrying, continue to the next attempt
142- // If the error status is 429 (Too Many Requests), handle rate limiting
143- if ( error . status === 429 ) {
177+ // Handle rate limiting if the error status is 429 (Too Many Requests) or 503 (Service Unavailable)
178+ if ( error . status === 429 || error . status === 503 ) {
144179 // Try to extract the "Retry-After" value from the response headers
145180 const retryAfterMs = getRetryAfterMs ( output ) ;
146181
0 commit comments