rekwest 7.2.7 → 8.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/src/redirects.js CHANGED
@@ -1,86 +1,86 @@
1
- import http2 from 'node:http2';
2
- import { isReadable } from 'node:stream';
3
- import {
4
- requestCredentials,
5
- requestRedirect,
6
- } from './constants.js';
7
- import { RequestError } from './errors.js';
8
- import rekwest from './index.js';
9
- import {
10
- isPipeStream,
11
- sameOrigin,
12
- } from './utils.js';
13
-
14
- const {
15
- HTTP2_HEADER_LOCATION,
16
- HTTP2_METHOD_GET,
17
- HTTP2_METHOD_HEAD,
18
- HTTP2_METHOD_POST,
19
- HTTP_STATUS_FOUND,
20
- HTTP_STATUS_MOVED_PERMANENTLY,
21
- HTTP_STATUS_PERMANENT_REDIRECT,
22
- HTTP_STATUS_SEE_OTHER,
23
- HTTP_STATUS_TEMPORARY_REDIRECT,
24
- } = http2.constants;
25
-
26
- export const redirects = (res, options) => {
27
- const {
28
- allowDowngrade,
29
- credentials,
30
- follow,
31
- redirect,
32
- url,
33
- } = options;
34
-
35
- if (follow && /3\d{2}/.test(res.statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
36
- if (redirect === requestRedirect.error) {
37
- throw new RequestError(`Unexpected redirect, redirect mode is set to: ${ redirect }`);
38
- }
39
-
40
- if (redirect === requestRedirect.follow) {
41
- const loc = new URL(res.headers[HTTP2_HEADER_LOCATION], url);
42
-
43
- if (!/^https?:/i.test(loc.protocol)) {
44
- throw new RequestError('URL scheme must be "http" or "https"');
45
- }
46
-
47
- if (!allowDowngrade && loc.protocol === 'http:' && url.protocol === 'https:') {
48
- throw new RequestError(
49
- `Protocol downgrade detected, redirect from "${ url.protocol }" to "${ loc.protocol }": ${ loc }`,
50
- );
51
- }
52
-
53
- if (!sameOrigin(loc, url)) {
54
- if (credentials !== requestCredentials.include) {
55
- options.credentials = requestCredentials.omit;
56
- }
57
-
58
- options.h2 = false;
59
- }
60
-
61
- if ([
62
- HTTP_STATUS_PERMANENT_REDIRECT,
63
- HTTP_STATUS_TEMPORARY_REDIRECT,
64
- ].includes(res.statusCode) && isPipeStream(options.body) && !isReadable(options.body)) {
65
- throw new RequestError(`Unable to ${ redirect } redirect with streamable body`);
66
- }
67
-
68
- if (([
69
- HTTP_STATUS_MOVED_PERMANENTLY,
70
- HTTP_STATUS_FOUND,
71
- ].includes(res.statusCode) && options.method === HTTP2_METHOD_POST)
72
- || (res.statusCode === HTTP_STATUS_SEE_OTHER && ![
73
- HTTP2_METHOD_GET,
74
- HTTP2_METHOD_HEAD,
75
- ].includes(options.method))) {
76
- options.body = null;
77
- options.method = HTTP2_METHOD_GET;
78
- }
79
-
80
- options.follow--;
81
- options.redirected = true;
82
-
83
- return rekwest(loc, options);
84
- }
85
- }
86
- };
1
+ import http2 from 'node:http2';
2
+ import { isReadable } from 'node:stream';
3
+ import {
4
+ requestCredentials,
5
+ requestRedirect,
6
+ } from './constants.js';
7
+ import { RequestError } from './errors.js';
8
+ import rekwest from './index.js';
9
+ import {
10
+ isPipeStream,
11
+ sameOrigin,
12
+ } from './utils.js';
13
+
14
+ const {
15
+ HTTP2_HEADER_LOCATION,
16
+ HTTP2_METHOD_GET,
17
+ HTTP2_METHOD_HEAD,
18
+ HTTP2_METHOD_POST,
19
+ HTTP_STATUS_FOUND,
20
+ HTTP_STATUS_MOVED_PERMANENTLY,
21
+ HTTP_STATUS_PERMANENT_REDIRECT,
22
+ HTTP_STATUS_SEE_OTHER,
23
+ HTTP_STATUS_TEMPORARY_REDIRECT,
24
+ } = http2.constants;
25
+
26
+ export const redirects = (res, options) => {
27
+ const {
28
+ allowDowngrade,
29
+ credentials,
30
+ follow,
31
+ redirect,
32
+ url,
33
+ } = options;
34
+
35
+ if (follow && /3\d{2}/.test(res.statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
36
+ if (redirect === requestRedirect.error) {
37
+ throw new RequestError(`Unexpected redirect, redirect mode is set to: ${ redirect }`);
38
+ }
39
+
40
+ if (redirect === requestRedirect.follow) {
41
+ const loc = new URL(res.headers[HTTP2_HEADER_LOCATION], url);
42
+
43
+ if (!/^https?:/i.test(loc.protocol)) {
44
+ throw new RequestError('URL scheme must be "http" or "https"');
45
+ }
46
+
47
+ if (!allowDowngrade && loc.protocol === 'http:' && url.protocol === 'https:') {
48
+ throw new RequestError(
49
+ `Protocol downgrade detected, redirect from "${ url.protocol }" to "${ loc.protocol }": ${ loc }`,
50
+ );
51
+ }
52
+
53
+ if (!sameOrigin(loc, url)) {
54
+ if (credentials !== requestCredentials.include) {
55
+ options.credentials = requestCredentials.omit;
56
+ }
57
+
58
+ options.h2 = false;
59
+ }
60
+
61
+ if ([
62
+ HTTP_STATUS_PERMANENT_REDIRECT,
63
+ HTTP_STATUS_TEMPORARY_REDIRECT,
64
+ ].includes(res.statusCode) && isPipeStream(options.body) && !isReadable(options.body)) {
65
+ throw new RequestError(`Unable to ${ redirect } redirect with streamable body`);
66
+ }
67
+
68
+ if (([
69
+ HTTP_STATUS_MOVED_PERMANENTLY,
70
+ HTTP_STATUS_FOUND,
71
+ ].includes(res.statusCode) && options.method === HTTP2_METHOD_POST)
72
+ || (res.statusCode === HTTP_STATUS_SEE_OTHER && ![
73
+ HTTP2_METHOD_GET,
74
+ HTTP2_METHOD_HEAD,
75
+ ].includes(options.method))) {
76
+ options.body = null;
77
+ options.method = HTTP2_METHOD_GET;
78
+ }
79
+
80
+ options.follow--;
81
+ options.redirected = true;
82
+
83
+ return rekwest(loc, options);
84
+ }
85
+ }
86
+ };
package/src/transfer.js CHANGED
@@ -1,106 +1,104 @@
1
- import http from 'node:http';
2
- import http2 from 'node:http2';
3
- import https from 'node:https';
4
- import { ackn } from './ackn.js';
5
- import { RequestError } from './errors.js';
6
- import { postflight } from './postflight.js';
7
- import { preflight } from './preflight.js';
8
- import { retries } from './retries.js';
9
- import { transform } from './transform.js';
10
- import {
11
- deepMerge,
12
- dispatch,
13
- isLikelyH2cPrefaceError,
14
- snoop,
15
- } from './utils.js';
16
-
17
- export const transfer = async (options) => {
18
- const { digest, redirected, thenable, url } = options;
19
-
20
- if (options.follow === 0) {
21
- throw new RequestError(`Maximum redirect reached at: ${ url.href }`);
22
- }
23
-
24
- if (url.protocol === 'https:') {
25
- options = !options.h2 ? await ackn(options) : {
26
- ...options, createConnection: null, protocol: url.protocol,
27
- };
28
- } else if (Reflect.has(options, 'alpnProtocol')) {
29
- for (const it of [
30
- 'alpnProtocol',
31
- 'createConnection',
32
- 'h2',
33
- 'protocol',
34
- ]) { Reflect.deleteProperty(options, it); }
35
- }
36
-
37
- try {
38
- options = await transform(preflight(options));
39
- } catch (err) {
40
- options.createConnection?.().destroy();
41
- throw err;
42
- }
43
-
44
- const promise = new Promise((resolve, reject) => {
45
- let client, req;
46
-
47
- if (options.h2) {
48
- client = http2.connect(url.origin, options);
49
- req = client.request(options.headers, options);
50
- } else {
51
- const { request } = url.protocol === 'http:' ? http : https;
52
-
53
- req = request(url, options);
54
- }
55
-
56
- snoop(client, req, options, { reject });
57
-
58
- req.once('response', (res) => postflight(req, res, options, {
59
- reject, resolve,
60
- }));
61
-
62
- dispatch(req, options);
63
- });
64
-
65
- try {
66
- const res = await promise;
67
-
68
- if (digest && !redirected) {
69
- res.body = await res.body();
70
- }
71
-
72
- return res;
73
- } catch (err) {
74
- if (isLikelyH2cPrefaceError(err)) {
75
- const { retry } = options;
76
-
77
- options = deepMerge(options, {
78
- h2: true,
79
- retry: {
80
- attempts: ++retry.attempts,
81
- errorCodes: [
82
- err.code,
83
- ...retry.errorCodes,
84
- ],
85
- interval: 1,
86
- },
87
- });
88
- }
89
-
90
- const result = retries(err, options);
91
-
92
- if (result) {
93
- return result;
94
- }
95
-
96
- if (digest && !redirected && err.body) {
97
- err.body = await err.body();
98
- }
99
-
100
- if (!thenable) {
101
- throw err;
102
- } else {
103
- return err;
104
- }
105
- }
106
- };
1
+ import http from 'node:http';
2
+ import http2 from 'node:http2';
3
+ import https from 'node:https';
4
+ import { ackn } from './ackn.js';
5
+ import { RequestError } from './errors.js';
6
+ import { postflight } from './postflight.js';
7
+ import { preflight } from './preflight.js';
8
+ import { retries } from './retries.js';
9
+ import { transform } from './transform.js';
10
+ import {
11
+ deepMerge,
12
+ dispatch,
13
+ isLikelyH2cPrefaceError,
14
+ snoop,
15
+ } from './utils.js';
16
+
17
+ export const transfer = async (options) => {
18
+ const { digest, redirected, thenable, url } = options;
19
+
20
+ if (options.follow === 0) {
21
+ throw new RequestError(`Maximum redirect reached at: ${ url.href }`);
22
+ }
23
+
24
+ if (url.protocol === 'https:') {
25
+ options = await ackn(options);
26
+ } else if (Reflect.has(options, 'alpnProtocol')) {
27
+ for (const it of [
28
+ 'alpnProtocol',
29
+ 'createConnection',
30
+ 'h2',
31
+ 'protocol',
32
+ ]) { Reflect.deleteProperty(options, it); }
33
+ }
34
+
35
+ try {
36
+ options = await transform(preflight(options));
37
+ } catch (err) {
38
+ options.createConnection?.().destroy();
39
+ throw err;
40
+ }
41
+
42
+ const promise = new Promise((resolve, reject) => {
43
+ let client, req;
44
+
45
+ if (options.h2) {
46
+ client = http2.connect(url.origin, options);
47
+ req = client.request(options.headers, options);
48
+ } else {
49
+ const { request } = url.protocol === 'http:' ? http : https;
50
+
51
+ req = request(url, options);
52
+ }
53
+
54
+ snoop(client, req, options, { reject });
55
+
56
+ req.once('response', (res) => postflight(req, res, options, {
57
+ reject, resolve,
58
+ }));
59
+
60
+ dispatch(req, options);
61
+ });
62
+
63
+ try {
64
+ const res = await promise;
65
+
66
+ if (digest && !redirected) {
67
+ res.body = await res.body();
68
+ }
69
+
70
+ return res;
71
+ } catch (err) {
72
+ if (isLikelyH2cPrefaceError(err)) {
73
+ const { retry } = options;
74
+
75
+ options = deepMerge(options, {
76
+ h2: true,
77
+ retry: {
78
+ attempts: ++retry.attempts,
79
+ errorCodes: [
80
+ err.code,
81
+ ...retry.errorCodes,
82
+ ],
83
+ interval: 1,
84
+ },
85
+ });
86
+ }
87
+
88
+ const result = retries(err, options);
89
+
90
+ if (result) {
91
+ return result;
92
+ }
93
+
94
+ if (digest && !redirected && err.body) {
95
+ err.body = await err.body();
96
+ }
97
+
98
+ if (!thenable) {
99
+ throw err;
100
+ } else {
101
+ return err;
102
+ }
103
+ }
104
+ };
package/src/transform.js CHANGED
@@ -1,112 +1,112 @@
1
- import http2 from 'node:http2';
2
- import {
3
- isReadable,
4
- Readable,
5
- } from 'node:stream';
6
- import { buffer } from 'node:stream/consumers';
7
- import { types } from 'node:util';
8
- import { encode } from './codecs.js';
9
- import {
10
- fdToAsyncIterable,
11
- isFormData,
12
- } from './formdata.js';
13
- import {
14
- APPLICATION_FORM_URLENCODED,
15
- APPLICATION_JSON,
16
- APPLICATION_OCTET_STREAM,
17
- } from './mediatypes.js';
18
- import {
19
- isBlobLike,
20
- isReadableStream,
21
- } from './utils.js';
22
-
23
- const {
24
- HTTP2_HEADER_CONTENT_ENCODING,
25
- HTTP2_HEADER_CONTENT_LENGTH,
26
- HTTP2_HEADER_CONTENT_TYPE,
27
- } = http2.constants;
28
-
29
- export const transform = async (options) => {
30
- let { body, headers } = options;
31
-
32
- if (!body) {
33
- return options;
34
- }
35
-
36
- if (!Buffer.isBuffer(body)) {
37
- switch (true) {
38
- case isBlobLike(body): {
39
- headers = {
40
- [HTTP2_HEADER_CONTENT_LENGTH]: body.size,
41
- [HTTP2_HEADER_CONTENT_TYPE]: body.type || APPLICATION_OCTET_STREAM,
42
- };
43
- body = body.stream();
44
- break;
45
- }
46
-
47
- case isFormData(body): {
48
- body = fdToAsyncIterable(body);
49
- headers = { [HTTP2_HEADER_CONTENT_TYPE]: body.contentType };
50
- break;
51
- }
52
-
53
- case types.isAnyArrayBuffer(body): {
54
- body = Buffer.from(body);
55
- break;
56
- }
57
-
58
- case types.isArrayBufferView(body): {
59
- body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
60
- break;
61
- }
62
-
63
- case Object(body) === body && !Reflect.has(body, Symbol.asyncIterator): {
64
- if (body.constructor === URLSearchParams) {
65
- headers = { [HTTP2_HEADER_CONTENT_TYPE]: APPLICATION_FORM_URLENCODED };
66
- body = body.toString();
67
- } else if (!(!Array.isArray(body) && Reflect.has(body, Symbol.iterator))) {
68
- headers = { [HTTP2_HEADER_CONTENT_TYPE]: APPLICATION_JSON };
69
- body = JSON.stringify(body);
70
- }
71
-
72
- break;
73
- }
74
-
75
- default:
76
- break;
77
- }
78
- }
79
-
80
- const encodings = options.headers[HTTP2_HEADER_CONTENT_ENCODING];
81
-
82
- if (Object(body) === body
83
- && (Reflect.has(body, Symbol.asyncIterator) || (!Array.isArray(body) && Reflect.has(body, Symbol.iterator)))) {
84
- body = isReadable(body) ? (isReadableStream(body) ? Readable.fromWeb(body) : body) : Readable.from(body);
85
- body = encodings ? encode(body, encodings, options) : body;
86
- } else if (encodings) {
87
- body = await buffer(encode(Readable.from(body), encodings, options));
88
- }
89
-
90
- if (options.bufferBody && Object(body) === body) {
91
- if (isReadable(body)) {
92
- body = await buffer(body);
93
- } else if (Reflect.has(body, Symbol.asyncIterator)) {
94
- body = await buffer(body);
95
- }
96
- }
97
-
98
- Object.assign(options.headers, {
99
- ...headers,
100
- ...!body[Symbol.asyncIterator] && {
101
- [HTTP2_HEADER_CONTENT_LENGTH]: Buffer.byteLength(body),
102
- },
103
- ...options.headers[HTTP2_HEADER_CONTENT_TYPE] && {
104
- [HTTP2_HEADER_CONTENT_TYPE]: options.headers[HTTP2_HEADER_CONTENT_TYPE],
105
- },
106
- });
107
-
108
- return {
109
- ...options,
110
- body,
111
- };
112
- };
1
+ import http2 from 'node:http2';
2
+ import {
3
+ isReadable,
4
+ Readable,
5
+ } from 'node:stream';
6
+ import { buffer } from 'node:stream/consumers';
7
+ import { types } from 'node:util';
8
+ import { encode } from './codecs.js';
9
+ import {
10
+ fdToAsyncIterable,
11
+ isFormData,
12
+ } from './formdata.js';
13
+ import {
14
+ APPLICATION_FORM_URLENCODED,
15
+ APPLICATION_JSON,
16
+ APPLICATION_OCTET_STREAM,
17
+ } from './mediatypes.js';
18
+ import {
19
+ isBlobLike,
20
+ isReadableStream,
21
+ } from './utils.js';
22
+
23
+ const {
24
+ HTTP2_HEADER_CONTENT_ENCODING,
25
+ HTTP2_HEADER_CONTENT_LENGTH,
26
+ HTTP2_HEADER_CONTENT_TYPE,
27
+ } = http2.constants;
28
+
29
+ export const transform = async (options) => {
30
+ let { body, headers } = options;
31
+
32
+ if (!body) {
33
+ return options;
34
+ }
35
+
36
+ if (!Buffer.isBuffer(body)) {
37
+ switch (true) {
38
+ case isBlobLike(body): {
39
+ headers = {
40
+ [HTTP2_HEADER_CONTENT_LENGTH]: body.size,
41
+ [HTTP2_HEADER_CONTENT_TYPE]: body.type || APPLICATION_OCTET_STREAM,
42
+ };
43
+ body = body.stream();
44
+ break;
45
+ }
46
+
47
+ case isFormData(body): {
48
+ body = fdToAsyncIterable(body);
49
+ headers = { [HTTP2_HEADER_CONTENT_TYPE]: body.contentType };
50
+ break;
51
+ }
52
+
53
+ case types.isAnyArrayBuffer(body): {
54
+ body = Buffer.from(body);
55
+ break;
56
+ }
57
+
58
+ case types.isArrayBufferView(body): {
59
+ body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
60
+ break;
61
+ }
62
+
63
+ case Object(body) === body && !Reflect.has(body, Symbol.asyncIterator): {
64
+ if (body.constructor === URLSearchParams) {
65
+ headers = { [HTTP2_HEADER_CONTENT_TYPE]: APPLICATION_FORM_URLENCODED };
66
+ body = body.toString();
67
+ } else if (!(!Array.isArray(body) && Reflect.has(body, Symbol.iterator))) {
68
+ headers = { [HTTP2_HEADER_CONTENT_TYPE]: APPLICATION_JSON };
69
+ body = JSON.stringify(body);
70
+ }
71
+
72
+ break;
73
+ }
74
+
75
+ default:
76
+ break;
77
+ }
78
+ }
79
+
80
+ const encodings = options.headers[HTTP2_HEADER_CONTENT_ENCODING];
81
+
82
+ if (Object(body) === body
83
+ && (Reflect.has(body, Symbol.asyncIterator) || (!Array.isArray(body) && Reflect.has(body, Symbol.iterator)))) {
84
+ body = isReadable(body) ? (isReadableStream(body) ? Readable.fromWeb(body) : body) : Readable.from(body);
85
+ body = encodings ? encode(body, encodings, options) : body;
86
+ } else if (encodings) {
87
+ body = await buffer(encode(Readable.from(body), encodings, options));
88
+ }
89
+
90
+ if (options.bufferBody && Object(body) === body) {
91
+ if (isReadable(body)) {
92
+ body = await buffer(body);
93
+ } else if (Reflect.has(body, Symbol.asyncIterator)) {
94
+ body = await buffer(body);
95
+ }
96
+ }
97
+
98
+ Object.assign(options.headers, {
99
+ ...headers,
100
+ ...!body[Symbol.asyncIterator] && {
101
+ [HTTP2_HEADER_CONTENT_LENGTH]: Buffer.byteLength(body),
102
+ },
103
+ ...options.headers[HTTP2_HEADER_CONTENT_TYPE] && {
104
+ [HTTP2_HEADER_CONTENT_TYPE]: options.headers[HTTP2_HEADER_CONTENT_TYPE],
105
+ },
106
+ });
107
+
108
+ return {
109
+ ...options,
110
+ body,
111
+ };
112
+ };