@e-mc/request 0.7.16 → 0.7.18

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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2024 An Pham
1
+ Copyright 2023 An Pham
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
4
 
@@ -0,0 +1,561 @@
1
+ "use strict";
2
+ const fs = require("node:fs");
3
+ const http2 = require("node:http2");
4
+ const yaml = require("js-yaml");
5
+ const types_1 = require("@e-mc/types");
6
+ const module_1 = require("@e-mc/module");
7
+ const util_1 = require("@e-mc/request/util");
8
+ const kOutStream = Symbol('outStream');
9
+ const kOptions = Symbol('options');
10
+ let LOG_TIMEFORMAT = 'readable';
11
+ class HttpAdapter {
12
+ instance;
13
+ state;
14
+ uri;
15
+ static isUnsupported(value) {
16
+ return value === 421 || value === 505;
17
+ }
18
+ static isDowngrade(err) {
19
+ return err instanceof Error && (err.code === 'ERR_HTTP2_ERROR' || this.isUnsupported(Math.abs(err.errno)));
20
+ }
21
+ static wasAborted(err) {
22
+ return err instanceof Error && err.message.startsWith("Aborted");
23
+ }
24
+ static isConnectionError(err) {
25
+ switch (err instanceof Error && err.code) {
26
+ case 'ETIMEDOUT':
27
+ case 'ECONNRESET':
28
+ return true;
29
+ default:
30
+ return false;
31
+ }
32
+ }
33
+ static defineHostConfig({ settings }) {
34
+ const time_format = settings?.time_format;
35
+ switch (time_format) {
36
+ case 'readable':
37
+ case 'relative':
38
+ case 'none':
39
+ LOG_TIMEFORMAT = time_format;
40
+ break;
41
+ }
42
+ }
43
+ contentLength = 0;
44
+ retries = 0;
45
+ redirects = 0;
46
+ closed = false;
47
+ aborted = false;
48
+ timeout = null;
49
+ dataTime = null;
50
+ delayTime = undefined;
51
+ opts;
52
+ client;
53
+ resolve;
54
+ reject;
55
+ startTime;
56
+ [kOutStream] = null;
57
+ [kOptions];
58
+ constructor(instance, state, uri, options) {
59
+ this.instance = instance;
60
+ this.state = state;
61
+ this.uri = uri;
62
+ this[kOptions] = options;
63
+ this.startTime = state.log ? process.hrtime.bigint() : BigInt(0);
64
+ this.setOpts();
65
+ }
66
+ async start() {
67
+ return new Promise((resolve, reject) => {
68
+ this.resolve = resolve;
69
+ this.reject = reject;
70
+ this.init();
71
+ });
72
+ }
73
+ init() {
74
+ this.aborted = false;
75
+ this.setWriteStream();
76
+ this.client = this.instance.open(this.uri, this.opts);
77
+ if (this.opts.httpVersion === 2) {
78
+ this.client
79
+ .on('response', (headers, flags) => {
80
+ if (this.destroyed) {
81
+ return;
82
+ }
83
+ const statusCode = headers[':status'];
84
+ if (statusCode < 300) {
85
+ this.acceptResponse(headers);
86
+ }
87
+ else if (statusCode < 400) {
88
+ this.redirectResponse(statusCode, headers.location);
89
+ }
90
+ else if (statusCode === 401 ||
91
+ statusCode === 402 ||
92
+ statusCode === 403 ||
93
+ statusCode === 404 ||
94
+ statusCode === 407 ||
95
+ statusCode === 410) {
96
+ this.terminate(this.formatStatus(statusCode));
97
+ }
98
+ else if (this.isRetry(statusCode)) {
99
+ this.retryResponse(statusCode, headers['retry-after']);
100
+ }
101
+ else if (HttpAdapter.isUnsupported(statusCode)) {
102
+ this.retryDownload(true, this.formatNgFlags(http2.constants.NGHTTP2_PROTOCOL_ERROR, statusCode));
103
+ }
104
+ else {
105
+ switch (flags) {
106
+ case http2.constants.NGHTTP2_PROTOCOL_ERROR:
107
+ case http2.constants.NGHTTP2_INADEQUATE_SECURITY:
108
+ case http2.constants.NGHTTP2_HTTP_1_1_REQUIRED:
109
+ this.retryDownload(true, this.formatNgFlags(flags, statusCode, headers.location));
110
+ break;
111
+ default:
112
+ this.retryDownload(false, this.formatStatus(statusCode));
113
+ break;
114
+ }
115
+ }
116
+ })
117
+ .on('unknownProtocol', () => {
118
+ if (this.aborted) {
119
+ return;
120
+ }
121
+ this.retryDownload(true, 'Unknown protocol (HTTP/2)');
122
+ })
123
+ .on('aborted', () => {
124
+ this.aborted = true;
125
+ this.terminate((0, types_1.createAbortError)());
126
+ })
127
+ .on('error', async (err) => {
128
+ if (this.aborted) {
129
+ return;
130
+ }
131
+ if (HttpAdapter.wasAborted(err)) {
132
+ this.errorResponse(err);
133
+ }
134
+ else {
135
+ switch (!HttpAdapter.isDowngrade(err) && await this.host.hasProtocol(2)) {
136
+ case 1:
137
+ this.errorResponse(err);
138
+ break;
139
+ case 2:
140
+ this.retryDownload(false, err);
141
+ break;
142
+ default:
143
+ this.retryDownload(true, err);
144
+ break;
145
+ }
146
+ }
147
+ });
148
+ }
149
+ else {
150
+ this.client
151
+ .on('response', res => {
152
+ if (this.destroyed) {
153
+ return;
154
+ }
155
+ const statusCode = res.statusCode;
156
+ if (statusCode < 300) {
157
+ this.acceptResponse(res.headers);
158
+ }
159
+ else if (statusCode < 400) {
160
+ this.redirectResponse(statusCode, res.headers.location);
161
+ }
162
+ else if (this.isRetry(statusCode)) {
163
+ this.retryResponse(statusCode, res.headers['retry-after']);
164
+ }
165
+ else {
166
+ this.terminate(this.formatStatus(statusCode));
167
+ }
168
+ })
169
+ .on('abort', () => {
170
+ this.aborted = true;
171
+ this.terminate((0, types_1.createAbortError)());
172
+ })
173
+ .on('error', err => {
174
+ if (this.aborted) {
175
+ return;
176
+ }
177
+ this.errorResponse(err);
178
+ });
179
+ }
180
+ this.client.on('timeout', () => {
181
+ if (this.aborted) {
182
+ return;
183
+ }
184
+ if (++this.retries <= this.retryLimit) {
185
+ this.abortResponse();
186
+ this.retryTimeout();
187
+ }
188
+ else {
189
+ this.terminate(this.formatStatus(408));
190
+ }
191
+ });
192
+ }
193
+ setOpts(uri) {
194
+ if (uri) {
195
+ this.uri = uri;
196
+ }
197
+ this.opts = this.instance.opts(this.uri, this[kOptions]);
198
+ }
199
+ setWriteStream() {
200
+ const pipeTo = this.pipeTo;
201
+ if (typeof pipeTo === 'string') {
202
+ try {
203
+ this.outStream = fs.createWriteStream(pipeTo, { emitClose: false, highWaterMark: this.host.streamSize });
204
+ }
205
+ catch (err) {
206
+ this.terminate(err);
207
+ }
208
+ }
209
+ else if (pipeTo) {
210
+ this.outStream = pipeTo;
211
+ }
212
+ }
213
+ retryDownload(downgrade, message) {
214
+ if (this.aborted) {
215
+ return;
216
+ }
217
+ this.abortResponse();
218
+ if (downgrade) {
219
+ const host = this.host;
220
+ host.failed(2);
221
+ if (host.version > 1) {
222
+ if (this.state.verbose) {
223
+ this.instance.formatMessage(1024, 'HTTP2', ["Unsupported protocol", host.origin], message, { failed: true });
224
+ }
225
+ host.version = 1;
226
+ }
227
+ }
228
+ this.opts.httpVersion = 1;
229
+ this.init();
230
+ }
231
+ acceptResponse(headers) {
232
+ const opts = this.opts;
233
+ if ('outHeaders' in opts) {
234
+ opts.outHeaders = headers;
235
+ }
236
+ if ('outFilename' in opts) {
237
+ opts.outFilename = (0, util_1.parseHeader)(headers, 'content-disposition');
238
+ }
239
+ const pipeline = this.pipeTo ? !(0, types_1.isString)(this.pipeTo) : false;
240
+ const enabled = opts.connected?.call(this.client, headers) !== false && !pipeline;
241
+ const maxBufferSize = opts.maxBufferSize ? (0, types_1.alignSize)(opts.maxBufferSize) : 0;
242
+ this.contentLength = parseInt(headers['content-length'] || '0');
243
+ const updating = opts.progressId !== undefined && !!this.instance.host && this.contentLength > 0;
244
+ const readTimeout = this.instance.readTimeout;
245
+ let log = this.state.log, buffer = null, dataLength = 0;
246
+ this.client.once('readable', () => {
247
+ if (readTimeout > 0) {
248
+ this.timeout = setTimeout(() => {
249
+ this.terminate((0, types_1.errorValue)("Timeout was exceeded", this.uri.toString()));
250
+ }, readTimeout);
251
+ }
252
+ if (log) {
253
+ switch (this.instance.settings?.time_format || LOG_TIMEFORMAT) {
254
+ case 'readable':
255
+ this.delayTime = process.hrtime.bigint() - this.startTime;
256
+ break;
257
+ case 'relative':
258
+ this.delayTime = Date.now() - this.instance.startTime;
259
+ break;
260
+ case 'none':
261
+ if (!this.silent) {
262
+ log = false;
263
+ }
264
+ break;
265
+ }
266
+ }
267
+ this.dataTime = process.hrtime.bigint();
268
+ if (updating) {
269
+ this.updateProgress(0, 0);
270
+ }
271
+ });
272
+ if (enabled) {
273
+ const encoding = opts.encoding;
274
+ this.client.on('data', (chunk) => {
275
+ if (buffer) {
276
+ if (typeof buffer === 'string') {
277
+ buffer += typeof chunk === 'string' ? chunk : chunk.toString(encoding);
278
+ }
279
+ else if (Array.isArray(buffer)) {
280
+ buffer.push(typeof chunk === 'string' ? Buffer.from(chunk, encoding) : chunk);
281
+ }
282
+ else {
283
+ buffer = Buffer.concat([buffer, typeof chunk === 'string' ? Buffer.from(chunk, encoding) : chunk]);
284
+ }
285
+ }
286
+ else {
287
+ buffer = typeof chunk === 'string' ? chunk : [chunk];
288
+ }
289
+ if (updating) {
290
+ dataLength += Buffer.byteLength(chunk, encoding);
291
+ this.updateProgress(dataLength, this.contentLength);
292
+ }
293
+ if (maxBufferSize > 0) {
294
+ if (!updating) {
295
+ dataLength += Buffer.byteLength(chunk, encoding);
296
+ }
297
+ if (dataLength > maxBufferSize) {
298
+ this.terminate((0, types_1.errorValue)("Size limit was exceeded", this.uri.toString()));
299
+ }
300
+ }
301
+ });
302
+ }
303
+ this.client.once('end', () => {
304
+ if (this.closed || this.aborted) {
305
+ return;
306
+ }
307
+ this.close();
308
+ const encoding = opts.encoding;
309
+ let result;
310
+ if (buffer) {
311
+ if (Array.isArray(buffer)) {
312
+ buffer = Buffer.concat(buffer);
313
+ if (encoding) {
314
+ buffer = buffer.toString(encoding);
315
+ }
316
+ }
317
+ dataLength = Buffer.byteLength(buffer, encoding);
318
+ if (updating) {
319
+ this.updateProgress(dataLength, dataLength);
320
+ }
321
+ if (typeof buffer === 'string') {
322
+ if (buffer.startsWith('\uFEFF') && encoding !== 'utf16le' && encoding !== 'utf-16le') {
323
+ buffer = buffer.substring(1);
324
+ }
325
+ if (opts.outFormat) {
326
+ const { out: format, parser } = opts.outFormat;
327
+ let packageName;
328
+ try {
329
+ switch (format) {
330
+ case 'yaml':
331
+ result = yaml.load(buffer, parser);
332
+ break;
333
+ case 'json5':
334
+ result = require(packageName = 'json5').parse(buffer);
335
+ break;
336
+ case 'xml':
337
+ result = new (require(packageName = 'fast-xml-parser').XMLParser)(parser).parse(buffer);
338
+ break;
339
+ case 'toml':
340
+ result = require(packageName = 'toml').parse(buffer);
341
+ break;
342
+ default:
343
+ result = JSON.parse(buffer);
344
+ break;
345
+ }
346
+ if (!(0, types_1.isObject)(result)) {
347
+ result = null;
348
+ }
349
+ }
350
+ catch (err) {
351
+ if (this.state.verbose && !(packageName && this.instance.checkPackage(err, packageName))) {
352
+ this.instance.writeFail(["Unable to parse URI response", format], err, 1024);
353
+ }
354
+ result = null;
355
+ }
356
+ }
357
+ }
358
+ if (result === undefined) {
359
+ result = buffer;
360
+ }
361
+ }
362
+ else {
363
+ if (updating) {
364
+ this.updateProgress(0, this.contentLength);
365
+ }
366
+ if (enabled && this.instance.readExpect === 'always') {
367
+ this.terminate("No data received");
368
+ return;
369
+ }
370
+ result = encoding && !pipeline ? '' : null;
371
+ }
372
+ this.endResponse(result, dataLength, log);
373
+ });
374
+ this.host.success(this.httpVersion);
375
+ }
376
+ updateProgress(dataLength, contentLength) {
377
+ const { host, moduleName } = this.instance;
378
+ host.updateProgress(moduleName, this.opts.progressId, dataLength, contentLength, this.dataTime);
379
+ }
380
+ endResponse(result, dataLength = 0, logging = this.state.log) {
381
+ if (logging) {
382
+ const messageUnit = this.dataTime && dataLength > 0 ? (0, util_1.getTransferRate)(dataLength, (0, types_1.convertTime)(process.hrtime.bigint() - this.dataTime, false) * 1000) : undefined;
383
+ this.instance.writeTimeProcess('HTTP' + this.httpVersion, this.opts.statusMessage || this.uri.toString(), this.startTime, {
384
+ type: 1024,
385
+ queue: !!this.instance.host,
386
+ titleBgColor: !result ? 'bgBlue' : undefined,
387
+ messageUnit,
388
+ messageUnitMinWidth: 9,
389
+ delayTime: this.delayTime,
390
+ bypassLog: module_1.hasLogType(32768)
391
+ });
392
+ }
393
+ this.resolve(result);
394
+ }
395
+ redirectResponse(statusCode, location) {
396
+ if (location) {
397
+ if (this.opts.followRedirect === false) {
398
+ this.terminate(this.formatStatus(statusCode, "Follow redirect was disabled"));
399
+ }
400
+ else if (++this.redirects <= this.redirectLimit) {
401
+ this.abortResponse();
402
+ this.setOpts((0, util_1.fromURL)(this.opts.url, location));
403
+ this.init();
404
+ }
405
+ else {
406
+ this.terminate(this.formatStatus(statusCode, "Exceeded redirect limit"));
407
+ }
408
+ }
409
+ else {
410
+ this.terminate(this.formatStatus(statusCode, "Missing redirect location"));
411
+ }
412
+ }
413
+ abortResponse() {
414
+ if (this.closed) {
415
+ return;
416
+ }
417
+ this.aborted = true;
418
+ this.client.destroy();
419
+ this.instance.reset(this);
420
+ this.cleanup();
421
+ }
422
+ errorResponse(err) {
423
+ if (HttpAdapter.wasAborted(err)) {
424
+ this.terminate(err);
425
+ }
426
+ else if ((0, util_1.checkRetryable)(err) && ++this.retries <= this.retryLimit) {
427
+ this.abortResponse();
428
+ if (HttpAdapter.isConnectionError(err)) {
429
+ this.retryTimeout();
430
+ }
431
+ else {
432
+ setTimeout(() => {
433
+ this.init();
434
+ }, this.retryWait);
435
+ }
436
+ }
437
+ else {
438
+ this.host.error(this.httpVersion);
439
+ this.terminate(err);
440
+ }
441
+ }
442
+ retryResponse(statusCode, retryAfter) {
443
+ this.abortResponse();
444
+ if (retryAfter && this.retryAfter > 0) {
445
+ let offset = +retryAfter || new Date(retryAfter);
446
+ if (offset instanceof Date) {
447
+ offset = Math.max(0, offset.getTime() - Date.now());
448
+ }
449
+ else {
450
+ offset *= 1000;
451
+ }
452
+ if (offset > 0) {
453
+ if (offset <= this.retryAfter) {
454
+ this.sendWarning(`Retry After (${retryAfter})`);
455
+ setTimeout(() => {
456
+ this.init();
457
+ }, offset);
458
+ }
459
+ else {
460
+ this.terminate(this.formatStatus(statusCode));
461
+ }
462
+ return;
463
+ }
464
+ }
465
+ this.sendWarning(this.formatRetry((0, util_1.fromStatusCode)(statusCode)));
466
+ if ((0, util_1.isRetryable)(statusCode, true)) {
467
+ setImmediate(this.init.bind(this));
468
+ }
469
+ else {
470
+ setTimeout(() => {
471
+ this.init();
472
+ }, this.retryWait);
473
+ }
474
+ }
475
+ isRetry(value) {
476
+ return (0, util_1.isRetryable)(value) && ++this.retries <= this.retryLimit;
477
+ }
478
+ retryTimeout() {
479
+ this.sendWarning(this.formatRetry("HTTP connection timeout"));
480
+ this.init();
481
+ }
482
+ terminate(err) {
483
+ if (this.closed) {
484
+ return;
485
+ }
486
+ this.cleanup();
487
+ this.close();
488
+ this.reject(typeof err === 'string' ? new Error(err) : err);
489
+ }
490
+ sendWarning(message) {
491
+ if (this.state.verbose) {
492
+ const { host, url } = this.opts;
493
+ this.instance.formatMessage(1024, 'HTTP' + this.httpVersion, [message, host.origin], url.toString(), { ...module_1.LOG_STYLE_WARN });
494
+ }
495
+ }
496
+ formatStatus(value, hint) {
497
+ return value + ': ' + (hint || (0, util_1.fromStatusCode)(value)) + ` (${this.uri.toString()})`;
498
+ }
499
+ close() {
500
+ this.closed = true;
501
+ this.instance.reset(this);
502
+ if (this.aborted && !this.client.aborted) {
503
+ this.abortController?.abort();
504
+ }
505
+ }
506
+ cleanup() {
507
+ if ((0, types_1.isString)(this.pipeTo) && this.outStream) {
508
+ (0, util_1.cleanupStream)(this.outStream, this.pipeTo);
509
+ this.outStream = null;
510
+ }
511
+ }
512
+ formatNgFlags(value, statusCode, location) {
513
+ return location ? `Using HTTP 1.1 for URL redirect (${location})` : this.formatStatus(statusCode, value ? 'NGHTTP2 Error ' + value : '');
514
+ }
515
+ formatRetry(message) {
516
+ return `${message} (${this.retries} / ${this.retryLimit})`;
517
+ }
518
+ set abortController(value) {
519
+ this.opts.outAbort = value;
520
+ }
521
+ get abortController() {
522
+ return this.opts.outAbort || null;
523
+ }
524
+ set outStream(value) {
525
+ this[kOutStream] = value;
526
+ if (value) {
527
+ this.opts.outStream = value;
528
+ }
529
+ }
530
+ get outStream() {
531
+ return this[kOutStream];
532
+ }
533
+ get destroyed() {
534
+ return this.client.destroyed || this.httpVersion === 2 && this.client.aborted;
535
+ }
536
+ get host() {
537
+ return this.opts.host;
538
+ }
539
+ get httpVersion() {
540
+ return this.opts.httpVersion;
541
+ }
542
+ get pipeTo() {
543
+ return this.opts.pipeTo;
544
+ }
545
+ get silent() {
546
+ return this.opts.silent === false;
547
+ }
548
+ get retryLimit() {
549
+ return this.state.config.retryLimit;
550
+ }
551
+ get retryWait() {
552
+ return this.state.config.retryWait;
553
+ }
554
+ get retryAfter() {
555
+ return this.state.config.retryAfter;
556
+ }
557
+ get redirectLimit() {
558
+ return this.state.config.redirectLimit;
559
+ }
560
+ }
561
+ module.exports = HttpAdapter;
package/index.js CHANGED
@@ -1006,13 +1006,14 @@ class Request extends module_1.default {
1006
1006
  return Promise.reject(err);
1007
1007
  }
1008
1008
  }
1009
- let pathname, headers, binOpts, silent;
1009
+ let pathname, headers, binOpts, signal, silent;
1010
1010
  if (options) {
1011
1011
  if (typeof options === 'string') {
1012
1012
  pathname = options;
1013
1013
  }
1014
1014
  else {
1015
- ({ pathname, headers, binOpts, silent } = options);
1015
+ ({ pathname, binOpts, signal, silent } = options);
1016
+ headers = (0, util_1.parseOutgoingHeaders)(options.headers);
1016
1017
  if ((0, types_1.isArray)(binOpts)) {
1017
1018
  let next = false;
1018
1019
  binOpts = binOpts.filter(opt => !((0, types_1.isString)(opt) && /^-[a-z][\S\s]*$/i.test(opt.trim()))).map((opt) => {
@@ -1316,7 +1317,13 @@ class Request extends module_1.default {
1316
1317
  }
1317
1318
  for (const item of ARIA2.PID_QUEUE) {
1318
1319
  try {
1319
- process.kill(item[0], 0);
1320
+ if (pid === item[0] && (signal === null || signal === void 0 ? void 0 : signal.aborted)) {
1321
+ process.kill(item[0]);
1322
+ closeTorrent(item[0]);
1323
+ }
1324
+ else {
1325
+ process.kill(item[0], 0);
1326
+ }
1320
1327
  }
1321
1328
  catch {
1322
1329
  closeTorrent(item[0]);
@@ -1374,7 +1381,7 @@ class Request extends module_1.default {
1374
1381
  open(uri, options) {
1375
1382
  var _l;
1376
1383
  var _m, _o;
1377
- let { host, url, httpVersion, method = 'GET', encoding, format, headers, postData, keepAlive, agentTimeout, socketPath, timeout = this._config.connectTimeout, outStream } = options;
1384
+ let { host, url, httpVersion, method = 'GET', encoding, format, headers: outgoing, postData, keepAlive, agentTimeout, socketPath, timeout = this._config.connectTimeout, outStream } = options, headers = (0, util_1.parseOutgoingHeaders)(outgoing);
1378
1385
  const getting = method === 'GET';
1379
1386
  const posting = method === 'POST';
1380
1387
  if (format) {
@@ -1629,6 +1636,9 @@ class Request extends module_1.default {
1629
1636
  const ac = new AbortController();
1630
1637
  this[kDownloading].add(options.outAbort = ac);
1631
1638
  stream.addAbortSignal(ac.signal, request);
1639
+ if (options.signal) {
1640
+ stream.addAbortSignal(options.signal, request);
1641
+ }
1632
1642
  this.signal.addEventListener('abort', () => ac.abort(), { once: true });
1633
1643
  }
1634
1644
  if (posting) {
@@ -1678,7 +1688,7 @@ class Request extends module_1.default {
1678
1688
  else {
1679
1689
  options = {};
1680
1690
  }
1681
- const headers = options.headers || (options.headers = {});
1691
+ const headers = (0, util_1.parseOutgoingHeaders)(options.headers || (options.headers = {}));
1682
1692
  for (const attr in headers) {
1683
1693
  const name = attr.toLowerCase();
1684
1694
  if (name === 'content-type' || name === 'content-length') {
@@ -1841,7 +1851,7 @@ class Request extends module_1.default {
1841
1851
  }
1842
1852
  const client = this.open(href, request);
1843
1853
  const { host, url, encoding, outFormat } = request;
1844
- let buffer, aborted;
1854
+ let buffer, aborted = false;
1845
1855
  ({ httpVersion, outAbort } = request);
1846
1856
  const isAborted = () => client.destroyed || httpVersion === 2 && client.aborted;
1847
1857
  const isRetry = (value) => (0, util_1.isRetryable)(value) && ++retries <= this._config.retryLimit;
@@ -1856,14 +1866,11 @@ class Request extends module_1.default {
1856
1866
  if (timeout) {
1857
1867
  clearTimeout(timeout);
1858
1868
  }
1859
- buffer = null;
1860
- aborted = true;
1861
1869
  if (outAbort) {
1862
- if (!client.aborted) {
1863
- outAbort.abort();
1864
- }
1865
1870
  this[kDownloading].delete(outAbort);
1866
1871
  }
1872
+ buffer = null;
1873
+ aborted = true;
1867
1874
  client.destroy();
1868
1875
  };
1869
1876
  const retryTimeout = () => {
@@ -1888,7 +1895,6 @@ class Request extends module_1.default {
1888
1895
  var _l;
1889
1896
  if (readTimeout > 0) {
1890
1897
  timeout = setTimeout(() => {
1891
- abortResponse();
1892
1898
  throwError((0, types_1.errorValue)("Timeout was exceeded", href.toString()));
1893
1899
  }, readTimeout);
1894
1900
  }
@@ -2017,9 +2023,9 @@ class Request extends module_1.default {
2017
2023
  host.success(httpVersion);
2018
2024
  };
2019
2025
  const redirectResponse = (statusCode, location) => {
2020
- abortResponse();
2021
2026
  if (location) {
2022
2027
  if (++redirects <= this._config.redirectLimit) {
2028
+ abortResponse();
2023
2029
  downloadUri.call(this, Request.fromURL(url, location));
2024
2030
  }
2025
2031
  else {
@@ -2031,8 +2037,8 @@ class Request extends module_1.default {
2031
2037
  }
2032
2038
  };
2033
2039
  const errorResponse = (err) => {
2034
- abortResponse();
2035
2040
  if ((0, util_1.checkRetryable)(err) && ++retries <= this._config.retryLimit) {
2041
+ abortResponse();
2036
2042
  if (isConnectionTimeout(err)) {
2037
2043
  retryTimeout();
2038
2044
  }
@@ -2175,7 +2181,6 @@ class Request extends module_1.default {
2175
2181
  retryResponse(statusCode, res.headers['retry-after']);
2176
2182
  }
2177
2183
  else {
2178
- abortResponse();
2179
2184
  throwError(formatStatus(statusCode));
2180
2185
  }
2181
2186
  })
@@ -2193,8 +2198,8 @@ class Request extends module_1.default {
2193
2198
  if (aborted) {
2194
2199
  return;
2195
2200
  }
2196
- abortResponse();
2197
2201
  if (++retries <= this._config.retryLimit) {
2202
+ abortResponse();
2198
2203
  retryTimeout();
2199
2204
  }
2200
2205
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e-mc/request",
3
- "version": "0.7.16",
3
+ "version": "0.7.18",
4
4
  "description": "Request constructor for E-mc.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -20,8 +20,8 @@
20
20
  "license": "MIT",
21
21
  "homepage": "https://github.com/anpham6/e-mc#readme",
22
22
  "dependencies": {
23
- "@e-mc/module": "0.7.16",
24
- "@e-mc/types": "0.7.16",
23
+ "@e-mc/module": "0.7.18",
24
+ "@e-mc/types": "0.7.18",
25
25
  "combined-stream": "^1.0.8",
26
26
  "js-yaml": "^4.1.0",
27
27
  "which": "^2.0.2"
package/util.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { IncomingHttpHeaders, OutgoingHttpHeaders } from 'http';
4
4
 
5
5
  declare namespace util {
6
6
  function parseHeader<T = unknown>(headers: IncomingHttpHeaders, name: string): T | undefined;
7
+ function parseOutgoingHeaders(headers: OutgoingHttpHeaders | Headers | undefined): OutgoingHttpHeaders | undefined;
7
8
  function normalizeHeaders(headers: OutgoingHttpHeaders): OutgoingHttpHeaders;
8
9
  function getBasicAuth(auth: AuthValue): string;
9
10
  function getBasicAuth(username: unknown, password?: unknown): string;
package/util.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.fromSeconds = exports.asFloat = exports.asInt = exports.trimPath = exports.isRetryable = exports.checkRetryable = exports.hasBasicAuth = exports.getBasicAuth = exports.normalizeHeaders = exports.parseHeader = void 0;
3
+ exports.fromSeconds = exports.asFloat = exports.asInt = exports.trimPath = exports.isRetryable = exports.checkRetryable = exports.hasBasicAuth = exports.getBasicAuth = exports.normalizeHeaders = exports.parseOutgoingHeaders = exports.parseHeader = void 0;
4
4
  const util = require("util");
5
5
  const module_1 = require("@e-mc/module");
6
6
  const types_1 = require("@e-mc/types");
@@ -25,6 +25,24 @@ function parseHeader(headers, name) {
25
25
  }
26
26
  }
27
27
  exports.parseHeader = parseHeader;
28
+ function parseOutgoingHeaders(headers) {
29
+ if (headers) {
30
+ if (globalThis.Headers && headers instanceof Headers) {
31
+ const result = {};
32
+ headers.forEach((value, key) => {
33
+ if (key === 'set-cookie') {
34
+ (result[key] || (result[key] = [])).push(value);
35
+ }
36
+ else {
37
+ result[key] = value;
38
+ }
39
+ });
40
+ return result;
41
+ }
42
+ return headers;
43
+ }
44
+ }
45
+ exports.parseOutgoingHeaders = parseOutgoingHeaders;
28
46
  function normalizeHeaders(headers) {
29
47
  const result = Object.create(null);
30
48
  for (const name in headers) {
@@ -37,11 +55,11 @@ function normalizeHeaders(headers) {
37
55
  value = value.trim();
38
56
  break;
39
57
  default:
40
- if (Array.isArray(value)) {
41
- value = value.map(out => module_1.default.asString(out).trim());
42
- break;
58
+ if (!(0, types_1.isArray)(value)) {
59
+ continue;
43
60
  }
44
- continue;
61
+ value = value.map(out => module_1.default.asString(out).trim());
62
+ break;
45
63
  }
46
64
  if (value) {
47
65
  result[trimPath(name.trim().toLowerCase())] = value;