rekwest 4.0.0 → 4.1.0
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/README.md +187 -187
- package/dist/constants.js +21 -0
- package/dist/cookies.js +1 -1
- package/dist/formdata.js +12 -12
- package/dist/index.js +35 -12
- package/dist/utils.js +21 -25
- package/package.json +3 -3
- package/src/constants.mjs +23 -0
- package/src/cookies.mjs +2 -2
- package/src/formdata.mjs +16 -13
- package/src/index.mjs +44 -13
- package/src/utils.mjs +19 -23
package/README.md
CHANGED
|
@@ -1,187 +1,187 @@
|
|
|
1
|
-
The robust request library that humanity deserves 🌐
|
|
2
|
-
---
|
|
3
|
-
This package provides highly likely functional and **easy-to-use** abstraction atop of
|
|
4
|
-
native [http(s).request](https://nodejs.org/api/https.html#https_https_request_url_options_callback)
|
|
5
|
-
and [http2.request](https://nodejs.org/api/http2.html#http2_clienthttp2session_request_headers_options).
|
|
6
|
-
|
|
7
|
-
## Abstract
|
|
8
|
-
|
|
9
|
-
* Fetch-alike
|
|
10
|
-
* Cool-beans 🫐 config options (with defaults)
|
|
11
|
-
* Automatic HTTP/2 support (ALPN negotiation)
|
|
12
|
-
* Automatic or opt-in body parse (with non-UTF-8 charset decoding)
|
|
13
|
-
* Automatic and simplistic `Cookies` treatment (with built-in jar)
|
|
14
|
-
* Automatic decompression (with opt-in body compression)
|
|
15
|
-
* Built-in streamable `File` & `FormData` interfaces
|
|
16
|
-
* Support redirects & retries with fine-grained tune-ups
|
|
17
|
-
* Support all legit request body types (include blobs & streams)
|
|
18
|
-
* Support both CJS and ESM module systems
|
|
19
|
-
* Fully promise-able and pipe-able
|
|
20
|
-
* Zero dependencies
|
|
21
|
-
|
|
22
|
-
## Prerequisites
|
|
23
|
-
|
|
24
|
-
* Node.js `>= 16.7.x`
|
|
25
|
-
|
|
26
|
-
## Installation
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
npm install rekwest --save
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Usage
|
|
33
|
-
|
|
34
|
-
```javascript
|
|
35
|
-
import rekwest, { constants } from 'rekwest';
|
|
36
|
-
|
|
37
|
-
const {
|
|
38
|
-
HTTP2_HEADER_AUTHORIZATION,
|
|
39
|
-
HTTP2_HEADER_CONTENT_ENCODING,
|
|
40
|
-
HTTP2_METHOD_POST,
|
|
41
|
-
HTTP_STATUS_OK,
|
|
42
|
-
} = constants;
|
|
43
|
-
|
|
44
|
-
const url = 'https://somewhe.re/somewhat/endpoint';
|
|
45
|
-
|
|
46
|
-
const res = await rekwest(url, {
|
|
47
|
-
body: { celestial: 'payload' },
|
|
48
|
-
headers: {
|
|
49
|
-
[HTTP2_HEADER_AUTHORIZATION]: 'Bearer [token]',
|
|
50
|
-
[HTTP2_HEADER_CONTENT_ENCODING]: 'br', // enables: body compression
|
|
51
|
-
/** [HTTP2_HEADER_CONTENT_TYPE]
|
|
52
|
-
* is undue for
|
|
53
|
-
* Array/Blob/File/FormData/Object/URLSearchParams body types
|
|
54
|
-
* and will be set automatically, with an option to override it here
|
|
55
|
-
*/
|
|
56
|
-
},
|
|
57
|
-
method: HTTP2_METHOD_POST,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
console.assert(res.statusCode === HTTP_STATUS_OK);
|
|
61
|
-
console.info(res.headers);
|
|
62
|
-
console.log(res.body);
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
```javascript
|
|
68
|
-
import rekwest, {
|
|
69
|
-
constants,
|
|
70
|
-
Blob,
|
|
71
|
-
File,
|
|
72
|
-
FormData,
|
|
73
|
-
} from 'rekwest';
|
|
74
|
-
import { Readable } from 'node:stream';
|
|
75
|
-
|
|
76
|
-
const {
|
|
77
|
-
HTTP2_HEADER_AUTHORIZATION,
|
|
78
|
-
HTTP2_HEADER_CONTENT_ENCODING,
|
|
79
|
-
HTTP2_METHOD_POST,
|
|
80
|
-
HTTP_STATUS_OK,
|
|
81
|
-
} = constants;
|
|
82
|
-
|
|
83
|
-
const blob = new Blob(['bits']);
|
|
84
|
-
const file = new File(['bits'], 'file.dab');
|
|
85
|
-
const readable = Readable.from('bits');
|
|
86
|
-
|
|
87
|
-
const fd = new FormData({
|
|
88
|
-
aux: Date.now(), // either [[key, value]], or kv sequenceable
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
fd.append('celestial', 'payload');
|
|
92
|
-
fd.append('blob', blob, 'blob.dab');
|
|
93
|
-
fd.append('file', file);
|
|
94
|
-
fd.append('readable', readable, 'readable.dab');
|
|
95
|
-
|
|
96
|
-
const url = 'https://somewhe.re/somewhat/endpoint';
|
|
97
|
-
|
|
98
|
-
const res = await rekwest(url, {
|
|
99
|
-
body: fd,
|
|
100
|
-
headers: {
|
|
101
|
-
[HTTP2_HEADER_AUTHORIZATION]: 'Bearer [token]',
|
|
102
|
-
[HTTP2_HEADER_CONTENT_ENCODING]: 'br', // enables: body compression
|
|
103
|
-
},
|
|
104
|
-
method: HTTP2_METHOD_POST,
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
console.assert(res.statusCode === HTTP_STATUS_OK);
|
|
108
|
-
console.info(res.headers);
|
|
109
|
-
console.log(res.body);
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### API
|
|
113
|
-
|
|
114
|
-
#### `rekwest(url[, options])`
|
|
115
|
-
|
|
116
|
-
* `url` **{string | URL}** The URL to send the request to
|
|
117
|
-
* `options` **{Object}**
|
|
118
|
-
Extends [https.RequestOptions](https://nodejs.org/api/https.html#https_https_request_url_options_callback)
|
|
119
|
-
along with
|
|
120
|
-
extra [http2.ClientSessionOptions](https://nodejs.org/api/http2.html#http2_http2_connect_authority_options_listener)
|
|
121
|
-
& [http2.ClientSessionRequestOptions](https://nodejs.org/api/http2.html#http2_clienthttp2session_request_headers_options)
|
|
122
|
-
and [tls.ConnectionOptions](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback)
|
|
123
|
-
for HTTP/2 attunes
|
|
124
|
-
* `body` **{string | Array | ArrayBuffer | ArrayBufferView | AsyncIterator | Blob | Buffer | DataView | File |
|
|
125
|
-
FormData | Iterator | Object | Readable | ReadableStream | SharedArrayBuffer | URLSearchParams}** The body to send
|
|
126
|
-
with the request
|
|
127
|
-
* `cookies` **{boolean | Array<[k, v]> | Cookies | Object | URLSearchParams}** `Default: true` The cookies to add to
|
|
128
|
-
the request
|
|
129
|
-
* `digest` **{boolean}** `Default: true` Controls whether to read the response stream or simply add a mixin
|
|
130
|
-
* `follow` **{number}** `Default: 20` The number of redirects to follow
|
|
131
|
-
* `h2` **{boolean}** `Default: false` Forces the use of HTTP/2 protocol
|
|
132
|
-
* `headers` **{Object}** The headers to add to the request
|
|
133
|
-
* `maxRetryAfter` **{number}** The upper limit of `retry-after` header. If unset, it will use `timeout` value
|
|
134
|
-
* `parse` **{boolean}** `Default: true` Controls whether to parse response body or simply return a buffer
|
|
135
|
-
* `redirect` **{error | follow | manual}** `Default: follow` Controls the redirect flows
|
|
136
|
-
* `retry` **{Object}** Represents the retry options
|
|
137
|
-
* `attempts` **{number}** `Default: 0` The number of retry attempts
|
|
138
|
-
* `backoffStrategy` **{string}** `Default: interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)`
|
|
139
|
-
The backoff strategy algorithm that increases logarithmically. To fixate set value to `interval * 1`
|
|
140
|
-
* `interval` **{number}** `Default: 1e3` The initial retry interval
|
|
141
|
-
* `retryAfter` **{boolean}** `Default: true` Controls `retry-after` header receptiveness
|
|
142
|
-
* `statusCodes` **{number[]}** `Default: [429, 503]` The list of status codes to retry on
|
|
143
|
-
* `thenable` **{boolean}** `Default: false` Controls the promise resolutions
|
|
144
|
-
* `timeout` **{number}** `Default: 3e5` The number of milliseconds a request can take before termination
|
|
145
|
-
* `trimTrailingSlashes` **{boolean}** `Default: false` Controls whether to trim trailing slashes in the URL before
|
|
146
|
-
proceed with the request
|
|
147
|
-
* **Returns:** Promise that resolves to
|
|
148
|
-
extended [http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
|
|
149
|
-
or [http2.ClientHttp2Stream](https://nodejs.org/api/http2.html#http2_class_clienthttp2stream) which is respectively
|
|
150
|
-
readable and duplex streams
|
|
151
|
-
* if `degist: true` & `parse: true`
|
|
152
|
-
* `body` **{string | Array | Buffer | Object}** The body based on its content type
|
|
153
|
-
* if `degist: false`
|
|
154
|
-
* `arrayBuffer` **{AsyncFunction}** Reads the response and returns **ArrayBuffer**
|
|
155
|
-
* `blob` **{AsyncFunction}** Reads the response and returns **Blob**
|
|
156
|
-
* `body` **{AsyncFunction}** Reads the response and returns **Buffer** if `parse: false`
|
|
157
|
-
* `json` **{AsyncFunction}** Reads the response and returns **Object**
|
|
158
|
-
* `text` **{AsyncFunction}** Reads the response and returns **String**
|
|
159
|
-
* `bodyUsed` **{boolean}** Indicates whether the response were read or not
|
|
160
|
-
* `cookies` **{undefined | Cookies}** The cookies sent and received with the response
|
|
161
|
-
* `headers` **{Object}** The headers received with the response
|
|
162
|
-
* `httpVersion` **{string}** Indicates protocol version negotiated with the server
|
|
163
|
-
* `ok` **{boolean}** Indicates if the response was successful (statusCode: **200-299**)
|
|
164
|
-
* `redirected` **{boolean}** Indicates if the response is the result of a redirect
|
|
165
|
-
* `statusCode` **{number}** Indicates the status code of the response
|
|
166
|
-
* `trailers` **{undefined | Object}** The trailer headers received with the response
|
|
167
|
-
|
|
168
|
-
---
|
|
169
|
-
|
|
170
|
-
#### `rekwest.defaults`
|
|
171
|
-
|
|
172
|
-
The object to fulfill with default [options](#rekwesturl-options)
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
#### `rekwest.stream(url[, options])`
|
|
177
|
-
|
|
178
|
-
The method with limited functionality to use with streams and/or pipes
|
|
179
|
-
|
|
180
|
-
* No automata
|
|
181
|
-
* No redirects
|
|
182
|
-
* Pass `h2: true` in options to use HTTP/2 protocol
|
|
183
|
-
* Use `ackn({ url: URL })` method beforehand to check the available protocols
|
|
184
|
-
|
|
185
|
-
---
|
|
186
|
-
|
|
187
|
-
For more details, please check tests (coverage: **>97%**) in the repository
|
|
1
|
+
The robust request library that humanity deserves 🌐
|
|
2
|
+
---
|
|
3
|
+
This package provides highly likely functional and **easy-to-use** abstraction atop of
|
|
4
|
+
native [http(s).request](https://nodejs.org/api/https.html#https_https_request_url_options_callback)
|
|
5
|
+
and [http2.request](https://nodejs.org/api/http2.html#http2_clienthttp2session_request_headers_options).
|
|
6
|
+
|
|
7
|
+
## Abstract
|
|
8
|
+
|
|
9
|
+
* Fetch-alike
|
|
10
|
+
* Cool-beans 🫐 config options (with defaults)
|
|
11
|
+
* Automatic HTTP/2 support (ALPN negotiation)
|
|
12
|
+
* Automatic or opt-in body parse (with non-UTF-8 charset decoding)
|
|
13
|
+
* Automatic and simplistic `Cookies` treatment (with built-in jar)
|
|
14
|
+
* Automatic decompression (with opt-in body compression)
|
|
15
|
+
* Built-in streamable `File` & `FormData` interfaces
|
|
16
|
+
* Support redirects & retries with fine-grained tune-ups
|
|
17
|
+
* Support all legit request body types (include blobs & streams)
|
|
18
|
+
* Support both CJS and ESM module systems
|
|
19
|
+
* Fully promise-able and pipe-able
|
|
20
|
+
* Zero dependencies
|
|
21
|
+
|
|
22
|
+
## Prerequisites
|
|
23
|
+
|
|
24
|
+
* Node.js `>= 16.7.x`
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install rekwest --save
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Usage
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
import rekwest, { constants } from 'rekwest';
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
HTTP2_HEADER_AUTHORIZATION,
|
|
39
|
+
HTTP2_HEADER_CONTENT_ENCODING,
|
|
40
|
+
HTTP2_METHOD_POST,
|
|
41
|
+
HTTP_STATUS_OK,
|
|
42
|
+
} = constants;
|
|
43
|
+
|
|
44
|
+
const url = 'https://somewhe.re/somewhat/endpoint';
|
|
45
|
+
|
|
46
|
+
const res = await rekwest(url, {
|
|
47
|
+
body: { celestial: 'payload' },
|
|
48
|
+
headers: {
|
|
49
|
+
[HTTP2_HEADER_AUTHORIZATION]: 'Bearer [token]',
|
|
50
|
+
[HTTP2_HEADER_CONTENT_ENCODING]: 'br', // enables: body compression
|
|
51
|
+
/** [HTTP2_HEADER_CONTENT_TYPE]
|
|
52
|
+
* is undue for
|
|
53
|
+
* Array/Blob/File/FormData/Object/URLSearchParams body types
|
|
54
|
+
* and will be set automatically, with an option to override it here
|
|
55
|
+
*/
|
|
56
|
+
},
|
|
57
|
+
method: HTTP2_METHOD_POST,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
console.assert(res.statusCode === HTTP_STATUS_OK);
|
|
61
|
+
console.info(res.headers);
|
|
62
|
+
console.log(res.body);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
import rekwest, {
|
|
69
|
+
constants,
|
|
70
|
+
Blob,
|
|
71
|
+
File,
|
|
72
|
+
FormData,
|
|
73
|
+
} from 'rekwest';
|
|
74
|
+
import { Readable } from 'node:stream';
|
|
75
|
+
|
|
76
|
+
const {
|
|
77
|
+
HTTP2_HEADER_AUTHORIZATION,
|
|
78
|
+
HTTP2_HEADER_CONTENT_ENCODING,
|
|
79
|
+
HTTP2_METHOD_POST,
|
|
80
|
+
HTTP_STATUS_OK,
|
|
81
|
+
} = constants;
|
|
82
|
+
|
|
83
|
+
const blob = new Blob(['bits']);
|
|
84
|
+
const file = new File(['bits'], 'file.dab');
|
|
85
|
+
const readable = Readable.from('bits');
|
|
86
|
+
|
|
87
|
+
const fd = new FormData({
|
|
88
|
+
aux: Date.now(), // either [[key, value]], or kv sequenceable
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
fd.append('celestial', 'payload');
|
|
92
|
+
fd.append('blob', blob, 'blob.dab');
|
|
93
|
+
fd.append('file', file);
|
|
94
|
+
fd.append('readable', readable, 'readable.dab');
|
|
95
|
+
|
|
96
|
+
const url = 'https://somewhe.re/somewhat/endpoint';
|
|
97
|
+
|
|
98
|
+
const res = await rekwest(url, {
|
|
99
|
+
body: fd,
|
|
100
|
+
headers: {
|
|
101
|
+
[HTTP2_HEADER_AUTHORIZATION]: 'Bearer [token]',
|
|
102
|
+
[HTTP2_HEADER_CONTENT_ENCODING]: 'br', // enables: body compression
|
|
103
|
+
},
|
|
104
|
+
method: HTTP2_METHOD_POST,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
console.assert(res.statusCode === HTTP_STATUS_OK);
|
|
108
|
+
console.info(res.headers);
|
|
109
|
+
console.log(res.body);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### API
|
|
113
|
+
|
|
114
|
+
#### `rekwest(url[, options])`
|
|
115
|
+
|
|
116
|
+
* `url` **{string | URL}** The URL to send the request to
|
|
117
|
+
* `options` **{Object}**
|
|
118
|
+
Extends [https.RequestOptions](https://nodejs.org/api/https.html#https_https_request_url_options_callback)
|
|
119
|
+
along with
|
|
120
|
+
extra [http2.ClientSessionOptions](https://nodejs.org/api/http2.html#http2_http2_connect_authority_options_listener)
|
|
121
|
+
& [http2.ClientSessionRequestOptions](https://nodejs.org/api/http2.html#http2_clienthttp2session_request_headers_options)
|
|
122
|
+
and [tls.ConnectionOptions](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback)
|
|
123
|
+
for HTTP/2 attunes
|
|
124
|
+
* `body` **{string | Array | ArrayBuffer | ArrayBufferView | AsyncIterator | Blob | Buffer | DataView | File |
|
|
125
|
+
FormData | Iterator | Object | Readable | ReadableStream | SharedArrayBuffer | URLSearchParams}** The body to send
|
|
126
|
+
with the request
|
|
127
|
+
* `cookies` **{boolean | Array<[k, v]> | Cookies | Object | URLSearchParams}** `Default: true` The cookies to add to
|
|
128
|
+
the request
|
|
129
|
+
* `digest` **{boolean}** `Default: true` Controls whether to read the response stream or simply add a mixin
|
|
130
|
+
* `follow` **{number}** `Default: 20` The number of redirects to follow
|
|
131
|
+
* `h2` **{boolean}** `Default: false` Forces the use of HTTP/2 protocol
|
|
132
|
+
* `headers` **{Object}** The headers to add to the request
|
|
133
|
+
* `maxRetryAfter` **{number}** The upper limit of `retry-after` header. If unset, it will use `timeout` value
|
|
134
|
+
* `parse` **{boolean}** `Default: true` Controls whether to parse response body or simply return a buffer
|
|
135
|
+
* `redirect` **{error | follow | manual}** `Default: follow` Controls the redirect flows
|
|
136
|
+
* `retry` **{Object}** Represents the retry options
|
|
137
|
+
* `attempts` **{number}** `Default: 0` The number of retry attempts
|
|
138
|
+
* `backoffStrategy` **{string}** `Default: interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)`
|
|
139
|
+
The backoff strategy algorithm that increases logarithmically. To fixate set value to `interval * 1`
|
|
140
|
+
* `interval` **{number}** `Default: 1e3` The initial retry interval
|
|
141
|
+
* `retryAfter` **{boolean}** `Default: true` Controls `retry-after` header receptiveness
|
|
142
|
+
* `statusCodes` **{number[]}** `Default: [429, 503]` The list of status codes to retry on
|
|
143
|
+
* `thenable` **{boolean}** `Default: false` Controls the promise resolutions
|
|
144
|
+
* `timeout` **{number}** `Default: 3e5` The number of milliseconds a request can take before termination
|
|
145
|
+
* `trimTrailingSlashes` **{boolean}** `Default: false` Controls whether to trim trailing slashes in the URL before
|
|
146
|
+
proceed with the request
|
|
147
|
+
* **Returns:** Promise that resolves to
|
|
148
|
+
extended [http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
|
|
149
|
+
or [http2.ClientHttp2Stream](https://nodejs.org/api/http2.html#http2_class_clienthttp2stream) which is respectively
|
|
150
|
+
readable and duplex streams
|
|
151
|
+
* if `degist: true` & `parse: true`
|
|
152
|
+
* `body` **{string | Array | Buffer | Object}** The body based on its content type
|
|
153
|
+
* if `degist: false`
|
|
154
|
+
* `arrayBuffer` **{AsyncFunction}** Reads the response and returns **ArrayBuffer**
|
|
155
|
+
* `blob` **{AsyncFunction}** Reads the response and returns **Blob**
|
|
156
|
+
* `body` **{AsyncFunction}** Reads the response and returns **Buffer** if `parse: false`
|
|
157
|
+
* `json` **{AsyncFunction}** Reads the response and returns **Object**
|
|
158
|
+
* `text` **{AsyncFunction}** Reads the response and returns **String**
|
|
159
|
+
* `bodyUsed` **{boolean}** Indicates whether the response were read or not
|
|
160
|
+
* `cookies` **{undefined | Cookies}** The cookies sent and received with the response
|
|
161
|
+
* `headers` **{Object}** The headers received with the response
|
|
162
|
+
* `httpVersion` **{string}** Indicates protocol version negotiated with the server
|
|
163
|
+
* `ok` **{boolean}** Indicates if the response was successful (statusCode: **200-299**)
|
|
164
|
+
* `redirected` **{boolean}** Indicates if the response is the result of a redirect
|
|
165
|
+
* `statusCode` **{number}** Indicates the status code of the response
|
|
166
|
+
* `trailers` **{undefined | Object}** The trailer headers received with the response
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
#### `rekwest.defaults`
|
|
171
|
+
|
|
172
|
+
The object to fulfill with default [options](#rekwesturl-options)
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
#### `rekwest.stream(url[, options])`
|
|
177
|
+
|
|
178
|
+
The method with limited functionality to use with streams and/or pipes
|
|
179
|
+
|
|
180
|
+
* No automata
|
|
181
|
+
* No redirects
|
|
182
|
+
* Pass `h2: true` in options to use HTTP/2 protocol
|
|
183
|
+
* Use `ackn({ url: URL })` method beforehand to check the available protocols
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
For more details, please check tests (coverage: **>97%**) in the repository
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.redirectStatusCodes = exports.redirectModes = void 0;
|
|
5
|
+
var _nodeHttp = _interopRequireDefault(require("node:http2"));
|
|
6
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
7
|
+
const {
|
|
8
|
+
HTTP_STATUS_FOUND,
|
|
9
|
+
HTTP_STATUS_MOVED_PERMANENTLY,
|
|
10
|
+
HTTP_STATUS_PERMANENT_REDIRECT,
|
|
11
|
+
HTTP_STATUS_SEE_OTHER,
|
|
12
|
+
HTTP_STATUS_TEMPORARY_REDIRECT
|
|
13
|
+
} = _nodeHttp.default.constants;
|
|
14
|
+
const redirectModes = {
|
|
15
|
+
error: 'error',
|
|
16
|
+
follow: 'follow',
|
|
17
|
+
manual: 'manual'
|
|
18
|
+
};
|
|
19
|
+
exports.redirectModes = redirectModes;
|
|
20
|
+
const redirectStatusCodes = [HTTP_STATUS_MOVED_PERMANENTLY, HTTP_STATUS_FOUND, HTTP_STATUS_SEE_OTHER, HTTP_STATUS_TEMPORARY_REDIRECT, HTTP_STATUS_PERMANENT_REDIRECT];
|
|
21
|
+
exports.redirectStatusCodes = redirectStatusCodes;
|
package/dist/cookies.js
CHANGED
package/dist/formdata.js
CHANGED
|
@@ -31,7 +31,7 @@ class FormData {
|
|
|
31
31
|
} else {
|
|
32
32
|
yield encoder.encode(`${prefix}; name="${escape(normalize(name))}"${value.name ? `; filename="${escape(value.name)}"` : ''}${CRLF}${HTTP2_HEADER_CONTENT_TYPE}: ${value.type || _mediatypes.APPLICATION_OCTET_STREAM}${CRLF.repeat(2)}`);
|
|
33
33
|
yield* (0, _utils.tap)(value);
|
|
34
|
-
yield
|
|
34
|
+
yield new Uint8Array([13, 10]);
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
yield encoder.encode(`--${boundary}--`);
|
|
@@ -98,18 +98,18 @@ class FormData {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
append(...args) {
|
|
101
|
-
(0, _utils.
|
|
101
|
+
(0, _utils.brandCheck)(this, FormData);
|
|
102
102
|
this.#ensureArgs(args, 2, 'append');
|
|
103
103
|
this.#entries.push(this.constructor.#enfoldEntry(...args));
|
|
104
104
|
}
|
|
105
105
|
delete(...args) {
|
|
106
|
-
(0, _utils.
|
|
106
|
+
(0, _utils.brandCheck)(this, FormData);
|
|
107
107
|
this.#ensureArgs(args, 1, 'delete');
|
|
108
108
|
const name = (0, _nodeUtil.toUSVString)(args[0]);
|
|
109
109
|
this.#entries = this.#entries.filter(it => it.name !== name);
|
|
110
110
|
}
|
|
111
111
|
forEach(...args) {
|
|
112
|
-
(0, _utils.
|
|
112
|
+
(0, _utils.brandCheck)(this, FormData);
|
|
113
113
|
this.#ensureArgs(args, 1, 'forEach');
|
|
114
114
|
const [callback, thisArg] = args;
|
|
115
115
|
for (const entry of this) {
|
|
@@ -117,25 +117,25 @@ class FormData {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
get(...args) {
|
|
120
|
-
(0, _utils.
|
|
120
|
+
(0, _utils.brandCheck)(this, FormData);
|
|
121
121
|
this.#ensureArgs(args, 1, 'get');
|
|
122
122
|
const name = (0, _nodeUtil.toUSVString)(args[0]);
|
|
123
123
|
return (this.#entries.find(it => it.name === name) ?? {}).value ?? null;
|
|
124
124
|
}
|
|
125
125
|
getAll(...args) {
|
|
126
|
-
(0, _utils.
|
|
126
|
+
(0, _utils.brandCheck)(this, FormData);
|
|
127
127
|
this.#ensureArgs(args, 1, 'getAll');
|
|
128
128
|
const name = (0, _nodeUtil.toUSVString)(args[0]);
|
|
129
129
|
return this.#entries.filter(it => it.name === name).map(it => it.value);
|
|
130
130
|
}
|
|
131
131
|
has(...args) {
|
|
132
|
-
(0, _utils.
|
|
132
|
+
(0, _utils.brandCheck)(this, FormData);
|
|
133
133
|
this.#ensureArgs(args, 1, 'has');
|
|
134
134
|
const name = (0, _nodeUtil.toUSVString)(args[0]);
|
|
135
135
|
return !!this.#entries.find(it => it.name === name);
|
|
136
136
|
}
|
|
137
137
|
set(...args) {
|
|
138
|
-
(0, _utils.
|
|
138
|
+
(0, _utils.brandCheck)(this, FormData);
|
|
139
139
|
this.#ensureArgs(args, 2, 'set');
|
|
140
140
|
const entry = this.constructor.#enfoldEntry(...args);
|
|
141
141
|
const idx = this.#entries.findIndex(it => it.name === entry.name);
|
|
@@ -146,7 +146,7 @@ class FormData {
|
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
*entries() {
|
|
149
|
-
(0, _utils.
|
|
149
|
+
(0, _utils.brandCheck)(this, FormData);
|
|
150
150
|
for (const {
|
|
151
151
|
name,
|
|
152
152
|
value
|
|
@@ -155,19 +155,19 @@ class FormData {
|
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
*keys() {
|
|
158
|
-
(0, _utils.
|
|
158
|
+
(0, _utils.brandCheck)(this, FormData);
|
|
159
159
|
for (const [name] of this) {
|
|
160
160
|
yield name;
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
*values() {
|
|
164
|
-
(0, _utils.
|
|
164
|
+
(0, _utils.brandCheck)(this, FormData);
|
|
165
165
|
for (const [, value] of this) {
|
|
166
166
|
yield value;
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
[Symbol.iterator]() {
|
|
170
|
-
(0, _utils.
|
|
170
|
+
(0, _utils.brandCheck)(this, FormData);
|
|
171
171
|
return this.entries();
|
|
172
172
|
}
|
|
173
173
|
}
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,13 @@ Object.keys(_ackn).forEach(function (key) {
|
|
|
20
20
|
if (key in exports && exports[key] === _ackn[key]) return;
|
|
21
21
|
exports[key] = _ackn[key];
|
|
22
22
|
});
|
|
23
|
+
var _constants = require("./constants");
|
|
24
|
+
Object.keys(_constants).forEach(function (key) {
|
|
25
|
+
if (key === "default" || key === "__esModule") return;
|
|
26
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
27
|
+
if (key in exports && exports[key] === _constants[key]) return;
|
|
28
|
+
exports[key] = _constants[key];
|
|
29
|
+
});
|
|
23
30
|
var _cookies = require("./cookies");
|
|
24
31
|
Object.keys(_cookies).forEach(function (key) {
|
|
25
32
|
if (key === "default" || key === "__esModule") return;
|
|
@@ -61,14 +68,16 @@ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "functio
|
|
|
61
68
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
62
69
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
63
70
|
const {
|
|
64
|
-
|
|
71
|
+
HTTP2_HEADER_AUTHORIZATION,
|
|
65
72
|
HTTP2_HEADER_CONTENT_TYPE,
|
|
66
73
|
HTTP2_HEADER_LOCATION,
|
|
67
74
|
HTTP2_HEADER_RETRY_AFTER,
|
|
68
75
|
HTTP2_HEADER_SET_COOKIE,
|
|
69
76
|
HTTP2_METHOD_GET,
|
|
70
77
|
HTTP2_METHOD_HEAD,
|
|
78
|
+
HTTP2_METHOD_POST,
|
|
71
79
|
HTTP_STATUS_BAD_REQUEST,
|
|
80
|
+
HTTP_STATUS_FOUND,
|
|
72
81
|
HTTP_STATUS_MOVED_PERMANENTLY,
|
|
73
82
|
HTTP_STATUS_SEE_OTHER,
|
|
74
83
|
HTTP_STATUS_SERVICE_UNAVAILABLE,
|
|
@@ -160,23 +169,37 @@ async function rekwest(...args) {
|
|
|
160
169
|
enumerable: true,
|
|
161
170
|
value: cookies !== false && _cookies.Cookies.jar.has(url.origin) ? _cookies.Cookies.jar.get(url.origin) : void 0
|
|
162
171
|
});
|
|
163
|
-
|
|
164
|
-
|
|
172
|
+
const {
|
|
173
|
+
statusCode
|
|
174
|
+
} = res;
|
|
175
|
+
if (follow && /3\d{2}/.test(statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
|
|
176
|
+
if (!_constants.redirectStatusCodes.includes(statusCode)) {
|
|
177
|
+
return res.emit('error', new RangeError(`Invalid status code: ${statusCode}`));
|
|
178
|
+
}
|
|
179
|
+
if (redirect === _constants.redirectModes.error) {
|
|
165
180
|
return res.emit('error', new _errors.RequestError(`Unexpected redirect, redirect mode is set to '${redirect}'.`));
|
|
166
181
|
}
|
|
167
|
-
if (redirect ===
|
|
168
|
-
|
|
169
|
-
if (
|
|
182
|
+
if (redirect === _constants.redirectModes.follow) {
|
|
183
|
+
const location = new URL(res.headers[HTTP2_HEADER_LOCATION], url);
|
|
184
|
+
if (!/^https?:/.test(location.protocol)) {
|
|
185
|
+
return res.emit('error', new _errors.RequestError('URL scheme must be "http" or "https".'));
|
|
186
|
+
}
|
|
187
|
+
if (!(0, _utils.sameOrigin)(location, url)) {
|
|
188
|
+
Reflect.deleteProperty(options.headers, HTTP2_HEADER_AUTHORIZATION);
|
|
189
|
+
location.password = location.username = '';
|
|
190
|
+
}
|
|
191
|
+
options.url = location;
|
|
192
|
+
if (statusCode !== HTTP_STATUS_SEE_OTHER && options?.body?.pipe?.constructor === Function) {
|
|
170
193
|
return res.emit('error', new _errors.RequestError(`Unable to ${redirect} redirect with streamable body.`));
|
|
171
194
|
}
|
|
172
195
|
options.follow--;
|
|
173
|
-
if (
|
|
174
|
-
Reflect.deleteProperty(options.headers,
|
|
175
|
-
options.method = HTTP2_METHOD_GET;
|
|
196
|
+
if ([HTTP_STATUS_MOVED_PERMANENTLY, HTTP_STATUS_FOUND].includes(statusCode) && request.method === HTTP2_METHOD_POST || statusCode === HTTP_STATUS_SEE_OTHER && ![HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(options.method)) {
|
|
197
|
+
Object.keys(options.headers).filter(it => /^content-/i.test(it)).forEach(it => Reflect.deleteProperty(options.headers, it));
|
|
176
198
|
options.body = null;
|
|
199
|
+
options.method = HTTP2_METHOD_GET;
|
|
177
200
|
}
|
|
178
201
|
Reflect.set(options, 'redirected', true);
|
|
179
|
-
if (
|
|
202
|
+
if (statusCode === HTTP_STATUS_MOVED_PERMANENTLY && res.headers[HTTP2_HEADER_RETRY_AFTER]) {
|
|
180
203
|
let interval = res.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
181
204
|
interval = Number(interval) * 1000 || new Date(interval) - Date.now();
|
|
182
205
|
if (interval > options.maxRetryAfter) {
|
|
@@ -189,7 +212,7 @@ async function rekwest(...args) {
|
|
|
189
212
|
return rekwest(options.url, options).then(resolve, reject);
|
|
190
213
|
}
|
|
191
214
|
}
|
|
192
|
-
if (
|
|
215
|
+
if (statusCode >= HTTP_STATUS_BAD_REQUEST) {
|
|
193
216
|
return reject((0, _utils.mixin)(res, options));
|
|
194
217
|
}
|
|
195
218
|
resolve((0, _utils.mixin)(res, options));
|
|
@@ -245,7 +268,7 @@ Reflect.defineProperty(rekwest, 'stream', {
|
|
|
245
268
|
[HTTP2_HEADER_CONTENT_TYPE]: _mediatypes.APPLICATION_OCTET_STREAM
|
|
246
269
|
}
|
|
247
270
|
}, (0, _utils.sanitize)(...args)),
|
|
248
|
-
redirect:
|
|
271
|
+
redirect: _constants.redirectModes.manual
|
|
249
272
|
});
|
|
250
273
|
const {
|
|
251
274
|
h2,
|
package/dist/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
exports.__esModule = true;
|
|
4
|
-
exports.sanitize = exports.
|
|
4
|
+
exports.sanitize = exports.sameOrigin = exports.preflight = exports.mixin = exports.merge = exports.dispatch = exports.decompress = exports.compress = exports.brandCheck = exports.affix = exports.admix = void 0;
|
|
5
5
|
exports.tap = tap;
|
|
6
6
|
exports.transform = void 0;
|
|
7
7
|
var _nodeBuffer = require("node:buffer");
|
|
@@ -10,6 +10,7 @@ var _nodeStream = require("node:stream");
|
|
|
10
10
|
var _consumers = require("node:stream/consumers");
|
|
11
11
|
var _nodeUtil = require("node:util");
|
|
12
12
|
var _nodeZlib = _interopRequireDefault(require("node:zlib"));
|
|
13
|
+
var _constants = require("./constants");
|
|
13
14
|
var _cookies = require("./cookies");
|
|
14
15
|
var _errors = require("./errors");
|
|
15
16
|
var _file = require("./file");
|
|
@@ -71,12 +72,12 @@ const affix = (client, req, options) => {
|
|
|
71
72
|
});
|
|
72
73
|
};
|
|
73
74
|
exports.affix = affix;
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
75
|
+
const brandCheck = (value, ctor) => {
|
|
76
|
+
if (!(value instanceof ctor)) {
|
|
76
77
|
throw new TypeError('Illegal invocation');
|
|
77
78
|
}
|
|
78
79
|
};
|
|
79
|
-
exports.
|
|
80
|
+
exports.brandCheck = brandCheck;
|
|
80
81
|
const compress = (readable, encodings = '') => {
|
|
81
82
|
const encoders = [];
|
|
82
83
|
encodings = unwind(encodings);
|
|
@@ -156,7 +157,7 @@ const mixin = (res, {
|
|
|
156
157
|
arrayBuffer: {
|
|
157
158
|
enumerable: true,
|
|
158
159
|
value: async function () {
|
|
159
|
-
|
|
160
|
+
brandCheck(this, res?.constructor);
|
|
160
161
|
parse &&= false;
|
|
161
162
|
const {
|
|
162
163
|
buffer,
|
|
@@ -169,7 +170,7 @@ const mixin = (res, {
|
|
|
169
170
|
blob: {
|
|
170
171
|
enumerable: true,
|
|
171
172
|
value: async function () {
|
|
172
|
-
|
|
173
|
+
brandCheck(this, res?.constructor);
|
|
173
174
|
const val = await this.arrayBuffer();
|
|
174
175
|
return new _nodeBuffer.Blob([val]);
|
|
175
176
|
}
|
|
@@ -177,7 +178,7 @@ const mixin = (res, {
|
|
|
177
178
|
json: {
|
|
178
179
|
enumerable: true,
|
|
179
180
|
value: async function () {
|
|
180
|
-
|
|
181
|
+
brandCheck(this, res?.constructor);
|
|
181
182
|
const val = await this.text();
|
|
182
183
|
return JSON.parse(val);
|
|
183
184
|
}
|
|
@@ -185,7 +186,7 @@ const mixin = (res, {
|
|
|
185
186
|
text: {
|
|
186
187
|
enumerable: true,
|
|
187
188
|
value: async function () {
|
|
188
|
-
|
|
189
|
+
brandCheck(this, res?.constructor);
|
|
189
190
|
const blob = await this.blob();
|
|
190
191
|
return blob.text();
|
|
191
192
|
}
|
|
@@ -196,7 +197,7 @@ const mixin = (res, {
|
|
|
196
197
|
body: {
|
|
197
198
|
enumerable: true,
|
|
198
199
|
value: async function () {
|
|
199
|
-
|
|
200
|
+
brandCheck(this, res?.constructor);
|
|
200
201
|
if (this.bodyUsed) {
|
|
201
202
|
throw new TypeError('Response stream already read');
|
|
202
203
|
}
|
|
@@ -281,8 +282,8 @@ const preflight = options => {
|
|
|
281
282
|
};
|
|
282
283
|
options.method ??= method;
|
|
283
284
|
options.parse ??= true;
|
|
284
|
-
options.redirect ??=
|
|
285
|
-
if (!
|
|
285
|
+
options.redirect ??= _constants.redirectModes.follow;
|
|
286
|
+
if (!Reflect.has(_constants.redirectModes, options.redirect)) {
|
|
286
287
|
options.createConnection?.().destroy();
|
|
287
288
|
throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${options.redirect}' is not a valid enum value.`);
|
|
288
289
|
}
|
|
@@ -291,12 +292,6 @@ const preflight = options => {
|
|
|
291
292
|
return options;
|
|
292
293
|
};
|
|
293
294
|
exports.preflight = preflight;
|
|
294
|
-
const redirects = {
|
|
295
|
-
error: 'error',
|
|
296
|
-
follow: 'follow',
|
|
297
|
-
manual: 'manual'
|
|
298
|
-
};
|
|
299
|
-
exports.redirects = redirects;
|
|
300
295
|
const sanitize = (url, options = {}) => {
|
|
301
296
|
if (options.trimTrailingSlashes) {
|
|
302
297
|
url = `${url}`.replace(/(?<!:)\/+/gi, '/');
|
|
@@ -307,6 +302,8 @@ const sanitize = (url, options = {}) => {
|
|
|
307
302
|
});
|
|
308
303
|
};
|
|
309
304
|
exports.sanitize = sanitize;
|
|
305
|
+
const sameOrigin = (a, b) => a.protocol === b.protocol && a.hostname === b.hostname && a.port === b.port;
|
|
306
|
+
exports.sameOrigin = sameOrigin;
|
|
310
307
|
async function* tap(value) {
|
|
311
308
|
if (Reflect.has(value, Symbol.asyncIterator)) {
|
|
312
309
|
yield* value;
|
|
@@ -355,17 +352,16 @@ const transform = async options => {
|
|
|
355
352
|
}
|
|
356
353
|
}
|
|
357
354
|
const encodings = options.headers[HTTP2_HEADER_CONTENT_ENCODING];
|
|
358
|
-
if (
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
body = await (0, _consumers.buffer)(compress(_nodeStream.Readable.from(body), encodings));
|
|
363
|
-
}
|
|
364
|
-
} else if (body === Object(body) && (Reflect.has(body, Symbol.asyncIterator) || !Array.isArray(body) && Reflect.has(body, Symbol.iterator))) {
|
|
365
|
-
body = _nodeStream.Readable.from(body);
|
|
355
|
+
if (body === Object(body) && (Reflect.has(body, Symbol.asyncIterator) || !Array.isArray(body) && Reflect.has(body, Symbol.iterator))) {
|
|
356
|
+
body = encodings ? compress(_nodeStream.Readable.from(body), encodings) : _nodeStream.Readable.from(body);
|
|
357
|
+
} else if (encodings) {
|
|
358
|
+
body = await (0, _consumers.buffer)(compress(_nodeStream.Readable.from(body), encodings));
|
|
366
359
|
}
|
|
367
360
|
Object.assign(options.headers, {
|
|
368
361
|
...headers,
|
|
362
|
+
...(!body[Symbol.asyncIterator] && {
|
|
363
|
+
[HTTP2_HEADER_CONTENT_LENGTH]: Buffer.byteLength(body)
|
|
364
|
+
}),
|
|
369
365
|
...(options.headers[HTTP2_HEADER_CONTENT_TYPE] && {
|
|
370
366
|
[HTTP2_HEADER_CONTENT_TYPE]: options.headers[HTTP2_HEADER_CONTENT_TYPE]
|
|
371
367
|
})
|
package/package.json
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
},
|
|
10
10
|
"devDependencies": {
|
|
11
11
|
"@babel/cli": "^7.19.3",
|
|
12
|
-
"@babel/core": "^7.20.
|
|
12
|
+
"@babel/core": "^7.20.5",
|
|
13
13
|
"@babel/eslint-parser": "^7.19.1",
|
|
14
14
|
"@babel/preset-env": "^7.20.2",
|
|
15
15
|
"c8": "^7.12.0",
|
|
16
|
-
"eslint": "^8.
|
|
16
|
+
"eslint": "^8.28.0",
|
|
17
17
|
"eslint-config-ultra-refined": "^2.10.0",
|
|
18
18
|
"mocha": "^10.1.0"
|
|
19
19
|
},
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"test:bail": "mocha --bail",
|
|
68
68
|
"test:cover": "c8 --include=src --reporter=lcov --reporter=text npm test"
|
|
69
69
|
},
|
|
70
|
-
"version": "4.
|
|
70
|
+
"version": "4.1.0"
|
|
71
71
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import http2 from 'node:http2';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
HTTP_STATUS_FOUND,
|
|
5
|
+
HTTP_STATUS_MOVED_PERMANENTLY,
|
|
6
|
+
HTTP_STATUS_PERMANENT_REDIRECT,
|
|
7
|
+
HTTP_STATUS_SEE_OTHER,
|
|
8
|
+
HTTP_STATUS_TEMPORARY_REDIRECT,
|
|
9
|
+
} = http2.constants;
|
|
10
|
+
|
|
11
|
+
export const redirectModes = {
|
|
12
|
+
error: 'error',
|
|
13
|
+
follow: 'follow',
|
|
14
|
+
manual: 'manual',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const redirectStatusCodes = [
|
|
18
|
+
HTTP_STATUS_MOVED_PERMANENTLY,
|
|
19
|
+
HTTP_STATUS_FOUND,
|
|
20
|
+
HTTP_STATUS_SEE_OTHER,
|
|
21
|
+
HTTP_STATUS_TEMPORARY_REDIRECT,
|
|
22
|
+
HTTP_STATUS_PERMANENT_REDIRECT,
|
|
23
|
+
];
|
package/src/cookies.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { brandCheck } from './utils.mjs';
|
|
2
2
|
|
|
3
3
|
export class Cookies extends URLSearchParams {
|
|
4
4
|
|
|
@@ -20,7 +20,7 @@ export class Cookies extends URLSearchParams {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
toString() {
|
|
23
|
-
|
|
23
|
+
brandCheck(this, Cookies);
|
|
24
24
|
|
|
25
25
|
return super.toString().split('&').join('; ').trim();
|
|
26
26
|
}
|
package/src/formdata.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
MULTIPART_FORM_DATA,
|
|
8
8
|
} from './mediatypes.mjs';
|
|
9
9
|
import {
|
|
10
|
-
|
|
10
|
+
brandCheck,
|
|
11
11
|
tap,
|
|
12
12
|
} from './utils.mjs';
|
|
13
13
|
|
|
@@ -46,7 +46,10 @@ export class FormData {
|
|
|
46
46
|
value.type || APPLICATION_OCTET_STREAM
|
|
47
47
|
}${ CRLF.repeat(2) }`);
|
|
48
48
|
yield* tap(value);
|
|
49
|
-
yield
|
|
49
|
+
yield new Uint8Array([
|
|
50
|
+
13,
|
|
51
|
+
10,
|
|
52
|
+
]);
|
|
50
53
|
}
|
|
51
54
|
}
|
|
52
55
|
|
|
@@ -144,13 +147,13 @@ export class FormData {
|
|
|
144
147
|
}
|
|
145
148
|
|
|
146
149
|
append(...args) {
|
|
147
|
-
|
|
150
|
+
brandCheck(this, FormData);
|
|
148
151
|
this.#ensureArgs(args, 2, 'append');
|
|
149
152
|
this.#entries.push(this.constructor.#enfoldEntry(...args));
|
|
150
153
|
}
|
|
151
154
|
|
|
152
155
|
delete(...args) {
|
|
153
|
-
|
|
156
|
+
brandCheck(this, FormData);
|
|
154
157
|
this.#ensureArgs(args, 1, 'delete');
|
|
155
158
|
const name = toUSVString(args[0]);
|
|
156
159
|
|
|
@@ -158,7 +161,7 @@ export class FormData {
|
|
|
158
161
|
}
|
|
159
162
|
|
|
160
163
|
forEach(...args) {
|
|
161
|
-
|
|
164
|
+
brandCheck(this, FormData);
|
|
162
165
|
this.#ensureArgs(args, 1, 'forEach');
|
|
163
166
|
const [callback, thisArg] = args;
|
|
164
167
|
|
|
@@ -171,7 +174,7 @@ export class FormData {
|
|
|
171
174
|
}
|
|
172
175
|
|
|
173
176
|
get(...args) {
|
|
174
|
-
|
|
177
|
+
brandCheck(this, FormData);
|
|
175
178
|
this.#ensureArgs(args, 1, 'get');
|
|
176
179
|
const name = toUSVString(args[0]);
|
|
177
180
|
|
|
@@ -179,7 +182,7 @@ export class FormData {
|
|
|
179
182
|
}
|
|
180
183
|
|
|
181
184
|
getAll(...args) {
|
|
182
|
-
|
|
185
|
+
brandCheck(this, FormData);
|
|
183
186
|
this.#ensureArgs(args, 1, 'getAll');
|
|
184
187
|
const name = toUSVString(args[0]);
|
|
185
188
|
|
|
@@ -187,7 +190,7 @@ export class FormData {
|
|
|
187
190
|
}
|
|
188
191
|
|
|
189
192
|
has(...args) {
|
|
190
|
-
|
|
193
|
+
brandCheck(this, FormData);
|
|
191
194
|
this.#ensureArgs(args, 1, 'has');
|
|
192
195
|
const name = toUSVString(args[0]);
|
|
193
196
|
|
|
@@ -195,7 +198,7 @@ export class FormData {
|
|
|
195
198
|
}
|
|
196
199
|
|
|
197
200
|
set(...args) {
|
|
198
|
-
|
|
201
|
+
brandCheck(this, FormData);
|
|
199
202
|
this.#ensureArgs(args, 2, 'set');
|
|
200
203
|
const entry = this.constructor.#enfoldEntry(...args);
|
|
201
204
|
const idx = this.#entries.findIndex((it) => it.name === entry.name);
|
|
@@ -208,7 +211,7 @@ export class FormData {
|
|
|
208
211
|
}
|
|
209
212
|
|
|
210
213
|
* entries() {
|
|
211
|
-
|
|
214
|
+
brandCheck(this, FormData);
|
|
212
215
|
for (const { name, value } of this.#entries) {
|
|
213
216
|
yield [
|
|
214
217
|
name,
|
|
@@ -218,21 +221,21 @@ export class FormData {
|
|
|
218
221
|
}
|
|
219
222
|
|
|
220
223
|
* keys() {
|
|
221
|
-
|
|
224
|
+
brandCheck(this, FormData);
|
|
222
225
|
for (const [name] of this) {
|
|
223
226
|
yield name;
|
|
224
227
|
}
|
|
225
228
|
}
|
|
226
229
|
|
|
227
230
|
* values() {
|
|
228
|
-
|
|
231
|
+
brandCheck(this, FormData);
|
|
229
232
|
for (const [, value] of this) {
|
|
230
233
|
yield value;
|
|
231
234
|
}
|
|
232
235
|
}
|
|
233
236
|
|
|
234
237
|
[Symbol.iterator]() {
|
|
235
|
-
|
|
238
|
+
brandCheck(this, FormData);
|
|
236
239
|
|
|
237
240
|
return this.entries();
|
|
238
241
|
}
|
package/src/index.mjs
CHANGED
|
@@ -3,6 +3,10 @@ import http2 from 'node:http2';
|
|
|
3
3
|
import https from 'node:https';
|
|
4
4
|
import { setTimeout as setTimeoutPromise } from 'node:timers/promises';
|
|
5
5
|
import { ackn } from './ackn.mjs';
|
|
6
|
+
import {
|
|
7
|
+
redirectModes,
|
|
8
|
+
redirectStatusCodes,
|
|
9
|
+
} from './constants.mjs';
|
|
6
10
|
import { Cookies } from './cookies.mjs';
|
|
7
11
|
import { RequestError } from './errors.mjs';
|
|
8
12
|
import { APPLICATION_OCTET_STREAM } from './mediatypes.mjs';
|
|
@@ -13,7 +17,7 @@ import {
|
|
|
13
17
|
merge,
|
|
14
18
|
mixin,
|
|
15
19
|
preflight,
|
|
16
|
-
|
|
20
|
+
sameOrigin,
|
|
17
21
|
sanitize,
|
|
18
22
|
transform,
|
|
19
23
|
} from './utils.mjs';
|
|
@@ -21,6 +25,7 @@ import {
|
|
|
21
25
|
export { constants } from 'node:http2';
|
|
22
26
|
|
|
23
27
|
export * from './ackn.mjs';
|
|
28
|
+
export * from './constants.mjs';
|
|
24
29
|
export * from './cookies.mjs';
|
|
25
30
|
export * from './errors.mjs';
|
|
26
31
|
export * from './file.mjs';
|
|
@@ -29,14 +34,16 @@ export * as mediatypes from './mediatypes.mjs';
|
|
|
29
34
|
export * from './utils.mjs';
|
|
30
35
|
|
|
31
36
|
const {
|
|
32
|
-
|
|
37
|
+
HTTP2_HEADER_AUTHORIZATION,
|
|
33
38
|
HTTP2_HEADER_CONTENT_TYPE,
|
|
34
39
|
HTTP2_HEADER_LOCATION,
|
|
35
40
|
HTTP2_HEADER_RETRY_AFTER,
|
|
36
41
|
HTTP2_HEADER_SET_COOKIE,
|
|
37
42
|
HTTP2_METHOD_GET,
|
|
38
43
|
HTTP2_METHOD_HEAD,
|
|
44
|
+
HTTP2_METHOD_POST,
|
|
39
45
|
HTTP_STATUS_BAD_REQUEST,
|
|
46
|
+
HTTP_STATUS_FOUND,
|
|
40
47
|
HTTP_STATUS_MOVED_PERMANENTLY,
|
|
41
48
|
HTTP_STATUS_SEE_OTHER,
|
|
42
49
|
HTTP_STATUS_SERVICE_UNAVAILABLE,
|
|
@@ -148,29 +155,53 @@ export default async function rekwest(...args) {
|
|
|
148
155
|
: void 0,
|
|
149
156
|
});
|
|
150
157
|
|
|
151
|
-
|
|
152
|
-
|
|
158
|
+
const { statusCode } = res;
|
|
159
|
+
|
|
160
|
+
if (follow && /3\d{2}/.test(statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
|
|
161
|
+
if (!redirectStatusCodes.includes(statusCode)) {
|
|
162
|
+
return res.emit('error', new RangeError(`Invalid status code: ${ statusCode }`));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (redirect === redirectModes.error) {
|
|
153
166
|
return res.emit('error', new RequestError(`Unexpected redirect, redirect mode is set to '${ redirect }'.`));
|
|
154
167
|
}
|
|
155
168
|
|
|
156
|
-
if (redirect ===
|
|
157
|
-
|
|
169
|
+
if (redirect === redirectModes.follow) {
|
|
170
|
+
const location = new URL(res.headers[HTTP2_HEADER_LOCATION], url);
|
|
171
|
+
|
|
172
|
+
if (!/^https?:/.test(location.protocol)) {
|
|
173
|
+
return res.emit('error', new RequestError('URL scheme must be "http" or "https".'));
|
|
174
|
+
}
|
|
158
175
|
|
|
159
|
-
if (
|
|
176
|
+
if (!sameOrigin(location, url)) {
|
|
177
|
+
Reflect.deleteProperty(options.headers, HTTP2_HEADER_AUTHORIZATION);
|
|
178
|
+
location.password = location.username = '';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
options.url = location;
|
|
182
|
+
|
|
183
|
+
if (statusCode !== HTTP_STATUS_SEE_OTHER && options?.body?.pipe?.constructor === Function) {
|
|
160
184
|
return res.emit('error', new RequestError(`Unable to ${ redirect } redirect with streamable body.`));
|
|
161
185
|
}
|
|
162
186
|
|
|
163
187
|
options.follow--;
|
|
164
188
|
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
189
|
+
if (([
|
|
190
|
+
HTTP_STATUS_MOVED_PERMANENTLY,
|
|
191
|
+
HTTP_STATUS_FOUND,
|
|
192
|
+
].includes(statusCode) && request.method === HTTP2_METHOD_POST) || (statusCode === HTTP_STATUS_SEE_OTHER && ![
|
|
193
|
+
HTTP2_METHOD_GET,
|
|
194
|
+
HTTP2_METHOD_HEAD,
|
|
195
|
+
].includes(options.method))) {
|
|
196
|
+
Object.keys(options.headers).filter((it) => /^content-/i.test(it))
|
|
197
|
+
.forEach((it) => Reflect.deleteProperty(options.headers, it));
|
|
168
198
|
options.body = null;
|
|
199
|
+
options.method = HTTP2_METHOD_GET;
|
|
169
200
|
}
|
|
170
201
|
|
|
171
202
|
Reflect.set(options, 'redirected', true);
|
|
172
203
|
|
|
173
|
-
if (
|
|
204
|
+
if (statusCode === HTTP_STATUS_MOVED_PERMANENTLY && res.headers[HTTP2_HEADER_RETRY_AFTER]) {
|
|
174
205
|
let interval = res.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
175
206
|
|
|
176
207
|
interval = Number(interval) * 1000 || new Date(interval) - Date.now();
|
|
@@ -186,7 +217,7 @@ export default async function rekwest(...args) {
|
|
|
186
217
|
}
|
|
187
218
|
}
|
|
188
219
|
|
|
189
|
-
if (
|
|
220
|
+
if (statusCode >= HTTP_STATUS_BAD_REQUEST) {
|
|
190
221
|
return reject(mixin(res, options));
|
|
191
222
|
}
|
|
192
223
|
|
|
@@ -245,7 +276,7 @@ Reflect.defineProperty(rekwest, 'stream', {
|
|
|
245
276
|
...merge(rekwest.defaults, {
|
|
246
277
|
headers: { [HTTP2_HEADER_CONTENT_TYPE]: APPLICATION_OCTET_STREAM },
|
|
247
278
|
}, sanitize(...args)),
|
|
248
|
-
redirect:
|
|
279
|
+
redirect: redirectModes.manual,
|
|
249
280
|
});
|
|
250
281
|
|
|
251
282
|
const { h2, url } = options;
|
package/src/utils.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { buffer } from 'node:stream/consumers';
|
|
8
8
|
import { types } from 'node:util';
|
|
9
9
|
import zlib from 'node:zlib';
|
|
10
|
+
import { redirectModes } from './constants.mjs';
|
|
10
11
|
import { Cookies } from './cookies.mjs';
|
|
11
12
|
import { TimeoutError } from './errors.mjs';
|
|
12
13
|
import { File } from './file.mjs';
|
|
@@ -79,8 +80,8 @@ export const affix = (client, req, options) => {
|
|
|
79
80
|
});
|
|
80
81
|
};
|
|
81
82
|
|
|
82
|
-
export const
|
|
83
|
-
if (
|
|
83
|
+
export const brandCheck = (value, ctor) => {
|
|
84
|
+
if (!(value instanceof ctor)) {
|
|
84
85
|
throw new TypeError('Illegal invocation');
|
|
85
86
|
}
|
|
86
87
|
};
|
|
@@ -174,7 +175,7 @@ export const mixin = (res, { digest = false, parse = false } = {}) => {
|
|
|
174
175
|
arrayBuffer: {
|
|
175
176
|
enumerable: true,
|
|
176
177
|
value: async function () {
|
|
177
|
-
|
|
178
|
+
brandCheck(this, res?.constructor);
|
|
178
179
|
parse &&= false;
|
|
179
180
|
const { buffer, byteLength, byteOffset } = await this.body();
|
|
180
181
|
|
|
@@ -184,7 +185,7 @@ export const mixin = (res, { digest = false, parse = false } = {}) => {
|
|
|
184
185
|
blob: {
|
|
185
186
|
enumerable: true,
|
|
186
187
|
value: async function () {
|
|
187
|
-
|
|
188
|
+
brandCheck(this, res?.constructor);
|
|
188
189
|
const val = await this.arrayBuffer();
|
|
189
190
|
|
|
190
191
|
return new Blob([val]);
|
|
@@ -193,7 +194,7 @@ export const mixin = (res, { digest = false, parse = false } = {}) => {
|
|
|
193
194
|
json: {
|
|
194
195
|
enumerable: true,
|
|
195
196
|
value: async function () {
|
|
196
|
-
|
|
197
|
+
brandCheck(this, res?.constructor);
|
|
197
198
|
const val = await this.text();
|
|
198
199
|
|
|
199
200
|
return JSON.parse(val);
|
|
@@ -202,7 +203,7 @@ export const mixin = (res, { digest = false, parse = false } = {}) => {
|
|
|
202
203
|
text: {
|
|
203
204
|
enumerable: true,
|
|
204
205
|
value: async function () {
|
|
205
|
-
|
|
206
|
+
brandCheck(this, res?.constructor);
|
|
206
207
|
const blob = await this.blob();
|
|
207
208
|
|
|
208
209
|
return blob.text();
|
|
@@ -215,7 +216,7 @@ export const mixin = (res, { digest = false, parse = false } = {}) => {
|
|
|
215
216
|
body: {
|
|
216
217
|
enumerable: true,
|
|
217
218
|
value: async function () {
|
|
218
|
-
|
|
219
|
+
brandCheck(this, res?.constructor);
|
|
219
220
|
|
|
220
221
|
if (this.bodyUsed) {
|
|
221
222
|
throw new TypeError('Response stream already read');
|
|
@@ -314,9 +315,9 @@ export const preflight = (options) => {
|
|
|
314
315
|
|
|
315
316
|
options.method ??= method;
|
|
316
317
|
options.parse ??= true;
|
|
317
|
-
options.redirect ??=
|
|
318
|
+
options.redirect ??= redirectModes.follow;
|
|
318
319
|
|
|
319
|
-
if (!
|
|
320
|
+
if (!Reflect.has(redirectModes, options.redirect)) {
|
|
320
321
|
options.createConnection?.().destroy();
|
|
321
322
|
throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${
|
|
322
323
|
options.redirect
|
|
@@ -329,12 +330,6 @@ export const preflight = (options) => {
|
|
|
329
330
|
return options;
|
|
330
331
|
};
|
|
331
332
|
|
|
332
|
-
export const redirects = {
|
|
333
|
-
error: 'error',
|
|
334
|
-
follow: 'follow',
|
|
335
|
-
manual: 'manual',
|
|
336
|
-
};
|
|
337
|
-
|
|
338
333
|
export const sanitize = (url, options = {}) => {
|
|
339
334
|
if (options.trimTrailingSlashes) {
|
|
340
335
|
url = `${ url }`.replace(/(?<!:)\/+/gi, '/');
|
|
@@ -345,6 +340,8 @@ export const sanitize = (url, options = {}) => {
|
|
|
345
340
|
return Object.assign(options, { url });
|
|
346
341
|
};
|
|
347
342
|
|
|
343
|
+
export const sameOrigin = (a, b) => a.protocol === b.protocol && a.hostname === b.hostname && a.port === b.port;
|
|
344
|
+
|
|
348
345
|
export async function* tap(value) {
|
|
349
346
|
if (Reflect.has(value, Symbol.asyncIterator)) {
|
|
350
347
|
yield* value;
|
|
@@ -389,19 +386,18 @@ export const transform = async (options) => {
|
|
|
389
386
|
|
|
390
387
|
const encodings = options.headers[HTTP2_HEADER_CONTENT_ENCODING];
|
|
391
388
|
|
|
392
|
-
if (
|
|
393
|
-
if (Reflect.has(body, Symbol.asyncIterator)) {
|
|
394
|
-
body = compress(Readable.from(body), encodings);
|
|
395
|
-
} else {
|
|
396
|
-
body = await buffer(compress(Readable.from(body), encodings));
|
|
397
|
-
}
|
|
398
|
-
} else if (body === Object(body)
|
|
389
|
+
if (body === Object(body)
|
|
399
390
|
&& (Reflect.has(body, Symbol.asyncIterator) || (!Array.isArray(body) && Reflect.has(body, Symbol.iterator)))) {
|
|
400
|
-
body = Readable.from(body);
|
|
391
|
+
body = encodings ? compress(Readable.from(body), encodings) : Readable.from(body);
|
|
392
|
+
} else if (encodings) {
|
|
393
|
+
body = await buffer(compress(Readable.from(body), encodings));
|
|
401
394
|
}
|
|
402
395
|
|
|
403
396
|
Object.assign(options.headers, {
|
|
404
397
|
...headers,
|
|
398
|
+
...!body[Symbol.asyncIterator] && {
|
|
399
|
+
[HTTP2_HEADER_CONTENT_LENGTH]: Buffer.byteLength(body),
|
|
400
|
+
},
|
|
405
401
|
...options.headers[HTTP2_HEADER_CONTENT_TYPE] && {
|
|
406
402
|
[HTTP2_HEADER_CONTENT_TYPE]: options.headers[HTTP2_HEADER_CONTENT_TYPE],
|
|
407
403
|
},
|