@e-mc/request 0.13.7 → 0.13.9

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @e-mc/request
2
2
 
3
- * NodeJS 18.20.5 LTS
3
+ * NodeJS 18.20 LTS
4
4
  * ES2022
5
5
 
6
6
  ## General Usage
@@ -9,12 +9,12 @@
9
9
 
10
10
  ## Interface
11
11
 
12
- * [View Source](https://www.unpkg.com/@e-mc/types@0.13.7/lib/index.d.ts)
12
+ * [View Source](https://www.unpkg.com/@e-mc/types@0.13.9/lib/index.d.ts)
13
13
 
14
14
  ```typescript
15
15
  import type { IModule, ModuleConstructor } from "./index";
16
16
  import type { HttpAgentSettings, HttpProtocolVersion, HttpRequestClient, InternetProtocolVersion } from "./http";
17
- import type { ApplyOptions, Aria2Options, FormDataPart, HeadersOnCallback, HostConfig, IHttpAdapter, OpenOptions, PostOptions, ProxySettings, PutOptions, RcloneOptions, ReadExpectType, RequestInit, StatusOnCallback } from "./request";
17
+ import type { ApplyOptions, Aria2Options, HeadersOnCallback, HostConfig, IHttpAdapter, OpenOptions, PostFileParts, PostOptions, ProxySettings, PutOptions, RcloneOptions, ReadExpectType, RequestInit, StatusOnCallback } from "./request";
18
18
  import type { DnsLookupSettings, RequestModule, RequestSettings } from "./settings";
19
19
 
20
20
  import type { ClientRequest, OutgoingHttpHeaders } from "node:http";
@@ -45,14 +45,15 @@ interface IRequest extends IModule {
45
45
  rclone(uri: string | URL, pathname: string | URL): Promise<string[]>;
46
46
  rclone(uri: string | URL, options?: RcloneOptions): Promise<string[]>;
47
47
  json(uri: string | URL, options?: OpenOptions): Promise<object | null>;
48
+ blob(uri: string | URL, options?: OpenOptions): Promise<Blob | null>;
48
49
  pipe(uri: string | URL, to: Writable, options?: OpenOptions): Promise<null>;
49
50
  opts(url: string | URL, options?: OpenOptions): HostConfig;
50
51
  open(uri: string | URL, options: OpenOptions): HttpRequestClient;
51
52
  head(uri: string | URL, options?: OpenOptions): ClientRequest;
52
53
  put(uri: string | URL, data: unknown, options: PutOptions): Promise<Buffer | string | null>;
53
54
  put(uri: string | URL, data: unknown, contentType?: string, options?: PutOptions): Promise<Buffer | string | null>;
54
- post(uri: string | URL, parts: FormDataPart[]): Promise<Buffer | string | null>;
55
- post(uri: string | URL, form: Record<string, unknown>, parts: FormDataPart[]): Promise<Buffer | string | null>;
55
+ post(uri: string | URL, parts: PostFileParts): Promise<Buffer | string | null>;
56
+ post(uri: string | URL, form: Record<string, unknown>, parts: PostFileParts): Promise<Buffer | string | null>;
56
57
  post(uri: string | URL, data: unknown, options: PostOptions): Promise<Buffer | string | null>;
57
58
  post(uri: string | URL, data: unknown, contentType?: string, options?: PostOptions): Promise<Buffer | string | null>;
58
59
  get(uri: string | URL, format: "json" | "yaml" | "json5" | "xml" | "toml"): Promise<object | null>;
@@ -252,9 +253,9 @@ instance.get("http://hostname/path/config.yml", options).then(data => {
252
253
 
253
254
  ## References
254
255
 
255
- - https://www.unpkg.com/@e-mc/types@0.13.7/lib/http.d.ts
256
- - https://www.unpkg.com/@e-mc/types@0.13.7/lib/request.d.ts
257
- - https://www.unpkg.com/@e-mc/types@0.13.7/lib/settings.d.ts
256
+ - https://www.unpkg.com/@e-mc/types@0.13.9/lib/http.d.ts
257
+ - https://www.unpkg.com/@e-mc/types@0.13.9/lib/request.d.ts
258
+ - https://www.unpkg.com/@e-mc/types@0.13.9/lib/settings.d.ts
258
259
 
259
260
  * https://www.npmjs.com/package/@types/node
260
261
 
@@ -249,6 +249,9 @@ class HttpAdapter {
249
249
  if ('outFilename' in opts) {
250
250
  opts.outFilename = (0, util_1.parseHeader)(headers, 'content-disposition');
251
251
  }
252
+ if ('outContentType' in opts) {
253
+ opts.outContentType = headers['content-type'];
254
+ }
252
255
  const pipeline = this.pipeTo ? !(0, types_1.isString)(this.pipeTo) : false;
253
256
  const enabled = opts.connected?.call(this.client, headers) !== false && !pipeline;
254
257
  const maxBufferSize = opts.maxBufferSize ? (0, types_1.alignSize)(opts.maxBufferSize) : 0;
@@ -356,7 +359,7 @@ class HttpAdapter {
356
359
  break;
357
360
  case 'toml':
358
361
  try {
359
- result = require('smol-toml').parse(buffer);
362
+ result = require('smol-toml').parse(buffer, parser);
360
363
  }
361
364
  catch {
362
365
  result = require(packageName = 'toml').parse(buffer);
@@ -378,6 +381,9 @@ class HttpAdapter {
378
381
  }
379
382
  }
380
383
  }
384
+ else if (opts.outFormat) {
385
+ opts.outFormat = undefined;
386
+ }
381
387
  if (result === undefined) {
382
388
  result = buffer;
383
389
  }
package/index.js CHANGED
@@ -23,6 +23,7 @@ const adapter_1 = require("@e-mc/request/http/adapter");
23
23
  const kRequest = Symbol.for('request:constructor');
24
24
  const SUPPORTED_NODE20 = (0, types_1.supported)(20);
25
25
  const SUPPORTED_ZSTD = (0, types_1.supported)(23, 8) || (0, types_1.supported)(22, 15, true);
26
+ const SUPPORTED_FILE = (0, types_1.supported)(19, 2) || (0, types_1.supported)(18, 13, true);
26
27
  const SUPPORTED_PROXY = (0, types_1.supported)(24, 5);
27
28
  const REGEXP_GLOBWITHIN = /\\\?|(?:(?<!\\)(?:\*|\[!?[^!\]]+\]|\{(?:[^,]+,)+[^}]+\}|[!?+*@]\((?:[^|]+\|)*[^)]+\)|\?.*\?|\?$))/;
28
29
  const REGEXP_RCLONE = /^rclone:\?/i;
@@ -114,6 +115,7 @@ let KEEP_ALIVE = null;
114
115
  let ACCEPT_ENCODING = false;
115
116
  let READ_TIMEOUT = 0;
116
117
  let AGENT_TIMEOUT = 0;
118
+ let MAX_CONCURRENT_STREAMS = 100;
117
119
  let LOG_HTTP = false;
118
120
  let LOG_TIMEPROCESS = true;
119
121
  function setDnsCache(hostname, value, expires) {
@@ -414,8 +416,7 @@ function createAgentOptions(options) {
414
416
  }
415
417
  }
416
418
  const hasResponse = (value) => value >= 200 && (value < 300 || value === 304);
417
- const isDirEnd = (value) => value.endsWith('/') || value.endsWith(path.sep);
418
- const trimCharEnd = (value) => value.substring(0, value.length - 1);
419
+ const isDirEnd = (value) => (value = value.at(-1), value === '/' || value === path.sep);
419
420
  const configureDns = (family, options) => family === 0 ? options : { family, hints: family === 6 ? dns.V4MAPPED : 0 };
420
421
  const ignoreOpt = (opts, ...values) => !opts?.some(opt => values.some(value => opt === value || opt.startsWith(value + '=')));
421
422
  const escapeQuote = (value) => value.replace(/[\\"]/g, capture => '\\' + capture);
@@ -488,7 +489,7 @@ class Request extends module_1 {
488
489
  try {
489
490
  for (const uri in proxy) {
490
491
  const host = new URL(uri);
491
- ARIA2.PROXY[trimCharEnd(host.protocol)] = { host };
492
+ ARIA2.PROXY[host.protocol.slice(0, -1)] = { host };
492
493
  }
493
494
  }
494
495
  catch {
@@ -587,11 +588,14 @@ class Request extends module_1 {
587
588
  }
588
589
  }
589
590
  if (request) {
590
- let { read_timeout, agent, use, headers, certs } = request, agent_timeout;
591
+ let { read_timeout, max_concurrent_streams, agent, use, headers, certs } = request, agent_timeout;
591
592
  if ((read_timeout = (0, util_1.fromSeconds)(read_timeout)) >= 0) {
592
593
  READ_TIMEOUT = read_timeout;
593
594
  }
594
- if ((0, types_1.isObject)(agent)) {
595
+ if ((max_concurrent_streams = (0, util_1.asInt)(max_concurrent_streams)) > 0) {
596
+ MAX_CONCURRENT_STREAMS = max_concurrent_streams;
597
+ }
598
+ if (agent) {
595
599
  let { keep_alive, timeout, proxy_env } = agent;
596
600
  if ((agent_timeout = (0, util_1.fromSeconds)(timeout)) > 0) {
597
601
  AGENT_TIMEOUT = agent_timeout;
@@ -620,8 +624,8 @@ class Request extends module_1 {
620
624
  break;
621
625
  }
622
626
  }
623
- if ((0, types_1.isPlainObject)(headers)) {
624
- for (const href in headers) {
627
+ for (const href in headers) {
628
+ if ((0, types_1.isObject)(headers[href])) {
625
629
  HTTP.HEADERS[href] = (0, util_1.normalizeHeaders)(headers[href]);
626
630
  }
627
631
  }
@@ -1121,15 +1125,10 @@ class Request extends module_1 {
1121
1125
  ({ signal, silent } = options);
1122
1126
  ({ pathname, headers, binOpts } = this.parseBinOpts(options, ['--daemon'], ['--input-file']));
1123
1127
  }
1124
- try {
1125
- if ((0, types_1.isString)(uri) && module_1.isURL(uri)) {
1126
- uri = new URL(uri);
1127
- }
1128
- pathname = checkBinTarget(this, "aria2", uri, pathname, 'aria2', binOpts);
1129
- }
1130
- catch (err) {
1131
- return Promise.reject(err);
1128
+ if ((0, types_1.isString)(uri) && module_1.isURL(uri)) {
1129
+ uri = new URL(uri);
1132
1130
  }
1131
+ pathname = checkBinTarget(this, "aria2", uri, pathname, 'aria2', binOpts);
1133
1132
  silent ??= this.#singleton;
1134
1133
  return new Promise((resolve, reject) => {
1135
1134
  let protocol, origin, username, password;
@@ -1224,7 +1223,7 @@ class Request extends module_1 {
1224
1223
  }
1225
1224
  }
1226
1225
  }
1227
- switch (protocol = trimCharEnd(protocol)) {
1226
+ switch (protocol = protocol.slice(0, -1)) {
1228
1227
  case 'http':
1229
1228
  case 'https':
1230
1229
  if (ignoreOpt(binOpts, '--http-no-cache')) {
@@ -1394,12 +1393,7 @@ class Request extends module_1 {
1394
1393
  default:
1395
1394
  return Promise.reject((0, types_1.errorMessage)("rclone", "Invalid command", command || uri));
1396
1395
  }
1397
- try {
1398
- pathname = checkBinTarget(this, "rclone", uri, pathname, command, binOpts);
1399
- }
1400
- catch (err) {
1401
- return Promise.reject(err);
1402
- }
1396
+ pathname = checkBinTarget(this, "rclone", uri, pathname, command, binOpts);
1403
1397
  silent ??= this.#singleton;
1404
1398
  return new Promise((resolve, reject) => {
1405
1399
  const init = [
@@ -1614,6 +1608,21 @@ class Request extends module_1 {
1614
1608
  options.format = 'json';
1615
1609
  return this.get(uri, options);
1616
1610
  }
1611
+ async blob(uri, options = {}) {
1612
+ options.outContentType = undefined;
1613
+ options.outFilename = undefined;
1614
+ options.outBlob = true;
1615
+ const data = await this.get(uri, options);
1616
+ if (!data) {
1617
+ return null;
1618
+ }
1619
+ const format = options.outFormat?.out;
1620
+ const type = { type: format ? 'application/' + format : options.outContentType || (typeof data === 'string' ? 'text/plain' : undefined) };
1621
+ if (SUPPORTED_FILE && options.outFilename) {
1622
+ return new File([Buffer.from(data)], path.basename(options.outFilename), type);
1623
+ }
1624
+ return new Blob([Buffer.from(data)], type);
1625
+ }
1617
1626
  async pipe(uri, to, options = {}) {
1618
1627
  options.pipeTo = to;
1619
1628
  return this.get(uri, options);
@@ -1681,7 +1690,9 @@ class Request extends module_1 {
1681
1690
  }
1682
1691
  headers.accept += ', text/plain';
1683
1692
  options.encoding = (0, types_1.getEncoding)(encoding);
1684
- options.outFormat = { out: format, parser };
1693
+ if (!options.outBlob) {
1694
+ options.outFormat = { out: format, parser };
1695
+ }
1685
1696
  }
1686
1697
  if (typeof uri !== 'string') {
1687
1698
  url = uri;
@@ -1754,7 +1765,22 @@ class Request extends module_1 {
1754
1765
  }
1755
1766
  }
1756
1767
  if (!proxy && httpVersion !== 1 && ((httpVersion || host.version) === 2 && version !== 1 || secure && version === 2 && host.failed(2, true) === 0)) {
1757
- request = (this.#session[0][origin] ||= http2.connect(origin, { lookup: this.lookupDns(hostname), ca, cert, key, ciphers, minVersion, settings: host.localhost ? { maxFrameSize: 16777215, enablePush: false } : { enablePush: false } })).request({ ...baseHeaders, ...host_1.getBasicAuth(url), ...headers, ':path': pathname, ':method': method });
1768
+ let session = this.#session[0][origin], conn, maxConcurrentStreams = 0;
1769
+ if (session) {
1770
+ maxConcurrentStreams = session[2];
1771
+ }
1772
+ else {
1773
+ maxConcurrentStreams = options.maxConcurrentStreams || MAX_CONCURRENT_STREAMS;
1774
+ session = [[], 0, maxConcurrentStreams];
1775
+ }
1776
+ if (session[0].length * maxConcurrentStreams <= session[1]) {
1777
+ conn = http2.connect(origin, { lookup: this.lookupDns(hostname), ca, cert, key, ciphers, minVersion, settings: host.localhost ? { enablePush: false, maxConcurrentStreams, maxFrameSize: 16777215 } : { enablePush: false, maxConcurrentStreams } });
1778
+ session[0].push(conn);
1779
+ }
1780
+ else {
1781
+ conn = session[0].at(-1);
1782
+ }
1783
+ request = conn.request({ ...baseHeaders, ...host_1.getBasicAuth(url), ...headers, ':path': pathname, ':method': method });
1758
1784
  if (getting) {
1759
1785
  const listenerMap = {};
1760
1786
  const onEvent = request.on.bind(request);
@@ -1770,13 +1796,18 @@ class Request extends module_1 {
1770
1796
  if (this.matchStatus(statusCode, url, response, request, options) && hasResponse(statusCode)) {
1771
1797
  const contentEncoding = response['content-encoding'];
1772
1798
  if (checkEncoding(request, statusCode, contentEncoding) && (emitter = this.pipeline(request, contentEncoding, outStream))) {
1773
- for (const event in listenerMap) {
1774
- const [name, type] = event.split('-');
1775
- for (const listener of listenerMap[event]) {
1776
- if (name !== 'error') {
1777
- request.removeListener(name, listener);
1799
+ if (typeof emitter === 'number') {
1800
+ this.abortHeaders(request, options, { href: url.href, statusCode: emitter, message: (0, util_1.fromStatusCode)(emitter) });
1801
+ }
1802
+ else {
1803
+ for (const event in listenerMap) {
1804
+ const [name, type] = event.split('-');
1805
+ for (const listener of listenerMap[event]) {
1806
+ if (name !== 'error') {
1807
+ request.removeListener(name, listener);
1808
+ }
1809
+ emitter[type](name === 'end' ? 'finish' : name, listener);
1778
1810
  }
1779
- emitter[type](name === 'end' ? 'finish' : name, listener);
1780
1811
  }
1781
1812
  }
1782
1813
  }
@@ -1837,6 +1868,7 @@ class Request extends module_1 {
1837
1868
  };
1838
1869
  }
1839
1870
  options.httpVersion = 2;
1871
+ session[1]++;
1840
1872
  }
1841
1873
  else {
1842
1874
  let agent;
@@ -1899,9 +1931,14 @@ class Request extends module_1 {
1899
1931
  const contentEncoding = incoming['content-encoding'];
1900
1932
  let source;
1901
1933
  if (checkEncoding(request, statusCode, contentEncoding) && (source = this.pipeline(response, contentEncoding, outStream))) {
1902
- source.once('finish', () => {
1903
- request.emit('end');
1904
- });
1934
+ if (typeof source === 'number') {
1935
+ this.abortHeaders(request, options, { href: url.href, statusCode: source, message: (0, util_1.fromStatusCode)(source) });
1936
+ }
1937
+ else {
1938
+ source.once('finish', () => {
1939
+ request.emit('end');
1940
+ });
1941
+ }
1905
1942
  }
1906
1943
  else {
1907
1944
  if (encoding) {
@@ -2093,9 +2130,18 @@ class Request extends module_1 {
2093
2130
  addValue(name, data[name]);
2094
2131
  }
2095
2132
  }
2096
- for (let { name, data: target, value, contentType: type, filename } of parts) {
2097
- if (!name) {
2098
- continue;
2133
+ for (const part of parts) {
2134
+ let name, target, value, type, filename;
2135
+ if ((0, types_1.isPlainObject)(part)) {
2136
+ ({ name, data: target, value, contentType: type, filename } = part);
2137
+ }
2138
+ else if ((0, types_1.supported)(18, 13) && part instanceof File) {
2139
+ name = part.name;
2140
+ type = part.type;
2141
+ target = Buffer.from(await part.arrayBuffer());
2142
+ }
2143
+ if (!(0, types_1.isString)(name)) {
2144
+ throw (0, types_1.errorValue)('Name of file part was invalid', uri.toString());
2099
2145
  }
2100
2146
  if (target) {
2101
2147
  if (typeof target === 'string') {
@@ -2209,7 +2255,9 @@ class Request extends module_1 {
2209
2255
  const session = this.#session;
2210
2256
  session.forEach((protocol, index) => {
2211
2257
  for (const host in protocol) {
2212
- protocol[host].close();
2258
+ for (const conn of protocol[host][0]) {
2259
+ conn.close();
2260
+ }
2213
2261
  }
2214
2262
  session[index] = Object.create(null);
2215
2263
  });
@@ -2226,28 +2274,30 @@ class Request extends module_1 {
2226
2274
  for (const value of encoding.split(/\s*,\s*/).reverse()) {
2227
2275
  const next = this.fromEncoding(value, { chunkSize });
2228
2276
  if (!next) {
2229
- return;
2277
+ pipeTo = undefined;
2278
+ break;
2230
2279
  }
2231
2280
  pipeTo = pipeTo ? pipeTo.pipe(next) : next;
2232
2281
  }
2233
2282
  }
2234
- if (pipeTo) {
2235
- if (outStream) {
2236
- stream.pipeline(response, pipeTo, outStream, err => {
2237
- if (err) {
2238
- response.emit('error', err);
2239
- }
2240
- });
2241
- }
2242
- else {
2243
- stream.pipeline(response, pipeTo, err => {
2244
- if (err) {
2245
- response.emit('error', err);
2246
- }
2247
- });
2248
- }
2249
- return pipeTo;
2283
+ if (!pipeTo) {
2284
+ return 415;
2285
+ }
2286
+ if (outStream) {
2287
+ stream.pipeline(response, pipeTo, outStream, err => {
2288
+ if (err) {
2289
+ response.emit('error', err);
2290
+ }
2291
+ });
2292
+ }
2293
+ else {
2294
+ stream.pipeline(response, pipeTo, err => {
2295
+ if (err) {
2296
+ response.emit('error', err);
2297
+ }
2298
+ });
2250
2299
  }
2300
+ return pipeTo;
2251
2301
  }
2252
2302
  fromEncoding(value, options) {
2253
2303
  switch (value) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e-mc/request",
3
- "version": "0.13.7",
3
+ "version": "0.13.9",
4
4
  "description": "Request constructor for E-mc.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -19,8 +19,8 @@
19
19
  "license": "BSD-3-Clause",
20
20
  "homepage": "https://github.com/anpham6/e-mc#readme",
21
21
  "dependencies": {
22
- "@e-mc/module": "0.13.7",
23
- "@e-mc/types": "0.13.7",
22
+ "@e-mc/module": "0.13.9",
23
+ "@e-mc/types": "0.13.9",
24
24
  "combined-stream": "^1.0.8",
25
25
  "js-yaml": "^4.1.1",
26
26
  "picomatch": "^4.0.3",
package/util.d.ts CHANGED
@@ -13,7 +13,7 @@ declare namespace util {
13
13
  function hasBasicAuth(value: string): boolean;
14
14
  function checkRetryable(err: unknown): boolean;
15
15
  function isRetryable(value: number, timeout?: boolean): boolean;
16
- function parseHttpProxy(value?: string): HttpProxySettings | undefined;
16
+ function parseHttpProxy(value?: string, ignoreEnv?: boolean): HttpProxySettings | undefined;
17
17
  function trimPath(value: string, char?: string): string;
18
18
  function asInt(value: unknown): number;
19
19
  function asFloat(value: unknown): number;