got 14.5.0 → 14.6.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/dist/source/as-promise/index.js +30 -2
- package/dist/source/core/index.d.ts +4 -1
- package/dist/source/core/index.js +163 -100
- package/dist/source/core/options.d.ts +94 -9
- package/dist/source/core/options.js +115 -60
- package/dist/source/core/utils/get-body-size.js +3 -0
- package/dist/source/core/utils/is-unix-socket-url.d.ts +16 -0
- package/dist/source/core/utils/is-unix-socket-url.js +21 -0
- package/dist/source/create.js +9 -1
- package/package.json +9 -8
- package/readme.md +1 -0
|
@@ -18,12 +18,14 @@ export default function asPromise(firstRequest) {
|
|
|
18
18
|
let globalResponse;
|
|
19
19
|
let normalizedOptions;
|
|
20
20
|
const emitter = new EventEmitter();
|
|
21
|
+
let promiseSettled = false;
|
|
21
22
|
const promise = new PCancelable((resolve, reject, onCancel) => {
|
|
22
23
|
onCancel(() => {
|
|
23
24
|
globalRequest.destroy();
|
|
24
25
|
});
|
|
25
26
|
onCancel.shouldReject = false;
|
|
26
27
|
onCancel(() => {
|
|
28
|
+
promiseSettled = true;
|
|
27
29
|
reject(new CancelError(globalRequest));
|
|
28
30
|
});
|
|
29
31
|
const makeRequest = (retryCount) => {
|
|
@@ -82,7 +84,7 @@ export default function asPromise(firstRequest) {
|
|
|
82
84
|
}
|
|
83
85
|
throw new RetryError(request);
|
|
84
86
|
});
|
|
85
|
-
if (!(is.object(response) && is.number(response.statusCode) &&
|
|
87
|
+
if (!(is.object(response) && is.number(response.statusCode) && 'body' in response)) {
|
|
86
88
|
throw new TypeError('The `afterResponse` hook returned an invalid value');
|
|
87
89
|
}
|
|
88
90
|
}
|
|
@@ -97,12 +99,27 @@ export default function asPromise(firstRequest) {
|
|
|
97
99
|
return;
|
|
98
100
|
}
|
|
99
101
|
request.destroy();
|
|
102
|
+
promiseSettled = true;
|
|
100
103
|
resolve(request.options.resolveBodyOnly ? response.body : response);
|
|
101
104
|
});
|
|
105
|
+
let handledFinalError = false;
|
|
102
106
|
const onError = (error) => {
|
|
103
107
|
if (promise.isCanceled) {
|
|
104
108
|
return;
|
|
105
109
|
}
|
|
110
|
+
// Route errors emitted directly on the stream (e.g., EPIPE from Node.js)
|
|
111
|
+
// through retry logic first, then handle them here after retries are exhausted.
|
|
112
|
+
// See https://github.com/sindresorhus/got/issues/1995
|
|
113
|
+
if (!request._stopReading) {
|
|
114
|
+
request._beforeError(error);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Allow the manual re-emission from Request to land only once.
|
|
118
|
+
if (handledFinalError) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
handledFinalError = true;
|
|
122
|
+
promiseSettled = true;
|
|
106
123
|
const { options } = request;
|
|
107
124
|
if (error instanceof HTTPError && !options.throwHttpErrors) {
|
|
108
125
|
const { response } = error;
|
|
@@ -112,10 +129,21 @@ export default function asPromise(firstRequest) {
|
|
|
112
129
|
}
|
|
113
130
|
reject(error);
|
|
114
131
|
};
|
|
115
|
-
|
|
132
|
+
// Use .on() instead of .once() to keep the listener active across retries.
|
|
133
|
+
// When _stopReading is false, we return early and the error gets re-emitted
|
|
134
|
+
// after retry logic completes, so we need this listener to remain active.
|
|
135
|
+
// See https://github.com/sindresorhus/got/issues/1995
|
|
136
|
+
request.on('error', onError);
|
|
116
137
|
const previousBody = request.options?.body;
|
|
117
138
|
request.once('retry', (newRetryCount, error) => {
|
|
118
139
|
firstRequest = undefined;
|
|
140
|
+
// If promise already settled, don't retry
|
|
141
|
+
// This prevents the race condition in #1489 where a late error
|
|
142
|
+
// (e.g., ECONNRESET after successful response) triggers retry
|
|
143
|
+
// after the promise has already resolved/rejected
|
|
144
|
+
if (promiseSettled) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
119
147
|
const newBody = request.options.body;
|
|
120
148
|
if (previousBody === newBody && is.nodeStream(newBody)) {
|
|
121
149
|
error.message = 'Cannot retry with consumed body stream';
|
|
@@ -88,11 +88,11 @@ export default class Request extends Duplex implements RequestEvents<Request> {
|
|
|
88
88
|
requestUrl?: URL;
|
|
89
89
|
redirectUrls: URL[];
|
|
90
90
|
retryCount: number;
|
|
91
|
+
_stopReading: boolean;
|
|
91
92
|
private _requestOptions;
|
|
92
93
|
private _stopRetry;
|
|
93
94
|
private _downloadedSize;
|
|
94
95
|
private _uploadedSize;
|
|
95
|
-
private _stopReading;
|
|
96
96
|
private readonly _pipedServerResponses;
|
|
97
97
|
private _request?;
|
|
98
98
|
private _responseSize?;
|
|
@@ -106,6 +106,8 @@ export default class Request extends Duplex implements RequestEvents<Request> {
|
|
|
106
106
|
private _nativeResponse?;
|
|
107
107
|
private _flushed;
|
|
108
108
|
private _aborted;
|
|
109
|
+
private _expectedContentLength?;
|
|
110
|
+
private _byteCounter?;
|
|
109
111
|
private _requestInitialized;
|
|
110
112
|
constructor(url: UrlType, options?: OptionsType, defaults?: DefaultsType);
|
|
111
113
|
flush(): Promise<void>;
|
|
@@ -118,6 +120,7 @@ export default class Request extends Duplex implements RequestEvents<Request> {
|
|
|
118
120
|
end?: boolean;
|
|
119
121
|
}): T;
|
|
120
122
|
unpipe<T extends NodeJS.WritableStream>(destination: T): this;
|
|
123
|
+
private _checkContentLengthMismatch;
|
|
121
124
|
private _finalizeBody;
|
|
122
125
|
private _onResponseBase;
|
|
123
126
|
private _setRawBody;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import { Buffer } from 'node:buffer';
|
|
3
|
-
import { Duplex } from 'node:stream';
|
|
4
|
-
import { gunzip, inflate, brotliDecompress } from 'node:zlib';
|
|
5
|
-
import { promisify } from 'node:util';
|
|
3
|
+
import { Duplex, Transform } from 'node:stream';
|
|
6
4
|
import http, { ServerResponse } from 'node:http';
|
|
7
5
|
import timer from '@szmarczak/http-timer';
|
|
8
6
|
import CacheableRequest, { CacheError as CacheableCacheError, } from 'cacheable-request';
|
|
@@ -19,7 +17,7 @@ import calculateRetryDelay from './calculate-retry-delay.js';
|
|
|
19
17
|
import Options from './options.js';
|
|
20
18
|
import { isResponseOk } from './response.js';
|
|
21
19
|
import isClientRequest from './utils/is-client-request.js';
|
|
22
|
-
import isUnixSocketURL from './utils/is-unix-socket-url.js';
|
|
20
|
+
import isUnixSocketURL, { getUnixSocketPath } from './utils/is-unix-socket-url.js';
|
|
23
21
|
import { RequestError, ReadError, MaxRedirectsError, HTTPError, TimeoutError, UploadError, CacheError, AbortError, } from './errors.js';
|
|
24
22
|
const supportsBrotli = is.string(process.versions.brotli);
|
|
25
23
|
const methodsWithoutBody = new Set(['GET', 'HEAD']);
|
|
@@ -35,6 +33,17 @@ const proxiedRequestEvents = [
|
|
|
35
33
|
'upgrade',
|
|
36
34
|
];
|
|
37
35
|
const noop = () => { };
|
|
36
|
+
/**
|
|
37
|
+
Stream transform that counts bytes passing through.
|
|
38
|
+
Used to track compressed bytes before decompression for content-length validation.
|
|
39
|
+
*/
|
|
40
|
+
class ByteCounter extends Transform {
|
|
41
|
+
count = 0;
|
|
42
|
+
_transform(chunk, _encoding, callback) {
|
|
43
|
+
this.count += chunk.length;
|
|
44
|
+
callback(null, chunk);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
38
47
|
export default class Request extends Duplex {
|
|
39
48
|
// @ts-expect-error - Ignoring for now.
|
|
40
49
|
['constructor'];
|
|
@@ -45,10 +54,10 @@ export default class Request extends Duplex {
|
|
|
45
54
|
requestUrl;
|
|
46
55
|
redirectUrls;
|
|
47
56
|
retryCount;
|
|
57
|
+
_stopReading;
|
|
48
58
|
_stopRetry;
|
|
49
59
|
_downloadedSize;
|
|
50
60
|
_uploadedSize;
|
|
51
|
-
_stopReading;
|
|
52
61
|
_pipedServerResponses;
|
|
53
62
|
_request;
|
|
54
63
|
_responseSize;
|
|
@@ -61,6 +70,8 @@ export default class Request extends Duplex {
|
|
|
61
70
|
_nativeResponse;
|
|
62
71
|
_flushed;
|
|
63
72
|
_aborted;
|
|
73
|
+
_expectedContentLength;
|
|
74
|
+
_byteCounter;
|
|
64
75
|
// We need this because `this._request` if `undefined` when using cache
|
|
65
76
|
_requestInitialized;
|
|
66
77
|
constructor(url, options, defaults) {
|
|
@@ -112,7 +123,18 @@ export default class Request extends Duplex {
|
|
|
112
123
|
}
|
|
113
124
|
this.flush = async () => {
|
|
114
125
|
this.flush = async () => { };
|
|
115
|
-
|
|
126
|
+
// Defer error emission to next tick to allow user to attach error handlers
|
|
127
|
+
process.nextTick(() => {
|
|
128
|
+
// _beforeError requires options to access retry logic and hooks
|
|
129
|
+
if (this.options) {
|
|
130
|
+
this._beforeError(error);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// Options is undefined, skip _beforeError and destroy directly
|
|
134
|
+
const requestError = error instanceof RequestError ? error : new RequestError(error.message, error, this);
|
|
135
|
+
this.destroy(requestError);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
116
138
|
};
|
|
117
139
|
return;
|
|
118
140
|
}
|
|
@@ -368,6 +390,19 @@ export default class Request extends Duplex {
|
|
|
368
390
|
if (this._request) {
|
|
369
391
|
this._request.destroy();
|
|
370
392
|
}
|
|
393
|
+
// Workaround: http-timer only sets timings.end when the response emits 'end'.
|
|
394
|
+
// When a stream is destroyed before completion, the 'end' event may not fire,
|
|
395
|
+
// leaving timings.end undefined. This should ideally be fixed in http-timer
|
|
396
|
+
// by listening to the 'close' event, but we handle it here for now.
|
|
397
|
+
// Only set timings.end if there was no error or abort (to maintain semantic correctness).
|
|
398
|
+
const timings = this._request?.timings;
|
|
399
|
+
if (timings && is.undefined(timings.end) && !is.undefined(timings.response) && is.undefined(timings.error) && is.undefined(timings.abort)) {
|
|
400
|
+
timings.end = Date.now();
|
|
401
|
+
if (is.undefined(timings.phases.total)) {
|
|
402
|
+
timings.phases.download = timings.end - timings.response;
|
|
403
|
+
timings.phases.total = timings.end - timings.start;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
371
406
|
if (error !== null && !is.undefined(error) && !(error instanceof RequestError)) {
|
|
372
407
|
error = new RequestError(error.message, error, this);
|
|
373
408
|
}
|
|
@@ -386,6 +421,22 @@ export default class Request extends Duplex {
|
|
|
386
421
|
super.unpipe(destination);
|
|
387
422
|
return this;
|
|
388
423
|
}
|
|
424
|
+
_checkContentLengthMismatch() {
|
|
425
|
+
if (this.options.strictContentLength && this._expectedContentLength !== undefined) {
|
|
426
|
+
// Use ByteCounter's count when available (for compressed responses),
|
|
427
|
+
// otherwise use _downloadedSize (for uncompressed responses)
|
|
428
|
+
const actualSize = this._byteCounter?.count ?? this._downloadedSize;
|
|
429
|
+
if (actualSize !== this._expectedContentLength) {
|
|
430
|
+
this._beforeError(new ReadError({
|
|
431
|
+
message: `Content-Length mismatch: expected ${this._expectedContentLength} bytes, received ${actualSize} bytes`,
|
|
432
|
+
name: 'Error',
|
|
433
|
+
code: 'ERR_HTTP_CONTENT_LENGTH_MISMATCH',
|
|
434
|
+
}, this));
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
389
440
|
async _finalizeBody() {
|
|
390
441
|
const { options } = this;
|
|
391
442
|
const { headers } = options;
|
|
@@ -473,86 +524,19 @@ export default class Request extends Duplex {
|
|
|
473
524
|
|| statusCode === 204
|
|
474
525
|
|| statusCode === 205
|
|
475
526
|
|| statusCode === 304;
|
|
476
|
-
const nativeResponse = response;
|
|
477
527
|
if (options.decompress && !hasNoBody) {
|
|
528
|
+
// When strictContentLength is enabled, track compressed bytes by listening to
|
|
529
|
+
// the native response's data events before decompression
|
|
530
|
+
if (options.strictContentLength) {
|
|
531
|
+
this._byteCounter = new ByteCounter();
|
|
532
|
+
this._nativeResponse.on('data', (chunk) => {
|
|
533
|
+
this._byteCounter.count += chunk.length;
|
|
534
|
+
});
|
|
535
|
+
}
|
|
478
536
|
response = decompressResponse(response);
|
|
479
537
|
}
|
|
480
|
-
// For revalidated cached responses (304 Not Modified), cacheable-request may return
|
|
481
|
-
// a ResponseLike object with the body stored in a .body property but not pushed into
|
|
482
|
-
// the stream. The stream is created and may be ended without data, and the 'end' event is
|
|
483
|
-
// never emitted, causing Got to hang waiting for it. Detect and fix this.
|
|
484
|
-
const nativeAsAny = nativeResponse;
|
|
485
|
-
if (nativeAsAny.body) {
|
|
486
|
-
// Mark the response as complete immediately (ResponseLike doesn't have req.res.complete)
|
|
487
|
-
nativeResponse.complete = true;
|
|
488
|
-
response.complete = true;
|
|
489
|
-
// Use setImmediate to check if this is a stuck revalidated response
|
|
490
|
-
setImmediate(() => {
|
|
491
|
-
// Check if the stream ended with no data (revalidated response)
|
|
492
|
-
if (nativeResponse.readableEnded && !nativeResponse.readableLength) {
|
|
493
|
-
// The body is in nativeAsAny.body but was never pushed to the stream.
|
|
494
|
-
// We need to push it to the native response so it flows through decompression if needed.
|
|
495
|
-
try {
|
|
496
|
-
// Push the body to the native stream
|
|
497
|
-
nativeResponse.push(nativeAsAny.body);
|
|
498
|
-
// eslint-disable-next-line unicorn/no-array-push-push
|
|
499
|
-
nativeResponse.push(null);
|
|
500
|
-
// Update download size with the cached body length
|
|
501
|
-
let bodyLength = 0;
|
|
502
|
-
if (Buffer.isBuffer(nativeAsAny.body)) {
|
|
503
|
-
bodyLength = nativeAsAny.body.length;
|
|
504
|
-
}
|
|
505
|
-
else if (typeof nativeAsAny.body === 'string') {
|
|
506
|
-
bodyLength = Buffer.byteLength(nativeAsAny.body);
|
|
507
|
-
}
|
|
508
|
-
this._downloadedSize += bodyLength;
|
|
509
|
-
}
|
|
510
|
-
catch {
|
|
511
|
-
// If push fails (stream already ended), we need to decompress manually for compressed responses
|
|
512
|
-
const encoding = nativeResponse.headers['content-encoding'];
|
|
513
|
-
if (encoding && Buffer.isBuffer(nativeAsAny.body)) {
|
|
514
|
-
// Decompress the body based on the encoding
|
|
515
|
-
let decompressAsync;
|
|
516
|
-
switch (encoding) {
|
|
517
|
-
case 'gzip': {
|
|
518
|
-
decompressAsync = promisify(gunzip);
|
|
519
|
-
break;
|
|
520
|
-
}
|
|
521
|
-
case 'deflate': {
|
|
522
|
-
decompressAsync = promisify(inflate);
|
|
523
|
-
break;
|
|
524
|
-
}
|
|
525
|
-
case 'br': {
|
|
526
|
-
decompressAsync = promisify(brotliDecompress);
|
|
527
|
-
break;
|
|
528
|
-
}
|
|
529
|
-
default: {
|
|
530
|
-
break;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
if (decompressAsync) {
|
|
534
|
-
// Decompress asynchronously and set rawBody
|
|
535
|
-
void decompressAsync(nativeAsAny.body).then((decompressed) => {
|
|
536
|
-
response.rawBody = decompressed;
|
|
537
|
-
this._downloadedSize += nativeAsAny.body.length;
|
|
538
|
-
response.emit('end');
|
|
539
|
-
}).catch(() => {
|
|
540
|
-
// Decompression failed, use compressed body as-is
|
|
541
|
-
response.rawBody = nativeAsAny.body;
|
|
542
|
-
response.emit('end');
|
|
543
|
-
});
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
// Not compressed or decompression not needed, set rawBody directly
|
|
548
|
-
response.rawBody = nativeAsAny.body;
|
|
549
|
-
response.emit('end');
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
538
|
const typedResponse = response;
|
|
555
|
-
typedResponse.statusMessage = typedResponse.statusMessage
|
|
539
|
+
typedResponse.statusMessage = typedResponse.statusMessage || http.STATUS_CODES[statusCode]; // eslint-disable-line @typescript-eslint/prefer-nullish-coalescing -- The status message can be empty.
|
|
556
540
|
typedResponse.url = options.url.toString();
|
|
557
541
|
typedResponse.requestUrl = this.requestUrl;
|
|
558
542
|
typedResponse.redirectUrls = this.redirectUrls;
|
|
@@ -564,6 +548,18 @@ export default class Request extends Duplex {
|
|
|
564
548
|
this._isFromCache = typedResponse.isFromCache;
|
|
565
549
|
this._responseSize = Number(response.headers['content-length']) || undefined;
|
|
566
550
|
this.response = typedResponse;
|
|
551
|
+
// Workaround for http-timer bug: when connecting to an IP address (no DNS lookup),
|
|
552
|
+
// http-timer sets lookup = connect instead of lookup = socket, resulting in
|
|
553
|
+
// dns = lookup - socket being a small positive number instead of 0.
|
|
554
|
+
// See https://github.com/sindresorhus/got/issues/2279
|
|
555
|
+
const { timings } = response;
|
|
556
|
+
if (timings?.lookup !== undefined && timings.socket !== undefined && timings.connect !== undefined && timings.lookup === timings.connect && timings.phases.dns !== 0) {
|
|
557
|
+
// Fix the DNS phase to be 0 and set lookup to socket time
|
|
558
|
+
timings.phases.dns = 0;
|
|
559
|
+
timings.lookup = timings.socket;
|
|
560
|
+
// Recalculate TCP time to be the full time from socket to connect
|
|
561
|
+
timings.phases.tcp = timings.connect - timings.socket;
|
|
562
|
+
}
|
|
567
563
|
response.once('error', (error) => {
|
|
568
564
|
this._aborted = true;
|
|
569
565
|
// Force clean-up, because some packages don't do this.
|
|
@@ -573,11 +569,14 @@ export default class Request extends Duplex {
|
|
|
573
569
|
});
|
|
574
570
|
response.once('aborted', () => {
|
|
575
571
|
this._aborted = true;
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
572
|
+
// Check if there's a content-length mismatch to provide a more specific error
|
|
573
|
+
if (!this._checkContentLengthMismatch()) {
|
|
574
|
+
this._beforeError(new ReadError({
|
|
575
|
+
name: 'Error',
|
|
576
|
+
message: 'The server aborted pending request',
|
|
577
|
+
code: 'ECONNRESET',
|
|
578
|
+
}, this));
|
|
579
|
+
}
|
|
581
580
|
});
|
|
582
581
|
const rawCookies = response.headers['set-cookie'];
|
|
583
582
|
if (is.object(options.cookieJar) && rawCookies) {
|
|
@@ -639,7 +638,11 @@ export default class Request extends Duplex {
|
|
|
639
638
|
return;
|
|
640
639
|
}
|
|
641
640
|
// Redirecting to a different site, clear sensitive data.
|
|
642
|
-
|
|
641
|
+
// For UNIX sockets, different socket paths are also different origins.
|
|
642
|
+
const isDifferentOrigin = redirectUrl.hostname !== url.hostname
|
|
643
|
+
|| redirectUrl.port !== url.port
|
|
644
|
+
|| getUnixSocketPath(url) !== getUnixSocketPath(redirectUrl);
|
|
645
|
+
if (isDifferentOrigin) {
|
|
643
646
|
if ('host' in updatedOptions.headers) {
|
|
644
647
|
delete updatedOptions.headers.host;
|
|
645
648
|
}
|
|
@@ -684,13 +687,32 @@ export default class Request extends Duplex {
|
|
|
684
687
|
this._beforeError(new HTTPError(typedResponse));
|
|
685
688
|
return;
|
|
686
689
|
}
|
|
690
|
+
// Store the expected content-length from the native response for validation.
|
|
691
|
+
// This is the content-length before decompression, which is what actually gets transferred.
|
|
692
|
+
// Skip storing for responses that shouldn't have bodies per RFC 9110.
|
|
693
|
+
// When decompression occurs, only store if strictContentLength is enabled.
|
|
694
|
+
const wasDecompressed = response !== this._nativeResponse;
|
|
695
|
+
if (!hasNoBody && (!wasDecompressed || options.strictContentLength)) {
|
|
696
|
+
const contentLengthHeader = this._nativeResponse.headers['content-length'];
|
|
697
|
+
if (contentLengthHeader !== undefined) {
|
|
698
|
+
const expectedLength = Number(contentLengthHeader);
|
|
699
|
+
if (!Number.isNaN(expectedLength) && expectedLength >= 0) {
|
|
700
|
+
this._expectedContentLength = expectedLength;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
687
704
|
// Set up end listener AFTER redirect check to avoid emitting progress for redirect responses
|
|
688
|
-
|
|
705
|
+
response.once('end', () => {
|
|
706
|
+
// Validate content-length if it was provided
|
|
707
|
+
// Per RFC 9112: "If the sender closes the connection before the indicated number
|
|
708
|
+
// of octets are received, the recipient MUST consider the message to be incomplete"
|
|
709
|
+
if (this._checkContentLengthMismatch()) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
689
712
|
this._responseSize = this._downloadedSize;
|
|
690
713
|
this.emit('downloadProgress', this.downloadProgress);
|
|
691
714
|
this.push(null);
|
|
692
|
-
};
|
|
693
|
-
response.once('end', endStream);
|
|
715
|
+
});
|
|
694
716
|
this.emit('downloadProgress', this.downloadProgress);
|
|
695
717
|
response.on('readable', () => {
|
|
696
718
|
if (this._triggerRead) {
|
|
@@ -715,12 +737,22 @@ export default class Request extends Duplex {
|
|
|
715
737
|
if (destination.headersSent) {
|
|
716
738
|
continue;
|
|
717
739
|
}
|
|
718
|
-
//
|
|
740
|
+
// Check if decompression actually occurred by comparing stream objects.
|
|
741
|
+
// decompressResponse wraps the response stream when it decompresses,
|
|
742
|
+
// so response !== this._nativeResponse indicates decompression happened.
|
|
743
|
+
const wasDecompressed = response !== this._nativeResponse;
|
|
719
744
|
for (const key in response.headers) {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
745
|
+
if (Object.hasOwn(response.headers, key)) {
|
|
746
|
+
const value = response.headers[key];
|
|
747
|
+
// When decompression occurred, skip content-encoding and content-length
|
|
748
|
+
// as they refer to the compressed data, not the decompressed stream.
|
|
749
|
+
if (wasDecompressed && (key === 'content-encoding' || key === 'content-length')) {
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
// Skip if value is undefined
|
|
753
|
+
if (value !== undefined) {
|
|
754
|
+
destination.setHeader(key, value);
|
|
755
|
+
}
|
|
724
756
|
}
|
|
725
757
|
}
|
|
726
758
|
destination.statusCode = statusCode;
|
|
@@ -805,6 +837,19 @@ export default class Request extends Duplex {
|
|
|
805
837
|
if (is.nodeStream(body)) {
|
|
806
838
|
body.pipe(currentRequest);
|
|
807
839
|
}
|
|
840
|
+
else if (is.buffer(body)) {
|
|
841
|
+
// Buffer should be sent directly without conversion
|
|
842
|
+
this._writeRequest(body, undefined, () => { });
|
|
843
|
+
currentRequest.end();
|
|
844
|
+
}
|
|
845
|
+
else if (is.typedArray(body)) {
|
|
846
|
+
// Typed arrays should be treated like buffers, not iterated over
|
|
847
|
+
// Create a Uint8Array view over the data (Node.js streams accept Uint8Array)
|
|
848
|
+
const typedArray = body;
|
|
849
|
+
const uint8View = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength);
|
|
850
|
+
this._writeRequest(uint8View, undefined, () => { });
|
|
851
|
+
currentRequest.end();
|
|
852
|
+
}
|
|
808
853
|
else if (is.asyncIterable(body) || (is.iterable(body) && !is.string(body) && !isBuffer(body))) {
|
|
809
854
|
(async () => {
|
|
810
855
|
try {
|
|
@@ -884,9 +929,15 @@ export default class Request extends Duplex {
|
|
|
884
929
|
response._readableState.autoDestroy = false;
|
|
885
930
|
if (request) {
|
|
886
931
|
const fix = () => {
|
|
932
|
+
// For ResponseLike objects from cache, set complete to true if not already set.
|
|
933
|
+
// For real HTTP responses, copy from the underlying response.
|
|
887
934
|
if (response.req) {
|
|
888
935
|
response.complete = response.req.res.complete;
|
|
889
936
|
}
|
|
937
|
+
else if (response.complete === undefined) {
|
|
938
|
+
// ResponseLike from cache should have complete = true
|
|
939
|
+
response.complete = true;
|
|
940
|
+
}
|
|
890
941
|
};
|
|
891
942
|
response.prependOnceListener('end', fix);
|
|
892
943
|
fix();
|
|
@@ -933,7 +984,7 @@ export default class Request extends Duplex {
|
|
|
933
984
|
let request;
|
|
934
985
|
for (const hook of options.hooks.beforeRequest) {
|
|
935
986
|
// eslint-disable-next-line no-await-in-loop
|
|
936
|
-
const result = await hook(options);
|
|
987
|
+
const result = await hook(options, { retryCount: this.retryCount });
|
|
937
988
|
if (!is.undefined(result)) {
|
|
938
989
|
// @ts-expect-error Skip the type mismatch to support abstract responses
|
|
939
990
|
request = () => result;
|
|
@@ -992,12 +1043,12 @@ export default class Request extends Duplex {
|
|
|
992
1043
|
}
|
|
993
1044
|
async _error(error) {
|
|
994
1045
|
try {
|
|
995
|
-
if (error instanceof HTTPError && !this.options.throwHttpErrors) {
|
|
1046
|
+
if (this.options && error instanceof HTTPError && !this.options.throwHttpErrors) {
|
|
996
1047
|
// This branch can be reached only when using the Promise API
|
|
997
1048
|
// Skip calling the hooks on purpose.
|
|
998
1049
|
// See https://github.com/sindresorhus/got/issues/2103
|
|
999
1050
|
}
|
|
1000
|
-
else {
|
|
1051
|
+
else if (this.options) {
|
|
1001
1052
|
for (const hook of this.options.hooks.beforeError) {
|
|
1002
1053
|
// eslint-disable-next-line no-await-in-loop
|
|
1003
1054
|
error = await hook(error);
|
|
@@ -1008,10 +1059,22 @@ export default class Request extends Duplex {
|
|
|
1008
1059
|
error = new RequestError(error_.message, error_, this);
|
|
1009
1060
|
}
|
|
1010
1061
|
this.destroy(error);
|
|
1062
|
+
// Manually emit error for Promise API to ensure it receives it.
|
|
1063
|
+
// Node.js streams may not re-emit if an error was already emitted during retry attempts.
|
|
1064
|
+
// Only emit for Promise API (_noPipe = true) to avoid double emissions in stream mode.
|
|
1065
|
+
// Use process.nextTick to defer emission and allow destroy() to complete first.
|
|
1066
|
+
// See https://github.com/sindresorhus/got/issues/1995
|
|
1067
|
+
if (this._noPipe) {
|
|
1068
|
+
process.nextTick(() => {
|
|
1069
|
+
this.emit('error', error);
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1011
1072
|
}
|
|
1012
1073
|
_writeRequest(chunk, encoding, callback) {
|
|
1013
1074
|
if (!this._request || this._request.destroyed) {
|
|
1014
|
-
//
|
|
1075
|
+
// When there's no request (e.g., using cached response from beforeRequest hook),
|
|
1076
|
+
// we still need to call the callback to allow the stream to finish properly.
|
|
1077
|
+
callback();
|
|
1015
1078
|
return;
|
|
1016
1079
|
}
|
|
1017
1080
|
this._request.write(chunk, encoding, (error) => {
|
|
@@ -39,9 +39,34 @@ export type PromiseCookieJar = {
|
|
|
39
39
|
getCookieString: (url: string) => Promise<string>;
|
|
40
40
|
setCookie: (rawCookie: string, url: string) => Promise<unknown>;
|
|
41
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
Utility type to override specific properties in a type.
|
|
44
|
+
|
|
45
|
+
Uses `Omit` to remove properties before adding them back to ensure proper type replacement rather than intersection, which handles edge cases with optional/required properties correctly.
|
|
46
|
+
*/
|
|
47
|
+
type OverrideProperties<T, U> = Omit<T, keyof U> & U;
|
|
48
|
+
/**
|
|
49
|
+
Represents the runtime state of Options as seen by hooks after normalization.
|
|
50
|
+
|
|
51
|
+
Some Options properties accept multiple input types but are normalized to a single type internally by the Options class setters. This type reflects the actual runtime types that hooks receive, ensuring type safety when accessing options within hook functions.
|
|
52
|
+
*/
|
|
53
|
+
export type NormalizedOptions = OverrideProperties<Options, {
|
|
54
|
+
url: URL | undefined;
|
|
55
|
+
dnsCache: CacheableLookup | undefined;
|
|
56
|
+
cache: StorageAdapter | undefined;
|
|
57
|
+
prefixUrl: string;
|
|
58
|
+
}>;
|
|
42
59
|
export type InitHook = (init: OptionsInit, self: Options) => void;
|
|
43
|
-
export type
|
|
44
|
-
|
|
60
|
+
export type BeforeRequestHookContext = {
|
|
61
|
+
/**
|
|
62
|
+
The current retry count.
|
|
63
|
+
|
|
64
|
+
It will be `0` for the initial request and increment for each retry.
|
|
65
|
+
*/
|
|
66
|
+
retryCount: number;
|
|
67
|
+
};
|
|
68
|
+
export type BeforeRequestHook = (options: NormalizedOptions, context: BeforeRequestHookContext) => Promisable<void | Response | ResponseLike>;
|
|
69
|
+
export type BeforeRedirectHook = (updatedOptions: NormalizedOptions, plainResponse: PlainResponse) => Promisable<void>;
|
|
45
70
|
export type BeforeErrorHook = (error: RequestError) => Promisable<RequestError>;
|
|
46
71
|
export type BeforeRetryHook = (error: RequestError, retryCount: number) => Promisable<void>;
|
|
47
72
|
export type AfterResponseHook<ResponseType = unknown> = (response: Response<ResponseType>, retryWithMergedOptions: (options: OptionsInit) => never) => Promisable<Response | CancelableRequest<Response>>;
|
|
@@ -142,6 +167,8 @@ export type Hooks = {
|
|
|
142
167
|
/**
|
|
143
168
|
Called right before making the request with `options.createNativeRequestOptions()`.
|
|
144
169
|
|
|
170
|
+
The second argument is a context object containing request state information.
|
|
171
|
+
|
|
145
172
|
This hook is especially useful in conjunction with `got.extend()` when you want to sign your request.
|
|
146
173
|
|
|
147
174
|
@default []
|
|
@@ -162,7 +189,7 @@ export type Hooks = {
|
|
|
162
189
|
json: {payload: 'old'},
|
|
163
190
|
hooks: {
|
|
164
191
|
beforeRequest: [
|
|
165
|
-
options => {
|
|
192
|
+
(options, context) => {
|
|
166
193
|
options.body = JSON.stringify({payload: 'new'});
|
|
167
194
|
options.headers['content-length'] = options.body.length.toString();
|
|
168
195
|
}
|
|
@@ -172,6 +199,28 @@ export type Hooks = {
|
|
|
172
199
|
);
|
|
173
200
|
```
|
|
174
201
|
|
|
202
|
+
**Example using `context.retryCount`:**
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
import got from 'got';
|
|
206
|
+
|
|
207
|
+
await got('https://httpbin.org/status/500', {
|
|
208
|
+
retry: {
|
|
209
|
+
limit: 2
|
|
210
|
+
},
|
|
211
|
+
hooks: {
|
|
212
|
+
beforeRequest: [
|
|
213
|
+
(options, context) => {
|
|
214
|
+
// Only log on the initial request, not on retries
|
|
215
|
+
if (context.retryCount === 0) {
|
|
216
|
+
console.log('Making initial request');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
175
224
|
**Tip:**
|
|
176
225
|
> - You can indirectly override the `request` function by early returning a [`ClientRequest`-like](https://nodejs.org/api/http.html#http_class_http_clientrequest) instance or a [`IncomingMessage`-like](https://nodejs.org/api/http.html#http_class_http_incomingmessage) instance. This is very useful when creating a custom cache mechanism.
|
|
177
226
|
> - [Read more about this tip](https://github.com/sindresorhus/got/blob/main/documentation/cache.md#advanced-caching-mechanisms).
|
|
@@ -487,6 +536,27 @@ export type HttpsOptions = {
|
|
|
487
536
|
dhparam?: SecureContextOptions['dhparam'];
|
|
488
537
|
ecdhCurve?: SecureContextOptions['ecdhCurve'];
|
|
489
538
|
certificateRevocationLists?: SecureContextOptions['crl'];
|
|
539
|
+
/**
|
|
540
|
+
Optionally affect the OpenSSL protocol behavior, which is not usually necessary. This should be used carefully if at all!
|
|
541
|
+
|
|
542
|
+
The value is a numeric bitmask of the `SSL_OP_*` options from OpenSSL.
|
|
543
|
+
|
|
544
|
+
For example, to allow connections to legacy servers that do not support secure renegotiation, you can use `crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT`.
|
|
545
|
+
|
|
546
|
+
@example
|
|
547
|
+
```
|
|
548
|
+
import crypto from 'node:crypto';
|
|
549
|
+
import got from 'got';
|
|
550
|
+
|
|
551
|
+
// Allow connections to servers with legacy renegotiation
|
|
552
|
+
await got('https://legacy-server.com', {
|
|
553
|
+
https: {
|
|
554
|
+
secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
```
|
|
558
|
+
*/
|
|
559
|
+
secureOptions?: number;
|
|
490
560
|
};
|
|
491
561
|
export type PaginateData<BodyType, ElementType> = {
|
|
492
562
|
response: Response<BodyType>;
|
|
@@ -735,7 +805,7 @@ export default class Options {
|
|
|
735
805
|
|
|
736
806
|
__Note #4__: This option is not enumerable and will not be merged with the instance defaults.
|
|
737
807
|
|
|
738
|
-
The `content-length` header will be automatically set if `body` is a `string` / `Buffer` / [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) / [`form-data` instance](https://github.com/form-data/form-data), and `content-length` and `transfer-encoding` are not manually set in `options.headers`.
|
|
808
|
+
The `content-length` header will be automatically set if `body` is a `string` / `Buffer` / typed array ([`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), etc.) / [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) / [`form-data` instance](https://github.com/form-data/form-data), and `content-length` and `transfer-encoding` are not manually set in `options.headers`.
|
|
739
809
|
|
|
740
810
|
Since Got 12, the `content-length` is not automatically set when `body` is a `fs.createReadStream`.
|
|
741
811
|
|
|
@@ -756,8 +826,8 @@ export default class Options {
|
|
|
756
826
|
});
|
|
757
827
|
```
|
|
758
828
|
*/
|
|
759
|
-
get body(): string | Buffer | Readable | Generator | AsyncGenerator | Iterable<unknown> | AsyncIterable<unknown> | FormDataLike | undefined;
|
|
760
|
-
set body(value: string | Buffer | Readable | Generator | AsyncGenerator | Iterable<unknown> | AsyncIterable<unknown> | FormDataLike | undefined);
|
|
829
|
+
get body(): string | Buffer | Readable | Generator | AsyncGenerator | Iterable<unknown> | AsyncIterable<unknown> | FormDataLike | ArrayBufferView | undefined;
|
|
830
|
+
set body(value: string | Buffer | Readable | Generator | AsyncGenerator | Iterable<unknown> | AsyncIterable<unknown> | FormDataLike | ArrayBufferView | undefined);
|
|
761
831
|
/**
|
|
762
832
|
The form body is converted to a query string using [`(new URLSearchParams(object)).toString()`](https://nodejs.org/api/url.html#url_constructor_new_urlsearchparams_obj).
|
|
763
833
|
|
|
@@ -770,7 +840,9 @@ export default class Options {
|
|
|
770
840
|
get form(): Record<string, any> | undefined;
|
|
771
841
|
set form(value: Record<string, any> | undefined);
|
|
772
842
|
/**
|
|
773
|
-
JSON body. If the `
|
|
843
|
+
JSON request body. If the `content-type` header is not set, it will be set to `application/json`.
|
|
844
|
+
|
|
845
|
+
__Important__: This option only affects the request body you send to the server. To parse the response as JSON, you must either call `.json()` on the promise or set `responseType: 'json'` in the options.
|
|
774
846
|
|
|
775
847
|
__Note #1__: If you provide this option, `got.stream()` will be read-only.
|
|
776
848
|
|
|
@@ -1202,6 +1274,18 @@ export default class Options {
|
|
|
1202
1274
|
set maxHeaderSize(value: number | undefined);
|
|
1203
1275
|
get enableUnixSockets(): boolean;
|
|
1204
1276
|
set enableUnixSockets(value: boolean);
|
|
1277
|
+
/**
|
|
1278
|
+
Throw an error if the server response's `content-length` header value doesn't match the number of bytes received.
|
|
1279
|
+
|
|
1280
|
+
This is useful for detecting truncated responses and follows RFC 9112 requirements for message completeness.
|
|
1281
|
+
|
|
1282
|
+
__Note__: Responses without a `content-length` header are not validated.
|
|
1283
|
+
__Note__: When enabled and validation fails, a `ReadError` with code `ERR_HTTP_CONTENT_LENGTH_MISMATCH` will be thrown.
|
|
1284
|
+
|
|
1285
|
+
@default false
|
|
1286
|
+
*/
|
|
1287
|
+
get strictContentLength(): boolean;
|
|
1288
|
+
set strictContentLength(value: boolean);
|
|
1205
1289
|
toJSON(): {
|
|
1206
1290
|
headers: Headers;
|
|
1207
1291
|
timeout: Delays;
|
|
@@ -1215,7 +1299,7 @@ export default class Options {
|
|
|
1215
1299
|
h2session: ClientHttp2Session | undefined;
|
|
1216
1300
|
decompress: boolean;
|
|
1217
1301
|
prefixUrl: string | URL;
|
|
1218
|
-
body: string | Buffer | Readable | Generator | AsyncGenerator | Iterable<unknown> | AsyncIterable<unknown> | FormDataLike | undefined;
|
|
1302
|
+
body: string | Buffer | Readable | Generator | AsyncGenerator | Iterable<unknown> | AsyncIterable<unknown> | FormDataLike | ArrayBufferView | undefined;
|
|
1219
1303
|
form: Record<string, any> | undefined;
|
|
1220
1304
|
url: string | URL | undefined;
|
|
1221
1305
|
cookieJar: PromiseCookieJar | ToughCookieJar | undefined;
|
|
@@ -1248,6 +1332,7 @@ export default class Options {
|
|
|
1248
1332
|
setHost: boolean;
|
|
1249
1333
|
maxHeaderSize: number | undefined;
|
|
1250
1334
|
enableUnixSockets: boolean;
|
|
1335
|
+
strictContentLength: boolean;
|
|
1251
1336
|
};
|
|
1252
1337
|
createNativeRequestOptions(): {
|
|
1253
1338
|
ALPNProtocols: string[] | undefined;
|
|
@@ -1268,6 +1353,7 @@ export default class Options {
|
|
|
1268
1353
|
dhparam: string | Buffer<ArrayBufferLike> | undefined;
|
|
1269
1354
|
ecdhCurve: string | undefined;
|
|
1270
1355
|
crl: string | Buffer<ArrayBufferLike> | (string | Buffer<ArrayBufferLike>)[] | undefined;
|
|
1356
|
+
secureOptions: number | undefined;
|
|
1271
1357
|
lookup: {
|
|
1272
1358
|
(hostname: string, family: import("cacheable-lookup").IPFamily, callback: (error: NodeJS.ErrnoException | null, address: string, family: import("cacheable-lookup").IPFamily) => void): void;
|
|
1273
1359
|
(hostname: string, callback: (error: NodeJS.ErrnoException | null, address: string, family: import("cacheable-lookup").IPFamily) => void): void;
|
|
@@ -1314,7 +1400,6 @@ export default class Options {
|
|
|
1314
1400
|
clientCertEngine?: string | undefined;
|
|
1315
1401
|
privateKeyEngine?: string | undefined;
|
|
1316
1402
|
privateKeyIdentifier?: string | undefined;
|
|
1317
|
-
secureOptions?: number | undefined;
|
|
1318
1403
|
secureProtocol?: string | undefined;
|
|
1319
1404
|
sessionIdContext?: string | undefined;
|
|
1320
1405
|
ticketKeys?: Buffer | undefined;
|
|
@@ -11,11 +11,43 @@ import http2wrapper from 'http2-wrapper';
|
|
|
11
11
|
import { isFormData } from 'form-data-encoder';
|
|
12
12
|
import parseLinkHeader from './parse-link-header.js';
|
|
13
13
|
const [major, minor] = process.versions.node.split('.').map(Number);
|
|
14
|
+
/**
|
|
15
|
+
Generic helper that wraps any assertion function to add context to error messages.
|
|
16
|
+
*/
|
|
17
|
+
function wrapAssertionWithContext(optionName, assertionFn) {
|
|
18
|
+
try {
|
|
19
|
+
assertionFn();
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error instanceof Error) {
|
|
23
|
+
error.message = `Option '${optionName}': ${error.message}`;
|
|
24
|
+
}
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
Helper function that wraps assert.any() to provide better error messages.
|
|
30
|
+
When assertion fails, it includes the option name in the error message.
|
|
31
|
+
*/
|
|
32
|
+
function assertAny(optionName, validators, value) {
|
|
33
|
+
wrapAssertionWithContext(optionName, () => {
|
|
34
|
+
assert.any(validators, value);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
Helper function that wraps assert.plainObject() to provide better error messages.
|
|
39
|
+
When assertion fails, it includes the option name in the error message.
|
|
40
|
+
*/
|
|
41
|
+
function assertPlainObject(optionName, value) {
|
|
42
|
+
wrapAssertionWithContext(optionName, () => {
|
|
43
|
+
assert.plainObject(value);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
14
46
|
function validateSearchParameters(searchParameters) {
|
|
15
47
|
// eslint-disable-next-line guard-for-in
|
|
16
48
|
for (const key in searchParameters) {
|
|
17
49
|
const value = searchParameters[key];
|
|
18
|
-
|
|
50
|
+
assertAny(`searchParams.${key}`, [is.string, is.number, is.boolean, is.null, is.undefined], value);
|
|
19
51
|
}
|
|
20
52
|
}
|
|
21
53
|
const globalCache = new Map();
|
|
@@ -179,6 +211,7 @@ const defaultInternals = {
|
|
|
179
211
|
dhparam: undefined,
|
|
180
212
|
ecdhCurve: undefined,
|
|
181
213
|
certificateRevocationLists: undefined,
|
|
214
|
+
secureOptions: undefined,
|
|
182
215
|
},
|
|
183
216
|
encoding: undefined,
|
|
184
217
|
resolveBodyOnly: false,
|
|
@@ -217,6 +250,7 @@ const defaultInternals = {
|
|
|
217
250
|
maxHeaderSize: undefined,
|
|
218
251
|
signal: undefined,
|
|
219
252
|
enableUnixSockets: false,
|
|
253
|
+
strictContentLength: false,
|
|
220
254
|
};
|
|
221
255
|
const cloneInternals = (internals) => {
|
|
222
256
|
const { hooks, retry } = internals;
|
|
@@ -336,9 +370,9 @@ export default class Options {
|
|
|
336
370
|
_merging;
|
|
337
371
|
_init;
|
|
338
372
|
constructor(input, options, defaults) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
373
|
+
assertAny('input', [is.string, is.urlInstance, is.object, is.undefined], input);
|
|
374
|
+
assertAny('options', [is.object, is.undefined], options);
|
|
375
|
+
assertAny('defaults', [is.object, is.undefined], defaults);
|
|
342
376
|
if (input instanceof Options || options instanceof Options) {
|
|
343
377
|
throw new TypeError('The defaults must be passed as the third argument');
|
|
344
378
|
}
|
|
@@ -456,7 +490,7 @@ export default class Options {
|
|
|
456
490
|
return this._internals.request;
|
|
457
491
|
}
|
|
458
492
|
set request(value) {
|
|
459
|
-
|
|
493
|
+
assertAny('request', [is.function, is.undefined], value);
|
|
460
494
|
this._internals.request = value;
|
|
461
495
|
}
|
|
462
496
|
/**
|
|
@@ -485,14 +519,14 @@ export default class Options {
|
|
|
485
519
|
return this._internals.agent;
|
|
486
520
|
}
|
|
487
521
|
set agent(value) {
|
|
488
|
-
|
|
522
|
+
assertPlainObject('agent', value);
|
|
489
523
|
// eslint-disable-next-line guard-for-in
|
|
490
524
|
for (const key in value) {
|
|
491
525
|
if (!(key in this._internals.agent)) {
|
|
492
526
|
throw new TypeError(`Unexpected agent option: ${key}`);
|
|
493
527
|
}
|
|
494
528
|
// @ts-expect-error - No idea why `value[key]` doesn't work here.
|
|
495
|
-
|
|
529
|
+
assertAny(`agent.${key}`, [is.object, is.undefined, (v) => v === false], value[key]);
|
|
496
530
|
}
|
|
497
531
|
if (this._merging) {
|
|
498
532
|
Object.assign(this._internals.agent, value);
|
|
@@ -545,14 +579,14 @@ export default class Options {
|
|
|
545
579
|
return this._internals.timeout;
|
|
546
580
|
}
|
|
547
581
|
set timeout(value) {
|
|
548
|
-
|
|
582
|
+
assertPlainObject('timeout', value);
|
|
549
583
|
// eslint-disable-next-line guard-for-in
|
|
550
584
|
for (const key in value) {
|
|
551
585
|
if (!(key in this._internals.timeout)) {
|
|
552
586
|
throw new Error(`Unexpected timeout option: ${key}`);
|
|
553
587
|
}
|
|
554
588
|
// @ts-expect-error - No idea why `value[key]` doesn't work here.
|
|
555
|
-
|
|
589
|
+
assertAny(`timeout.${key}`, [is.number, is.undefined], value[key]);
|
|
556
590
|
}
|
|
557
591
|
if (this._merging) {
|
|
558
592
|
Object.assign(this._internals.timeout, value);
|
|
@@ -606,7 +640,7 @@ export default class Options {
|
|
|
606
640
|
return this._internals.prefixUrl;
|
|
607
641
|
}
|
|
608
642
|
set prefixUrl(value) {
|
|
609
|
-
|
|
643
|
+
assertAny('prefixUrl', [is.string, is.urlInstance], value);
|
|
610
644
|
if (value === '') {
|
|
611
645
|
this._internals.prefixUrl = '';
|
|
612
646
|
return;
|
|
@@ -630,7 +664,7 @@ export default class Options {
|
|
|
630
664
|
|
|
631
665
|
__Note #4__: This option is not enumerable and will not be merged with the instance defaults.
|
|
632
666
|
|
|
633
|
-
The `content-length` header will be automatically set if `body` is a `string` / `Buffer` / [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) / [`form-data` instance](https://github.com/form-data/form-data), and `content-length` and `transfer-encoding` are not manually set in `options.headers`.
|
|
667
|
+
The `content-length` header will be automatically set if `body` is a `string` / `Buffer` / typed array ([`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), etc.) / [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) / [`form-data` instance](https://github.com/form-data/form-data), and `content-length` and `transfer-encoding` are not manually set in `options.headers`.
|
|
634
668
|
|
|
635
669
|
Since Got 12, the `content-length` is not automatically set when `body` is a `fs.createReadStream`.
|
|
636
670
|
|
|
@@ -655,7 +689,7 @@ export default class Options {
|
|
|
655
689
|
return this._internals.body;
|
|
656
690
|
}
|
|
657
691
|
set body(value) {
|
|
658
|
-
|
|
692
|
+
assertAny('body', [is.string, is.buffer, is.nodeStream, is.generator, is.asyncGenerator, is.iterable, is.asyncIterable, isFormData, is.typedArray, is.undefined], value);
|
|
659
693
|
if (is.nodeStream(value)) {
|
|
660
694
|
assert.truthy(value.readable);
|
|
661
695
|
}
|
|
@@ -678,7 +712,7 @@ export default class Options {
|
|
|
678
712
|
return this._internals.form;
|
|
679
713
|
}
|
|
680
714
|
set form(value) {
|
|
681
|
-
|
|
715
|
+
assertAny('form', [is.plainObject, is.undefined], value);
|
|
682
716
|
if (value !== undefined) {
|
|
683
717
|
assert.undefined(this._internals.body);
|
|
684
718
|
assert.undefined(this._internals.json);
|
|
@@ -686,7 +720,9 @@ export default class Options {
|
|
|
686
720
|
this._internals.form = value;
|
|
687
721
|
}
|
|
688
722
|
/**
|
|
689
|
-
JSON body. If the `
|
|
723
|
+
JSON request body. If the `content-type` header is not set, it will be set to `application/json`.
|
|
724
|
+
|
|
725
|
+
__Important__: This option only affects the request body you send to the server. To parse the response as JSON, you must either call `.json()` on the promise or set `responseType: 'json'` in the options.
|
|
690
726
|
|
|
691
727
|
__Note #1__: If you provide this option, `got.stream()` will be read-only.
|
|
692
728
|
|
|
@@ -724,7 +760,7 @@ export default class Options {
|
|
|
724
760
|
return this._internals.url;
|
|
725
761
|
}
|
|
726
762
|
set url(value) {
|
|
727
|
-
|
|
763
|
+
assertAny('url', [is.string, is.urlInstance, is.undefined], value);
|
|
728
764
|
if (value === undefined) {
|
|
729
765
|
this._internals.url = undefined;
|
|
730
766
|
return;
|
|
@@ -784,7 +820,7 @@ export default class Options {
|
|
|
784
820
|
return this._internals.cookieJar;
|
|
785
821
|
}
|
|
786
822
|
set cookieJar(value) {
|
|
787
|
-
|
|
823
|
+
assertAny('cookieJar', [is.object, is.undefined], value);
|
|
788
824
|
if (value === undefined) {
|
|
789
825
|
this._internals.cookieJar = undefined;
|
|
790
826
|
return;
|
|
@@ -871,7 +907,7 @@ export default class Options {
|
|
|
871
907
|
return this._internals.searchParams;
|
|
872
908
|
}
|
|
873
909
|
set searchParams(value) {
|
|
874
|
-
|
|
910
|
+
assertAny('searchParams', [is.string, is.object, is.undefined], value);
|
|
875
911
|
const url = this._internals.url;
|
|
876
912
|
if (value === undefined) {
|
|
877
913
|
this._internals.searchParams = undefined;
|
|
@@ -931,7 +967,7 @@ export default class Options {
|
|
|
931
967
|
return this._internals.dnsLookup;
|
|
932
968
|
}
|
|
933
969
|
set dnsLookup(value) {
|
|
934
|
-
|
|
970
|
+
assertAny('dnsLookup', [is.function, is.undefined], value);
|
|
935
971
|
this._internals.dnsLookup = value;
|
|
936
972
|
}
|
|
937
973
|
/**
|
|
@@ -948,7 +984,7 @@ export default class Options {
|
|
|
948
984
|
return this._internals.dnsCache;
|
|
949
985
|
}
|
|
950
986
|
set dnsCache(value) {
|
|
951
|
-
|
|
987
|
+
assertAny('dnsCache', [is.object, is.boolean, is.undefined], value);
|
|
952
988
|
if (value === true) {
|
|
953
989
|
this._internals.dnsCache = getGlobalDnsCache();
|
|
954
990
|
}
|
|
@@ -1018,7 +1054,7 @@ export default class Options {
|
|
|
1018
1054
|
}
|
|
1019
1055
|
const typedKnownHookEvent = knownHookEvent;
|
|
1020
1056
|
const hooks = value[typedKnownHookEvent];
|
|
1021
|
-
|
|
1057
|
+
assertAny(`hooks.${knownHookEvent}`, [is.array, is.undefined], hooks);
|
|
1022
1058
|
if (hooks) {
|
|
1023
1059
|
for (const hook of hooks) {
|
|
1024
1060
|
assert.function(hook);
|
|
@@ -1053,7 +1089,7 @@ export default class Options {
|
|
|
1053
1089
|
return this._internals.followRedirect;
|
|
1054
1090
|
}
|
|
1055
1091
|
set followRedirect(value) {
|
|
1056
|
-
|
|
1092
|
+
assertAny('followRedirect', [is.boolean, is.function], value);
|
|
1057
1093
|
this._internals.followRedirect = value;
|
|
1058
1094
|
}
|
|
1059
1095
|
get followRedirects() {
|
|
@@ -1083,7 +1119,7 @@ export default class Options {
|
|
|
1083
1119
|
return this._internals.cache;
|
|
1084
1120
|
}
|
|
1085
1121
|
set cache(value) {
|
|
1086
|
-
|
|
1122
|
+
assertAny('cache', [is.object, is.string, is.boolean, is.undefined], value);
|
|
1087
1123
|
if (value === true) {
|
|
1088
1124
|
this._internals.cache = globalCache;
|
|
1089
1125
|
}
|
|
@@ -1196,7 +1232,7 @@ export default class Options {
|
|
|
1196
1232
|
return this._internals.headers;
|
|
1197
1233
|
}
|
|
1198
1234
|
set headers(value) {
|
|
1199
|
-
|
|
1235
|
+
assertPlainObject('headers', value);
|
|
1200
1236
|
if (this._merging) {
|
|
1201
1237
|
Object.assign(this._internals.headers, lowercaseKeys(value));
|
|
1202
1238
|
}
|
|
@@ -1340,15 +1376,15 @@ export default class Options {
|
|
|
1340
1376
|
return this._internals.retry;
|
|
1341
1377
|
}
|
|
1342
1378
|
set retry(value) {
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1379
|
+
assertPlainObject('retry', value);
|
|
1380
|
+
assertAny('retry.calculateDelay', [is.function, is.undefined], value.calculateDelay);
|
|
1381
|
+
assertAny('retry.maxRetryAfter', [is.number, is.undefined], value.maxRetryAfter);
|
|
1382
|
+
assertAny('retry.limit', [is.number, is.undefined], value.limit);
|
|
1383
|
+
assertAny('retry.methods', [is.array, is.undefined], value.methods);
|
|
1384
|
+
assertAny('retry.statusCodes', [is.array, is.undefined], value.statusCodes);
|
|
1385
|
+
assertAny('retry.errorCodes', [is.array, is.undefined], value.errorCodes);
|
|
1386
|
+
assertAny('retry.noise', [is.number, is.undefined], value.noise);
|
|
1387
|
+
assertAny('retry.enforceRetryRules', [is.boolean, is.undefined], value.enforceRetryRules);
|
|
1352
1388
|
if (value.noise && Math.abs(value.noise) > 100) {
|
|
1353
1389
|
throw new Error(`The maximum acceptable retry noise is +/- 100ms, got ${value.noise}`);
|
|
1354
1390
|
}
|
|
@@ -1377,7 +1413,7 @@ export default class Options {
|
|
|
1377
1413
|
return this._internals.localAddress;
|
|
1378
1414
|
}
|
|
1379
1415
|
set localAddress(value) {
|
|
1380
|
-
|
|
1416
|
+
assertAny('localAddress', [is.string, is.undefined], value);
|
|
1381
1417
|
this._internals.localAddress = value;
|
|
1382
1418
|
}
|
|
1383
1419
|
/**
|
|
@@ -1396,7 +1432,7 @@ export default class Options {
|
|
|
1396
1432
|
return this._internals.createConnection;
|
|
1397
1433
|
}
|
|
1398
1434
|
set createConnection(value) {
|
|
1399
|
-
|
|
1435
|
+
assertAny('createConnection', [is.function, is.undefined], value);
|
|
1400
1436
|
this._internals.createConnection = value;
|
|
1401
1437
|
}
|
|
1402
1438
|
/**
|
|
@@ -1408,11 +1444,11 @@ export default class Options {
|
|
|
1408
1444
|
return this._internals.cacheOptions;
|
|
1409
1445
|
}
|
|
1410
1446
|
set cacheOptions(value) {
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1447
|
+
assertPlainObject('cacheOptions', value);
|
|
1448
|
+
assertAny('cacheOptions.shared', [is.boolean, is.undefined], value.shared);
|
|
1449
|
+
assertAny('cacheOptions.cacheHeuristic', [is.number, is.undefined], value.cacheHeuristic);
|
|
1450
|
+
assertAny('cacheOptions.immutableMinTimeToLive', [is.number, is.undefined], value.immutableMinTimeToLive);
|
|
1451
|
+
assertAny('cacheOptions.ignoreCargoCult', [is.boolean, is.undefined], value.ignoreCargoCult);
|
|
1416
1452
|
for (const key in value) {
|
|
1417
1453
|
if (!(key in this._internals.cacheOptions)) {
|
|
1418
1454
|
throw new Error(`Cache option \`${key}\` does not exist`);
|
|
@@ -1432,25 +1468,26 @@ export default class Options {
|
|
|
1432
1468
|
return this._internals.https;
|
|
1433
1469
|
}
|
|
1434
1470
|
set https(value) {
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1471
|
+
assertPlainObject('https', value);
|
|
1472
|
+
assertAny('https.rejectUnauthorized', [is.boolean, is.undefined], value.rejectUnauthorized);
|
|
1473
|
+
assertAny('https.checkServerIdentity', [is.function, is.undefined], value.checkServerIdentity);
|
|
1474
|
+
assertAny('https.serverName', [is.string, is.undefined], value.serverName);
|
|
1475
|
+
assertAny('https.certificateAuthority', [is.string, is.object, is.array, is.undefined], value.certificateAuthority);
|
|
1476
|
+
assertAny('https.key', [is.string, is.object, is.array, is.undefined], value.key);
|
|
1477
|
+
assertAny('https.certificate', [is.string, is.object, is.array, is.undefined], value.certificate);
|
|
1478
|
+
assertAny('https.passphrase', [is.string, is.undefined], value.passphrase);
|
|
1479
|
+
assertAny('https.pfx', [is.string, is.buffer, is.array, is.undefined], value.pfx);
|
|
1480
|
+
assertAny('https.alpnProtocols', [is.array, is.undefined], value.alpnProtocols);
|
|
1481
|
+
assertAny('https.ciphers', [is.string, is.undefined], value.ciphers);
|
|
1482
|
+
assertAny('https.dhparam', [is.string, is.buffer, is.undefined], value.dhparam);
|
|
1483
|
+
assertAny('https.signatureAlgorithms', [is.string, is.undefined], value.signatureAlgorithms);
|
|
1484
|
+
assertAny('https.minVersion', [is.string, is.undefined], value.minVersion);
|
|
1485
|
+
assertAny('https.maxVersion', [is.string, is.undefined], value.maxVersion);
|
|
1486
|
+
assertAny('https.honorCipherOrder', [is.boolean, is.undefined], value.honorCipherOrder);
|
|
1487
|
+
assertAny('https.tlsSessionLifetime', [is.number, is.undefined], value.tlsSessionLifetime);
|
|
1488
|
+
assertAny('https.ecdhCurve', [is.string, is.undefined], value.ecdhCurve);
|
|
1489
|
+
assertAny('https.certificateRevocationLists', [is.string, is.buffer, is.array, is.undefined], value.certificateRevocationLists);
|
|
1490
|
+
assertAny('https.secureOptions', [is.number, is.undefined], value.secureOptions);
|
|
1454
1491
|
for (const key in value) {
|
|
1455
1492
|
if (!(key in this._internals.https)) {
|
|
1456
1493
|
throw new Error(`HTTPS option \`${key}\` does not exist`);
|
|
@@ -1480,7 +1517,7 @@ export default class Options {
|
|
|
1480
1517
|
if (value === null) {
|
|
1481
1518
|
throw new TypeError('To get a Buffer, set `options.responseType` to `buffer` instead');
|
|
1482
1519
|
}
|
|
1483
|
-
|
|
1520
|
+
assertAny('encoding', [is.string, is.undefined], value);
|
|
1484
1521
|
this._internals.encoding = value;
|
|
1485
1522
|
}
|
|
1486
1523
|
/**
|
|
@@ -1580,7 +1617,7 @@ export default class Options {
|
|
|
1580
1617
|
return this._internals.maxHeaderSize;
|
|
1581
1618
|
}
|
|
1582
1619
|
set maxHeaderSize(value) {
|
|
1583
|
-
|
|
1620
|
+
assertAny('maxHeaderSize', [is.number, is.undefined], value);
|
|
1584
1621
|
this._internals.maxHeaderSize = value;
|
|
1585
1622
|
}
|
|
1586
1623
|
get enableUnixSockets() {
|
|
@@ -1590,6 +1627,23 @@ export default class Options {
|
|
|
1590
1627
|
assert.boolean(value);
|
|
1591
1628
|
this._internals.enableUnixSockets = value;
|
|
1592
1629
|
}
|
|
1630
|
+
/**
|
|
1631
|
+
Throw an error if the server response's `content-length` header value doesn't match the number of bytes received.
|
|
1632
|
+
|
|
1633
|
+
This is useful for detecting truncated responses and follows RFC 9112 requirements for message completeness.
|
|
1634
|
+
|
|
1635
|
+
__Note__: Responses without a `content-length` header are not validated.
|
|
1636
|
+
__Note__: When enabled and validation fails, a `ReadError` with code `ERR_HTTP_CONTENT_LENGTH_MISMATCH` will be thrown.
|
|
1637
|
+
|
|
1638
|
+
@default false
|
|
1639
|
+
*/
|
|
1640
|
+
get strictContentLength() {
|
|
1641
|
+
return this._internals.strictContentLength;
|
|
1642
|
+
}
|
|
1643
|
+
set strictContentLength(value) {
|
|
1644
|
+
assert.boolean(value);
|
|
1645
|
+
this._internals.strictContentLength = value;
|
|
1646
|
+
}
|
|
1593
1647
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
1594
1648
|
toJSON() {
|
|
1595
1649
|
return { ...this._internals };
|
|
@@ -1648,6 +1702,7 @@ export default class Options {
|
|
|
1648
1702
|
dhparam: https.dhparam,
|
|
1649
1703
|
ecdhCurve: https.ecdhCurve,
|
|
1650
1704
|
crl: https.certificateRevocationLists,
|
|
1705
|
+
secureOptions: https.secureOptions,
|
|
1651
1706
|
// HTTP options
|
|
1652
1707
|
lookup: internals.dnsLookup ?? internals.dnsCache?.lookup,
|
|
1653
1708
|
family: internals.dnsLookupIpVersion,
|
|
@@ -15,6 +15,9 @@ export default async function getBodySize(body, headers) {
|
|
|
15
15
|
if (is.buffer(body)) {
|
|
16
16
|
return body.length;
|
|
17
17
|
}
|
|
18
|
+
if (is.typedArray(body)) {
|
|
19
|
+
return body.byteLength;
|
|
20
|
+
}
|
|
18
21
|
if (isFormData(body)) {
|
|
19
22
|
try {
|
|
20
23
|
return await promisify(body.getLength.bind(body))();
|
|
@@ -1 +1,17 @@
|
|
|
1
1
|
export default function isUnixSocketURL(url: URL): boolean;
|
|
2
|
+
/**
|
|
3
|
+
Extract the socket path from a UNIX socket URL.
|
|
4
|
+
|
|
5
|
+
@example
|
|
6
|
+
```
|
|
7
|
+
getUnixSocketPath(new URL('http://unix/foo:/path'));
|
|
8
|
+
//=> '/foo'
|
|
9
|
+
|
|
10
|
+
getUnixSocketPath(new URL('unix:/foo:/path'));
|
|
11
|
+
//=> '/foo'
|
|
12
|
+
|
|
13
|
+
getUnixSocketPath(new URL('http://example.com'));
|
|
14
|
+
//=> undefined
|
|
15
|
+
```
|
|
16
|
+
*/
|
|
17
|
+
export declare function getUnixSocketPath(url: URL): string | undefined;
|
|
@@ -2,3 +2,24 @@
|
|
|
2
2
|
export default function isUnixSocketURL(url) {
|
|
3
3
|
return url.protocol === 'unix:' || url.hostname === 'unix';
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
Extract the socket path from a UNIX socket URL.
|
|
7
|
+
|
|
8
|
+
@example
|
|
9
|
+
```
|
|
10
|
+
getUnixSocketPath(new URL('http://unix/foo:/path'));
|
|
11
|
+
//=> '/foo'
|
|
12
|
+
|
|
13
|
+
getUnixSocketPath(new URL('unix:/foo:/path'));
|
|
14
|
+
//=> '/foo'
|
|
15
|
+
|
|
16
|
+
getUnixSocketPath(new URL('http://example.com'));
|
|
17
|
+
//=> undefined
|
|
18
|
+
```
|
|
19
|
+
*/
|
|
20
|
+
export function getUnixSocketPath(url) {
|
|
21
|
+
if (!isUnixSocketURL(url)) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return /(?<socketPath>.+?):(?<path>.+)/.exec(`${url.pathname}${url.search}`)?.groups?.socketPath;
|
|
25
|
+
}
|
package/dist/source/create.js
CHANGED
|
@@ -139,7 +139,15 @@ const create = (defaults) => {
|
|
|
139
139
|
}
|
|
140
140
|
else {
|
|
141
141
|
normalizedOptions.merge(optionsToMerge);
|
|
142
|
-
|
|
142
|
+
try {
|
|
143
|
+
assert.any([is.urlInstance, is.undefined], optionsToMerge.url);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (error instanceof Error) {
|
|
147
|
+
error.message = `Option 'pagination.paginate.url': ${error.message}`;
|
|
148
|
+
}
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
143
151
|
if (optionsToMerge.url !== undefined) {
|
|
144
152
|
normalizedOptions.prefixUrl = '';
|
|
145
153
|
normalizedOptions.url = optionsToMerge.url;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "got",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.6.0",
|
|
4
4
|
"description": "Human-friendly and powerful HTTP request library for Node.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "sindresorhus/got",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"test": "xo && tsc --noEmit && NODE_OPTIONS='--import=tsx/esm' ava",
|
|
19
|
+
"test:coverage": "xo && tsc --noEmit && NODE_OPTIONS='--import=tsx/esm' c8 ava",
|
|
19
20
|
"release": "np",
|
|
20
21
|
"build": "del-cli dist && tsc",
|
|
21
22
|
"prepare": "npm run build"
|
|
@@ -40,6 +41,8 @@
|
|
|
40
41
|
"network",
|
|
41
42
|
"gzip",
|
|
42
43
|
"brotli",
|
|
44
|
+
"zstd",
|
|
45
|
+
"zstandard",
|
|
43
46
|
"requests",
|
|
44
47
|
"human-friendly",
|
|
45
48
|
"axios",
|
|
@@ -52,7 +55,7 @@
|
|
|
52
55
|
"@szmarczak/http-timer": "^5.0.1",
|
|
53
56
|
"cacheable-lookup": "^7.0.0",
|
|
54
57
|
"cacheable-request": "^13.0.12",
|
|
55
|
-
"decompress-response": "^
|
|
58
|
+
"decompress-response": "^10.0.0",
|
|
56
59
|
"form-data-encoder": "^4.0.2",
|
|
57
60
|
"http2-wrapper": "^2.2.1",
|
|
58
61
|
"keyv": "^5.5.3",
|
|
@@ -78,6 +81,7 @@
|
|
|
78
81
|
"benchmark": "^2.1.4",
|
|
79
82
|
"bluebird": "^3.7.2",
|
|
80
83
|
"body-parser": "^1.20.3",
|
|
84
|
+
"c8": "^10.1.3",
|
|
81
85
|
"create-cert": "^1.0.6",
|
|
82
86
|
"create-test-server": "^3.0.1",
|
|
83
87
|
"del-cli": "^6.0.0",
|
|
@@ -90,7 +94,6 @@
|
|
|
90
94
|
"nock": "^13.5.5",
|
|
91
95
|
"node-fetch": "^3.3.2",
|
|
92
96
|
"np": "^10.0.5",
|
|
93
|
-
"nyc": "^17.1.0",
|
|
94
97
|
"p-event": "^6.0.1",
|
|
95
98
|
"pem": "^1.14.8",
|
|
96
99
|
"pify": "^6.1.0",
|
|
@@ -117,17 +120,15 @@
|
|
|
117
120
|
},
|
|
118
121
|
"workerThreads": false
|
|
119
122
|
},
|
|
120
|
-
"
|
|
123
|
+
"c8": {
|
|
121
124
|
"reporter": [
|
|
122
125
|
"text",
|
|
123
126
|
"html",
|
|
124
127
|
"lcov"
|
|
125
128
|
],
|
|
126
|
-
"extension": [
|
|
127
|
-
".ts"
|
|
128
|
-
],
|
|
129
129
|
"exclude": [
|
|
130
|
-
"
|
|
130
|
+
"test/**",
|
|
131
|
+
"dist/**"
|
|
131
132
|
]
|
|
132
133
|
},
|
|
133
134
|
"xo": {
|
package/readme.md
CHANGED
|
@@ -127,6 +127,7 @@ By default, Got will retry on failure. To disable this option, set [`options.ret
|
|
|
127
127
|
|
|
128
128
|
#### Integration
|
|
129
129
|
|
|
130
|
+
- [x] [Diagnostics Channel](documentation/diagnostics-channel.md)
|
|
130
131
|
- [x] [TypeScript support](documentation/typescript.md)
|
|
131
132
|
- [x] [AWS](documentation/tips.md#aws)
|
|
132
133
|
- [x] [Testing](documentation/tips.md#testing)
|