@e-mc/request 0.6.13 → 0.6.15

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
@@ -384,7 +384,7 @@ class Request extends module_1.default {
384
384
  if ((lowest_speed_limit = parseSize(lowest_speed_limit, true)) !== undefined) {
385
385
  ARIA2.LOWEST_SPEED_LIMIT = lowest_speed_limit;
386
386
  }
387
- if (always_resume) {
387
+ if (typeof always_resume === 'boolean') {
388
388
  ARIA2.ALWAYS_RESUME = always_resume;
389
389
  }
390
390
  if ((0, types_1.isString)(file_allocation)) {
@@ -398,7 +398,7 @@ class Request extends module_1.default {
398
398
  break;
399
399
  }
400
400
  }
401
- if (conf_path && this.isPath(conf_path = path.resolve(conf_path))) {
401
+ if (conf_path === '' || (0, types_1.isString)(conf_path) && this.isPath(conf_path = path.resolve(conf_path))) {
402
402
  ARIA2.CONF_PATH = conf_path;
403
403
  }
404
404
  }
@@ -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) => {
@@ -1026,7 +1027,7 @@ class Request extends module_1.default {
1026
1027
  case 'string': {
1027
1028
  const value = opt.trim();
1028
1029
  if (value.startsWith('--')) {
1029
- const match = /^(--[a-z]+[a-z0-9-]*)(=)?\s*(.*)$/.exec(value);
1030
+ const match = /^(--[a-z]+[a-z0-9-]*)(=)?\s*(.*)$/s.exec(value);
1030
1031
  if (match) {
1031
1032
  switch (match[1]) {
1032
1033
  case '--daemon':
@@ -1082,8 +1083,8 @@ class Request extends module_1.default {
1082
1083
  ({ protocol, origin, username, password, href: uri } = uri);
1083
1084
  }
1084
1085
  const escapeQuote = (value) => {
1085
- value = value.replace(/"/g, '\\"');
1086
- return PLATFORM_WIN32 ? value : value.replace(/[ $`]/g, capture => (capture !== ' ' ? '\\' : '') + '\\' + capture);
1086
+ value = value.replace(/(?<!\\)"/g, '\\"');
1087
+ return PLATFORM_WIN32 ? value : value.replace(/(?<!\\)\$/g, '\\$');
1087
1088
  };
1088
1089
  const init = [
1089
1090
  `--dir="${escapeQuote(pathname)}"`,
@@ -1228,10 +1229,13 @@ class Request extends module_1.default {
1228
1229
  }
1229
1230
  if (binOpts) {
1230
1231
  for (const leading of binOpts) {
1231
- for (let i = 0; i < opts.length; ++i) {
1232
- if (opts[i].startsWith(leading)) {
1233
- opts.splice(i--, i);
1234
- break;
1232
+ if (leading.startsWith('-')) {
1233
+ const pattern = new RegExp(`^${leading}(?:=|$)`);
1234
+ for (let i = 0; i < opts.length; ++i) {
1235
+ if (pattern.test(opts[i])) {
1236
+ opts.splice(i--, i);
1237
+ break;
1238
+ }
1235
1239
  }
1236
1240
  }
1237
1241
  }
@@ -1243,18 +1247,14 @@ class Request extends module_1.default {
1243
1247
  opts.push(`"${escapeQuote(uri)}"`);
1244
1248
  args = args.concat(init, opts);
1245
1249
  const startTime = Date.now();
1246
- let out = '', message = '', aborted;
1250
+ let out = '', message = '';
1247
1251
  const errorResponse = (pid, err) => {
1248
- aborted = true;
1249
1252
  closeTorrent(pid);
1250
1253
  reject(err);
1251
1254
  };
1252
1255
  const { pid, stdout, stderr } = child_process.spawn(module_1.default.sanitizeCmd(ARIA2.BIN), args, { cwd: pathname, shell: true, signal: this.signal, uid: ARIA2.EXEC_UID, gid: ARIA2.EXEC_GID })
1253
1256
  .on('exit', code => {
1254
1257
  closeTorrent(pid);
1255
- if (aborted) {
1256
- return;
1257
- }
1258
1258
  if (this.aborted) {
1259
1259
  errorResponse(pid, (0, types_1.createAbortError)());
1260
1260
  return;
@@ -1285,10 +1285,15 @@ class Request extends module_1.default {
1285
1285
  break;
1286
1286
  }
1287
1287
  }
1288
- if (result.length && !silent && LOG_HTTP && LOG_TIMEPROCESS) {
1289
- this.writeTimeProcess("aria2", uri, startTime, { type: 1024, queue: true, messageUnit, messageUnitMinWidth: 9 });
1288
+ if (result.length > 0) {
1289
+ if (!silent && LOG_HTTP && LOG_TIMEPROCESS) {
1290
+ this.writeTimeProcess("aria2", uri, startTime, { type: 1024, queue: true, messageUnit, messageUnitMinWidth: 9, bypassLog: true });
1291
+ }
1292
+ this.addLog(types_1.STATUS_TYPE.INFO, out, currentTime, currentTime - startTime, "aria2", uri);
1293
+ }
1294
+ else {
1295
+ this.addLog(types_1.STATUS_TYPE.ERROR, 'No files were successfully downloaded', currentTime, currentTime - startTime, "aria2", uri);
1290
1296
  }
1291
- this.addLog(result.length ? types_1.STATUS_TYPE.INFO : types_1.STATUS_TYPE.ERROR, out, currentTime, currentTime - startTime, "aria2", uri);
1292
1297
  resolve(result);
1293
1298
  }
1294
1299
  else {
@@ -1311,7 +1316,13 @@ class Request extends module_1.default {
1311
1316
  }
1312
1317
  for (const item of ARIA2.PID_QUEUE) {
1313
1318
  try {
1314
- process.kill(item[0], 0);
1319
+ if (pid === item[0] && (signal === null || signal === void 0 ? void 0 : signal.aborted)) {
1320
+ process.kill(item[0]);
1321
+ closeTorrent(item[0]);
1322
+ }
1323
+ else {
1324
+ process.kill(item[0], 0);
1325
+ }
1315
1326
  }
1316
1327
  catch {
1317
1328
  closeTorrent(item[0]);
@@ -1369,7 +1380,7 @@ class Request extends module_1.default {
1369
1380
  open(uri, options) {
1370
1381
  var _l;
1371
1382
  var _m, _o;
1372
- let { host, url, httpVersion, method = 'GET', encoding, format, headers, postData, keepAlive, agentTimeout, socketPath, timeout = this._config.connectTimeout, outStream } = options;
1383
+ 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);
1373
1384
  const getting = method === 'GET';
1374
1385
  const posting = method === 'POST';
1375
1386
  if (format) {
@@ -1624,6 +1635,9 @@ class Request extends module_1.default {
1624
1635
  const ac = new AbortController();
1625
1636
  this[kDownloading].add(options.outAbort = ac);
1626
1637
  stream.addAbortSignal(ac.signal, request);
1638
+ if (options.signal) {
1639
+ stream.addAbortSignal(options.signal, request);
1640
+ }
1627
1641
  this.signal.addEventListener('abort', () => ac.abort(), { once: true });
1628
1642
  }
1629
1643
  if (posting) {
@@ -1673,7 +1687,7 @@ class Request extends module_1.default {
1673
1687
  else {
1674
1688
  options = {};
1675
1689
  }
1676
- const headers = options.headers || (options.headers = {});
1690
+ const headers = (0, util_1.parseOutgoingHeaders)(options.headers || (options.headers = {}));
1677
1691
  for (const attr in headers) {
1678
1692
  const name = attr.toLowerCase();
1679
1693
  if (name === 'content-type' || name === 'content-length') {
@@ -1836,7 +1850,7 @@ class Request extends module_1.default {
1836
1850
  }
1837
1851
  const client = this.open(href, request);
1838
1852
  const { host, url, encoding, outFormat } = request;
1839
- let buffer, aborted;
1853
+ let buffer, aborted = false;
1840
1854
  ({ httpVersion, outAbort } = request);
1841
1855
  const isAborted = () => client.destroyed || httpVersion === 2 && client.aborted;
1842
1856
  const isRetry = (value) => (0, util_1.isRetryable)(value) && ++retries <= this._config.retryLimit;
@@ -1851,14 +1865,11 @@ class Request extends module_1.default {
1851
1865
  if (timeout) {
1852
1866
  clearTimeout(timeout);
1853
1867
  }
1854
- buffer = null;
1855
- aborted = true;
1856
1868
  if (outAbort) {
1857
- if (!client.aborted) {
1858
- outAbort.abort();
1859
- }
1860
1869
  this[kDownloading].delete(outAbort);
1861
1870
  }
1871
+ buffer = null;
1872
+ aborted = true;
1862
1873
  client.destroy();
1863
1874
  };
1864
1875
  const retryTimeout = () => {
@@ -1883,7 +1894,6 @@ class Request extends module_1.default {
1883
1894
  var _l;
1884
1895
  if (readTimeout > 0) {
1885
1896
  timeout = setTimeout(() => {
1886
- abortResponse();
1887
1897
  throwError((0, types_1.errorValue)("Timeout was exceeded", href.toString()));
1888
1898
  }, readTimeout);
1889
1899
  }
@@ -2012,9 +2022,9 @@ class Request extends module_1.default {
2012
2022
  host.success(httpVersion);
2013
2023
  };
2014
2024
  const redirectResponse = (statusCode, location) => {
2015
- abortResponse();
2016
2025
  if (location) {
2017
2026
  if (++redirects <= this._config.redirectLimit) {
2027
+ abortResponse();
2018
2028
  downloadUri.call(this, Request.fromURL(url, location));
2019
2029
  }
2020
2030
  else {
@@ -2026,8 +2036,8 @@ class Request extends module_1.default {
2026
2036
  }
2027
2037
  };
2028
2038
  const errorResponse = (err) => {
2029
- abortResponse();
2030
2039
  if ((0, util_1.checkRetryable)(err) && ++retries <= this._config.retryLimit) {
2040
+ abortResponse();
2031
2041
  if (isConnectionTimeout(err)) {
2032
2042
  retryTimeout();
2033
2043
  }
@@ -2170,7 +2180,6 @@ class Request extends module_1.default {
2170
2180
  retryResponse(statusCode, res.headers['retry-after']);
2171
2181
  }
2172
2182
  else {
2173
- abortResponse();
2174
2183
  throwError(formatStatus(statusCode));
2175
2184
  }
2176
2185
  })
@@ -2188,8 +2197,8 @@ class Request extends module_1.default {
2188
2197
  if (aborted) {
2189
2198
  return;
2190
2199
  }
2191
- abortResponse();
2192
2200
  if (++retries <= this._config.retryLimit) {
2201
+ abortResponse();
2193
2202
  retryTimeout();
2194
2203
  }
2195
2204
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e-mc/request",
3
- "version": "0.6.13",
3
+ "version": "0.6.15",
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.6.13",
24
- "@e-mc/types": "0.6.13",
23
+ "@e-mc/module": "0.6.15",
24
+ "@e-mc/types": "0.6.15",
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;