rekwest 6.2.1 → 7.0.1

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.
Files changed (44) hide show
  1. package/README.md +11 -10
  2. package/dist/codecs.cjs +55 -0
  3. package/dist/{config.js → config.cjs} +15 -19
  4. package/dist/{cookies.js → cookies.cjs} +2 -2
  5. package/dist/{formdata.js → formdata.cjs} +8 -11
  6. package/dist/{index.js → index.cjs} +27 -15
  7. package/dist/{mixin.js → mixin.cjs} +22 -17
  8. package/dist/postflight.cjs +60 -0
  9. package/dist/{preflight.js → preflight.cjs} +13 -23
  10. package/dist/redirects.cjs +63 -0
  11. package/dist/retries.cjs +57 -0
  12. package/dist/{transfer.js → transfer.cjs} +13 -40
  13. package/dist/transform.cjs +105 -0
  14. package/dist/{utils.js → utils.cjs} +57 -80
  15. package/dist/{validation.js → validation.cjs} +1 -1
  16. package/package.json +14 -13
  17. package/src/{ackn.mjs → ackn.js} +33 -33
  18. package/src/codecs.js +55 -0
  19. package/src/{config.mjs → config.js} +88 -93
  20. package/src/{constants.mjs → constants.js} +29 -29
  21. package/src/{cookies.mjs → cookies.js} +100 -100
  22. package/src/{formdata.mjs → formdata.js} +8 -14
  23. package/src/{index.mjs → index.js} +22 -22
  24. package/src/{mediatypes.mjs → mediatypes.js} +6 -6
  25. package/src/{mixin.mjs → mixin.js} +25 -26
  26. package/src/postflight.js +56 -0
  27. package/src/{preflight.mjs → preflight.js} +84 -91
  28. package/src/redirects.js +79 -0
  29. package/src/retries.js +51 -0
  30. package/src/transfer.js +92 -0
  31. package/src/transform.js +109 -0
  32. package/src/utils.js +187 -0
  33. package/src/{validation.mjs → validation.js} +33 -33
  34. package/dist/postflight.js +0 -117
  35. package/dist/transform.js +0 -79
  36. package/src/postflight.mjs +0 -136
  37. package/src/transfer.mjs +0 -121
  38. package/src/transform.mjs +0 -82
  39. package/src/utils.mjs +0 -205
  40. /package/dist/{ackn.js → ackn.cjs} +0 -0
  41. /package/dist/{constants.js → constants.cjs} +0 -0
  42. /package/dist/{errors.js → errors.cjs} +0 -0
  43. /package/dist/{mediatypes.js → mediatypes.cjs} +0 -0
  44. /package/src/{errors.mjs → errors.js} +0 -0
@@ -0,0 +1,56 @@
1
+ import http2 from 'node:http2';
2
+ import { Cookies } from './cookies.js';
3
+ import { mixin } from './mixin.js';
4
+ import { redirects } from './redirects.js';
5
+ import { augment } from './utils.js';
6
+
7
+ const {
8
+ HTTP2_HEADER_SET_COOKIE,
9
+ HTTP_STATUS_BAD_REQUEST,
10
+ } = http2.constants;
11
+
12
+ export const postflight = (req, res, options, { reject, resolve }) => {
13
+ const { cookies, h2, url } = options;
14
+ let headers;
15
+
16
+ if (h2) {
17
+ headers = res;
18
+ res = req;
19
+ } else {
20
+ res.once('error', reject);
21
+ }
22
+
23
+ augment(res, headers, options);
24
+
25
+ if (cookies !== false && res.headers[HTTP2_HEADER_SET_COOKIE]) {
26
+ if (Cookies.jar.has(url.origin)) {
27
+ const cookie = new Cookies(res.headers[HTTP2_HEADER_SET_COOKIE], options);
28
+
29
+ Cookies.jar.get(url.origin).forEach((val, key) => {
30
+ if (!cookie.has(key)) {
31
+ cookie.set(key, val);
32
+ }
33
+ });
34
+ Cookies.jar.set(url.origin, cookie);
35
+ } else {
36
+ Cookies.jar.set(url.origin, new Cookies(res.headers[HTTP2_HEADER_SET_COOKIE], options));
37
+ }
38
+ }
39
+
40
+ Reflect.defineProperty(res, 'cookies', {
41
+ enumerable: true,
42
+ value: cookies !== false && Cookies.jar.has(url.origin) ? Cookies.jar.get(url.origin) : void 0,
43
+ });
44
+
45
+ const willRedirect = redirects(res, options);
46
+
47
+ if (Object(willRedirect) === willRedirect) {
48
+ return willRedirect.then(resolve, reject);
49
+ }
50
+
51
+ if (res.statusCode >= HTTP_STATUS_BAD_REQUEST) {
52
+ return reject(mixin(res, options));
53
+ }
54
+
55
+ resolve(mixin(res, options));
56
+ };
@@ -1,91 +1,84 @@
1
- import http2 from 'node:http2';
2
- import { isZstdSupported } from './config.mjs';
3
- import { requestCredentials } from './constants.mjs';
4
- import { Cookies } from './cookies.mjs';
5
-
6
- const {
7
- HTTP2_HEADER_ACCEPT_ENCODING,
8
- HTTP2_HEADER_AUTHORITY,
9
- HTTP2_HEADER_AUTHORIZATION,
10
- HTTP2_HEADER_COOKIE,
11
- HTTP2_HEADER_METHOD,
12
- HTTP2_HEADER_PATH,
13
- HTTP2_HEADER_SCHEME,
14
- HTTP2_METHOD_GET,
15
- HTTP2_METHOD_HEAD,
16
- } = http2.constants;
17
-
18
- export const preflight = (options) => {
19
- const { cookies, credentials, h2, headers, method, url } = options;
20
-
21
- if (h2) {
22
- options.endStream = [
23
- HTTP2_METHOD_GET,
24
- HTTP2_METHOD_HEAD,
25
- ].includes(method);
26
- }
27
-
28
- if (cookies !== false && credentials !== requestCredentials.omit) {
29
- let cookie = Cookies.jar.has(url.origin);
30
-
31
- if (cookies === Object(cookies) && [
32
- requestCredentials.include,
33
- requestCredentials.sameOrigin,
34
- ].includes(credentials)) {
35
- if (cookie) {
36
- cookie = new Cookies(cookies, options);
37
-
38
- Cookies.jar.get(url.origin).forEach((val, key) => {
39
- if (!cookie.has(key)) {
40
- cookie.set(key, val);
41
- }
42
- });
43
- Cookies.jar.set(url.origin, cookie);
44
- } else {
45
- cookie = new Cookies(cookies, options);
46
- Cookies.jar.set(url.origin, cookie);
47
- }
48
- } else {
49
- cookie &&= Cookies.jar.get(url.origin);
50
- }
51
-
52
- options.headers = {
53
- ...cookie && { [HTTP2_HEADER_COOKIE]: cookie },
54
- ...headers,
55
- };
56
- }
57
-
58
- if (credentials === requestCredentials.omit) {
59
- options.cookies = false;
60
- for (const it of Object.keys(options.headers ?? {})
61
- .filter((val) => new RegExp(`^(${
62
- HTTP2_HEADER_AUTHORIZATION }|${ HTTP2_HEADER_COOKIE
63
- })$`, 'i').test(val))) { Reflect.deleteProperty(options.headers, it); }
64
-
65
- url.password = url.username = '';
66
- }
67
-
68
- options.headers = {
69
- ...Object.entries(options.headers ?? {})
70
- .reduce((acc, [key, val]) => {
71
- acc[key.toLowerCase()] = val;
72
-
73
- if (acc[HTTP2_HEADER_ACCEPT_ENCODING]?.match(/\bzstd\b/i) && !isZstdSupported) {
74
- acc[HTTP2_HEADER_ACCEPT_ENCODING] = val.replace(/\s?zstd,?/gi, '').trim();
75
- if (!acc[HTTP2_HEADER_ACCEPT_ENCODING]) {
76
- Reflect.deleteProperty(acc, HTTP2_HEADER_ACCEPT_ENCODING);
77
- }
78
- }
79
-
80
- return acc;
81
- }, {}),
82
- ...h2 && {
83
- [HTTP2_HEADER_AUTHORITY]: url.host,
84
- [HTTP2_HEADER_METHOD]: method,
85
- [HTTP2_HEADER_PATH]: `${ url.pathname }${ url.search }`,
86
- [HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, ''),
87
- },
88
- };
89
-
90
- return options;
91
- };
1
+ import http2 from 'node:http2';
2
+ import { requestCredentials } from './constants.js';
3
+ import { Cookies } from './cookies.js';
4
+ import { stripHeaders } from './utils.js';
5
+
6
+ const {
7
+ HTTP2_HEADER_AUTHORITY,
8
+ HTTP2_HEADER_AUTHORIZATION,
9
+ HTTP2_HEADER_COOKIE,
10
+ HTTP2_HEADER_METHOD,
11
+ HTTP2_HEADER_PATH,
12
+ HTTP2_HEADER_SCHEME,
13
+ HTTP2_METHOD_GET,
14
+ HTTP2_METHOD_HEAD,
15
+ } = http2.constants;
16
+
17
+ export const preflight = (options) => {
18
+ let { cookies, credentials, h2, headers, method, url } = options;
19
+
20
+ if (h2) {
21
+ options.endStream = [
22
+ HTTP2_METHOD_GET,
23
+ HTTP2_METHOD_HEAD,
24
+ ].includes(method);
25
+ } else {
26
+ headers = stripHeaders(headers, [
27
+ HTTP2_HEADER_AUTHORITY,
28
+ HTTP2_HEADER_METHOD,
29
+ HTTP2_HEADER_PATH,
30
+ HTTP2_HEADER_SCHEME,
31
+ ]);
32
+ }
33
+
34
+ if (credentials === requestCredentials.omit) {
35
+ headers = stripHeaders(headers, [
36
+ HTTP2_HEADER_AUTHORIZATION,
37
+ HTTP2_HEADER_COOKIE,
38
+ ]);
39
+ options.cookies = false;
40
+ url.password = url.username = '';
41
+ }
42
+
43
+ if (cookies !== false && credentials !== requestCredentials.omit) {
44
+ let cookie = Cookies.jar.has(url.origin);
45
+
46
+ if (Object(cookies) === cookies && [
47
+ requestCredentials.include,
48
+ requestCredentials.sameOrigin,
49
+ ].includes(credentials)) {
50
+ if (cookie) {
51
+ cookie = new Cookies(cookies, options);
52
+
53
+ Cookies.jar.get(url.origin).forEach((val, key) => {
54
+ if (!cookie.has(key)) {
55
+ cookie.set(key, val);
56
+ }
57
+ });
58
+ Cookies.jar.set(url.origin, cookie);
59
+ } else {
60
+ cookie = new Cookies(cookies, options);
61
+ Cookies.jar.set(url.origin, cookie);
62
+ }
63
+ } else {
64
+ cookie &&= Cookies.jar.get(url.origin);
65
+ }
66
+
67
+ options.headers = {
68
+ ...cookie && { [HTTP2_HEADER_COOKIE]: cookie },
69
+ ...headers,
70
+ };
71
+ }
72
+
73
+ options.headers = {
74
+ ...headers,
75
+ ...h2 && {
76
+ [HTTP2_HEADER_AUTHORITY]: url.host,
77
+ [HTTP2_HEADER_METHOD]: method,
78
+ [HTTP2_HEADER_PATH]: `${ url.pathname }${ url.search }`,
79
+ [HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, ''),
80
+ },
81
+ };
82
+
83
+ return options;
84
+ };
@@ -0,0 +1,79 @@
1
+ import http2 from 'node:http2';
2
+ import { isReadable } from 'node:stream';
3
+ import {
4
+ requestCredentials,
5
+ requestRedirect,
6
+ requestRedirectCodes,
7
+ } from './constants.js';
8
+ import { RequestError } from './errors.js';
9
+ import rekwest from './index.js';
10
+ import {
11
+ isPipeStream,
12
+ sameOrigin,
13
+ } from './utils.js';
14
+
15
+ const {
16
+ HTTP2_HEADER_LOCATION,
17
+ HTTP2_METHOD_GET,
18
+ HTTP2_METHOD_HEAD,
19
+ HTTP2_METHOD_POST,
20
+ HTTP_STATUS_FOUND,
21
+ HTTP_STATUS_MOVED_PERMANENTLY,
22
+ HTTP_STATUS_PERMANENT_REDIRECT,
23
+ HTTP_STATUS_SEE_OTHER,
24
+ HTTP_STATUS_TEMPORARY_REDIRECT,
25
+ } = http2.constants;
26
+
27
+ export const redirects = (res, options) => {
28
+ const { credentials, follow, redirect, url } = options;
29
+
30
+ if (follow && /3\d{2}/.test(res.statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
31
+ if (!requestRedirectCodes.includes(res.statusCode)) {
32
+ return res.emit('error', new RangeError(`Invalid status code: ${ res.statusCode }`));
33
+ }
34
+
35
+ if (redirect === requestRedirect.error) {
36
+ return res.emit('error', new RequestError(`Unexpected redirect, redirect mode is set to '${ redirect }'.`));
37
+ }
38
+
39
+ if (redirect === requestRedirect.follow) {
40
+ const location = new URL(res.headers[HTTP2_HEADER_LOCATION], url);
41
+
42
+ if (!/^https?:/i.test(location.protocol)) {
43
+ return res.emit('error', new RequestError('URL scheme must be "http" or "https".'));
44
+ }
45
+
46
+ if (!sameOrigin(location, url)) {
47
+ if (credentials !== requestCredentials.include) {
48
+ options.credentials = requestCredentials.omit;
49
+ }
50
+
51
+ options.h2 = false;
52
+ }
53
+
54
+ if ([
55
+ HTTP_STATUS_PERMANENT_REDIRECT,
56
+ HTTP_STATUS_TEMPORARY_REDIRECT,
57
+ ].includes(res.statusCode) && isPipeStream(options.body) && !isReadable(options.body)) {
58
+ return res.emit('error', new RequestError(`Unable to ${ redirect } redirect with streamable body.`));
59
+ }
60
+
61
+ if (([
62
+ HTTP_STATUS_MOVED_PERMANENTLY,
63
+ HTTP_STATUS_FOUND,
64
+ ].includes(res.statusCode) && options.method === HTTP2_METHOD_POST)
65
+ || (res.statusCode === HTTP_STATUS_SEE_OTHER && ![
66
+ HTTP2_METHOD_GET,
67
+ HTTP2_METHOD_HEAD,
68
+ ].includes(options.method))) {
69
+ options.body = null;
70
+ options.method = HTTP2_METHOD_GET;
71
+ }
72
+
73
+ options.follow--;
74
+ options.redirected = true;
75
+
76
+ return rekwest(location, options);
77
+ }
78
+ }
79
+ };
package/src/retries.js ADDED
@@ -0,0 +1,51 @@
1
+ import http2 from 'node:http2';
2
+ import { isReadable } from 'node:stream';
3
+ import { setTimeout as setTimeoutPromise } from 'node:timers/promises';
4
+ import { RequestError } from './errors.js';
5
+ import rekwest from './index.js';
6
+ import { isPipeStream } from './utils.js';
7
+
8
+ const {
9
+ HTTP2_HEADER_RETRY_AFTER,
10
+ HTTP2_METHOD_GET,
11
+ HTTP2_METHOD_HEAD,
12
+ } = http2.constants;
13
+
14
+ export const retries = (ex, options) => {
15
+ const { body, maxRetryAfter, method, retry, url } = options;
16
+
17
+ if (retry?.attempts > 0) {
18
+ if (![
19
+ HTTP2_METHOD_GET,
20
+ HTTP2_METHOD_HEAD,
21
+ ].includes(method) && isPipeStream(body) && !isReadable(body)) {
22
+ throw new RequestError('Request stream already read.', { cause: ex });
23
+ }
24
+
25
+ if (retry.errorCodes?.includes(ex.code) || retry.statusCodes?.includes(ex.statusCode)) {
26
+ let { interval } = retry;
27
+
28
+ if (retry.retryAfter && ex.headers?.[HTTP2_HEADER_RETRY_AFTER]) {
29
+ interval = ex.headers[HTTP2_HEADER_RETRY_AFTER];
30
+ interval = Math.abs(Number(interval) * 1e3 || new Date(interval) - Date.now()) || 0;
31
+ if (interval > maxRetryAfter) {
32
+ throw new RequestError(
33
+ `Maximum '${ HTTP2_HEADER_RETRY_AFTER }' limit exceeded: ${ interval } ms.`,
34
+ { cause: ex },
35
+ );
36
+ }
37
+ } else {
38
+ interval = new Function('interval', `return Math.ceil(${ retry.backoffStrategy });`)(interval);
39
+ }
40
+
41
+ if (interval < 0) {
42
+ interval = 0;
43
+ }
44
+
45
+ retry.attempts--;
46
+ retry.interval = interval;
47
+
48
+ return setTimeoutPromise(interval).then(() => rekwest(url, options));
49
+ }
50
+ }
51
+ };
@@ -0,0 +1,92 @@
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
+ dispatch,
12
+ snoop,
13
+ } from './utils.js';
14
+
15
+ export const transfer = async (options) => {
16
+ const { digest, redirected, thenable, url } = options;
17
+
18
+ if (options.follow === 0) {
19
+ throw new RequestError(`Maximum redirect reached at: ${ url.href }`);
20
+ }
21
+
22
+ if (url.protocol === 'https:') {
23
+ options = !options.h2 ? await ackn(options) : {
24
+ ...options, createConnection: null, protocol: url.protocol,
25
+ };
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 (ex) {
38
+ options.createConnection?.().destroy();
39
+ throw ex;
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);
55
+
56
+ req.once('aborted', reject);
57
+ req.once('error', reject);
58
+ req.once('frameError', reject);
59
+ req.once('goaway', reject);
60
+ req.once('response', (res) => postflight(req, res, options, {
61
+ reject, resolve,
62
+ }));
63
+
64
+ dispatch(req, options);
65
+ });
66
+
67
+ try {
68
+ const res = await promise;
69
+
70
+ if (digest && !redirected) {
71
+ res.body = await res.body();
72
+ }
73
+
74
+ return res;
75
+ } catch (ex) {
76
+ const willRetry = retries(ex, options);
77
+
78
+ if (willRetry) {
79
+ return willRetry;
80
+ }
81
+
82
+ if (digest && !redirected && ex.body) {
83
+ ex.body = await ex.body();
84
+ }
85
+
86
+ if (!thenable) {
87
+ throw ex;
88
+ } else {
89
+ return ex;
90
+ }
91
+ }
92
+ };
@@ -0,0 +1,109 @@
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 { FormData } from './formdata.js';
10
+ import {
11
+ APPLICATION_FORM_URLENCODED,
12
+ APPLICATION_JSON,
13
+ APPLICATION_OCTET_STREAM,
14
+ } from './mediatypes.js';
15
+ import {
16
+ isFileLike,
17
+ isReadableStream,
18
+ } from './utils.js';
19
+
20
+ const {
21
+ HTTP2_HEADER_CONTENT_ENCODING,
22
+ HTTP2_HEADER_CONTENT_LENGTH,
23
+ HTTP2_HEADER_CONTENT_TYPE,
24
+ } = http2.constants;
25
+
26
+ export const transform = async (options) => {
27
+ let { body, headers } = options;
28
+
29
+ if (!body) {
30
+ return options;
31
+ }
32
+
33
+ if (!Buffer.isBuffer(body)) {
34
+ switch (true) {
35
+ case isFileLike(body): {
36
+ headers = {
37
+ [HTTP2_HEADER_CONTENT_LENGTH]: body.size,
38
+ [HTTP2_HEADER_CONTENT_TYPE]: body.type || APPLICATION_OCTET_STREAM,
39
+ };
40
+ body = body.stream();
41
+ break;
42
+ }
43
+
44
+ case FormData.alike(body): {
45
+ body = FormData.actuate(body);
46
+ headers = { [HTTP2_HEADER_CONTENT_TYPE]: body.contentType };
47
+ break;
48
+ }
49
+
50
+ case types.isAnyArrayBuffer(body): {
51
+ body = Buffer.from(body);
52
+ break;
53
+ }
54
+
55
+ case types.isArrayBufferView(body): {
56
+ body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
57
+ break;
58
+ }
59
+
60
+ case Object(body) === body && !Reflect.has(body, Symbol.asyncIterator): {
61
+ if (body.constructor === URLSearchParams) {
62
+ headers = { [HTTP2_HEADER_CONTENT_TYPE]: APPLICATION_FORM_URLENCODED };
63
+ body = body.toString();
64
+ } else if (!(!Array.isArray(body) && Reflect.has(body, Symbol.iterator))) {
65
+ headers = { [HTTP2_HEADER_CONTENT_TYPE]: APPLICATION_JSON };
66
+ body = JSON.stringify(body);
67
+ }
68
+
69
+ break;
70
+ }
71
+
72
+ default:
73
+ break;
74
+ }
75
+ }
76
+
77
+ const encodings = options.headers[HTTP2_HEADER_CONTENT_ENCODING];
78
+
79
+ if (Object(body) === body
80
+ && (Reflect.has(body, Symbol.asyncIterator) || (!Array.isArray(body) && Reflect.has(body, Symbol.iterator)))) {
81
+ body = isReadable(body) ? (isReadableStream(body) ? Readable.fromWeb(body) : body) : Readable.from(body);
82
+ body = encodings ? encode(body, encodings, options) : body;
83
+ } else if (encodings) {
84
+ body = await buffer(encode(Readable.from(body), encodings, options));
85
+ }
86
+
87
+ if (options.bufferBody && Object(body) === body) {
88
+ if (isReadable(body)) {
89
+ body = await buffer(body);
90
+ } else if (Reflect.has(body, Symbol.asyncIterator)) {
91
+ body = await buffer(body);
92
+ }
93
+ }
94
+
95
+ Object.assign(options.headers, {
96
+ ...headers,
97
+ ...!body[Symbol.asyncIterator] && {
98
+ [HTTP2_HEADER_CONTENT_LENGTH]: Buffer.byteLength(body),
99
+ },
100
+ ...options.headers[HTTP2_HEADER_CONTENT_TYPE] && {
101
+ [HTTP2_HEADER_CONTENT_TYPE]: options.headers[HTTP2_HEADER_CONTENT_TYPE],
102
+ },
103
+ });
104
+
105
+ return {
106
+ ...options,
107
+ body,
108
+ };
109
+ };