rekwest 5.0.1 → 5.0.3

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/dist/transfer.js CHANGED
@@ -35,7 +35,9 @@ const transfer = async (options, overact) => {
35
35
  protocol: url.protocol
36
36
  };
37
37
  } else if (Reflect.has(options, 'alpnProtocol')) {
38
- ['alpnProtocol', 'createConnection', 'h2', 'protocol'].forEach(it => Reflect.deleteProperty(options, it));
38
+ for (const it of ['alpnProtocol', 'createConnection', 'h2', 'protocol']) {
39
+ Reflect.deleteProperty(options, it);
40
+ }
39
41
  }
40
42
  try {
41
43
  options = await (0, _transform.transform)((0, _preflight.preflight)(options));
package/dist/utils.js CHANGED
@@ -126,8 +126,7 @@ const isReadableStream = instance => {
126
126
  return ReadableStream.name === instance?.[Symbol.toStringTag];
127
127
  };
128
128
  exports.isReadableStream = isReadableStream;
129
- const maxRetryAfter = Symbol('maxRetryAfter');
130
- exports.maxRetryAfter = maxRetryAfter;
129
+ const maxRetryAfter = exports.maxRetryAfter = Symbol('maxRetryAfter');
131
130
  const maxRetryAfterError = (interval, options) => new _errors.RequestError(`Maximum '${HTTP2_HEADER_RETRY_AFTER}' limit exceeded: ${interval} ms.`, options);
132
131
  exports.maxRetryAfterError = maxRetryAfterError;
133
132
  const merge = (target, ...rest) => {
package/package.json CHANGED
@@ -1,73 +1,74 @@
1
- {
2
- "author": {
3
- "name": "Yehor Sergeenko",
4
- "email": "yehor.sergeenko@gmail.com",
5
- "url": "https://github.com/bricss"
6
- },
7
- "bugs": {
8
- "url": "https://github.com/bricss/rekwest/issues"
9
- },
10
- "devDependencies": {
11
- "@babel/cli": "^7.22.15",
12
- "@babel/core": "^7.22.20",
13
- "@babel/eslint-parser": "^7.22.15",
14
- "@babel/preset-env": "^7.22.20",
15
- "c8": "^8.0.1",
16
- "eslint": "^8.50.0",
17
- "eslint-config-ultra-refined": "^2.18.0",
18
- "mocha": "^10.2.0"
19
- },
20
- "description": "The robust request library that humanity deserves 🌐",
21
- "engines": {
22
- "node": ">=18.13.0"
23
- },
24
- "exports": {
25
- "import": "./src/index.mjs",
26
- "require": "./dist/index.js"
27
- },
28
- "files": [
29
- "dist",
30
- "src"
31
- ],
32
- "homepage": "https://github.com/bricss/rekwest#readme",
33
- "keywords": [
34
- "alpn",
35
- "backoff",
36
- "brotli",
37
- "cookie",
38
- "deflate",
39
- "fetch",
40
- "formdata",
41
- "gzip",
42
- "http",
43
- "https",
44
- "h2",
45
- "h2c",
46
- "http2",
47
- "multipart",
48
- "request",
49
- "redirect",
50
- "retry",
51
- "retry-after",
52
- "stream",
53
- "upload"
54
- ],
55
- "license": "MIT",
56
- "name": "rekwest",
57
- "repository": {
58
- "type": "git",
59
- "url": "https://github.com/bricss/rekwest.git"
60
- },
61
- "scripts": {
62
- "build": "rm -rf dist && npx babel src -d dist",
63
- "cert:gen": "openssl req -days 365 -keyout localhost.key -newkey ec -nodes -pkeyopt ec_paramgen_curve:prime256v1 -subj //SKIP=1/CN=localhost -out localhost.cert -x509",
64
- "cert:ken": "openssl x509 -in localhost.cert -noout -text",
65
- "lint": "eslint . --ext .cjs,.js,.mjs",
66
- "prepack": "npm run build && sh pony.sh",
67
- "pretest": "rm -rf coverage && npm run cert:gen",
68
- "test": "mocha",
69
- "test:bail": "mocha --bail",
70
- "test:cover": "c8 --include=src --reporter=lcov --reporter=text npm test"
71
- },
72
- "version": "5.0.1"
73
- }
1
+ {
2
+ "author": {
3
+ "name": "Yehor Sergeenko",
4
+ "email": "yehor.sergeenko@gmail.com",
5
+ "url": "https://github.com/bricss"
6
+ },
7
+ "bugs": {
8
+ "url": "https://github.com/bricss/rekwest/issues"
9
+ },
10
+ "devDependencies": {
11
+ "@babel/cli": "^7.23.0",
12
+ "@babel/core": "^7.23.3",
13
+ "@babel/eslint-parser": "^7.23.3",
14
+ "@babel/preset-env": "^7.23.3",
15
+ "@stylistic/eslint-plugin-js": "^1.1.0",
16
+ "c8": "^8.0.1",
17
+ "eslint": "^8.53.0",
18
+ "eslint-config-ultra-refined": "^2.19.1",
19
+ "mocha": "^10.2.0"
20
+ },
21
+ "description": "The robust request library that humanity deserves 🌐",
22
+ "engines": {
23
+ "node": ">=18.13.0"
24
+ },
25
+ "exports": {
26
+ "import": "./src/index.mjs",
27
+ "require": "./dist/index.js"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "src"
32
+ ],
33
+ "homepage": "https://github.com/bricss/rekwest#readme",
34
+ "keywords": [
35
+ "alpn",
36
+ "backoff",
37
+ "brotli",
38
+ "cookie",
39
+ "deflate",
40
+ "fetch",
41
+ "formdata",
42
+ "gzip",
43
+ "http",
44
+ "https",
45
+ "h2",
46
+ "h2c",
47
+ "http2",
48
+ "multipart",
49
+ "request",
50
+ "redirect",
51
+ "retry",
52
+ "retry-after",
53
+ "stream",
54
+ "upload"
55
+ ],
56
+ "license": "MIT",
57
+ "name": "rekwest",
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "git+https://github.com/bricss/rekwest.git"
61
+ },
62
+ "scripts": {
63
+ "build": "rm -rf dist && npx babel src -d dist",
64
+ "cert:gen": "openssl req -days 365 -keyout localhost.key -newkey ec -nodes -pkeyopt ec_paramgen_curve:prime256v1 -subj //SKIP=1/CN=localhost -out localhost.cert -x509",
65
+ "cert:ken": "openssl x509 -in localhost.cert -noout -text",
66
+ "lint": "eslint . --ext .cjs,.js,.mjs",
67
+ "prepack": "npm run build && sh pony.sh",
68
+ "pretest": "rm -rf coverage && npm run cert:gen",
69
+ "test": "mocha",
70
+ "test:bail": "mocha --bail",
71
+ "test:cover": "c8 --include=src --reporter=lcov --reporter=text npm test"
72
+ },
73
+ "version": "5.0.3"
74
+ }
package/src/cookies.mjs CHANGED
@@ -1,92 +1,97 @@
1
- import {
2
- brandCheck,
3
- toCamelCase,
4
- } from './utils.mjs';
5
-
6
- export class Cookies extends URLSearchParams {
7
-
8
- static #finalizers = new Set();
9
- static jar = new Map();
10
-
11
- static #register(target, value) {
12
- const finalizer = new FinalizationRegistry((heldValue) => {
13
- clearTimeout(heldValue);
14
- this.#finalizers.delete(finalizer);
15
- });
16
-
17
- finalizer.register(target, value);
18
- this.#finalizers.add(finalizer);
19
- }
20
-
21
- #chronometry = new Map();
22
-
23
- get [Symbol.toStringTag]() {
24
- return this.constructor.name;
25
- }
26
-
27
- constructor(input, { cookiesTTL } = { cookiesTTL: false }) {
28
- if (Array.isArray(input) && input.every((it) => !Array.isArray(it))) {
29
- input = input.map((it) => {
30
- if (!cookiesTTL) {
31
- return [it.split(';').at(0).trim()];
32
- }
33
-
34
- const [cookie, ...attrs] = it.split(';').map((it) => it.trim());
35
- const ttl = attrs.reduce((acc, val) => {
36
- if (/(?:Expires|Max-Age)=/i.test(val)) {
37
- const [key, value] = val.toLowerCase().split('=');
38
-
39
- acc[toCamelCase(key)] = !Number.isNaN(Number(value)) ? value * 1e3 : Date.parse(value) - Date.now();
40
- }
41
-
42
- return acc;
43
- }, {});
44
-
45
- return [
46
- cookie.replace(/\u0022/g, ''),
47
- Object.keys(ttl).length ? ttl : null,
48
- ];
49
- });
50
- }
51
-
52
- super(Array.isArray(input) ? input.map((it) => it.at(0)).join('&') : input);
53
-
54
- if (Array.isArray(input) && cookiesTTL) {
55
- input.filter((it) => it.at(1)).forEach(([cookie, ttl]) => {
56
- cookie = cookie.split('=').at(0);
57
- if (this.#chronometry.has(cookie)) {
58
- clearTimeout(this.#chronometry.get(cookie));
59
- this.#chronometry.delete(cookie);
60
- }
61
-
62
- const { expires, maxAge } = ttl;
63
-
64
- [
65
- maxAge,
66
- expires,
67
- ].filter((it) => Number.isInteger(it)).some((ms) => {
68
- const ref = new WeakRef(this);
69
- const tid = setTimeout(() => {
70
- const ctx = ref.deref();
71
-
72
- if (ctx) {
73
- ctx.#chronometry.delete(cookie);
74
- ctx.delete(cookie);
75
- }
76
- }, Math.max(ms, 0));
77
-
78
- this.constructor.#register(this, tid);
79
-
80
- return this.#chronometry.set(cookie, tid);
81
- });
82
- });
83
- }
84
- }
85
-
86
- toString() {
87
- brandCheck(this, Cookies);
88
-
89
- return super.toString().split('&').join('; ').trim();
90
- }
91
-
92
- }
1
+ import {
2
+ brandCheck,
3
+ toCamelCase,
4
+ } from './utils.mjs';
5
+
6
+ export class Cookies extends URLSearchParams {
7
+
8
+ static #finalizers = new Set();
9
+ static jar = new Map();
10
+
11
+ static #register(target, value) {
12
+ const finalizer = new FinalizationRegistry((heldValue) => {
13
+ clearTimeout(heldValue);
14
+ this.#finalizers.delete(finalizer);
15
+ });
16
+
17
+ finalizer.register(target, value);
18
+ this.#finalizers.add(finalizer);
19
+ }
20
+
21
+ #chronometry = new Map();
22
+
23
+ get [Symbol.toStringTag]() {
24
+ return this.constructor.name;
25
+ }
26
+
27
+ constructor(input, { cookiesTTL } = { cookiesTTL: false }) {
28
+ if (Array.isArray(input) && input.every((it) => !Array.isArray(it))) {
29
+ input = input.map((it) => {
30
+ if (!cookiesTTL) {
31
+ return [it.split(';').at(0).trim()];
32
+ }
33
+
34
+ const [cookie, ...attrs] = it.split(';').map((it) => it.trim());
35
+ const ttl = {};
36
+
37
+ for (const val of attrs) {
38
+ if (/(?:Expires|Max-Age)=/i.test(val)) {
39
+ const [key, value] = val.toLowerCase().split('=');
40
+
41
+ ttl[toCamelCase(key)] = !Number.isNaN(Number(value)) ? value * 1e3 : Date.parse(value) - Date.now();
42
+ }
43
+ }
44
+
45
+ return [
46
+ cookie.replace(/\u0022/g, ''),
47
+ Object.keys(ttl).length ? ttl : null,
48
+ ];
49
+ });
50
+ }
51
+
52
+ super(Array.isArray(input) ? input.map((it) => it.at(0)).join('&') : input);
53
+
54
+ if (Array.isArray(input) && cookiesTTL) {
55
+ for (const [cookie, ttl] of input.filter((it) => it.at(1))) {
56
+ const key = cookie.split('=').at(0);
57
+
58
+ if (this.#chronometry.has(key)) {
59
+ clearTimeout(this.#chronometry.get(key));
60
+ this.#chronometry.delete(key);
61
+ }
62
+
63
+ const { expires, maxAge } = ttl;
64
+
65
+ for (const ms of [
66
+ maxAge,
67
+ expires,
68
+ ]) {
69
+ if (!Number.isInteger(ms)) {
70
+ continue;
71
+ }
72
+
73
+ const ref = new WeakRef(this);
74
+ const tid = setTimeout(() => {
75
+ const ctx = ref.deref();
76
+
77
+ if (ctx) {
78
+ ctx.#chronometry.delete(key);
79
+ ctx.delete(key);
80
+ }
81
+ }, Math.max(ms, 0));
82
+
83
+ this.constructor.#register(this, tid);
84
+ this.#chronometry.set(key, tid);
85
+ break;
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ toString() {
92
+ brandCheck(this, Cookies);
93
+
94
+ return super.toString().split('&').join('; ').trim();
95
+ }
96
+
97
+ }
package/src/formdata.mjs CHANGED
@@ -116,7 +116,7 @@ export class FormData {
116
116
  input = Object.entries(input);
117
117
  }
118
118
 
119
- input.forEach(([key, value]) => this.append(key, value));
119
+ for (const [key, value] of input) { this.append(key, value); }
120
120
  }
121
121
  }
122
122
 
@@ -1,134 +1,136 @@
1
- import http2 from 'node:http2';
2
- import { setTimeout as setTimeoutPromise } from 'node:timers/promises';
3
- import {
4
- requestCredentials,
5
- requestRedirect,
6
- requestRedirectCodes,
7
- } from './constants.mjs';
8
- import { Cookies } from './cookies.mjs';
9
- import { RequestError } from './errors.mjs';
10
- import rekwest from './index.mjs';
11
- import { mixin } from './mixin.mjs';
12
- import {
13
- admix,
14
- maxRetryAfterError,
15
- sameOrigin,
16
- } from './utils.mjs';
17
-
18
- const {
19
- HTTP2_HEADER_LOCATION,
20
- HTTP2_HEADER_RETRY_AFTER,
21
- HTTP2_HEADER_SET_COOKIE,
22
- HTTP2_METHOD_GET,
23
- HTTP2_METHOD_HEAD,
24
- HTTP2_METHOD_POST,
25
- HTTP_STATUS_BAD_REQUEST,
26
- HTTP_STATUS_FOUND,
27
- HTTP_STATUS_MOVED_PERMANENTLY,
28
- HTTP_STATUS_SEE_OTHER,
29
- } = http2.constants;
30
-
31
- export const postflight = (req, res, options, { reject, resolve }) => {
32
- const { cookies, credentials, follow, h2, redirect, url } = options;
33
- let headers;
34
-
35
- if (h2) {
36
- headers = res;
37
- res = req;
38
- } else {
39
- res.once('error', reject);
40
- }
41
-
42
- admix(res, headers, options);
43
-
44
- if (cookies !== false && res.headers[HTTP2_HEADER_SET_COOKIE]) {
45
- if (Cookies.jar.has(url.origin)) {
46
- const cookie = new Cookies(res.headers[HTTP2_HEADER_SET_COOKIE], options);
47
-
48
- Cookies.jar.get(url.origin).forEach((val, key) => {
49
- if (!cookie.has(key)) {
50
- cookie.set(key, val);
51
- }
52
- });
53
- Cookies.jar.set(url.origin, cookie);
54
- } else {
55
- Cookies.jar.set(url.origin, new Cookies(res.headers[HTTP2_HEADER_SET_COOKIE], options));
56
- }
57
- }
58
-
59
- Reflect.defineProperty(res, 'cookies', {
60
- enumerable: true,
61
- value: cookies !== false && Cookies.jar.has(url.origin)
62
- ? Cookies.jar.get(url.origin)
63
- : void 0,
64
- });
65
-
66
- const { statusCode } = res;
67
-
68
- if (follow && /3\d{2}/.test(statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
69
- if (!requestRedirectCodes.includes(statusCode)) {
70
- return res.emit('error', new RangeError(`Invalid status code: ${ statusCode }`));
71
- }
72
-
73
- if (redirect === requestRedirect.error) {
74
- return res.emit('error', new RequestError(`Unexpected redirect, redirect mode is set to '${ redirect }'.`));
75
- }
76
-
77
- if (redirect === requestRedirect.follow) {
78
- const location = new URL(res.headers[HTTP2_HEADER_LOCATION], url);
79
-
80
- if (!/^https?:/i.test(location.protocol)) {
81
- return res.emit('error', new RequestError('URL scheme must be "http" or "https".'));
82
- }
83
-
84
- if (!sameOrigin(location, url)) {
85
- if (credentials !== requestCredentials.include) {
86
- options.credentials = requestCredentials.omit;
87
- }
88
-
89
- options.h2 = false;
90
- }
91
-
92
- if (statusCode !== HTTP_STATUS_SEE_OTHER && options.body?.pipe?.constructor === Function) {
93
- return res.emit('error', new RequestError(`Unable to ${ redirect } redirect with streamable body.`));
94
- }
95
-
96
- if (([
97
- HTTP_STATUS_MOVED_PERMANENTLY,
98
- HTTP_STATUS_FOUND,
99
- ].includes(statusCode) && options.method === HTTP2_METHOD_POST) || (statusCode === HTTP_STATUS_SEE_OTHER && ![
100
- HTTP2_METHOD_GET,
101
- HTTP2_METHOD_HEAD,
102
- ].includes(options.method))) {
103
- Object.keys(options.headers).filter((it) => /^content-/i.test(it))
104
- .forEach((it) => Reflect.deleteProperty(options.headers, it));
105
- options.body = null;
106
- options.method = HTTP2_METHOD_GET;
107
- }
108
-
109
- options.follow--;
110
- options.redirected = true;
111
- options.url = location;
112
-
113
- if (statusCode === HTTP_STATUS_MOVED_PERMANENTLY && res.headers[HTTP2_HEADER_RETRY_AFTER]) {
114
- let interval = res.headers[HTTP2_HEADER_RETRY_AFTER];
115
-
116
- interval = Number(interval) * 1000 || new Date(interval) - Date.now();
117
-
118
- if (interval > options.maxRetryAfter) {
119
- return res.emit('error', maxRetryAfterError(interval, { cause: mixin(res, options) }));
120
- }
121
-
122
- return setTimeoutPromise(interval).then(() => rekwest(options.url, options).then(resolve, reject));
123
- }
124
-
125
- return rekwest(options.url, options).then(resolve, reject);
126
- }
127
- }
128
-
129
- if (statusCode >= HTTP_STATUS_BAD_REQUEST) {
130
- return reject(mixin(res, options));
131
- }
132
-
133
- resolve(mixin(res, options));
134
- };
1
+ import http2 from 'node:http2';
2
+ import { setTimeout as setTimeoutPromise } from 'node:timers/promises';
3
+ import {
4
+ requestCredentials,
5
+ requestRedirect,
6
+ requestRedirectCodes,
7
+ } from './constants.mjs';
8
+ import { Cookies } from './cookies.mjs';
9
+ import { RequestError } from './errors.mjs';
10
+ import rekwest from './index.mjs';
11
+ import { mixin } from './mixin.mjs';
12
+ import {
13
+ admix,
14
+ maxRetryAfterError,
15
+ sameOrigin,
16
+ } from './utils.mjs';
17
+
18
+ const {
19
+ HTTP2_HEADER_LOCATION,
20
+ HTTP2_HEADER_RETRY_AFTER,
21
+ HTTP2_HEADER_SET_COOKIE,
22
+ HTTP2_METHOD_GET,
23
+ HTTP2_METHOD_HEAD,
24
+ HTTP2_METHOD_POST,
25
+ HTTP_STATUS_BAD_REQUEST,
26
+ HTTP_STATUS_FOUND,
27
+ HTTP_STATUS_MOVED_PERMANENTLY,
28
+ HTTP_STATUS_SEE_OTHER,
29
+ } = http2.constants;
30
+
31
+ export const postflight = (req, res, options, { reject, resolve }) => {
32
+ const { cookies, credentials, follow, h2, redirect, url } = options;
33
+ let headers;
34
+
35
+ if (h2) {
36
+ headers = res;
37
+ res = req;
38
+ } else {
39
+ res.once('error', reject);
40
+ }
41
+
42
+ admix(res, headers, options);
43
+
44
+ if (cookies !== false && res.headers[HTTP2_HEADER_SET_COOKIE]) {
45
+ if (Cookies.jar.has(url.origin)) {
46
+ const cookie = new Cookies(res.headers[HTTP2_HEADER_SET_COOKIE], options);
47
+
48
+ Cookies.jar.get(url.origin).forEach((val, key) => {
49
+ if (!cookie.has(key)) {
50
+ cookie.set(key, val);
51
+ }
52
+ });
53
+ Cookies.jar.set(url.origin, cookie);
54
+ } else {
55
+ Cookies.jar.set(url.origin, new Cookies(res.headers[HTTP2_HEADER_SET_COOKIE], options));
56
+ }
57
+ }
58
+
59
+ Reflect.defineProperty(res, 'cookies', {
60
+ enumerable: true,
61
+ value: cookies !== false && Cookies.jar.has(url.origin)
62
+ ? Cookies.jar.get(url.origin)
63
+ : void 0,
64
+ });
65
+
66
+ const { statusCode } = res;
67
+
68
+ if (follow && /3\d{2}/.test(statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
69
+ if (!requestRedirectCodes.includes(statusCode)) {
70
+ return res.emit('error', new RangeError(`Invalid status code: ${ statusCode }`));
71
+ }
72
+
73
+ if (redirect === requestRedirect.error) {
74
+ return res.emit('error', new RequestError(`Unexpected redirect, redirect mode is set to '${ redirect }'.`));
75
+ }
76
+
77
+ if (redirect === requestRedirect.follow) {
78
+ const location = new URL(res.headers[HTTP2_HEADER_LOCATION], url);
79
+
80
+ if (!/^https?:/i.test(location.protocol)) {
81
+ return res.emit('error', new RequestError('URL scheme must be "http" or "https".'));
82
+ }
83
+
84
+ if (!sameOrigin(location, url)) {
85
+ if (credentials !== requestCredentials.include) {
86
+ options.credentials = requestCredentials.omit;
87
+ }
88
+
89
+ options.h2 = false;
90
+ }
91
+
92
+ if (statusCode !== HTTP_STATUS_SEE_OTHER && options.body?.pipe?.constructor === Function) {
93
+ return res.emit('error', new RequestError(`Unable to ${ redirect } redirect with streamable body.`));
94
+ }
95
+
96
+ if (([
97
+ HTTP_STATUS_MOVED_PERMANENTLY,
98
+ HTTP_STATUS_FOUND,
99
+ ].includes(statusCode) && options.method === HTTP2_METHOD_POST) || (statusCode === HTTP_STATUS_SEE_OTHER && ![
100
+ HTTP2_METHOD_GET,
101
+ HTTP2_METHOD_HEAD,
102
+ ].includes(options.method))) {
103
+ for (const it of Object.keys(options.headers).filter((val) => /^content-/i.test(val))) {
104
+ Reflect.deleteProperty(options.headers, it);
105
+ }
106
+
107
+ options.body = null;
108
+ options.method = HTTP2_METHOD_GET;
109
+ }
110
+
111
+ options.follow--;
112
+ options.redirected = true;
113
+ options.url = location;
114
+
115
+ if (statusCode === HTTP_STATUS_MOVED_PERMANENTLY && res.headers[HTTP2_HEADER_RETRY_AFTER]) {
116
+ let interval = res.headers[HTTP2_HEADER_RETRY_AFTER];
117
+
118
+ interval = Number(interval) * 1000 || new Date(interval) - Date.now();
119
+
120
+ if (interval > options.maxRetryAfter) {
121
+ return res.emit('error', maxRetryAfterError(interval, { cause: mixin(res, options) }));
122
+ }
123
+
124
+ return setTimeoutPromise(interval).then(() => rekwest(options.url, options).then(resolve, reject));
125
+ }
126
+
127
+ return rekwest(options.url, options).then(resolve, reject);
128
+ }
129
+ }
130
+
131
+ if (statusCode >= HTTP_STATUS_BAD_REQUEST) {
132
+ return reject(mixin(res, options));
133
+ }
134
+
135
+ resolve(mixin(res, options));
136
+ };