1
- import type {
2
- ServerResponse ,
3
- IncomingHttpHeaders ,
4
- OutgoingHttpHeaders ,
5
- } from 'node:http'
6
- import type { RateLimit , RateLimitOptions } from './types'
7
-
8
- // Node or fetch
9
- export type ResponseObject = ServerResponse | Response
10
- export type HeadersObject =
11
- | IncomingHttpHeaders
12
- | OutgoingHttpHeaders
13
- | Headers
14
- | { [ key : string ] : string | string [ ] }
15
- export type ResponseOrHeadersObject = ResponseObject | HeadersObject
1
+ // /source/ratelimit-header-parser.ts
2
+ // The parser and associated functions
16
3
4
+ import type {
5
+ ResponseObject ,
6
+ HeadersObject ,
7
+ RateLimitInfo ,
8
+ ParserOptions ,
9
+ } from './types'
10
+
11
+ /**
12
+ * Parses the passed response/headers object and returns rate limit information.
13
+ *
14
+ * @param input {ResponseObject | HeadersObject} - The node/fetch-style response/headers object.
15
+ * @param passedOptions {Partial<ParserOptions> | undefined} - The configuration for the parser.
16
+ *
17
+ * @returns {RateLimitInfo | undefined } - The rate limit information parsed from the headers.
18
+ */
17
19
export function parseRateLimit (
18
- input : ResponseOrHeadersObject ,
19
- options ?: RateLimitOptions ,
20
- ) : RateLimit | undefined {
20
+ input : ResponseObject | HeadersObject ,
21
+ passedOptions ?: Partial < ParserOptions > ,
22
+ ) : RateLimitInfo | undefined {
23
+ // Default to no configuration.
24
+ const options = passedOptions ?? { }
25
+
26
+ // Get the headers object from the passed input.
27
+ let headers : HeadersObject
21
28
if (
22
29
'headers' in input &&
23
30
typeof input . headers === 'object' &&
24
31
! Array . isArray ( input . headers )
25
- ) {
26
- return parseHeadersObject ( input . headers , options )
27
- }
28
-
29
- if ( 'getHeaders' in input && typeof input . getHeaders === 'function' ) {
30
- return parseHeadersObject ( input . getHeaders ( ) , options )
31
- }
32
-
33
- return parseHeadersObject ( input as HeadersObject , options )
32
+ )
33
+ headers = input . headers
34
+ else if ( 'getHeaders' in input && typeof input . getHeaders === 'function' )
35
+ headers = input . getHeaders ( )
36
+ else headers = input as HeadersObject
37
+
38
+ // Parse the headers.
39
+ return parseHeaders ( headers , options )
34
40
}
35
41
36
- function parseHeadersObject (
37
- input : HeadersObject ,
38
- options : RateLimitOptions | undefined ,
39
- ) : RateLimit | undefined {
40
- const combined = getHeader ( input , 'ratelimit' )
42
+ /**
43
+ * The internal parser function.
44
+ */
45
+ function parseHeaders (
46
+ headers : HeadersObject ,
47
+ options : Partial < ParserOptions > ,
48
+ ) : RateLimitInfo | undefined {
49
+ const combined = getHeader ( headers , 'ratelimit' )
41
50
if ( combined ) return parseCombinedRateLimitHeader ( combined )
42
51
43
52
let prefix
44
- if ( getHeader ( input , 'ratelimit-remaining' ) ) {
53
+ if ( getHeader ( headers , 'ratelimit-remaining' ) ) {
45
54
prefix = 'ratelimit-'
46
- } else if ( getHeader ( input , 'x-ratelimit-remaining' ) ) {
55
+ } else if ( getHeader ( headers , 'x-ratelimit-remaining' ) ) {
47
56
prefix = 'x-ratelimit-'
48
- } else if ( getHeader ( input , 'x-rate-limit-remaining' ) ) {
57
+ } else if ( getHeader ( headers , 'x-rate-limit-remaining' ) ) {
49
58
// Twitter - https://developer.twitter.com/en/docs/twitter-api/rate-limits#headers-and-codes
50
59
prefix = 'x-rate-limit-'
51
60
} else {
@@ -57,18 +66,18 @@ function parseHeadersObject(
57
66
return
58
67
}
59
68
60
- const limit = toInt ( getHeader ( input , `${ prefix } limit` ) )
69
+ const limit = toInt ( getHeader ( headers , `${ prefix } limit` ) )
61
70
// Used - https://github.com/reddit-archive/reddit/wiki/API#rules
62
71
// used - https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#rate-limit-headers
63
72
// observed - https://docs.gitlab.com/ee/administration/settings/user_and_ip_rate_limits.html#response-headers
64
73
// note that || is valid here because used should always be at least 1, and || handles NaN correctly, whereas ?? doesn't
65
74
const used =
66
- toInt ( getHeader ( input , `${ prefix } used` ) ) ||
67
- toInt ( getHeader ( input , `${ prefix } observed` ) )
68
- const remaining = toInt ( getHeader ( input , `${ prefix } remaining` ) )
75
+ toInt ( getHeader ( headers , `${ prefix } used` ) ) ||
76
+ toInt ( getHeader ( headers , `${ prefix } observed` ) )
77
+ const remaining = toInt ( getHeader ( headers , `${ prefix } remaining` ) )
69
78
70
79
let reset : Date | undefined
71
- const resetRaw = getHeader ( input , `${ prefix } reset` )
80
+ const resetRaw = getHeader ( headers , `${ prefix } reset` )
72
81
const resetType = options ?. reset
73
82
switch ( resetType ) {
74
83
case 'date' : {
@@ -95,7 +104,7 @@ function parseHeadersObject(
95
104
if ( resetRaw ) reset = parseResetAuto ( resetRaw )
96
105
else {
97
106
// Fallback to retry-after
98
- const retryAfter = getHeader ( input , 'retry-after' )
107
+ const retryAfter = getHeader ( headers , 'retry-after' )
99
108
if ( retryAfter ) {
100
109
reset = parseResetUnix ( retryAfter )
101
110
}
@@ -114,10 +123,10 @@ function parseHeadersObject(
114
123
const reLimit = / l i m i t \s * = \s * ( \d + ) / i
115
124
const reRemaining = / r e m a i n i n g \s * = \s * ( \d + ) / i
116
125
const reReset = / r e s e t \s * = \s * ( \d + ) / i
117
- export function parseCombinedRateLimitHeader ( input : string ) : RateLimit {
118
- const limit = toInt ( reLimit . exec ( input ) ?. [ 1 ] )
119
- const remaining = toInt ( reRemaining . exec ( input ) ?. [ 1 ] )
120
- const resetSeconds = toInt ( reReset . exec ( input ) ?. [ 1 ] )
126
+ export function parseCombinedRateLimitHeader ( header : string ) : RateLimitInfo {
127
+ const limit = toInt ( reLimit . exec ( header ) ?. [ 1 ] )
128
+ const remaining = toInt ( reRemaining . exec ( header ) ?. [ 1 ] )
129
+ const resetSeconds = toInt ( reReset . exec ( header ) ?. [ 1 ] )
121
130
const reset = secondsToDate ( resetSeconds )
122
131
return {
123
132
limit,
0 commit comments