got 11.5.2 → 11.7.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.
@@ -25,7 +25,10 @@ const options_to_url_1 = require("./utils/options-to-url");
25
25
  const weakable_map_1 = require("./utils/weakable-map");
26
26
  const get_buffer_1 = require("./utils/get-buffer");
27
27
  const dns_ip_version_1 = require("./utils/dns-ip-version");
28
+ const is_response_ok_1 = require("./utils/is-response-ok");
28
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");
29
32
  const globalDnsCache = new cacheable_lookup_1.default();
30
33
  const kRequest = Symbol('request');
31
34
  const kResponse = Symbol('response');
@@ -43,10 +46,19 @@ const kTriggerRead = Symbol('triggerRead');
43
46
  const kBody = Symbol('body');
44
47
  const kJobs = Symbol('jobs');
45
48
  const kOriginalResponse = Symbol('originalResponse');
49
+ const kRetryTimeout = Symbol('retryTimeout');
46
50
  exports.kIsNormalizedAlready = Symbol('isNormalizedAlready');
47
51
  const supportsBrotli = is_1.default.string(process.versions.brotli);
48
52
  exports.withoutBody = new Set(['GET', 'HEAD']);
49
- 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
+ ];
50
62
  function validateSearchParameters(searchParameters) {
51
63
  // eslint-disable-next-line guard-for-in
52
64
  for (const key in searchParameters) {
@@ -103,6 +115,10 @@ exports.setNonEnumerableProperties = (sources, to) => {
103
115
  }
104
116
  Object.defineProperties(to, properties);
105
117
  };
118
+ /**
119
+ An error to be thrown when a request fails.
120
+ Contains a `code` property with error class code, like `ECONNREFUSED`.
121
+ */
106
122
  class RequestError extends Error {
107
123
  constructor(message, error, self) {
108
124
  var _a;
@@ -149,6 +165,10 @@ class RequestError extends Error {
149
165
  }
150
166
  }
151
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
+ */
152
172
  class MaxRedirectsError extends RequestError {
153
173
  constructor(request) {
154
174
  super(`Redirected ${request.options.maxRedirects} times. Aborting.`, {}, request);
@@ -156,6 +176,10 @@ class MaxRedirectsError extends RequestError {
156
176
  }
157
177
  }
158
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
+ */
159
183
  class HTTPError extends RequestError {
160
184
  constructor(response) {
161
185
  super(`Response code ${response.statusCode} (${response.statusMessage})`, {}, response.request);
@@ -163,6 +187,10 @@ class HTTPError extends RequestError {
163
187
  }
164
188
  }
165
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
+ */
166
194
  class CacheError extends RequestError {
167
195
  constructor(error, request) {
168
196
  super(error.message, error, request);
@@ -170,6 +198,9 @@ class CacheError extends RequestError {
170
198
  }
171
199
  }
172
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
+ */
173
204
  class UploadError extends RequestError {
174
205
  constructor(error, request) {
175
206
  super(error.message, error, request);
@@ -177,6 +208,10 @@ class UploadError extends RequestError {
177
208
  }
178
209
  }
179
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
+ */
180
215
  class TimeoutError extends RequestError {
181
216
  constructor(error, timings, request) {
182
217
  super(error.message, error, request);
@@ -186,6 +221,9 @@ class TimeoutError extends RequestError {
186
221
  }
187
222
  }
188
223
  exports.TimeoutError = TimeoutError;
224
+ /**
225
+ An error to be thrown when reading from response stream fails.
226
+ */
189
227
  class ReadError extends RequestError {
190
228
  constructor(error, request) {
191
229
  super(error.message, error, request);
@@ -193,6 +231,9 @@ class ReadError extends RequestError {
193
231
  }
194
232
  }
195
233
  exports.ReadError = ReadError;
234
+ /**
235
+ An error to be thrown when given an unsupported protocol.
236
+ */
196
237
  class UnsupportedProtocolError extends RequestError {
197
238
  constructor(options) {
198
239
  super(`Unsupported protocol "${options.url.protocol}"`, {}, options);
@@ -211,6 +252,9 @@ const proxiedRequestEvents = [
211
252
  class Request extends stream_1.Duplex {
212
253
  constructor(url, options = {}, defaults) {
213
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,
214
258
  // It needs to be zero because we're just proxying the data to another stream
215
259
  highWaterMark: 0
216
260
  });
@@ -222,6 +266,7 @@ class Request extends stream_1.Duplex {
222
266
  this[kStopReading] = false;
223
267
  this[kTriggerRead] = false;
224
268
  this[kJobs] = [];
269
+ this.retryCount = 0;
225
270
  // TODO: Remove this when targeting Node.js >= 12
226
271
  this._progressCallbacks = [];
227
272
  const unlockWrite = () => this._unlockWrite();
@@ -250,18 +295,28 @@ class Request extends stream_1.Duplex {
250
295
  if (json || body || form) {
251
296
  this._lockWrite();
252
297
  }
253
- (async (nonNormalizedOptions) => {
254
- var _a;
298
+ if (exports.kIsNormalizedAlready in options) {
299
+ this.options = options;
300
+ }
301
+ else {
255
302
  try {
256
- if (nonNormalizedOptions.body instanceof fs_1.ReadStream) {
257
- await waitForOpenFile(nonNormalizedOptions.body);
258
- }
259
- if (exports.kIsNormalizedAlready in nonNormalizedOptions) {
260
- this.options = nonNormalizedOptions;
303
+ // @ts-expect-error Common TypeScript bug saying that `this.constructor` is not accessible
304
+ this.options = this.constructor.normalizeArguments(url, options, defaults);
305
+ }
306
+ catch (error) {
307
+ // TODO: Move this to `_destroy()`
308
+ if (is_1.default.nodeStream(options.body)) {
309
+ options.body.destroy();
261
310
  }
262
- else {
263
- // @ts-expect-error Common TypeScript bug saying that `this.constructor` is not accessible
264
- this.options = this.constructor.normalizeArguments(url, nonNormalizedOptions, defaults);
311
+ this.destroy(error);
312
+ return;
313
+ }
314
+ }
315
+ (async () => {
316
+ var _a;
317
+ try {
318
+ if (this.options.body instanceof fs_1.ReadStream) {
319
+ await waitForOpenFile(this.options.body);
265
320
  }
266
321
  const { url: normalizedURL } = this.options;
267
322
  if (!normalizedURL) {
@@ -279,6 +334,8 @@ class Request extends stream_1.Duplex {
279
334
  for (const job of this[kJobs]) {
280
335
  job();
281
336
  }
337
+ // Prevent memory leak
338
+ this[kJobs].length = 0;
282
339
  this.requestInitialized = true;
283
340
  }
284
341
  catch (error) {
@@ -291,10 +348,10 @@ class Request extends stream_1.Duplex {
291
348
  this.destroy(error);
292
349
  }
293
350
  }
294
- })(options);
351
+ })();
295
352
  }
296
353
  static normalizeArguments(url, options, defaults) {
297
- var _a, _b, _c, _d;
354
+ var _a, _b, _c, _d, _e;
298
355
  const rawOptions = options;
299
356
  if (is_1.default.object(url) && !is_1.default.urlInstance(url)) {
300
357
  options = { ...defaults, ...url, ...options };
@@ -347,7 +404,9 @@ class Request extends stream_1.Duplex {
347
404
  is_1.assert.any([is_1.default.string, is_1.default.object, is_1.default.array, is_1.default.undefined], options.https.key);
348
405
  is_1.assert.any([is_1.default.string, is_1.default.object, is_1.default.array, is_1.default.undefined], options.https.certificate);
349
406
  is_1.assert.any([is_1.default.string, is_1.default.undefined], options.https.passphrase);
407
+ is_1.assert.any([is_1.default.string, is_1.default.buffer, is_1.default.array, is_1.default.undefined], options.https.pfx);
350
408
  }
409
+ is_1.assert.any([is_1.default.object, is_1.default.undefined], options.cacheOptions);
351
410
  // `options.method`
352
411
  if (is_1.default.string(options.method)) {
353
412
  options.method = options.method.toUpperCase();
@@ -405,15 +464,15 @@ class Request extends stream_1.Duplex {
405
464
  options.username = (_b = options.username) !== null && _b !== void 0 ? _b : '';
406
465
  options.password = (_c = options.password) !== null && _c !== void 0 ? _c : '';
407
466
  // `options.prefixUrl` & `options.url`
408
- if (options.prefixUrl) {
467
+ if (is_1.default.undefined(options.prefixUrl)) {
468
+ options.prefixUrl = (_d = defaults === null || defaults === void 0 ? void 0 : defaults.prefixUrl) !== null && _d !== void 0 ? _d : '';
469
+ }
470
+ else {
409
471
  options.prefixUrl = options.prefixUrl.toString();
410
472
  if (options.prefixUrl !== '' && !options.prefixUrl.endsWith('/')) {
411
473
  options.prefixUrl += '/';
412
474
  }
413
475
  }
414
- else {
415
- options.prefixUrl = '';
416
- }
417
476
  if (is_1.default.string(options.url)) {
418
477
  if (options.url.startsWith('/')) {
419
478
  throw new Error('`input` must not start with a slash when using `prefixUrl`');
@@ -424,6 +483,9 @@ class Request extends stream_1.Duplex {
424
483
  options.url = options_to_url_1.default(options.prefixUrl, options);
425
484
  }
426
485
  if (options.url) {
486
+ if ('port' in options) {
487
+ delete options.port;
488
+ }
427
489
  // Make it possible to change `options.prefixUrl`
428
490
  let { prefixUrl } = options;
429
491
  Object.defineProperty(options, 'prefixUrl', {
@@ -479,9 +541,7 @@ class Request extends stream_1.Duplex {
479
541
  getCookieString = util_1.promisify(getCookieString.bind(options.cookieJar));
480
542
  options.cookieJar = {
481
543
  setCookie,
482
- // TODO: Fix this when upgrading to TypeScript 4.
483
- // @ts-expect-error TypeScript thinks that promisifying callback(error, string) will result in Promise<void>
484
- getCookieString
544
+ getCookieString: getCookieString
485
545
  };
486
546
  }
487
547
  }
@@ -522,6 +582,8 @@ class Request extends stream_1.Duplex {
522
582
  }), cache));
523
583
  }
524
584
  }
585
+ // `options.cacheOptions`
586
+ options.cacheOptions = { ...options.cacheOptions };
525
587
  // `options.dnsCache`
526
588
  if (options.dnsCache === true) {
527
589
  options.dnsCache = globalDnsCache;
@@ -601,6 +663,9 @@ class Request extends stream_1.Duplex {
601
663
  if ('passphrase' in options) {
602
664
  deprecation_warning_1.default('"options.passphrase" was never documented, please use "options.https.passphrase"');
603
665
  }
666
+ if ('pfx' in options) {
667
+ deprecation_warning_1.default('"options.pfx" was never documented, please use "options.https.pfx"');
668
+ }
604
669
  // Other options
605
670
  if ('followRedirects' in options) {
606
671
  throw new TypeError('The `followRedirects` option does not exist. Use `followRedirect` instead.');
@@ -612,10 +677,10 @@ class Request extends stream_1.Duplex {
612
677
  }
613
678
  }
614
679
  }
615
- options.maxRedirects = (_d = options.maxRedirects) !== null && _d !== void 0 ? _d : 0;
680
+ options.maxRedirects = (_e = options.maxRedirects) !== null && _e !== void 0 ? _e : 0;
616
681
  // Set non-enumerable properties
617
682
  exports.setNonEnumerableProperties([defaults, rawOptions], options);
618
- return options;
683
+ return normalize_arguments_1.default(options, defaults);
619
684
  }
620
685
  _lockWrite() {
621
686
  const onLockedWrite = () => {
@@ -717,6 +782,7 @@ class Request extends stream_1.Duplex {
717
782
  typedResponse.request = this;
718
783
  typedResponse.isFromCache = response.fromCache || false;
719
784
  typedResponse.ip = this.ip;
785
+ typedResponse.retryCount = this.retryCount;
720
786
  this[kIsFromCache] = typedResponse.isFromCache;
721
787
  this[kResponseSize] = Number(response.headers['content-length']) || undefined;
722
788
  this[kResponse] = response;
@@ -754,7 +820,7 @@ class Request extends stream_1.Duplex {
754
820
  }
755
821
  if (options.followRedirect && response.headers.location && redirectCodes.has(statusCode)) {
756
822
  // We're being redirected, we don't care about the response.
757
- // It'd be besto to abort the request, but we can't because
823
+ // It'd be best to abort the request, but we can't because
758
824
  // we would have to sacrifice the TCP connection. We don't want that.
759
825
  response.resume();
760
826
  if (this[kRequest]) {
@@ -777,6 +843,8 @@ class Request extends stream_1.Duplex {
777
843
  if ('form' in options) {
778
844
  delete options.form;
779
845
  }
846
+ this[kBody] = undefined;
847
+ delete options.headers['content-length'];
780
848
  }
781
849
  if (this.redirects.length >= options.maxRedirects) {
782
850
  this._beforeError(new MaxRedirectsError(this));
@@ -790,7 +858,7 @@ class Request extends stream_1.Duplex {
790
858
  const redirectString = redirectUrl.toString();
791
859
  decodeURI(redirectString);
792
860
  // Redirecting to a different site, clear sensitive data.
793
- if (redirectUrl.hostname !== url.hostname) {
861
+ if (redirectUrl.hostname !== url.hostname || redirectUrl.port !== url.port) {
794
862
  if ('host' in options.headers) {
795
863
  delete options.headers.host;
796
864
  }
@@ -801,10 +869,14 @@ class Request extends stream_1.Duplex {
801
869
  delete options.headers.authorization;
802
870
  }
803
871
  if (options.username || options.password) {
804
- delete options.username;
805
- delete options.password;
872
+ options.username = '';
873
+ options.password = '';
806
874
  }
807
875
  }
876
+ else {
877
+ redirectUrl.username = options.username;
878
+ redirectUrl.password = options.password;
879
+ }
808
880
  this.redirects.push(redirectString);
809
881
  options.url = redirectUrl;
810
882
  for (const hook of options.hooks.beforeRedirect) {
@@ -820,16 +892,9 @@ class Request extends stream_1.Duplex {
820
892
  }
821
893
  return;
822
894
  }
823
- const limitStatusCode = options.followRedirect ? 299 : 399;
824
- const isOk = (statusCode >= 200 && statusCode <= limitStatusCode) || statusCode === 304;
825
- if (options.throwHttpErrors && !isOk) {
826
- // Normally we would have to use `void [await] this._beforeError(error)` everywhere,
827
- // but since there's `void (async () => { ... })()` inside of it, we don't have to.
895
+ if (options.isStream && options.throwHttpErrors && !is_response_ok_1.isResponseOk(typedResponse)) {
828
896
  this._beforeError(new HTTPError(typedResponse));
829
- // This is equivalent to this.destroyed
830
- if (this[kStopReading]) {
831
- return;
832
- }
897
+ return;
833
898
  }
834
899
  response.on('readable', () => {
835
900
  if (this[kTriggerRead]) {
@@ -866,6 +931,7 @@ class Request extends stream_1.Duplex {
866
931
  await this._onResponseBase(response);
867
932
  }
868
933
  catch (error) {
934
+ /* istanbul ignore next: better safe than sorry */
869
935
  this._beforeError(error);
870
936
  }
871
937
  }
@@ -903,9 +969,6 @@ class Request extends stream_1.Duplex {
903
969
  body.once('error', (error) => {
904
970
  this._beforeError(new UploadError(error, this));
905
971
  });
906
- body.once('end', () => {
907
- delete options.body;
908
- });
909
972
  }
910
973
  else {
911
974
  this._unlockWrite();
@@ -926,6 +989,8 @@ class Request extends stream_1.Duplex {
926
989
  // TODO: Remove `utils/url-to-options.ts` when `cacheable-request` is fixed
927
990
  Object.assign(options, url_to_options_1.default(url));
928
991
  // `http-cache-semantics` checks this
992
+ // TODO: Fix this ignore.
993
+ // @ts-expect-error
929
994
  delete options.url;
930
995
  let request;
931
996
  // This is ugly
@@ -947,7 +1012,7 @@ class Request extends stream_1.Duplex {
947
1012
  });
948
1013
  }
949
1014
  async _makeRequest() {
950
- var _a;
1015
+ var _a, _b, _c, _d, _e;
951
1016
  const { options } = this;
952
1017
  const { headers } = options;
953
1018
  for (const key in headers) {
@@ -978,6 +1043,9 @@ class Request extends stream_1.Duplex {
978
1043
  break;
979
1044
  }
980
1045
  }
1046
+ if (options.body && this[kBody] !== options.body) {
1047
+ this[kBody] = options.body;
1048
+ }
981
1049
  const { agent, request, timeout, url } = options;
982
1050
  if (options.dnsCache && !('lookup' in options)) {
983
1051
  options.lookup = options.dnsCache.lookup;
@@ -1013,14 +1081,20 @@ class Request extends stream_1.Duplex {
1013
1081
  // Prepare plain HTTP request options
1014
1082
  options[kRequest] = realFn;
1015
1083
  delete options.request;
1084
+ // TODO: Fix this ignore.
1085
+ // @ts-expect-error
1016
1086
  delete options.timeout;
1017
1087
  const requestOptions = options;
1088
+ requestOptions.shared = (_b = options.cacheOptions) === null || _b === void 0 ? void 0 : _b.shared;
1089
+ requestOptions.cacheHeuristic = (_c = options.cacheOptions) === null || _c === void 0 ? void 0 : _c.cacheHeuristic;
1090
+ requestOptions.immutableMinTimeToLive = (_d = options.cacheOptions) === null || _d === void 0 ? void 0 : _d.immutableMinTimeToLive;
1091
+ requestOptions.ignoreCargoCult = (_e = options.cacheOptions) === null || _e === void 0 ? void 0 : _e.ignoreCargoCult;
1018
1092
  // If `dnsLookupIpVersion` is not present do not override `family`
1019
1093
  if (options.dnsLookupIpVersion !== undefined) {
1020
1094
  try {
1021
1095
  requestOptions.family = dns_ip_version_1.dnsLookupIpVersionToFamily(options.dnsLookupIpVersion);
1022
1096
  }
1023
- catch (_b) {
1097
+ catch (_f) {
1024
1098
  throw new Error('Invalid `dnsLookupIpVersion` option value');
1025
1099
  }
1026
1100
  }
@@ -1044,6 +1118,9 @@ class Request extends stream_1.Duplex {
1044
1118
  if (options.https.passphrase) {
1045
1119
  requestOptions.passphrase = options.https.passphrase;
1046
1120
  }
1121
+ if (options.https.pfx) {
1122
+ requestOptions.pfx = options.https.pfx;
1123
+ }
1047
1124
  }
1048
1125
  try {
1049
1126
  let requestOrResponse = await fn(url, requestOptions);
@@ -1060,6 +1137,7 @@ class Request extends stream_1.Duplex {
1060
1137
  delete requestOptions.rejectUnauthorized;
1061
1138
  }
1062
1139
  if (options.https.checkServerIdentity) {
1140
+ // @ts-expect-error - This one will be removed when we remove the alias.
1063
1141
  delete requestOptions.checkServerIdentity;
1064
1142
  }
1065
1143
  if (options.https.certificateAuthority) {
@@ -1074,6 +1152,9 @@ class Request extends stream_1.Duplex {
1074
1152
  if (options.https.passphrase) {
1075
1153
  delete requestOptions.passphrase;
1076
1154
  }
1155
+ if (options.https.pfx) {
1156
+ delete requestOptions.pfx;
1157
+ }
1077
1158
  }
1078
1159
  if (isClientRequest(requestOrResponse)) {
1079
1160
  this._onRequest(requestOrResponse);
@@ -1098,34 +1179,97 @@ class Request extends stream_1.Duplex {
1098
1179
  throw new RequestError(error.message, error, this);
1099
1180
  }
1100
1181
  }
1182
+ async _error(error) {
1183
+ try {
1184
+ for (const hook of this.options.hooks.beforeError) {
1185
+ // eslint-disable-next-line no-await-in-loop
1186
+ error = await hook(error);
1187
+ }
1188
+ }
1189
+ catch (error_) {
1190
+ error = new RequestError(error_.message, error_, this);
1191
+ }
1192
+ this.destroy(error);
1193
+ }
1101
1194
  _beforeError(error) {
1102
- if (this.destroyed) {
1195
+ if (this[kStopReading]) {
1103
1196
  return;
1104
1197
  }
1198
+ const { options } = this;
1199
+ const retryCount = this.retryCount + 1;
1105
1200
  this[kStopReading] = true;
1106
1201
  if (!(error instanceof RequestError)) {
1107
1202
  error = new RequestError(error.message, error, this);
1108
1203
  }
1204
+ const typedError = error;
1205
+ const { response } = typedError;
1109
1206
  void (async () => {
1110
- try {
1111
- const { response } = error;
1112
- if (response) {
1113
- response.setEncoding(this._readableState.encoding);
1207
+ if (response && !response.body) {
1208
+ response.setEncoding(this._readableState.encoding);
1209
+ try {
1114
1210
  response.rawBody = await get_buffer_1.default(response);
1115
1211
  response.body = response.rawBody.toString();
1116
1212
  }
1117
- }
1118
- catch (_a) { }
1119
- try {
1120
- for (const hook of this.options.hooks.beforeError) {
1121
- // eslint-disable-next-line no-await-in-loop
1122
- error = await hook(error);
1213
+ catch (_a) { }
1214
+ }
1215
+ if (this.listenerCount('retry') !== 0) {
1216
+ let backoff;
1217
+ try {
1218
+ let retryAfter;
1219
+ if (response && 'retry-after' in response.headers) {
1220
+ retryAfter = Number(response.headers['retry-after']);
1221
+ if (Number.isNaN(retryAfter)) {
1222
+ retryAfter = Date.parse(response.headers['retry-after']) - Date.now();
1223
+ if (retryAfter <= 0) {
1224
+ retryAfter = 1;
1225
+ }
1226
+ }
1227
+ else {
1228
+ retryAfter *= 1000;
1229
+ }
1230
+ }
1231
+ backoff = await options.retry.calculateDelay({
1232
+ attemptCount: retryCount,
1233
+ retryOptions: options.retry,
1234
+ error: typedError,
1235
+ retryAfter,
1236
+ computedValue: calculate_retry_delay_1.default({
1237
+ attemptCount: retryCount,
1238
+ retryOptions: options.retry,
1239
+ error: typedError,
1240
+ retryAfter,
1241
+ computedValue: 0
1242
+ })
1243
+ });
1244
+ }
1245
+ catch (error_) {
1246
+ void this._error(new RequestError(error_.message, error_, this));
1247
+ return;
1248
+ }
1249
+ if (backoff) {
1250
+ const retry = async () => {
1251
+ try {
1252
+ for (const hook of this.options.hooks.beforeRetry) {
1253
+ // eslint-disable-next-line no-await-in-loop
1254
+ await hook(this.options, typedError, retryCount);
1255
+ }
1256
+ }
1257
+ catch (error_) {
1258
+ void this._error(new RequestError(error_.message, error, this));
1259
+ return;
1260
+ }
1261
+ // Something forced us to abort the retry
1262
+ if (this.destroyed) {
1263
+ return;
1264
+ }
1265
+ this.destroy();
1266
+ this.emit('retry', retryCount, error);
1267
+ };
1268
+ this[kRetryTimeout] = setTimeout(retry, backoff);
1269
+ return;
1123
1270
  }
1124
1271
  }
1125
- catch (error_) {
1126
- error = new RequestError(error_.message, error_, this);
1127
- }
1128
- this.destroy(error);
1272
+ void this._error(typedError);
1129
1273
  })();
1130
1274
  }
1131
1275
  _read() {
@@ -1162,6 +1306,10 @@ class Request extends stream_1.Duplex {
1162
1306
  }
1163
1307
  }
1164
1308
  _writeRequest(chunk, encoding, callback) {
1309
+ if (this[kRequest].destroyed) {
1310
+ // Probably the `ClientRequest` instance will throw
1311
+ return;
1312
+ }
1165
1313
  this._progressCallbacks.push(() => {
1166
1314
  this[kUploadedSize] += Buffer.byteLength(chunk, encoding);
1167
1315
  const progress = this.uploadProgress;
@@ -1212,6 +1360,8 @@ class Request extends stream_1.Duplex {
1212
1360
  _destroy(error, callback) {
1213
1361
  var _a;
1214
1362
  this[kStopReading] = true;
1363
+ // Prevent further retries
1364
+ clearTimeout(this[kRetryTimeout]);
1215
1365
  if (kRequest in this) {
1216
1366
  this[kCancelTimeouts]();
1217
1367
  // TODO: Remove the next `if` when these get fixed:
@@ -1225,10 +1375,19 @@ class Request extends stream_1.Duplex {
1225
1375
  }
1226
1376
  callback(error);
1227
1377
  }
1378
+ get _isAboutToError() {
1379
+ return this[kStopReading];
1380
+ }
1381
+ /**
1382
+ The remote IP address.
1383
+ */
1228
1384
  get ip() {
1229
1385
  var _a;
1230
1386
  return (_a = this[kRequest]) === null || _a === void 0 ? void 0 : _a.socket.remoteAddress;
1231
1387
  }
1388
+ /**
1389
+ Indicates whether the request has been aborted or not.
1390
+ */
1232
1391
  get aborted() {
1233
1392
  var _a, _b, _c;
1234
1393
  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);
@@ -1237,6 +1396,9 @@ class Request extends stream_1.Duplex {
1237
1396
  var _a;
1238
1397
  return (_a = this[kRequest]) === null || _a === void 0 ? void 0 : _a.socket;
1239
1398
  }
1399
+ /**
1400
+ Progress event for downloading (receiving a response).
1401
+ */
1240
1402
  get downloadProgress() {
1241
1403
  let percent;
1242
1404
  if (this[kResponseSize]) {
@@ -1254,6 +1416,9 @@ class Request extends stream_1.Duplex {
1254
1416
  total: this[kResponseSize]
1255
1417
  };
1256
1418
  }
1419
+ /**
1420
+ Progress event for uploading (sending a request).
1421
+ */
1257
1422
  get uploadProgress() {
1258
1423
  let percent;
1259
1424
  if (this[kBodySize]) {
@@ -1271,16 +1436,43 @@ class Request extends stream_1.Duplex {
1271
1436
  total: this[kBodySize]
1272
1437
  };
1273
1438
  }
1439
+ /**
1440
+ The object contains the following properties:
1441
+
1442
+ - `start` - Time when the request started.
1443
+ - `socket` - Time when a socket was assigned to the request.
1444
+ - `lookup` - Time when the DNS lookup finished.
1445
+ - `connect` - Time when the socket successfully connected.
1446
+ - `secureConnect` - Time when the socket securely connected.
1447
+ - `upload` - Time when the request finished uploading.
1448
+ - `response` - Time when the request fired `response` event.
1449
+ - `end` - Time when the response fired `end` event.
1450
+ - `error` - Time when the request fired `error` event.
1451
+ - `abort` - Time when the request fired `abort` event.
1452
+ - `phases`
1453
+ - `wait` - `timings.socket - timings.start`
1454
+ - `dns` - `timings.lookup - timings.socket`
1455
+ - `tcp` - `timings.connect - timings.lookup`
1456
+ - `tls` - `timings.secureConnect - timings.connect`
1457
+ - `request` - `timings.upload - (timings.secureConnect || timings.connect)`
1458
+ - `firstByte` - `timings.response - timings.upload`
1459
+ - `download` - `timings.end - timings.response`
1460
+ - `total` - `(timings.end || timings.error || timings.abort) - timings.start`
1461
+
1462
+ If something has not been measured yet, it will be `undefined`.
1463
+
1464
+ __Note__: The time is a `number` representing the milliseconds elapsed since the UNIX epoch.
1465
+ */
1274
1466
  get timings() {
1275
1467
  var _a;
1276
1468
  return (_a = this[kRequest]) === null || _a === void 0 ? void 0 : _a.timings;
1277
1469
  }
1470
+ /**
1471
+ Whether the response was retrieved from the cache.
1472
+ */
1278
1473
  get isFromCache() {
1279
1474
  return this[kIsFromCache];
1280
1475
  }
1281
- get _response() {
1282
- return this[kResponse];
1283
- }
1284
1476
  pipe(destination, options) {
1285
1477
  if (this[kStartedReading]) {
1286
1478
  throw new Error('Failed to pipe. The response has been emitted already.');
@@ -0,0 +1,2 @@
1
+ import { Response } from '..';
2
+ export declare const isResponseOk: (response: Response) => boolean;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isResponseOk = void 0;
4
+ exports.isResponseOk = (response) => {
5
+ const { statusCode } = response;
6
+ const limitStatusCode = response.request.options.followRedirect ? 299 : 399;
7
+ return (statusCode >= 200 && statusCode <= limitStatusCode) || statusCode === 304;
8
+ };