got 11.4.0 → 11.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.
Files changed (31) hide show
  1. package/dist/source/as-promise/index.d.ts +1 -3
  2. package/dist/source/as-promise/index.js +56 -117
  3. package/dist/source/as-promise/normalize-arguments.d.ts +3 -0
  4. package/dist/source/as-promise/normalize-arguments.js +78 -0
  5. package/dist/source/as-promise/parse-body.d.ts +3 -0
  6. package/dist/source/as-promise/parse-body.js +25 -0
  7. package/dist/source/as-promise/types.d.ts +235 -58
  8. package/dist/source/as-promise/types.js +29 -16
  9. package/dist/source/{as-promise → core}/calculate-retry-delay.d.ts +2 -1
  10. package/dist/source/core/calculate-retry-delay.js +29 -0
  11. package/dist/source/core/index.d.ts +851 -28
  12. package/dist/source/core/index.js +291 -55
  13. package/dist/source/core/utils/dns-ip-version.js +1 -0
  14. package/dist/source/core/utils/get-body-size.d.ts +2 -4
  15. package/dist/source/core/utils/is-form-data.d.ts +2 -2
  16. package/dist/source/core/utils/is-response-ok.d.ts +2 -0
  17. package/dist/source/core/utils/is-response-ok.js +8 -0
  18. package/dist/source/core/utils/options-to-url.d.ts +0 -1
  19. package/dist/source/core/utils/timed-out.js +1 -0
  20. package/dist/source/core/utils/url-to-options.d.ts +0 -1
  21. package/dist/source/core/utils/weakable-map.d.ts +1 -1
  22. package/dist/source/create.js +42 -13
  23. package/dist/source/index.js +16 -6
  24. package/dist/source/types.d.ts +245 -8
  25. package/dist/source/utils/deep-freeze.d.ts +1 -1
  26. package/dist/source/utils/deprecation-warning.js +1 -1
  27. package/package.json +30 -29
  28. package/readme.md +171 -35
  29. package/dist/source/as-promise/calculate-retry-delay.js +0 -38
  30. package/dist/source/as-promise/core.d.ts +0 -13
  31. package/dist/source/as-promise/core.js +0 -124
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UnsupportedProtocolError = exports.ReadError = exports.TimeoutError = exports.UploadError = exports.CacheError = exports.HTTPError = exports.MaxRedirectsError = exports.RequestError = exports.setNonEnumerableProperties = exports.knownHookEvents = exports.withoutBody = exports.kIsNormalizedAlready = void 0;
3
4
  const util_1 = require("util");
4
5
  const stream_1 = require("stream");
5
6
  const fs_1 = require("fs");
@@ -11,7 +12,7 @@ const http_timer_1 = require("@szmarczak/http-timer");
11
12
  const cacheable_lookup_1 = require("cacheable-lookup");
12
13
  const CacheableRequest = require("cacheable-request");
13
14
  const decompressResponse = require("decompress-response");
14
- // @ts-ignore Missing types
15
+ // @ts-expect-error Missing types
15
16
  const http2wrapper = require("http2-wrapper");
16
17
  const lowercaseKeys = require("lowercase-keys");
17
18
  const is_1 = require("@sindresorhus/is");
@@ -24,7 +25,11 @@ const options_to_url_1 = require("./utils/options-to-url");
24
25
  const weakable_map_1 = require("./utils/weakable-map");
25
26
  const get_buffer_1 = require("./utils/get-buffer");
26
27
  const dns_ip_version_1 = require("./utils/dns-ip-version");
28
+ const is_response_ok_1 = require("./utils/is-response-ok");
27
29
  const deprecation_warning_1 = require("../utils/deprecation-warning");
30
+ const normalize_arguments_1 = require("../as-promise/normalize-arguments");
31
+ const calculate_retry_delay_1 = require("./calculate-retry-delay");
32
+ const globalDnsCache = new cacheable_lookup_1.default();
28
33
  const kRequest = Symbol('request');
29
34
  const kResponse = Symbol('response');
30
35
  const kResponseSize = Symbol('responseSize');
@@ -41,10 +46,19 @@ const kTriggerRead = Symbol('triggerRead');
41
46
  const kBody = Symbol('body');
42
47
  const kJobs = Symbol('jobs');
43
48
  const kOriginalResponse = Symbol('originalResponse');
49
+ const kRetryTimeout = Symbol('retryTimeout');
44
50
  exports.kIsNormalizedAlready = Symbol('isNormalizedAlready');
45
51
  const supportsBrotli = is_1.default.string(process.versions.brotli);
46
52
  exports.withoutBody = new Set(['GET', 'HEAD']);
47
- exports.knownHookEvents = ['init', 'beforeRequest', 'beforeRedirect', 'beforeError'];
53
+ exports.knownHookEvents = [
54
+ 'init',
55
+ 'beforeRequest',
56
+ 'beforeRedirect',
57
+ 'beforeError',
58
+ 'beforeRetry',
59
+ // Promise-Only
60
+ 'afterResponse'
61
+ ];
48
62
  function validateSearchParameters(searchParameters) {
49
63
  // eslint-disable-next-line guard-for-in
50
64
  for (const key in searchParameters) {
@@ -62,6 +76,7 @@ const waitForOpenFile = async (file) => new Promise((resolve, reject) => {
62
76
  const onError = (error) => {
63
77
  reject(error);
64
78
  };
79
+ // Node.js 12 has incomplete types
65
80
  if (!file.pending) {
66
81
  resolve();
67
82
  }
@@ -93,13 +108,17 @@ exports.setNonEnumerableProperties = (sources, to) => {
93
108
  writable: true,
94
109
  configurable: true,
95
110
  enumerable: false,
96
- // @ts-ignore TS doesn't see the check above
111
+ // @ts-expect-error TS doesn't see the check above
97
112
  value: source[name]
98
113
  };
99
114
  }
100
115
  }
101
116
  Object.defineProperties(to, properties);
102
117
  };
118
+ /**
119
+ An error to be thrown when a request fails.
120
+ Contains a `code` property with error class code, like `ECONNREFUSED`.
121
+ */
103
122
  class RequestError extends Error {
104
123
  constructor(message, error, self) {
105
124
  var _a;
@@ -146,6 +165,10 @@ class RequestError extends Error {
146
165
  }
147
166
  }
148
167
  exports.RequestError = RequestError;
168
+ /**
169
+ An error to be thrown when the server redirects you more than ten times.
170
+ Includes a `response` property.
171
+ */
149
172
  class MaxRedirectsError extends RequestError {
150
173
  constructor(request) {
151
174
  super(`Redirected ${request.options.maxRedirects} times. Aborting.`, {}, request);
@@ -153,6 +176,10 @@ class MaxRedirectsError extends RequestError {
153
176
  }
154
177
  }
155
178
  exports.MaxRedirectsError = MaxRedirectsError;
179
+ /**
180
+ An error to be thrown when the server response code is not 2xx nor 3xx if `options.followRedirect` is `true`, but always except for 304.
181
+ Includes a `response` property.
182
+ */
156
183
  class HTTPError extends RequestError {
157
184
  constructor(response) {
158
185
  super(`Response code ${response.statusCode} (${response.statusMessage})`, {}, response.request);
@@ -160,6 +187,10 @@ class HTTPError extends RequestError {
160
187
  }
161
188
  }
162
189
  exports.HTTPError = HTTPError;
190
+ /**
191
+ An error to be thrown when a cache method fails.
192
+ For example, if the database goes down or there's a filesystem error.
193
+ */
163
194
  class CacheError extends RequestError {
164
195
  constructor(error, request) {
165
196
  super(error.message, error, request);
@@ -167,6 +198,9 @@ class CacheError extends RequestError {
167
198
  }
168
199
  }
169
200
  exports.CacheError = CacheError;
201
+ /**
202
+ An error to be thrown when the request body is a stream and an error occurs while reading from that stream.
203
+ */
170
204
  class UploadError extends RequestError {
171
205
  constructor(error, request) {
172
206
  super(error.message, error, request);
@@ -174,6 +208,10 @@ class UploadError extends RequestError {
174
208
  }
175
209
  }
176
210
  exports.UploadError = UploadError;
211
+ /**
212
+ An error to be thrown when the request is aborted due to a timeout.
213
+ Includes an `event` and `timings` property.
214
+ */
177
215
  class TimeoutError extends RequestError {
178
216
  constructor(error, timings, request) {
179
217
  super(error.message, error, request);
@@ -183,6 +221,9 @@ class TimeoutError extends RequestError {
183
221
  }
184
222
  }
185
223
  exports.TimeoutError = TimeoutError;
224
+ /**
225
+ An error to be thrown when reading from response stream fails.
226
+ */
186
227
  class ReadError extends RequestError {
187
228
  constructor(error, request) {
188
229
  super(error.message, error, request);
@@ -190,6 +231,9 @@ class ReadError extends RequestError {
190
231
  }
191
232
  }
192
233
  exports.ReadError = ReadError;
234
+ /**
235
+ An error to be thrown when given an unsupported protocol.
236
+ */
193
237
  class UnsupportedProtocolError extends RequestError {
194
238
  constructor(options) {
195
239
  super(`Unsupported protocol "${options.url.protocol}"`, {}, options);
@@ -208,6 +252,9 @@ const proxiedRequestEvents = [
208
252
  class Request extends stream_1.Duplex {
209
253
  constructor(url, options = {}, defaults) {
210
254
  super({
255
+ // This must be false, to enable throwing after destroy
256
+ // It is used for retry logic in Promise API
257
+ autoDestroy: false,
211
258
  // It needs to be zero because we're just proxying the data to another stream
212
259
  highWaterMark: 0
213
260
  });
@@ -219,6 +266,7 @@ class Request extends stream_1.Duplex {
219
266
  this[kStopReading] = false;
220
267
  this[kTriggerRead] = false;
221
268
  this[kJobs] = [];
269
+ this.retryCount = 0;
222
270
  // TODO: Remove this when targeting Node.js >= 12
223
271
  this._progressCallbacks = [];
224
272
  const unlockWrite = () => this._unlockWrite();
@@ -257,7 +305,7 @@ class Request extends stream_1.Duplex {
257
305
  this.options = nonNormalizedOptions;
258
306
  }
259
307
  else {
260
- // @ts-ignore Common TypeScript bug saying that `this.constructor` is not accessible
308
+ // @ts-expect-error Common TypeScript bug saying that `this.constructor` is not accessible
261
309
  this.options = this.constructor.normalizeArguments(url, nonNormalizedOptions, defaults);
262
310
  }
263
311
  const { url: normalizedURL } = this.options;
@@ -276,6 +324,8 @@ class Request extends stream_1.Duplex {
276
324
  for (const job of this[kJobs]) {
277
325
  job();
278
326
  }
327
+ // Prevent memory leak
328
+ this[kJobs].length = 0;
279
329
  this.requestInitialized = true;
280
330
  }
281
331
  catch (error) {
@@ -345,6 +395,7 @@ class Request extends stream_1.Duplex {
345
395
  is_1.assert.any([is_1.default.string, is_1.default.object, is_1.default.array, is_1.default.undefined], options.https.certificate);
346
396
  is_1.assert.any([is_1.default.string, is_1.default.undefined], options.https.passphrase);
347
397
  }
398
+ is_1.assert.any([is_1.default.object, is_1.default.undefined], options.cacheOptions);
348
399
  // `options.method`
349
400
  if (is_1.default.string(options.method)) {
350
401
  options.method = options.method.toUpperCase();
@@ -442,6 +493,7 @@ class Request extends stream_1.Duplex {
442
493
  }
443
494
  // Set search params
444
495
  if (options.searchParams) {
496
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
445
497
  options.url.search = options.searchParams.toString();
446
498
  }
447
499
  // Protocol check
@@ -475,7 +527,7 @@ class Request extends stream_1.Duplex {
475
527
  getCookieString = util_1.promisify(getCookieString.bind(options.cookieJar));
476
528
  options.cookieJar = {
477
529
  setCookie,
478
- getCookieString
530
+ getCookieString: getCookieString
479
531
  };
480
532
  }
481
533
  }
@@ -483,12 +535,44 @@ class Request extends stream_1.Duplex {
483
535
  const { cache } = options;
484
536
  if (cache) {
485
537
  if (!cacheableStore.has(cache)) {
486
- cacheableStore.set(cache, new CacheableRequest(((requestOptions, handler) => requestOptions[kRequest](requestOptions, handler)), cache));
538
+ cacheableStore.set(cache, new CacheableRequest(((requestOptions, handler) => {
539
+ const result = requestOptions[kRequest](requestOptions, handler);
540
+ // TODO: remove this when `cacheable-request` supports async request functions.
541
+ if (is_1.default.promise(result)) {
542
+ // @ts-expect-error
543
+ // We only need to implement the error handler in order to support HTTP2 caching.
544
+ // The result will be a promise anyway.
545
+ result.once = (event, handler) => {
546
+ if (event === 'error') {
547
+ result.catch(handler);
548
+ }
549
+ else if (event === 'abort') {
550
+ // The empty catch is needed here in case when
551
+ // it rejects before it's `await`ed in `_makeRequest`.
552
+ (async () => {
553
+ try {
554
+ const request = (await result);
555
+ request.once('abort', handler);
556
+ }
557
+ catch (_a) { }
558
+ })();
559
+ }
560
+ else {
561
+ /* istanbul ignore next: safety check */
562
+ throw new Error(`Unknown HTTP2 promise event: ${event}`);
563
+ }
564
+ return result;
565
+ };
566
+ }
567
+ return result;
568
+ }), cache));
487
569
  }
488
570
  }
571
+ // `options.cacheOptions`
572
+ options.cacheOptions = { ...options.cacheOptions };
489
573
  // `options.dnsCache`
490
574
  if (options.dnsCache === true) {
491
- options.dnsCache = new cacheable_lookup_1.default();
575
+ options.dnsCache = globalDnsCache;
492
576
  }
493
577
  else if (!is_1.default.undefined(options.dnsCache) && !options.dnsCache.lookup) {
494
578
  throw new TypeError(`Parameter \`dnsCache\` must be a CacheableLookup instance or a boolean, got ${is_1.default(options.dnsCache)}`);
@@ -579,7 +663,7 @@ class Request extends stream_1.Duplex {
579
663
  options.maxRedirects = (_d = options.maxRedirects) !== null && _d !== void 0 ? _d : 0;
580
664
  // Set non-enumerable properties
581
665
  exports.setNonEnumerableProperties([defaults, rawOptions], options);
582
- return options;
666
+ return normalize_arguments_1.default(options, defaults);
583
667
  }
584
668
  _lockWrite() {
585
669
  const onLockedWrite = () => {
@@ -665,7 +749,7 @@ class Request extends stream_1.Duplex {
665
749
  }
666
750
  this[kBodySize] = Number(headers['content-length']) || undefined;
667
751
  }
668
- async _onResponse(response) {
752
+ async _onResponseBase(response) {
669
753
  const { options } = this;
670
754
  const { url } = options;
671
755
  this[kOriginalResponse] = response;
@@ -681,6 +765,7 @@ class Request extends stream_1.Duplex {
681
765
  typedResponse.request = this;
682
766
  typedResponse.isFromCache = response.fromCache || false;
683
767
  typedResponse.ip = this.ip;
768
+ typedResponse.retryCount = this.retryCount;
684
769
  this[kIsFromCache] = typedResponse.isFromCache;
685
770
  this[kResponseSize] = Number(response.headers['content-length']) || undefined;
686
771
  this[kResponse] = response;
@@ -718,7 +803,7 @@ class Request extends stream_1.Duplex {
718
803
  }
719
804
  if (options.followRedirect && response.headers.location && redirectCodes.has(statusCode)) {
720
805
  // We're being redirected, we don't care about the response.
721
- // It'd be besto to abort the request, but we can't because
806
+ // It'd be best to abort the request, but we can't because
722
807
  // we would have to sacrifice the TCP connection. We don't want that.
723
808
  response.resume();
724
809
  if (this[kRequest]) {
@@ -765,7 +850,10 @@ class Request extends stream_1.Duplex {
765
850
  delete options.headers.authorization;
766
851
  }
767
852
  if (options.username || options.password) {
853
+ // TODO: Fix this ignore.
854
+ // @ts-expect-error
768
855
  delete options.username;
856
+ // @ts-expect-error
769
857
  delete options.password;
770
858
  }
771
859
  }
@@ -784,13 +872,9 @@ class Request extends stream_1.Duplex {
784
872
  }
785
873
  return;
786
874
  }
787
- const limitStatusCode = options.followRedirect ? 299 : 399;
788
- const isOk = (statusCode >= 200 && statusCode <= limitStatusCode) || statusCode === 304;
789
- if (options.throwHttpErrors && !isOk) {
790
- await this._beforeError(new HTTPError(typedResponse));
791
- if (this.destroyed) {
792
- return;
793
- }
875
+ if (options.isStream && options.throwHttpErrors && !is_response_ok_1.isResponseOk(typedResponse)) {
876
+ this._beforeError(new HTTPError(typedResponse));
877
+ return;
794
878
  }
795
879
  response.on('readable', () => {
796
880
  if (this[kTriggerRead]) {
@@ -822,6 +906,15 @@ class Request extends stream_1.Duplex {
822
906
  destination.statusCode = statusCode;
823
907
  }
824
908
  }
909
+ async _onResponse(response) {
910
+ try {
911
+ await this._onResponseBase(response);
912
+ }
913
+ catch (error) {
914
+ /* istanbul ignore next: better safe than sorry */
915
+ this._beforeError(error);
916
+ }
917
+ }
825
918
  _onRequest(request) {
826
919
  const { options } = this;
827
920
  const { timeout, url } = options;
@@ -829,11 +922,14 @@ class Request extends stream_1.Duplex {
829
922
  this[kCancelTimeouts] = timed_out_1.default(request, timeout, url);
830
923
  const responseEventName = options.cache ? 'cacheableResponse' : 'response';
831
924
  request.once(responseEventName, (response) => {
832
- this._onResponse(response);
925
+ void this._onResponse(response);
833
926
  });
834
927
  request.once('error', (error) => {
928
+ var _a;
835
929
  // Force clean-up, because some packages (e.g. nock) don't do this.
836
930
  request.destroy();
931
+ // Node.js <= 12.18.2 mistakenly emits the response `end` first.
932
+ (_a = request.res) === null || _a === void 0 ? void 0 : _a.removeAllListeners('end');
837
933
  if (error instanceof timed_out_1.TimeoutError) {
838
934
  error = new TimeoutError(error, this.timings, this);
839
935
  }
@@ -853,14 +949,11 @@ class Request extends stream_1.Duplex {
853
949
  body.once('error', (error) => {
854
950
  this._beforeError(new UploadError(error, this));
855
951
  });
856
- body.once('end', () => {
857
- delete options.body;
858
- });
859
952
  }
860
953
  else {
861
954
  this._unlockWrite();
862
955
  if (!is_1.default.undefined(body)) {
863
- this._writeRequest(body, null, () => { });
956
+ this._writeRequest(body, undefined, () => { });
864
957
  currentRequest.end();
865
958
  this._lockWrite();
866
959
  }
@@ -876,26 +969,30 @@ class Request extends stream_1.Duplex {
876
969
  // TODO: Remove `utils/url-to-options.ts` when `cacheable-request` is fixed
877
970
  Object.assign(options, url_to_options_1.default(url));
878
971
  // `http-cache-semantics` checks this
972
+ // TODO: Fix this ignore.
973
+ // @ts-expect-error
879
974
  delete options.url;
975
+ let request;
880
976
  // This is ugly
881
- const cacheRequest = cacheableStore.get(options.cache)(options, response => {
882
- const typedResponse = response;
883
- const { req } = typedResponse;
977
+ const cacheRequest = cacheableStore.get(options.cache)(options, async (response) => {
884
978
  // TODO: Fix `cacheable-response`
885
- typedResponse._readableState.autoDestroy = false;
886
- if (req) {
887
- req.emit('cacheableResponse', typedResponse);
979
+ response._readableState.autoDestroy = false;
980
+ if (request) {
981
+ (await request).emit('cacheableResponse', response);
888
982
  }
889
- resolve(typedResponse);
983
+ resolve(response);
890
984
  });
891
985
  // Restore options
892
986
  options.url = url;
893
987
  cacheRequest.once('error', reject);
894
- cacheRequest.once('request', resolve);
988
+ cacheRequest.once('request', async (requestOrPromise) => {
989
+ request = requestOrPromise;
990
+ resolve(request);
991
+ });
895
992
  });
896
993
  }
897
994
  async _makeRequest() {
898
- var _a;
995
+ var _a, _b, _c, _d, _e;
899
996
  const { options } = this;
900
997
  const { headers } = options;
901
998
  for (const key in headers) {
@@ -921,7 +1018,7 @@ class Request extends stream_1.Duplex {
921
1018
  // eslint-disable-next-line no-await-in-loop
922
1019
  const result = await hook(options);
923
1020
  if (!is_1.default.undefined(result)) {
924
- // @ts-ignore Skip the type mismatch to support abstract responses
1021
+ // @ts-expect-error Skip the type mismatch to support abstract responses
925
1022
  options.request = () => result;
926
1023
  break;
927
1024
  }
@@ -961,14 +1058,20 @@ class Request extends stream_1.Duplex {
961
1058
  // Prepare plain HTTP request options
962
1059
  options[kRequest] = realFn;
963
1060
  delete options.request;
1061
+ // TODO: Fix this ignore.
1062
+ // @ts-expect-error
964
1063
  delete options.timeout;
965
1064
  const requestOptions = options;
1065
+ requestOptions.shared = (_b = options.cacheOptions) === null || _b === void 0 ? void 0 : _b.shared;
1066
+ requestOptions.cacheHeuristic = (_c = options.cacheOptions) === null || _c === void 0 ? void 0 : _c.cacheHeuristic;
1067
+ requestOptions.immutableMinTimeToLive = (_d = options.cacheOptions) === null || _d === void 0 ? void 0 : _d.immutableMinTimeToLive;
1068
+ requestOptions.ignoreCargoCult = (_e = options.cacheOptions) === null || _e === void 0 ? void 0 : _e.ignoreCargoCult;
966
1069
  // If `dnsLookupIpVersion` is not present do not override `family`
967
1070
  if (options.dnsLookupIpVersion !== undefined) {
968
1071
  try {
969
1072
  requestOptions.family = dns_ip_version_1.dnsLookupIpVersionToFamily(options.dnsLookupIpVersion);
970
1073
  }
971
- catch (_b) {
1074
+ catch (_f) {
972
1075
  throw new Error('Invalid `dnsLookupIpVersion` option value');
973
1076
  }
974
1077
  }
@@ -1002,20 +1105,42 @@ class Request extends stream_1.Duplex {
1002
1105
  options.request = request;
1003
1106
  options.timeout = timeout;
1004
1107
  options.agent = agent;
1108
+ // HTTPS options restore
1109
+ if (options.https) {
1110
+ if ('rejectUnauthorized' in options.https) {
1111
+ delete requestOptions.rejectUnauthorized;
1112
+ }
1113
+ if (options.https.checkServerIdentity) {
1114
+ // @ts-expect-error - This one will be removed when we remove the alias.
1115
+ delete requestOptions.checkServerIdentity;
1116
+ }
1117
+ if (options.https.certificateAuthority) {
1118
+ delete requestOptions.ca;
1119
+ }
1120
+ if (options.https.certificate) {
1121
+ delete requestOptions.cert;
1122
+ }
1123
+ if (options.https.key) {
1124
+ delete requestOptions.key;
1125
+ }
1126
+ if (options.https.passphrase) {
1127
+ delete requestOptions.passphrase;
1128
+ }
1129
+ }
1005
1130
  if (isClientRequest(requestOrResponse)) {
1006
1131
  this._onRequest(requestOrResponse);
1007
1132
  // Emit the response after the stream has been ended
1008
1133
  }
1009
1134
  else if (this.writable) {
1010
1135
  this.once('finish', () => {
1011
- this._onResponse(requestOrResponse);
1136
+ void this._onResponse(requestOrResponse);
1012
1137
  });
1013
1138
  this._unlockWrite();
1014
1139
  this.end();
1015
1140
  this._lockWrite();
1016
1141
  }
1017
1142
  else {
1018
- this._onResponse(requestOrResponse);
1143
+ void this._onResponse(requestOrResponse);
1019
1144
  }
1020
1145
  }
1021
1146
  catch (error) {
@@ -1025,23 +1150,7 @@ class Request extends stream_1.Duplex {
1025
1150
  throw new RequestError(error.message, error, this);
1026
1151
  }
1027
1152
  }
1028
- async _beforeError(error) {
1029
- if (this.destroyed) {
1030
- return;
1031
- }
1032
- this[kStopReading] = true;
1033
- if (!(error instanceof RequestError)) {
1034
- error = new RequestError(error.message, error, this);
1035
- }
1036
- try {
1037
- const { response } = error;
1038
- if (response) {
1039
- response.setEncoding(this._readableState.encoding);
1040
- response.rawBody = await get_buffer_1.default(response);
1041
- response.body = response.rawBody.toString();
1042
- }
1043
- }
1044
- catch (_) { }
1153
+ async _error(error) {
1045
1154
  try {
1046
1155
  for (const hook of this.options.hooks.beforeError) {
1047
1156
  // eslint-disable-next-line no-await-in-loop
@@ -1053,6 +1162,87 @@ class Request extends stream_1.Duplex {
1053
1162
  }
1054
1163
  this.destroy(error);
1055
1164
  }
1165
+ _beforeError(error) {
1166
+ if (this[kStopReading]) {
1167
+ return;
1168
+ }
1169
+ const { options } = this;
1170
+ const retryCount = this.retryCount + 1;
1171
+ this[kStopReading] = true;
1172
+ if (!(error instanceof RequestError)) {
1173
+ error = new RequestError(error.message, error, this);
1174
+ }
1175
+ const typedError = error;
1176
+ const { response } = typedError;
1177
+ void (async () => {
1178
+ if (response && !response.body) {
1179
+ response.setEncoding(this._readableState.encoding);
1180
+ try {
1181
+ response.rawBody = await get_buffer_1.default(response);
1182
+ }
1183
+ catch (_a) { }
1184
+ response.body = response.rawBody.toString();
1185
+ }
1186
+ if (this.listenerCount('retry') !== 0) {
1187
+ let backoff;
1188
+ try {
1189
+ let retryAfter;
1190
+ if (response && 'retry-after' in response.headers) {
1191
+ retryAfter = Number(response.headers['retry-after']);
1192
+ if (Number.isNaN(retryAfter)) {
1193
+ retryAfter = Date.parse(response.headers['retry-after']) - Date.now();
1194
+ if (retryAfter <= 0) {
1195
+ retryAfter = 1;
1196
+ }
1197
+ }
1198
+ else {
1199
+ retryAfter *= 1000;
1200
+ }
1201
+ }
1202
+ backoff = await options.retry.calculateDelay({
1203
+ attemptCount: retryCount,
1204
+ retryOptions: options.retry,
1205
+ error: typedError,
1206
+ retryAfter,
1207
+ computedValue: calculate_retry_delay_1.default({
1208
+ attemptCount: retryCount,
1209
+ retryOptions: options.retry,
1210
+ error: typedError,
1211
+ retryAfter,
1212
+ computedValue: 0
1213
+ })
1214
+ });
1215
+ }
1216
+ catch (error_) {
1217
+ void this._error(new RequestError(error_.message, error_, this));
1218
+ return;
1219
+ }
1220
+ if (backoff) {
1221
+ const retry = async () => {
1222
+ try {
1223
+ for (const hook of this.options.hooks.beforeRetry) {
1224
+ // eslint-disable-next-line no-await-in-loop
1225
+ await hook(this.options, typedError, retryCount);
1226
+ }
1227
+ }
1228
+ catch (error_) {
1229
+ void this._error(new RequestError(error_.message, error, this));
1230
+ return;
1231
+ }
1232
+ // Something forced us to abort the retry
1233
+ if (this.destroyed) {
1234
+ return;
1235
+ }
1236
+ this.destroy();
1237
+ this.emit('retry', retryCount, error);
1238
+ };
1239
+ this[kRetryTimeout] = setTimeout(retry, backoff);
1240
+ return;
1241
+ }
1242
+ }
1243
+ void this._error(typedError);
1244
+ })();
1245
+ }
1056
1246
  _read() {
1057
1247
  this[kTriggerRead] = true;
1058
1248
  const response = this[kResponse];
@@ -1074,6 +1264,7 @@ class Request extends stream_1.Duplex {
1074
1264
  }
1075
1265
  }
1076
1266
  }
1267
+ // Node.js 12 has incorrect types, so the encoding must be a string
1077
1268
  _write(chunk, encoding, callback) {
1078
1269
  const write = () => {
1079
1270
  this._writeRequest(chunk, encoding, callback);
@@ -1135,6 +1326,9 @@ class Request extends stream_1.Duplex {
1135
1326
  }
1136
1327
  _destroy(error, callback) {
1137
1328
  var _a;
1329
+ this[kStopReading] = true;
1330
+ // Prevent further retries
1331
+ clearTimeout(this[kRetryTimeout]);
1138
1332
  if (kRequest in this) {
1139
1333
  this[kCancelTimeouts]();
1140
1334
  // TODO: Remove the next `if` when these get fixed:
@@ -1148,10 +1342,19 @@ class Request extends stream_1.Duplex {
1148
1342
  }
1149
1343
  callback(error);
1150
1344
  }
1345
+ get _isAboutToError() {
1346
+ return this[kStopReading];
1347
+ }
1348
+ /**
1349
+ The remote IP address.
1350
+ */
1151
1351
  get ip() {
1152
1352
  var _a;
1153
1353
  return (_a = this[kRequest]) === null || _a === void 0 ? void 0 : _a.socket.remoteAddress;
1154
1354
  }
1355
+ /**
1356
+ Indicates whether the request has been aborted or not.
1357
+ */
1155
1358
  get aborted() {
1156
1359
  var _a, _b, _c;
1157
1360
  return ((_b = (_a = this[kRequest]) === null || _a === void 0 ? void 0 : _a.destroyed) !== null && _b !== void 0 ? _b : this.destroyed) && !((_c = this[kOriginalResponse]) === null || _c === void 0 ? void 0 : _c.complete);
@@ -1160,6 +1363,9 @@ class Request extends stream_1.Duplex {
1160
1363
  var _a;
1161
1364
  return (_a = this[kRequest]) === null || _a === void 0 ? void 0 : _a.socket;
1162
1365
  }
1366
+ /**
1367
+ Progress event for downloading (receiving a response).
1368
+ */
1163
1369
  get downloadProgress() {
1164
1370
  let percent;
1165
1371
  if (this[kResponseSize]) {
@@ -1177,6 +1383,9 @@ class Request extends stream_1.Duplex {
1177
1383
  total: this[kResponseSize]
1178
1384
  };
1179
1385
  }
1386
+ /**
1387
+ Progress event for uploading (sending a request).
1388
+ */
1180
1389
  get uploadProgress() {
1181
1390
  let percent;
1182
1391
  if (this[kBodySize]) {
@@ -1194,16 +1403,43 @@ class Request extends stream_1.Duplex {
1194
1403
  total: this[kBodySize]
1195
1404
  };
1196
1405
  }
1406
+ /**
1407
+ The object contains the following properties:
1408
+
1409
+ - `start` - Time when the request started.
1410
+ - `socket` - Time when a socket was assigned to the request.
1411
+ - `lookup` - Time when the DNS lookup finished.
1412
+ - `connect` - Time when the socket successfully connected.
1413
+ - `secureConnect` - Time when the socket securely connected.
1414
+ - `upload` - Time when the request finished uploading.
1415
+ - `response` - Time when the request fired `response` event.
1416
+ - `end` - Time when the response fired `end` event.
1417
+ - `error` - Time when the request fired `error` event.
1418
+ - `abort` - Time when the request fired `abort` event.
1419
+ - `phases`
1420
+ - `wait` - `timings.socket - timings.start`
1421
+ - `dns` - `timings.lookup - timings.socket`
1422
+ - `tcp` - `timings.connect - timings.lookup`
1423
+ - `tls` - `timings.secureConnect - timings.connect`
1424
+ - `request` - `timings.upload - (timings.secureConnect || timings.connect)`
1425
+ - `firstByte` - `timings.response - timings.upload`
1426
+ - `download` - `timings.end - timings.response`
1427
+ - `total` - `(timings.end || timings.error || timings.abort) - timings.start`
1428
+
1429
+ If something has not been measured yet, it will be `undefined`.
1430
+
1431
+ __Note__: The time is a `number` representing the milliseconds elapsed since the UNIX epoch.
1432
+ */
1197
1433
  get timings() {
1198
1434
  var _a;
1199
1435
  return (_a = this[kRequest]) === null || _a === void 0 ? void 0 : _a.timings;
1200
1436
  }
1437
+ /**
1438
+ Whether the response was retrieved from the cache.
1439
+ */
1201
1440
  get isFromCache() {
1202
1441
  return this[kIsFromCache];
1203
1442
  }
1204
- get _response() {
1205
- return this[kResponse];
1206
- }
1207
1443
  pipe(destination, options) {
1208
1444
  if (this[kStartedReading]) {
1209
1445
  throw new Error('Failed to pipe. The response has been emitted already.');