rekwest 7.2.3 → 7.2.5
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 +63 -58
- package/dist/ackn.cjs +3 -3
- package/dist/cookies.cjs +45 -26
- package/dist/formdata.cjs +59 -60
- package/dist/preflight.cjs +1 -1
- package/dist/retries.cjs +2 -2
- package/dist/transfer.cjs +3 -5
- package/dist/transform.cjs +3 -3
- package/dist/utils.cjs +41 -38
- package/package.json +6 -5
- package/src/ackn.js +3 -3
- package/src/cookies.js +47 -30
- package/src/formdata.js +80 -89
- package/src/preflight.js +1 -1
- package/src/retries.js +2 -2
- package/src/transfer.js +1 -5
- package/src/transform.js +8 -5
- package/src/utils.js +36 -47
package/dist/utils.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.stripHeaders = exports.snoop = exports.sameOrigin = exports.normalizeHeaders = exports.normalize = exports.isReadableStream = exports.isPipeStream = exports.isLikelyH2cPrefaceError = exports.
|
|
6
|
+
exports.stripHeaders = exports.snoop = exports.sameOrigin = exports.normalizeHeaders = exports.normalize = exports.isReadableStream = exports.isPipeStream = exports.isLikelyH2cPrefaceError = exports.isBlobLike = exports.dispatch = exports.deepMerge = exports.cloneWith = exports.brandCheck = exports.augment = exports.addSearchParams = void 0;
|
|
7
7
|
exports.tap = tap;
|
|
8
8
|
exports.unwind = exports.toCamelCase = void 0;
|
|
9
9
|
var _nodeBuffer = require("node:buffer");
|
|
@@ -31,9 +31,7 @@ const addSearchParams = (url, params = {}) => {
|
|
|
31
31
|
};
|
|
32
32
|
exports.addSearchParams = addSearchParams;
|
|
33
33
|
const augment = (res, headers, options) => {
|
|
34
|
-
const
|
|
35
|
-
h2
|
|
36
|
-
} = options;
|
|
34
|
+
const h2 = /\bh2c?\b/i.test(res.session?.alpnProtocol);
|
|
37
35
|
if (h2) {
|
|
38
36
|
Reflect.defineProperty(res, 'headers', {
|
|
39
37
|
enumerable: true,
|
|
@@ -92,67 +90,74 @@ const dispatch = (req, {
|
|
|
92
90
|
body
|
|
93
91
|
}) => {
|
|
94
92
|
if ((0, _nodeStream.isReadable)(body)) {
|
|
93
|
+
body.once('error', err => req.session && req.emit('error', err) && req.destroy() || req.destroy(err));
|
|
95
94
|
body.pipe(req);
|
|
96
95
|
} else {
|
|
97
96
|
req.end(body);
|
|
98
97
|
}
|
|
99
98
|
};
|
|
100
99
|
exports.dispatch = dispatch;
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
exports.isFileLike = isFileLike;
|
|
105
|
-
const isLikelyH2cPrefaceError = err => {
|
|
106
|
-
return err.code === 'HPE_INVALID_CONSTANT';
|
|
107
|
-
};
|
|
100
|
+
const isBlobLike = val => val instanceof _nodeBuffer.Blob;
|
|
101
|
+
exports.isBlobLike = isBlobLike;
|
|
102
|
+
const isLikelyH2cPrefaceError = err => err.code === 'HPE_INVALID_CONSTANT';
|
|
108
103
|
exports.isLikelyH2cPrefaceError = isLikelyH2cPrefaceError;
|
|
109
|
-
const isPipeStream = val =>
|
|
110
|
-
return val instanceof _nodeStream.Readable;
|
|
111
|
-
};
|
|
104
|
+
const isPipeStream = val => val instanceof _nodeStream.Readable;
|
|
112
105
|
exports.isPipeStream = isPipeStream;
|
|
113
|
-
const isReadableStream = val =>
|
|
114
|
-
return val instanceof ReadableStream;
|
|
115
|
-
};
|
|
106
|
+
const isReadableStream = val => val instanceof ReadableStream;
|
|
116
107
|
exports.isReadableStream = isReadableStream;
|
|
117
108
|
const normalize = (url, options = {}) => {
|
|
118
109
|
if (!options.redirected) {
|
|
119
110
|
options = cloneWith(_config.default.defaults, options);
|
|
120
111
|
}
|
|
121
|
-
if (options.trimTrailingSlashes) {
|
|
122
|
-
url = `${url}`.replace(/(?<!:)\/+/g, '/');
|
|
123
|
-
}
|
|
124
|
-
if (options.stripTrailingSlash) {
|
|
125
|
-
url = `${url}`.replace(/\/$|\/(?=#)|\/(?=\?)/g, '');
|
|
126
|
-
}
|
|
127
112
|
return Object.assign(options, {
|
|
128
113
|
headers: normalizeHeaders(options.headers),
|
|
129
|
-
method: options.method
|
|
130
|
-
url: addSearchParams(new URL(url, options.baseURL), options.params)
|
|
114
|
+
method: options.method?.toUpperCase(),
|
|
115
|
+
url: addSearchParams(normalizeUrl(new URL(url, options.baseURL), options), options.params)
|
|
131
116
|
});
|
|
132
117
|
};
|
|
133
118
|
exports.normalize = normalize;
|
|
134
119
|
const normalizeHeaders = (headers = {}) => {
|
|
135
120
|
const acc = {};
|
|
136
|
-
for (
|
|
137
|
-
|
|
121
|
+
for (let [key, val] of Object.entries(headers)) {
|
|
122
|
+
key = key.toLowerCase();
|
|
138
123
|
acc[key] = val;
|
|
139
124
|
if (key === HTTP2_HEADER_ACCEPT_ENCODING && !_config.isZstdSupported) {
|
|
140
|
-
|
|
141
|
-
if (
|
|
142
|
-
acc[key] =
|
|
125
|
+
val = val.replace(/\s?zstd,?/gi, '').trim();
|
|
126
|
+
if (val) {
|
|
127
|
+
acc[key] = val;
|
|
143
128
|
} else {
|
|
144
|
-
Reflect.deleteProperty(acc,
|
|
129
|
+
Reflect.deleteProperty(acc, key);
|
|
145
130
|
}
|
|
146
131
|
}
|
|
147
132
|
}
|
|
148
133
|
return acc;
|
|
149
134
|
};
|
|
150
135
|
exports.normalizeHeaders = normalizeHeaders;
|
|
151
|
-
|
|
136
|
+
function normalizeUrl(url, {
|
|
137
|
+
trimTrailingSlashes,
|
|
138
|
+
stripTrailingSlash
|
|
139
|
+
} = {}) {
|
|
140
|
+
if (trimTrailingSlashes) {
|
|
141
|
+
url.pathname = url.pathname.replace(/\/{2,}/g, '/');
|
|
142
|
+
}
|
|
143
|
+
if (stripTrailingSlash && url.pathname !== '/') {
|
|
144
|
+
url.pathname = url.pathname.replace(/\/$/, '');
|
|
145
|
+
}
|
|
146
|
+
return url;
|
|
147
|
+
}
|
|
148
|
+
const sameOrigin = (a, b) => a.origin === b.origin;
|
|
152
149
|
exports.sameOrigin = sameOrigin;
|
|
153
|
-
const snoop = (client, req, options
|
|
150
|
+
const snoop = (client, req, options, {
|
|
151
|
+
reject
|
|
152
|
+
} = {
|
|
153
|
+
reject: () => void 0
|
|
154
|
+
}) => {
|
|
155
|
+
req.once('aborted', reject);
|
|
154
156
|
req.once('close', () => client?.close());
|
|
155
157
|
req.once('end', () => client?.close());
|
|
158
|
+
req.once('error', reject);
|
|
159
|
+
req.once('frameError', reject);
|
|
160
|
+
req.once('goaway', reject);
|
|
156
161
|
req.once('timeout', () => req.destroy(new _errors.TimeoutError(`Timed out after ${options.timeout} ms`)));
|
|
157
162
|
req.once('trailers', trailers => {
|
|
158
163
|
Reflect.defineProperty(req, 'trailers', {
|
|
@@ -162,9 +167,9 @@ const snoop = (client, req, options) => {
|
|
|
162
167
|
});
|
|
163
168
|
};
|
|
164
169
|
exports.snoop = snoop;
|
|
165
|
-
const stripHeaders = (headers = {},
|
|
166
|
-
|
|
167
|
-
return Object.fromEntries(Object.entries(headers).filter(([key]) => !
|
|
170
|
+
const stripHeaders = (headers = {}, keys = []) => {
|
|
171
|
+
keys = new Set(keys);
|
|
172
|
+
return Object.fromEntries(Object.entries(headers).filter(([key]) => !keys.has(key)));
|
|
168
173
|
};
|
|
169
174
|
exports.stripHeaders = stripHeaders;
|
|
170
175
|
async function* tap(val) {
|
|
@@ -172,8 +177,6 @@ async function* tap(val) {
|
|
|
172
177
|
yield* val;
|
|
173
178
|
} else if (val.stream) {
|
|
174
179
|
yield* val.stream();
|
|
175
|
-
} else {
|
|
176
|
-
yield await val.arrayBuffer();
|
|
177
180
|
}
|
|
178
181
|
}
|
|
179
182
|
const toCamelCase = str => str?.toLowerCase().replace(/\p{Punctuation}.|\p{White_Space}./gu, val => val.replace(/\p{Punctuation}+|\p{White_Space}+/gu, '').toUpperCase());
|
package/package.json
CHANGED
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
"@babel/cli": "^7.28.6",
|
|
13
13
|
"@babel/core": "^7.29.0",
|
|
14
14
|
"@babel/preset-env": "^7.29.0",
|
|
15
|
+
"@eslint/markdown": "^7.5.1",
|
|
15
16
|
"c8": "^10.1.3",
|
|
16
|
-
"eslint": "^10.0.
|
|
17
|
-
"eslint-config-ultra-refined": "^4.
|
|
17
|
+
"eslint": "^10.0.1",
|
|
18
|
+
"eslint-config-ultra-refined": "^4.1.2",
|
|
18
19
|
"mocha": "^11.7.5"
|
|
19
20
|
},
|
|
20
21
|
"engines": {
|
|
@@ -60,16 +61,16 @@
|
|
|
60
61
|
"url": "git+https://github.com/bricss/rekwest.git"
|
|
61
62
|
},
|
|
62
63
|
"scripts": {
|
|
63
|
-
"build": "rm -rf dist && npx babel src --out-dir dist --out-file-extension .cjs",
|
|
64
|
+
"build": "rm -rf dist && npx babel src --out-dir dist --out-file-extension .cjs && sh misc.sh",
|
|
64
65
|
"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
66
|
"cert:ken": "openssl x509 -in localhost.cert -noout -text",
|
|
66
67
|
"lint": "eslint --concurrency=auto",
|
|
67
|
-
"prepack": "npm run build &&
|
|
68
|
+
"prepack": "npm run build && npm run lint",
|
|
68
69
|
"pretest": "rm -rf coverage && npm run cert:gen",
|
|
69
70
|
"test": "mocha",
|
|
70
71
|
"test:bail": "mocha --bail",
|
|
71
72
|
"test:cover": "c8 --include=src --reporter=lcov --reporter=text npm test"
|
|
72
73
|
},
|
|
73
74
|
"type": "module",
|
|
74
|
-
"version": "7.2.
|
|
75
|
+
"version": "7.2.5"
|
|
75
76
|
}
|
package/src/ackn.js
CHANGED
|
@@ -23,11 +23,11 @@ export const ackn = (options) => new Promise((resolve, reject) => {
|
|
|
23
23
|
createConnection() {
|
|
24
24
|
return socket;
|
|
25
25
|
},
|
|
26
|
-
h2: /
|
|
26
|
+
h2: /\bh2\b/i.test(alpnProtocol),
|
|
27
27
|
protocol: url.protocol,
|
|
28
28
|
});
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
socket.
|
|
32
|
-
socket.
|
|
31
|
+
socket.once('error', reject);
|
|
32
|
+
socket.once('timeout', reject);
|
|
33
33
|
});
|
package/src/cookies.js
CHANGED
|
@@ -3,7 +3,13 @@ import {
|
|
|
3
3
|
toCamelCase,
|
|
4
4
|
} from './utils.js';
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
export const cookieRex = /^[\w-]+=(?:"[^"]*"|[^\p{Control};]*)(?:;\s*(?:[\w-]+=(?:"[^"]*"|[^\p{Control};]*)|[\w-]+))*$/u;
|
|
7
|
+
export const cookiePairRex = /(?:[^;"\s]+="[^"]*"|[^;]+)(?=;|$)/g;
|
|
8
|
+
export const illegalCookieChars = /\p{Control}/u;
|
|
9
|
+
export const isValidCookie = (str) => str?.constructor === String && cookieRex.test(str);
|
|
10
|
+
export const maxCookieLifetimeCap = 3456e7; // 400 days
|
|
11
|
+
export const maxCookieSize = 4096;
|
|
12
|
+
export const splitCookie = (str) => str.match(cookiePairRex).map((str) => str.trim());
|
|
7
13
|
|
|
8
14
|
export class Cookies extends URLSearchParams {
|
|
9
15
|
|
|
@@ -27,49 +33,60 @@ export class Cookies extends URLSearchParams {
|
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
constructor(input, { cookiesTTL } = { cookiesTTL: false }) {
|
|
30
|
-
if (
|
|
31
|
-
input = input
|
|
32
|
-
|
|
33
|
-
return [it.split(';')[0].trim()];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const [cookie, ...attrs] = it.split(';').map((it) => it.trim());
|
|
37
|
-
const ttl = {};
|
|
38
|
-
|
|
39
|
-
for (const attr of attrs) {
|
|
40
|
-
if (/(?:expires|max-age)=/i.test(attr)) {
|
|
41
|
-
const [key, val] = attr.toLowerCase().split('=');
|
|
42
|
-
const ms = Number.isFinite(Number.parseInt(val, 10)) ? val * 1e3 : Date.parse(val) - Date.now();
|
|
36
|
+
if (isValidCookie(input)) {
|
|
37
|
+
input = splitCookie(input);
|
|
38
|
+
}
|
|
43
39
|
|
|
44
|
-
|
|
40
|
+
const ttlMap = new Map();
|
|
41
|
+
|
|
42
|
+
if (Array.isArray(input)) {
|
|
43
|
+
if (input.every((it) => isValidCookie(it))) {
|
|
44
|
+
input = input.filter((it) => !illegalCookieChars.test(it) && it.length <= maxCookieSize);
|
|
45
|
+
input = input.map(splitCookie).map(([cookie, ...attrs]) => {
|
|
46
|
+
try {
|
|
47
|
+
cookie = cookie.split('=').map((it) => decodeURIComponent(it.trim()));
|
|
48
|
+
|
|
49
|
+
return cookie;
|
|
50
|
+
} finally {
|
|
51
|
+
if (cookiesTTL) {
|
|
52
|
+
for (const attr of attrs) {
|
|
53
|
+
if (/(?:expires|max-age)=/i.test(attr)) {
|
|
54
|
+
const [key, val] = attr.toLowerCase().split('=');
|
|
55
|
+
let interval = val * 1e3 || Date.parse(val) - Date.now();
|
|
56
|
+
|
|
57
|
+
if (interval < 0 || Number.isNaN(interval)) {
|
|
58
|
+
interval = 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
ttlMap.set(
|
|
62
|
+
cookie[0],
|
|
63
|
+
{ [toCamelCase(key.trim())]: Math.min(interval, maxCookieLifetimeCap) },
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
45
68
|
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return [
|
|
49
|
-
cookie.replace(/\u0022/g, ''),
|
|
50
|
-
Object.keys(ttl).length ? ttl : null,
|
|
51
|
-
];
|
|
52
|
-
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
53
71
|
}
|
|
54
72
|
|
|
55
|
-
super(
|
|
73
|
+
super(input);
|
|
56
74
|
|
|
57
|
-
if (
|
|
58
|
-
for (const [
|
|
59
|
-
const key = cookie.split('=')[0];
|
|
75
|
+
if (ttlMap.size) {
|
|
76
|
+
for (const [key, attrs] of ttlMap) {
|
|
60
77
|
|
|
61
78
|
if (this.#chronometry.has(key)) {
|
|
62
79
|
clearTimeout(this.#chronometry.get(key));
|
|
63
80
|
this.#chronometry.delete(key);
|
|
64
81
|
}
|
|
65
82
|
|
|
66
|
-
const { expires, maxAge } =
|
|
83
|
+
const { expires, maxAge } = attrs;
|
|
67
84
|
|
|
68
|
-
for (const
|
|
85
|
+
for (const interval of [
|
|
69
86
|
maxAge,
|
|
70
87
|
expires,
|
|
71
88
|
]) {
|
|
72
|
-
if (!Number.isInteger(
|
|
89
|
+
if (!Number.isInteger(interval)) {
|
|
73
90
|
continue;
|
|
74
91
|
}
|
|
75
92
|
|
|
@@ -81,7 +98,7 @@ export class Cookies extends URLSearchParams {
|
|
|
81
98
|
ctx.#chronometry.delete(key);
|
|
82
99
|
ctx.delete(key);
|
|
83
100
|
}
|
|
84
|
-
}, Math.max(
|
|
101
|
+
}, Math.max(interval, 0));
|
|
85
102
|
|
|
86
103
|
this.constructor.#register(this, tid);
|
|
87
104
|
this.#chronometry.set(key, tid);
|
package/src/formdata.js
CHANGED
|
@@ -7,7 +7,9 @@ import {
|
|
|
7
7
|
} from './mediatypes.js';
|
|
8
8
|
import {
|
|
9
9
|
brandCheck,
|
|
10
|
-
|
|
10
|
+
isBlobLike,
|
|
11
|
+
isPipeStream,
|
|
12
|
+
isReadableStream,
|
|
11
13
|
tap,
|
|
12
14
|
} from './utils.js';
|
|
13
15
|
|
|
@@ -19,58 +21,31 @@ const {
|
|
|
19
21
|
|
|
20
22
|
export class FormData {
|
|
21
23
|
|
|
22
|
-
static
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const redress = (str) => str.replace(/\r?\n|\r/g, CRLF);
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
contentType,
|
|
32
|
-
async* [Symbol.asyncIterator]() {
|
|
33
|
-
const encoder = new TextEncoder();
|
|
34
|
-
|
|
35
|
-
for (const [name, val] of fd) {
|
|
36
|
-
if (val.constructor === String) {
|
|
37
|
-
yield encoder.encode(`${ prefix }; name="${
|
|
38
|
-
escape(redress(name))
|
|
39
|
-
}"${ CRLF.repeat(2) }${ redress(val) }${ CRLF }`);
|
|
40
|
-
} else {
|
|
41
|
-
yield encoder.encode(`${ prefix }; name="${
|
|
42
|
-
escape(redress(name))
|
|
43
|
-
}"${ val.name ? `; filename="${ escape(val.name) }"` : '' }${ CRLF }${
|
|
44
|
-
HTTP2_HEADER_CONTENT_TYPE
|
|
45
|
-
}: ${
|
|
46
|
-
val.type || APPLICATION_OCTET_STREAM
|
|
47
|
-
}${ CRLF.repeat(2) }`);
|
|
48
|
-
yield* tap(val);
|
|
49
|
-
yield new Uint8Array([
|
|
50
|
-
13,
|
|
51
|
-
10,
|
|
52
|
-
]);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
yield encoder.encode(`--${ boundary }--`);
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
}
|
|
24
|
+
static #ensureArgs(args, expect, method) {
|
|
25
|
+
if (args.length < expect) {
|
|
26
|
+
throw new TypeError(`Failed to execute '${ method }' on '${
|
|
27
|
+
this[Symbol.toStringTag]
|
|
28
|
+
}': ${ expect } arguments required, but only ${ args.length } present`);
|
|
29
|
+
}
|
|
60
30
|
|
|
61
|
-
|
|
62
|
-
|
|
31
|
+
if (method === 'forEach') {
|
|
32
|
+
if (args[0]?.constructor !== Function) {
|
|
33
|
+
throw new TypeError(`Failed to execute '${ method }' on '${
|
|
34
|
+
this[Symbol.toStringTag]
|
|
35
|
+
}': parameter ${ expect } is not of type 'Function'`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
63
38
|
}
|
|
64
39
|
|
|
65
|
-
static #
|
|
40
|
+
static #formEntry(name, value, filename) {
|
|
66
41
|
name = String(name).toWellFormed();
|
|
67
42
|
filename &&= String(filename).toWellFormed();
|
|
68
43
|
|
|
69
|
-
if (
|
|
70
|
-
filename ??= value.name
|
|
44
|
+
if (isBlobLike(value)) {
|
|
45
|
+
filename ??= String(value.name ?? 'blob').toWellFormed();
|
|
71
46
|
value = new File([value], filename, value);
|
|
72
|
-
} else if (
|
|
73
|
-
value.name = filename;
|
|
47
|
+
} else if (isPipeStream(value) || isReadableStream(value)) {
|
|
48
|
+
value.name = filename ?? 'blob';
|
|
74
49
|
} else {
|
|
75
50
|
value = String(value).toWellFormed();
|
|
76
51
|
}
|
|
@@ -81,10 +56,6 @@ export class FormData {
|
|
|
81
56
|
};
|
|
82
57
|
}
|
|
83
58
|
|
|
84
|
-
static #ensureInstance(val) {
|
|
85
|
-
return isFileLike(val) || (Object(val) === val && Reflect.has(val, Symbol.asyncIterator));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
59
|
#entries = [];
|
|
89
60
|
|
|
90
61
|
get [Symbol.toStringTag]() {
|
|
@@ -98,13 +69,13 @@ export class FormData {
|
|
|
98
69
|
throw new TypeError(`Failed to construct '${
|
|
99
70
|
this[Symbol.toStringTag]
|
|
100
71
|
}': The provided value cannot be converted to a sequence`);
|
|
101
|
-
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!input.every((it) => it.length === 2)) {
|
|
102
75
|
throw new TypeError(`Failed to construct '${
|
|
103
76
|
this[Symbol.toStringTag]
|
|
104
77
|
}': Sequence initializer must only contain pair elements`);
|
|
105
78
|
}
|
|
106
|
-
|
|
107
|
-
input = Array.from(input);
|
|
108
79
|
} else if (!Reflect.has(input, Symbol.iterator)) {
|
|
109
80
|
input = Object.entries(input);
|
|
110
81
|
}
|
|
@@ -115,42 +86,15 @@ export class FormData {
|
|
|
115
86
|
}
|
|
116
87
|
}
|
|
117
88
|
|
|
118
|
-
#ensureArgs(args, expected, method) {
|
|
119
|
-
if (args.length < expected) {
|
|
120
|
-
throw new TypeError(`Failed to execute '${ method }' on '${
|
|
121
|
-
this[Symbol.toStringTag]
|
|
122
|
-
}': ${ expected } arguments required, but only ${ args.length } present`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if ([
|
|
126
|
-
'append',
|
|
127
|
-
'set',
|
|
128
|
-
].includes(method)) {
|
|
129
|
-
if (args.length === 3 && !this.constructor.#ensureInstance(args[1])) {
|
|
130
|
-
throw new TypeError(`Failed to execute '${ method }' on '${
|
|
131
|
-
this[Symbol.toStringTag]
|
|
132
|
-
}': parameter ${ expected } is not of type 'Blob'`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (method === 'forEach') {
|
|
137
|
-
if (args[0]?.constructor !== Function) {
|
|
138
|
-
throw new TypeError(`Failed to execute '${ method }' on '${
|
|
139
|
-
this[Symbol.toStringTag]
|
|
140
|
-
}': parameter ${ expected } is not of type 'Function'`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
89
|
append(...args) {
|
|
146
90
|
brandCheck(this, FormData);
|
|
147
|
-
this.#ensureArgs(args, 2,
|
|
148
|
-
this.#entries.push(this.constructor.#
|
|
91
|
+
this.constructor.#ensureArgs(args, 2, this.append.name);
|
|
92
|
+
this.#entries.push(this.constructor.#formEntry(...args));
|
|
149
93
|
}
|
|
150
94
|
|
|
151
95
|
delete(...args) {
|
|
152
96
|
brandCheck(this, FormData);
|
|
153
|
-
this.#ensureArgs(args, 1,
|
|
97
|
+
this.constructor.#ensureArgs(args, 1, this.delete.name);
|
|
154
98
|
const name = String(args[0]).toWellFormed();
|
|
155
99
|
|
|
156
100
|
this.#entries = this.#entries.filter((it) => it.name !== name);
|
|
@@ -158,7 +102,7 @@ export class FormData {
|
|
|
158
102
|
|
|
159
103
|
forEach(...args) {
|
|
160
104
|
brandCheck(this, FormData);
|
|
161
|
-
this.#ensureArgs(args, 1,
|
|
105
|
+
this.constructor.#ensureArgs(args, 1, this.forEach.name);
|
|
162
106
|
const [callback, thisArg] = args;
|
|
163
107
|
|
|
164
108
|
for (const entry of this) {
|
|
@@ -171,7 +115,7 @@ export class FormData {
|
|
|
171
115
|
|
|
172
116
|
get(...args) {
|
|
173
117
|
brandCheck(this, FormData);
|
|
174
|
-
this.#ensureArgs(args, 1,
|
|
118
|
+
this.constructor.#ensureArgs(args, 1, this.get.name);
|
|
175
119
|
const name = String(args[0]).toWellFormed();
|
|
176
120
|
|
|
177
121
|
return this.#entries.find((it) => it.name === name)?.value ?? null;
|
|
@@ -179,7 +123,7 @@ export class FormData {
|
|
|
179
123
|
|
|
180
124
|
getAll(...args) {
|
|
181
125
|
brandCheck(this, FormData);
|
|
182
|
-
this.#ensureArgs(args, 1,
|
|
126
|
+
this.constructor.#ensureArgs(args, 1, this.getAll.name);
|
|
183
127
|
const name = String(args[0]).toWellFormed();
|
|
184
128
|
|
|
185
129
|
return this.#entries.filter((it) => it.name === name).map((it) => it.value);
|
|
@@ -187,7 +131,7 @@ export class FormData {
|
|
|
187
131
|
|
|
188
132
|
has(...args) {
|
|
189
133
|
brandCheck(this, FormData);
|
|
190
|
-
this.#ensureArgs(args, 1,
|
|
134
|
+
this.constructor.#ensureArgs(args, 1, this.has.name);
|
|
191
135
|
const name = String(args[0]).toWellFormed();
|
|
192
136
|
|
|
193
137
|
return !!this.#entries.find((it) => it.name === name);
|
|
@@ -195,8 +139,8 @@ export class FormData {
|
|
|
195
139
|
|
|
196
140
|
set(...args) {
|
|
197
141
|
brandCheck(this, FormData);
|
|
198
|
-
this.#ensureArgs(args, 2,
|
|
199
|
-
const entry = this.constructor.#
|
|
142
|
+
this.constructor.#ensureArgs(args, 2, this.set.name);
|
|
143
|
+
const entry = this.constructor.#formEntry(...args);
|
|
200
144
|
const idx = this.#entries.findIndex((it) => it.name === entry.name);
|
|
201
145
|
|
|
202
146
|
if (idx !== -1) {
|
|
@@ -237,3 +181,50 @@ export class FormData {
|
|
|
237
181
|
}
|
|
238
182
|
|
|
239
183
|
}
|
|
184
|
+
|
|
185
|
+
export const fdToAsyncIterable = (fd) => {
|
|
186
|
+
const boundary = randomBytes(32).toString('hex');
|
|
187
|
+
const contentType = `${ MULTIPART_FORM_DATA }; boundary=${ boundary }`;
|
|
188
|
+
const prefix = `--${ boundary }${ CRLF }${ HTTP2_HEADER_CONTENT_DISPOSITION }: form-data`;
|
|
189
|
+
|
|
190
|
+
const escape = (str) => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22');
|
|
191
|
+
const normalize = (str) => str.replace(/\r?\n|\r/g, CRLF);
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
contentType,
|
|
195
|
+
async* [Symbol.asyncIterator]() {
|
|
196
|
+
const encoder = new TextEncoder();
|
|
197
|
+
|
|
198
|
+
for (const [name, val] of fd) {
|
|
199
|
+
if (val.constructor === String) {
|
|
200
|
+
yield encoder.encode(`${ prefix }; name="${
|
|
201
|
+
escape(normalize(name))
|
|
202
|
+
}"${ CRLF.repeat(2) }${ normalize(val) }${ CRLF }`);
|
|
203
|
+
} else {
|
|
204
|
+
yield encoder.encode(`${ prefix }; name="${
|
|
205
|
+
escape(normalize(name))
|
|
206
|
+
}"${ val.name ? `; filename="${ escape(val.name) }"` : '' }${ CRLF }${
|
|
207
|
+
HTTP2_HEADER_CONTENT_TYPE
|
|
208
|
+
}: ${
|
|
209
|
+
val.type || APPLICATION_OCTET_STREAM
|
|
210
|
+
}${ CRLF.repeat(2) }`);
|
|
211
|
+
yield* tap(val);
|
|
212
|
+
yield new Uint8Array([
|
|
213
|
+
13,
|
|
214
|
+
10,
|
|
215
|
+
]);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
yield encoder.encode(`--${ boundary }--${ CRLF }`);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export const isFormData = (val) => FormData.name === val?.[Symbol.toStringTag];
|
|
225
|
+
|
|
226
|
+
export const parseFormData = (str) => {
|
|
227
|
+
const rex = /^-+[^\r\n]+\r?\ncontent-disposition:\s*form-data;\s*name="(?<name>[^"]+)"(?:;\s*filename="(?<filename>[^"]+)")?(?:\r?\n[^\r\n:]+:[^\r\n]*)*\r?\n\r?\n(?<content>.*?)(?=\r?\n-+[^\r\n]+)/gims;
|
|
228
|
+
|
|
229
|
+
return [...str.matchAll(rex)].map(({ groups }) => structuredClone(groups));
|
|
230
|
+
};
|
package/src/preflight.js
CHANGED
package/src/retries.js
CHANGED
|
@@ -27,7 +27,7 @@ export const retries = (err, options) => {
|
|
|
27
27
|
|
|
28
28
|
if (retry.retryAfter && err.headers?.[HTTP2_HEADER_RETRY_AFTER]) {
|
|
29
29
|
interval = err.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
30
|
-
interval =
|
|
30
|
+
interval = interval * 1e3 || Date.parse(interval) - Date.now();
|
|
31
31
|
if (interval > retry.maxRetryAfter) {
|
|
32
32
|
throw new RequestError(
|
|
33
33
|
`Maximum '${ HTTP2_HEADER_RETRY_AFTER }' limit exceeded: ${ interval } ms`,
|
|
@@ -38,7 +38,7 @@ export const retries = (err, options) => {
|
|
|
38
38
|
interval = new Function('interval', `return Math.ceil(${ retry.backoffStrategy });`)(interval);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
if (interval < 0) {
|
|
41
|
+
if (interval < 0 || Number.isNaN(interval)) {
|
|
42
42
|
interval = 0;
|
|
43
43
|
}
|
|
44
44
|
|
package/src/transfer.js
CHANGED
|
@@ -53,12 +53,8 @@ export const transfer = async (options) => {
|
|
|
53
53
|
req = request(url, options);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
snoop(client, req, options);
|
|
56
|
+
snoop(client, req, options, { reject });
|
|
57
57
|
|
|
58
|
-
req.once('aborted', reject);
|
|
59
|
-
req.once('error', reject);
|
|
60
|
-
req.once('frameError', reject);
|
|
61
|
-
req.once('goaway', reject);
|
|
62
58
|
req.once('response', (res) => postflight(req, res, options, {
|
|
63
59
|
reject, resolve,
|
|
64
60
|
}));
|
package/src/transform.js
CHANGED
|
@@ -6,14 +6,17 @@ import {
|
|
|
6
6
|
import { buffer } from 'node:stream/consumers';
|
|
7
7
|
import { types } from 'node:util';
|
|
8
8
|
import { encode } from './codecs.js';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
fdToAsyncIterable,
|
|
11
|
+
isFormData,
|
|
12
|
+
} from './formdata.js';
|
|
10
13
|
import {
|
|
11
14
|
APPLICATION_FORM_URLENCODED,
|
|
12
15
|
APPLICATION_JSON,
|
|
13
16
|
APPLICATION_OCTET_STREAM,
|
|
14
17
|
} from './mediatypes.js';
|
|
15
18
|
import {
|
|
16
|
-
|
|
19
|
+
isBlobLike,
|
|
17
20
|
isReadableStream,
|
|
18
21
|
} from './utils.js';
|
|
19
22
|
|
|
@@ -32,7 +35,7 @@ export const transform = async (options) => {
|
|
|
32
35
|
|
|
33
36
|
if (!Buffer.isBuffer(body)) {
|
|
34
37
|
switch (true) {
|
|
35
|
-
case
|
|
38
|
+
case isBlobLike(body): {
|
|
36
39
|
headers = {
|
|
37
40
|
[HTTP2_HEADER_CONTENT_LENGTH]: body.size,
|
|
38
41
|
[HTTP2_HEADER_CONTENT_TYPE]: body.type || APPLICATION_OCTET_STREAM,
|
|
@@ -41,8 +44,8 @@ export const transform = async (options) => {
|
|
|
41
44
|
break;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
case
|
|
45
|
-
body =
|
|
47
|
+
case isFormData(body): {
|
|
48
|
+
body = fdToAsyncIterable(body);
|
|
46
49
|
headers = { [HTTP2_HEADER_CONTENT_TYPE]: body.contentType };
|
|
47
50
|
break;
|
|
48
51
|
}
|