@e-mc/request 0.5.15 → 0.5.17

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,554 @@
1
+ "use strict";
2
+ var _a;
3
+ const fs = require("node:fs");
4
+ const http2 = require("node:http2");
5
+ const yaml = require("js-yaml");
6
+ const types_1 = require("@e-mc/types");
7
+ const module_1 = require("@e-mc/module");
8
+ const util_1 = require("@e-mc/request/util");
9
+ const kOutStream = Symbol('outStream');
10
+ const kOptions = Symbol('options');
11
+ let LOG_TIMEFORMAT = 'readable';
12
+ class HttpAdapter {
13
+ static isUnsupported(value) {
14
+ return value === 421 || value === 505;
15
+ }
16
+ static isDowngrade(err) {
17
+ return err instanceof Error && (err.code === 'ERR_HTTP2_ERROR' || this.isUnsupported(Math.abs(err.errno)));
18
+ }
19
+ static wasAborted(err) {
20
+ return err instanceof Error && err.message.startsWith("Aborted");
21
+ }
22
+ static isConnectionError(err) {
23
+ switch (err instanceof Error && err.code) {
24
+ case 'ETIMEDOUT':
25
+ case 'ECONNRESET':
26
+ return true;
27
+ default:
28
+ return false;
29
+ }
30
+ }
31
+ static defineHostConfig({ settings }) {
32
+ const time_format = settings?.time_format;
33
+ switch (time_format) {
34
+ case 'readable':
35
+ case 'relative':
36
+ case 'none':
37
+ LOG_TIMEFORMAT = time_format;
38
+ break;
39
+ }
40
+ }
41
+ constructor(instance, state, uri, options) {
42
+ this.instance = instance;
43
+ this.state = state;
44
+ this.uri = uri;
45
+ this.contentLength = 0;
46
+ this.retries = 0;
47
+ this.redirects = 0;
48
+ this.closed = false;
49
+ this.aborted = false;
50
+ this.timeout = null;
51
+ this.dataTime = null;
52
+ this.delayTime = undefined;
53
+ this[_a] = null;
54
+ this[kOptions] = options;
55
+ this.startTime = state.log ? process.hrtime.bigint() : BigInt(0);
56
+ this.setOpts();
57
+ }
58
+ async start() {
59
+ return new Promise((resolve, reject) => {
60
+ this.resolve = resolve;
61
+ this.reject = reject;
62
+ this.init();
63
+ });
64
+ }
65
+ init() {
66
+ this.aborted = false;
67
+ this.setWriteStream();
68
+ this.client = this.instance.open(this.uri, this.opts);
69
+ if (this.opts.httpVersion === 2) {
70
+ this.client
71
+ .on('response', (headers, flags) => {
72
+ if (this.destroyed) {
73
+ return;
74
+ }
75
+ const statusCode = headers[':status'];
76
+ if (statusCode < 300) {
77
+ this.acceptResponse(headers);
78
+ }
79
+ else if (statusCode < 400) {
80
+ this.redirectResponse(statusCode, headers.location);
81
+ }
82
+ else if (statusCode === 401 ||
83
+ statusCode === 402 ||
84
+ statusCode === 403 ||
85
+ statusCode === 404 ||
86
+ statusCode === 407 ||
87
+ statusCode === 410) {
88
+ this.terminate(this.formatStatus(statusCode));
89
+ }
90
+ else if (this.isRetry(statusCode)) {
91
+ this.retryResponse(statusCode, headers['retry-after']);
92
+ }
93
+ else if (HttpAdapter.isUnsupported(statusCode)) {
94
+ this.retryDownload(true, this.formatNgFlags(http2.constants.NGHTTP2_PROTOCOL_ERROR, statusCode));
95
+ }
96
+ else {
97
+ switch (flags) {
98
+ case http2.constants.NGHTTP2_PROTOCOL_ERROR:
99
+ case http2.constants.NGHTTP2_INADEQUATE_SECURITY:
100
+ case http2.constants.NGHTTP2_HTTP_1_1_REQUIRED:
101
+ this.retryDownload(true, this.formatNgFlags(flags, statusCode, headers.location));
102
+ break;
103
+ default:
104
+ this.retryDownload(false, this.formatStatus(statusCode));
105
+ break;
106
+ }
107
+ }
108
+ })
109
+ .on('unknownProtocol', () => {
110
+ if (this.aborted) {
111
+ return;
112
+ }
113
+ this.retryDownload(true, 'Unknown protocol (HTTP/2)');
114
+ })
115
+ .on('aborted', () => {
116
+ this.aborted = true;
117
+ this.terminate((0, types_1.createAbortError)());
118
+ })
119
+ .on('error', async (err) => {
120
+ if (this.aborted) {
121
+ return;
122
+ }
123
+ if (HttpAdapter.wasAborted(err)) {
124
+ this.errorResponse(err);
125
+ }
126
+ else {
127
+ switch (!HttpAdapter.isDowngrade(err) && await this.host.hasProtocol(2)) {
128
+ case 1:
129
+ this.errorResponse(err);
130
+ break;
131
+ case 2:
132
+ this.retryDownload(false, err);
133
+ break;
134
+ default:
135
+ this.retryDownload(true, err);
136
+ break;
137
+ }
138
+ }
139
+ });
140
+ }
141
+ else {
142
+ this.client
143
+ .on('response', res => {
144
+ if (this.destroyed) {
145
+ return;
146
+ }
147
+ const statusCode = res.statusCode;
148
+ if (statusCode < 300) {
149
+ this.acceptResponse(res.headers);
150
+ }
151
+ else if (statusCode < 400) {
152
+ this.redirectResponse(statusCode, res.headers.location);
153
+ }
154
+ else if (this.isRetry(statusCode)) {
155
+ this.retryResponse(statusCode, res.headers['retry-after']);
156
+ }
157
+ else {
158
+ this.terminate(this.formatStatus(statusCode));
159
+ }
160
+ })
161
+ .on('abort', () => {
162
+ this.aborted = true;
163
+ this.terminate((0, types_1.createAbortError)());
164
+ })
165
+ .on('error', err => {
166
+ if (this.aborted) {
167
+ return;
168
+ }
169
+ this.errorResponse(err);
170
+ });
171
+ }
172
+ this.client.on('timeout', () => {
173
+ if (this.aborted) {
174
+ return;
175
+ }
176
+ if (++this.retries <= this.retryLimit) {
177
+ this.abortResponse();
178
+ this.retryTimeout();
179
+ }
180
+ else {
181
+ this.terminate(this.formatStatus(408));
182
+ }
183
+ });
184
+ }
185
+ setOpts(uri) {
186
+ if (uri) {
187
+ this.uri = uri;
188
+ }
189
+ this.opts = this.instance.opts(this.uri, this[kOptions]);
190
+ }
191
+ setWriteStream() {
192
+ const pipeTo = this.pipeTo;
193
+ if (typeof pipeTo === 'string') {
194
+ try {
195
+ this.outStream = fs.createWriteStream(pipeTo, { emitClose: false, highWaterMark: this.host.streamSize });
196
+ }
197
+ catch (err) {
198
+ this.terminate(err);
199
+ }
200
+ }
201
+ else if (pipeTo) {
202
+ this.outStream = pipeTo;
203
+ }
204
+ }
205
+ retryDownload(downgrade, message) {
206
+ if (this.aborted) {
207
+ return;
208
+ }
209
+ this.abortResponse();
210
+ if (downgrade) {
211
+ const host = this.host;
212
+ host.failed(2);
213
+ if (host.version > 1) {
214
+ if (this.state.verbose) {
215
+ this.instance.formatMessage(1024, 'HTTP2', ["Unsupported protocol", host.origin], message, { failed: true });
216
+ }
217
+ host.version = 1;
218
+ }
219
+ }
220
+ this.opts.httpVersion = 1;
221
+ this.init();
222
+ }
223
+ acceptResponse(headers) {
224
+ const opts = this.opts;
225
+ if ('outHeaders' in opts) {
226
+ opts.outHeaders = headers;
227
+ }
228
+ if ('outFilename' in opts) {
229
+ opts.outFilename = (0, util_1.parseHeader)(headers, 'content-disposition');
230
+ }
231
+ const pipeline = this.pipeTo ? !(0, types_1.isString)(this.pipeTo) : false;
232
+ const enabled = opts.connected?.call(this.client, headers) !== false && !pipeline;
233
+ const maxBufferSize = opts.maxBufferSize ? (0, types_1.alignSize)(opts.maxBufferSize) : 0;
234
+ this.contentLength = parseInt(headers['content-length'] || '0');
235
+ const updating = opts.progressId !== undefined && !!this.instance.host && this.contentLength > 0;
236
+ const readTimeout = this.instance.readTimeout;
237
+ let log = this.state.log, buffer = null, dataLength = 0;
238
+ this.client.once('readable', () => {
239
+ if (readTimeout > 0) {
240
+ this.timeout = setTimeout(() => {
241
+ this.terminate((0, types_1.errorValue)("Timeout was exceeded", this.uri.toString()));
242
+ }, readTimeout);
243
+ }
244
+ if (log) {
245
+ switch (this.instance.settings?.time_format || LOG_TIMEFORMAT) {
246
+ case 'readable':
247
+ this.delayTime = process.hrtime.bigint() - this.startTime;
248
+ break;
249
+ case 'relative':
250
+ this.delayTime = Date.now() - this.instance.startTime;
251
+ break;
252
+ case 'none':
253
+ if (!this.silent) {
254
+ log = false;
255
+ }
256
+ break;
257
+ }
258
+ }
259
+ this.dataTime = process.hrtime.bigint();
260
+ if (updating) {
261
+ this.updateProgress(0, 0);
262
+ }
263
+ });
264
+ if (enabled) {
265
+ const encoding = opts.encoding;
266
+ this.client.on('data', (chunk) => {
267
+ if (buffer) {
268
+ if (typeof buffer === 'string') {
269
+ buffer += typeof chunk === 'string' ? chunk : chunk.toString(encoding);
270
+ }
271
+ else if (Array.isArray(buffer)) {
272
+ buffer.push(typeof chunk === 'string' ? Buffer.from(chunk, encoding) : chunk);
273
+ }
274
+ else {
275
+ buffer = Buffer.concat([buffer, typeof chunk === 'string' ? Buffer.from(chunk, encoding) : chunk]);
276
+ }
277
+ }
278
+ else {
279
+ buffer = typeof chunk === 'string' ? chunk : [chunk];
280
+ }
281
+ if (updating) {
282
+ dataLength += Buffer.byteLength(chunk, encoding);
283
+ this.updateProgress(dataLength, this.contentLength);
284
+ }
285
+ if (maxBufferSize > 0) {
286
+ if (!updating) {
287
+ dataLength += Buffer.byteLength(chunk, encoding);
288
+ }
289
+ if (dataLength > maxBufferSize) {
290
+ this.terminate((0, types_1.errorValue)("Size limit was exceeded", this.uri.toString()));
291
+ }
292
+ }
293
+ });
294
+ }
295
+ this.client.once('end', () => {
296
+ if (this.closed || this.aborted) {
297
+ return;
298
+ }
299
+ this.close();
300
+ const encoding = opts.encoding;
301
+ let result;
302
+ if (buffer) {
303
+ if (Array.isArray(buffer)) {
304
+ buffer = Buffer.concat(buffer);
305
+ if (encoding) {
306
+ buffer = buffer.toString(encoding);
307
+ }
308
+ }
309
+ dataLength = Buffer.byteLength(buffer, encoding);
310
+ if (updating) {
311
+ this.updateProgress(dataLength, dataLength);
312
+ }
313
+ if (typeof buffer === 'string') {
314
+ if (buffer.startsWith('\uFEFF') && encoding !== 'utf16le' && encoding !== 'utf-16le') {
315
+ buffer = buffer.substring(1);
316
+ }
317
+ if (opts.outFormat) {
318
+ const { out: format, parser } = opts.outFormat;
319
+ let packageName;
320
+ try {
321
+ switch (format) {
322
+ case 'yaml':
323
+ result = yaml.load(buffer, parser);
324
+ break;
325
+ case 'json5':
326
+ result = require(packageName = 'json5').parse(buffer);
327
+ break;
328
+ case 'xml':
329
+ result = new (require(packageName = 'fast-xml-parser').XMLParser)(parser).parse(buffer);
330
+ break;
331
+ case 'toml':
332
+ result = require(packageName = 'toml').parse(buffer);
333
+ break;
334
+ default:
335
+ result = JSON.parse(buffer);
336
+ break;
337
+ }
338
+ if (!(0, types_1.isObject)(result)) {
339
+ result = null;
340
+ }
341
+ }
342
+ catch (err) {
343
+ if (this.state.verbose && !(packageName && this.instance.checkPackage(err, packageName))) {
344
+ this.instance.writeFail(["Unable to parse URI response", format], err, 1024);
345
+ }
346
+ result = null;
347
+ }
348
+ }
349
+ }
350
+ if (result === undefined) {
351
+ result = buffer;
352
+ }
353
+ }
354
+ else {
355
+ if (updating) {
356
+ this.updateProgress(0, this.contentLength);
357
+ }
358
+ if (enabled && this.instance.readExpect === 'always') {
359
+ this.terminate("No data received");
360
+ return;
361
+ }
362
+ result = encoding && !pipeline ? '' : null;
363
+ }
364
+ this.endResponse(result, dataLength, log);
365
+ });
366
+ this.host.success(this.httpVersion);
367
+ }
368
+ updateProgress(dataLength, contentLength) {
369
+ const { host, moduleName } = this.instance;
370
+ host.updateProgress(moduleName, this.opts.progressId, dataLength, contentLength, this.dataTime);
371
+ }
372
+ endResponse(result, dataLength = 0, logging = this.state.log) {
373
+ if (logging) {
374
+ const messageUnit = this.dataTime && dataLength > 0 ? (0, util_1.getTransferRate)(dataLength, (0, types_1.convertTime)(process.hrtime.bigint() - this.dataTime, false) * 1000) : undefined;
375
+ this.instance.writeTimeProcess('HTTP' + this.httpVersion, this.opts.statusMessage || this.uri.toString(), this.startTime, {
376
+ type: 1024,
377
+ queue: !!this.instance.host,
378
+ titleBgColor: !result ? 'bgBlue' : undefined,
379
+ messageUnit,
380
+ messageUnitMinWidth: 9,
381
+ delayTime: this.delayTime,
382
+ bypassLog: module_1.hasLogType(32768)
383
+ });
384
+ }
385
+ this.resolve(result);
386
+ }
387
+ redirectResponse(statusCode, location) {
388
+ if (location) {
389
+ if (this.opts.followRedirect === false) {
390
+ this.terminate(this.formatStatus(statusCode, "Follow redirect was disabled"));
391
+ }
392
+ else if (++this.redirects <= this.redirectLimit) {
393
+ this.abortResponse();
394
+ this.setOpts((0, util_1.fromURL)(this.opts.url, location));
395
+ this.init();
396
+ }
397
+ else {
398
+ this.terminate(this.formatStatus(statusCode, "Exceeded redirect limit"));
399
+ }
400
+ }
401
+ else {
402
+ this.terminate(this.formatStatus(statusCode, "Missing redirect location"));
403
+ }
404
+ }
405
+ abortResponse() {
406
+ if (this.closed) {
407
+ return;
408
+ }
409
+ this.aborted = true;
410
+ this.client.destroy();
411
+ this.instance.reset(this);
412
+ this.cleanup();
413
+ }
414
+ errorResponse(err) {
415
+ if (HttpAdapter.wasAborted(err)) {
416
+ this.terminate(err);
417
+ }
418
+ else if ((0, util_1.checkRetryable)(err) && ++this.retries <= this.retryLimit) {
419
+ this.abortResponse();
420
+ if (HttpAdapter.isConnectionError(err)) {
421
+ this.retryTimeout();
422
+ }
423
+ else {
424
+ setTimeout(() => {
425
+ this.init();
426
+ }, this.retryWait);
427
+ }
428
+ }
429
+ else {
430
+ this.host.error(this.httpVersion);
431
+ this.terminate(err);
432
+ }
433
+ }
434
+ retryResponse(statusCode, retryAfter) {
435
+ this.abortResponse();
436
+ if (retryAfter && this.retryAfter > 0) {
437
+ let offset = +retryAfter || new Date(retryAfter);
438
+ if (offset instanceof Date) {
439
+ offset = Math.max(0, offset.getTime() - Date.now());
440
+ }
441
+ else {
442
+ offset *= 1000;
443
+ }
444
+ if (offset > 0) {
445
+ if (offset <= this.retryAfter) {
446
+ this.sendWarning(`Retry After (${retryAfter})`);
447
+ setTimeout(() => {
448
+ this.init();
449
+ }, offset);
450
+ }
451
+ else {
452
+ this.terminate(this.formatStatus(statusCode));
453
+ }
454
+ return;
455
+ }
456
+ }
457
+ this.sendWarning(this.formatRetry((0, util_1.fromStatusCode)(statusCode)));
458
+ if ((0, util_1.isRetryable)(statusCode, true)) {
459
+ setImmediate(this.init.bind(this));
460
+ }
461
+ else {
462
+ setTimeout(() => {
463
+ this.init();
464
+ }, this.retryWait);
465
+ }
466
+ }
467
+ isRetry(value) {
468
+ return (0, util_1.isRetryable)(value) && ++this.retries <= this.retryLimit;
469
+ }
470
+ retryTimeout() {
471
+ this.sendWarning(this.formatRetry("HTTP connection timeout"));
472
+ this.init();
473
+ }
474
+ terminate(err) {
475
+ if (this.closed) {
476
+ return;
477
+ }
478
+ this.cleanup();
479
+ this.close();
480
+ this.reject(typeof err === 'string' ? new Error(err) : err);
481
+ }
482
+ sendWarning(message) {
483
+ if (this.state.verbose) {
484
+ const { host, url } = this.opts;
485
+ this.instance.formatMessage(1024, 'HTTP' + this.httpVersion, [message, host.origin], url.toString(), { ...module_1.LOG_STYLE_WARN });
486
+ }
487
+ }
488
+ formatStatus(value, hint) {
489
+ return value + ': ' + (hint || (0, util_1.fromStatusCode)(value)) + ` (${this.uri.toString()})`;
490
+ }
491
+ close() {
492
+ this.closed = true;
493
+ this.instance.reset(this);
494
+ if (this.aborted && !this.client.aborted) {
495
+ this.abortController?.abort();
496
+ }
497
+ }
498
+ cleanup() {
499
+ if ((0, types_1.isString)(this.pipeTo) && this.outStream) {
500
+ (0, util_1.cleanupStream)(this.outStream, this.pipeTo);
501
+ this.outStream = null;
502
+ }
503
+ }
504
+ formatNgFlags(value, statusCode, location) {
505
+ return location ? `Using HTTP 1.1 for URL redirect (${location})` : this.formatStatus(statusCode, value ? 'NGHTTP2 Error ' + value : '');
506
+ }
507
+ formatRetry(message) {
508
+ return `${message} (${this.retries} / ${this.retryLimit})`;
509
+ }
510
+ set abortController(value) {
511
+ this.opts.outAbort = value;
512
+ }
513
+ get abortController() {
514
+ return this.opts.outAbort || null;
515
+ }
516
+ set outStream(value) {
517
+ this[kOutStream] = value;
518
+ if (value) {
519
+ this.opts.outStream = value;
520
+ }
521
+ }
522
+ get outStream() {
523
+ return this[kOutStream];
524
+ }
525
+ get destroyed() {
526
+ return this.client.destroyed || this.httpVersion === 2 && this.client.aborted;
527
+ }
528
+ get host() {
529
+ return this.opts.host;
530
+ }
531
+ get httpVersion() {
532
+ return this.opts.httpVersion;
533
+ }
534
+ get pipeTo() {
535
+ return this.opts.pipeTo;
536
+ }
537
+ get silent() {
538
+ return this.opts.silent === false;
539
+ }
540
+ get retryLimit() {
541
+ return this.state.config.retryLimit;
542
+ }
543
+ get retryWait() {
544
+ return this.state.config.retryWait;
545
+ }
546
+ get retryAfter() {
547
+ return this.state.config.retryAfter;
548
+ }
549
+ get redirectLimit() {
550
+ return this.state.config.redirectLimit;
551
+ }
552
+ }
553
+ _a = kOutStream;
554
+ module.exports = HttpAdapter;
package/index.js CHANGED
@@ -366,7 +366,7 @@ class Request extends module_1.default {
366
366
  if ((lowest_speed_limit = parseSize(lowest_speed_limit, true)) !== undefined) {
367
367
  ARIA2.LOWEST_SPEED_LIMIT = lowest_speed_limit;
368
368
  }
369
- if (always_resume) {
369
+ if (typeof always_resume === 'boolean') {
370
370
  ARIA2.ALWAYS_RESUME = always_resume;
371
371
  }
372
372
  if ((0, types_1.isString)(file_allocation)) {
@@ -380,7 +380,7 @@ class Request extends module_1.default {
380
380
  break;
381
381
  }
382
382
  }
383
- if (conf_path && this.isPath(conf_path = path.resolve(conf_path))) {
383
+ if (conf_path === '' || (0, types_1.isString)(conf_path) && this.isPath(conf_path = path.resolve(conf_path))) {
384
384
  ARIA2.CONF_PATH = conf_path;
385
385
  }
386
386
  }
@@ -975,13 +975,14 @@ class Request extends module_1.default {
975
975
  return Promise.reject(err);
976
976
  }
977
977
  }
978
- let pathname, headers, binOpts, silent;
978
+ let pathname, headers, binOpts, signal, silent;
979
979
  if (options) {
980
980
  if (typeof options === 'string') {
981
981
  pathname = options;
982
982
  }
983
983
  else {
984
- ({ pathname, headers, binOpts, silent } = options);
984
+ ({ pathname, binOpts, signal, silent } = options);
985
+ headers = (0, util_1.parseOutgoingHeaders)(options.headers);
985
986
  if ((0, types_1.isArray)(binOpts)) {
986
987
  let next = false;
987
988
  binOpts = binOpts.filter(opt => !((0, types_1.isString)(opt) && /^-[a-z][\S\s]*$/i.test(opt.trim()))).map((opt) => {
@@ -995,7 +996,7 @@ class Request extends module_1.default {
995
996
  case 'string': {
996
997
  const value = opt.trim();
997
998
  if (value.startsWith('--')) {
998
- const match = /^(--[a-z]+[a-z0-9-]*)(=)?\s*(.*)$/.exec(value);
999
+ const match = /^(--[a-z]+[a-z0-9-]*)(=)?\s*(.*)$/s.exec(value);
999
1000
  if (match) {
1000
1001
  switch (match[1]) {
1001
1002
  case '--daemon':
@@ -1051,8 +1052,8 @@ class Request extends module_1.default {
1051
1052
  ({ protocol, origin, username, password, href: uri } = uri);
1052
1053
  }
1053
1054
  const escapeQuote = (value) => {
1054
- value = value.replace(/"/g, '\\"');
1055
- return PLATFORM_WIN32 ? value : value.replace(/[ $`]/g, capture => (capture !== ' ' ? '\\' : '') + '\\' + capture);
1055
+ value = value.replace(/(?<!\\)"/g, '\\"');
1056
+ return PLATFORM_WIN32 ? value : value.replace(/(?<!\\)\$/g, '\\$');
1056
1057
  };
1057
1058
  const init = [
1058
1059
  `--dir="${escapeQuote(pathname)}"`,
@@ -1197,10 +1198,13 @@ class Request extends module_1.default {
1197
1198
  }
1198
1199
  if (binOpts) {
1199
1200
  for (const leading of binOpts) {
1200
- for (let i = 0; i < opts.length; ++i) {
1201
- if (opts[i].startsWith(leading)) {
1202
- opts.splice(i--, i);
1203
- break;
1201
+ if (leading.startsWith('-')) {
1202
+ const pattern = new RegExp(`^${leading}(?:=|$)`);
1203
+ for (let i = 0; i < opts.length; ++i) {
1204
+ if (pattern.test(opts[i])) {
1205
+ opts.splice(i--, i);
1206
+ break;
1207
+ }
1204
1208
  }
1205
1209
  }
1206
1210
  }
@@ -1212,18 +1216,14 @@ class Request extends module_1.default {
1212
1216
  opts.push(`"${escapeQuote(uri)}"`);
1213
1217
  args = args.concat(init, opts);
1214
1218
  const startTime = Date.now();
1215
- let out = '', message = '', aborted;
1219
+ let out = '', message = '';
1216
1220
  const errorResponse = (pid, err) => {
1217
- aborted = true;
1218
1221
  closeTorrent(pid);
1219
1222
  reject(err);
1220
1223
  };
1221
1224
  const { pid, stdout, stderr } = child_process.spawn(module_1.default.sanitizeCmd(ARIA2.BIN), args, { cwd: pathname, shell: true, signal: this.signal })
1222
1225
  .on('exit', code => {
1223
1226
  closeTorrent(pid);
1224
- if (aborted) {
1225
- return;
1226
- }
1227
1227
  if (this.aborted) {
1228
1228
  errorResponse(pid, (0, types_1.createAbortError)());
1229
1229
  return;
@@ -1254,10 +1254,15 @@ class Request extends module_1.default {
1254
1254
  break;
1255
1255
  }
1256
1256
  }
1257
- if (result.length && !silent && LOG_HTTP && LOG_TIMEPROCESS) {
1258
- this.writeTimeProcess("aria2", uri, startTime, { type: 1024, queue: true, messageUnit, messageUnitMinWidth: 9 });
1257
+ if (result.length > 0) {
1258
+ if (!silent && LOG_HTTP && LOG_TIMEPROCESS) {
1259
+ this.writeTimeProcess("aria2", uri, startTime, { type: 1024, queue: true, messageUnit, messageUnitMinWidth: 9, bypassLog: true });
1260
+ }
1261
+ this.addLog(types_1.STATUS_TYPE.INFO, out, currentTime, currentTime - startTime, "aria2", uri);
1262
+ }
1263
+ else {
1264
+ this.addLog(types_1.STATUS_TYPE.ERROR, 'No files were successfully downloaded', currentTime, currentTime - startTime, "aria2", uri);
1259
1265
  }
1260
- this.addLog(result.length ? types_1.STATUS_TYPE.INFO : types_1.STATUS_TYPE.ERROR, out, currentTime, currentTime - startTime, "aria2", uri);
1261
1266
  resolve(result);
1262
1267
  }
1263
1268
  else {
@@ -1280,7 +1285,13 @@ class Request extends module_1.default {
1280
1285
  }
1281
1286
  for (const item of ARIA2.PID_QUEUE) {
1282
1287
  try {
1283
- process.kill(item[0], 0);
1288
+ if (pid === item[0] && (signal === null || signal === void 0 ? void 0 : signal.aborted)) {
1289
+ process.kill(item[0]);
1290
+ closeTorrent(item[0]);
1291
+ }
1292
+ else {
1293
+ process.kill(item[0], 0);
1294
+ }
1284
1295
  }
1285
1296
  catch {
1286
1297
  closeTorrent(item[0]);
@@ -1338,7 +1349,7 @@ class Request extends module_1.default {
1338
1349
  open(uri, options) {
1339
1350
  var _l;
1340
1351
  var _m, _o;
1341
- let { host, url, httpVersion, method = 'GET', encoding, format, headers, postData, keepAlive, agentTimeout, socketPath, timeout = this._config.connectTimeout, outStream } = options;
1352
+ 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);
1342
1353
  const getting = method === 'GET';
1343
1354
  const posting = method === 'POST';
1344
1355
  if (format) {
@@ -1585,6 +1596,9 @@ class Request extends module_1.default {
1585
1596
  const ac = new AbortController();
1586
1597
  this[kDownloading].add(options.outAbort = ac);
1587
1598
  stream.addAbortSignal(ac.signal, request);
1599
+ if (options.signal) {
1600
+ stream.addAbortSignal(options.signal, request);
1601
+ }
1588
1602
  this.signal.addEventListener('abort', () => ac.abort(), { once: true });
1589
1603
  }
1590
1604
  if (posting) {
@@ -1634,7 +1648,7 @@ class Request extends module_1.default {
1634
1648
  else {
1635
1649
  options = {};
1636
1650
  }
1637
- const headers = options.headers || (options.headers = {});
1651
+ const headers = (0, util_1.parseOutgoingHeaders)(options.headers || (options.headers = {}));
1638
1652
  for (const attr in headers) {
1639
1653
  switch (attr.toLowerCase()) {
1640
1654
  case 'content-type':
@@ -1800,7 +1814,7 @@ class Request extends module_1.default {
1800
1814
  }
1801
1815
  const client = this.open(href, request);
1802
1816
  const { host, url, encoding, outFormat } = request;
1803
- let buffer, aborted;
1817
+ let buffer, aborted = false;
1804
1818
  ({ httpVersion, outAbort } = request);
1805
1819
  const isAborted = () => client.destroyed || httpVersion === 2 && client.aborted;
1806
1820
  const isRetry = (value) => (0, util_1.isRetryable)(value) && ++retries <= this._config.retryLimit;
@@ -1815,14 +1829,11 @@ class Request extends module_1.default {
1815
1829
  if (timeout) {
1816
1830
  clearTimeout(timeout);
1817
1831
  }
1818
- buffer = null;
1819
- aborted = true;
1820
1832
  if (outAbort) {
1821
- if (!client.aborted) {
1822
- outAbort.abort();
1823
- }
1824
1833
  this[kDownloading].delete(outAbort);
1825
1834
  }
1835
+ buffer = null;
1836
+ aborted = true;
1826
1837
  client.destroy();
1827
1838
  };
1828
1839
  const retryTimeout = () => {
@@ -1846,7 +1857,6 @@ class Request extends module_1.default {
1846
1857
  var _l;
1847
1858
  if (this.readTimeout > 0) {
1848
1859
  timeout = setTimeout(() => {
1849
- abortResponse();
1850
1860
  throwError((0, types_1.errorValue)("Timeout was exceeded", href.toString()));
1851
1861
  }, this.readTimeout);
1852
1862
  }
@@ -1975,9 +1985,9 @@ class Request extends module_1.default {
1975
1985
  host.success(httpVersion);
1976
1986
  };
1977
1987
  const redirectResponse = (statusCode, location) => {
1978
- abortResponse();
1979
1988
  if (location) {
1980
1989
  if (++redirects <= this._config.redirectLimit) {
1990
+ abortResponse();
1981
1991
  downloadUri.call(this, Request.fromURL(url, location));
1982
1992
  }
1983
1993
  else {
@@ -1989,8 +1999,8 @@ class Request extends module_1.default {
1989
1999
  }
1990
2000
  };
1991
2001
  const errorResponse = (err) => {
1992
- abortResponse();
1993
2002
  if ((0, util_1.checkRetryable)(err) && ++retries <= this._config.retryLimit) {
2003
+ abortResponse();
1994
2004
  if (isConnectionTimeout(err)) {
1995
2005
  retryTimeout();
1996
2006
  }
@@ -2133,7 +2143,6 @@ class Request extends module_1.default {
2133
2143
  retryResponse(statusCode, res.headers['retry-after']);
2134
2144
  }
2135
2145
  else {
2136
- abortResponse();
2137
2146
  throwError(formatStatus(statusCode));
2138
2147
  }
2139
2148
  })
@@ -2151,8 +2160,8 @@ class Request extends module_1.default {
2151
2160
  if (aborted) {
2152
2161
  return;
2153
2162
  }
2154
- abortResponse();
2155
2163
  if (++retries <= this._config.retryLimit) {
2164
+ abortResponse();
2156
2165
  retryTimeout();
2157
2166
  }
2158
2167
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e-mc/request",
3
- "version": "0.5.15",
3
+ "version": "0.5.17",
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.5.15",
24
- "@e-mc/types": "0.5.15",
23
+ "@e-mc/module": "0.5.17",
24
+ "@e-mc/types": "0.5.17",
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
@@ -1,7 +1,10 @@
1
+ import type { AuthValue } from '@e-mc/types/lib/http';
2
+
1
3
  import type { IncomingHttpHeaders, OutgoingHttpHeaders } from 'http';
2
4
 
3
5
  declare namespace util {
4
6
  function parseHeader<T = unknown>(headers: IncomingHttpHeaders, name: string): T | undefined;
7
+ function parseOutgoingHeaders(headers: OutgoingHttpHeaders | Headers | undefined): OutgoingHttpHeaders | undefined;
5
8
  function normalizeHeaders(headers: OutgoingHttpHeaders): OutgoingHttpHeaders;
6
9
  function getBasicAuth(auth: AuthValue): string;
7
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;