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 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
@@ -15,7 +15,7 @@ class Cookies extends URLSearchParams {
15
15
  super(input);
16
16
  }
17
17
  toString() {
18
- (0, _utils.collate)(this, Cookies);
18
+ (0, _utils.brandCheck)(this, Cookies);
19
19
  return super.toString().split('&').join('; ').trim();
20
20
  }
21
21
  }
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 encoder.encode(CRLF);
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.collate)(this, FormData);
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.collate)(this, FormData);
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.collate)(this, FormData);
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.collate)(this, FormData);
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.collate)(this, FormData);
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.collate)(this, FormData);
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.collate)(this, FormData);
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.collate)(this, FormData);
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.collate)(this, FormData);
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.collate)(this, FormData);
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.collate)(this, FormData);
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
- HTTP2_HEADER_CONTENT_LENGTH,
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
- if (follow && /^3\d{2}$/.test(res.statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
164
- if (redirect === _utils.redirects.error) {
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 === _utils.redirects.follow) {
168
- options.url = new URL(res.headers[HTTP2_HEADER_LOCATION], url).href;
169
- if (res.statusCode !== HTTP_STATUS_SEE_OTHER && options?.body?.pipe?.constructor === Function) {
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 (res.statusCode === HTTP_STATUS_SEE_OTHER) {
174
- Reflect.deleteProperty(options.headers, HTTP2_HEADER_CONTENT_LENGTH);
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 (res.statusCode === HTTP_STATUS_MOVED_PERMANENTLY && res.headers[HTTP2_HEADER_RETRY_AFTER]) {
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 (res.statusCode >= HTTP_STATUS_BAD_REQUEST) {
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: _utils.redirects.manual
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.redirects = exports.preflight = exports.mixin = exports.merge = exports.dispatch = exports.decompress = exports.compress = exports.collate = exports.affix = exports.admix = void 0;
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 collate = (entity, primordial) => {
75
- if (entity?.constructor !== primordial) {
75
+ const brandCheck = (value, ctor) => {
76
+ if (!(value instanceof ctor)) {
76
77
  throw new TypeError('Illegal invocation');
77
78
  }
78
79
  };
79
- exports.collate = collate;
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
- collate(this, res?.constructor);
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
- collate(this, res?.constructor);
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
- collate(this, res?.constructor);
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
- collate(this, res?.constructor);
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
- collate(this, res?.constructor);
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 ??= redirects.follow;
285
- if (!Object.values(redirects).includes(options.redirect)) {
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 (encodings) {
359
- if (Reflect.has(body, Symbol.asyncIterator)) {
360
- body = compress(_nodeStream.Readable.from(body), encodings);
361
- } else {
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.2",
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.27.0",
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.0.0"
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 { collate } from './utils.mjs';
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
- collate(this, Cookies);
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
- collate,
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 encoder.encode(CRLF);
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
- collate(this, FormData);
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
- collate(this, FormData);
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
- collate(this, FormData);
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
- collate(this, FormData);
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
- collate(this, FormData);
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
- collate(this, FormData);
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
- collate(this, FormData);
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
- collate(this, FormData);
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
- collate(this, FormData);
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
- collate(this, FormData);
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
- collate(this, FormData);
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
- redirects,
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
- HTTP2_HEADER_CONTENT_LENGTH,
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
- if (follow && /^3\d{2}$/.test(res.statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
152
- if (redirect === redirects.error) {
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 === redirects.follow) {
157
- options.url = new URL(res.headers[HTTP2_HEADER_LOCATION], url).href;
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 (res.statusCode !== HTTP_STATUS_SEE_OTHER && options?.body?.pipe?.constructor === Function) {
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 (res.statusCode === HTTP_STATUS_SEE_OTHER) {
166
- Reflect.deleteProperty(options.headers, HTTP2_HEADER_CONTENT_LENGTH);
167
- options.method = HTTP2_METHOD_GET;
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 (res.statusCode === HTTP_STATUS_MOVED_PERMANENTLY && res.headers[HTTP2_HEADER_RETRY_AFTER]) {
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 (res.statusCode >= HTTP_STATUS_BAD_REQUEST) {
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: redirects.manual,
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 collate = (entity, primordial) => {
83
- if (entity?.constructor !== primordial) {
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
- collate(this, res?.constructor);
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
- collate(this, res?.constructor);
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
- collate(this, res?.constructor);
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
- collate(this, res?.constructor);
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
- collate(this, res?.constructor);
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 ??= redirects.follow;
318
+ options.redirect ??= redirectModes.follow;
318
319
 
319
- if (!Object.values(redirects).includes(options.redirect)) {
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 (encodings) {
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
  },