Musique/node_modules/miniget/dist/index.js

285 lines
11 KiB
JavaScript
Raw Normal View History

2021-12-04 19:00:33 +00:00
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const http_1 = __importDefault(require("http"));
const https_1 = __importDefault(require("https"));
const stream_1 = require("stream");
const httpLibs = { 'http:': http_1.default, 'https:': https_1.default };
const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
const retryStatusCodes = new Set([429, 503]);
// `request`, `response`, `abort`, left out, miniget will emit these.
const requestEvents = ['connect', 'continue', 'information', 'socket', 'timeout', 'upgrade'];
const responseEvents = ['aborted'];
Miniget.MinigetError = class MinigetError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
}
};
Miniget.defaultOptions = {
maxRedirects: 10,
maxRetries: 2,
maxReconnects: 0,
backoff: { inc: 100, max: 10000 },
};
function Miniget(url, options = {}) {
var _a;
const opts = Object.assign({}, Miniget.defaultOptions, options);
const stream = new stream_1.PassThrough({ highWaterMark: opts.highWaterMark });
stream.destroyed = stream.aborted = false;
let activeRequest;
let activeResponse;
let activeDecodedStream;
let redirects = 0;
let retries = 0;
let retryTimeout;
let reconnects = 0;
let contentLength;
let acceptRanges = false;
let rangeStart = 0, rangeEnd;
let downloaded = 0;
// Check if this is a ranged request.
if ((_a = opts.headers) === null || _a === void 0 ? void 0 : _a.Range) {
let r = /bytes=(\d+)-(\d+)?/.exec(`${opts.headers.Range}`);
if (r) {
rangeStart = parseInt(r[1], 10);
rangeEnd = parseInt(r[2], 10);
}
}
// Add `Accept-Encoding` header.
if (opts.acceptEncoding) {
opts.headers = Object.assign({
'Accept-Encoding': Object.keys(opts.acceptEncoding).join(', '),
}, opts.headers);
}
const downloadHasStarted = () => activeDecodedStream && downloaded > 0;
const downloadComplete = () => !acceptRanges || downloaded === contentLength;
const reconnect = (err) => {
activeDecodedStream = null;
retries = 0;
let inc = opts.backoff.inc;
let ms = Math.min(inc, opts.backoff.max);
retryTimeout = setTimeout(doDownload, ms);
stream.emit('reconnect', reconnects, err);
};
const reconnectIfEndedEarly = (err) => {
if (options.method !== 'HEAD' && !downloadComplete() && reconnects++ < opts.maxReconnects) {
reconnect(err);
return true;
}
return false;
};
const retryRequest = (retryOptions) => {
if (stream.destroyed) {
return false;
}
if (downloadHasStarted()) {
return reconnectIfEndedEarly(retryOptions.err);
}
else if ((!retryOptions.err || retryOptions.err.message === 'ENOTFOUND') &&
retries++ < opts.maxRetries) {
let ms = retryOptions.retryAfter ||
Math.min(retries * opts.backoff.inc, opts.backoff.max);
retryTimeout = setTimeout(doDownload, ms);
stream.emit('retry', retries, retryOptions.err);
return true;
}
return false;
};
const forwardEvents = (ee, events) => {
for (let event of events) {
ee.on(event, stream.emit.bind(stream, event));
}
};
const doDownload = () => {
let parsed = {}, httpLib;
try {
let urlObj = typeof url === 'string' ? new URL(url) : url;
parsed = Object.assign({}, {
host: urlObj.host,
hostname: urlObj.hostname,
path: urlObj.pathname + urlObj.search + urlObj.hash,
port: urlObj.port,
protocol: urlObj.protocol,
});
if (urlObj.username) {
parsed.auth = `${urlObj.username}:${urlObj.password}`;
}
httpLib = httpLibs[String(parsed.protocol)];
}
catch (err) {
// Let the error be caught by the if statement below.
}
if (!httpLib) {
stream.emit('error', new Miniget.MinigetError(`Invalid URL: ${url}`));
return;
}
Object.assign(parsed, opts);
if (acceptRanges && downloaded > 0) {
let start = downloaded + rangeStart;
let end = rangeEnd || '';
parsed.headers = Object.assign({}, parsed.headers, {
Range: `bytes=${start}-${end}`,
});
}
if (opts.transform) {
try {
parsed = opts.transform(parsed);
}
catch (err) {
stream.emit('error', err);
return;
}
if (!parsed || parsed.protocol) {
httpLib = httpLibs[String(parsed === null || parsed === void 0 ? void 0 : parsed.protocol)];
if (!httpLib) {
stream.emit('error', new Miniget.MinigetError('Invalid URL object from `transform` function'));
return;
}
}
}
const onError = (err) => {
if (stream.destroyed || stream.readableEnded) {
return;
}
// Needed for node v10.
if (stream._readableState.ended) {
return;
}
cleanup();
if (!retryRequest({ err })) {
stream.emit('error', err);
}
else {
activeRequest.removeListener('close', onRequestClose);
}
};
const onRequestClose = () => {
cleanup();
retryRequest({});
};
const cleanup = () => {
activeRequest.removeListener('close', onRequestClose);
activeResponse === null || activeResponse === void 0 ? void 0 : activeResponse.removeListener('data', onData);
activeDecodedStream === null || activeDecodedStream === void 0 ? void 0 : activeDecodedStream.removeListener('end', onEnd);
};
const onData = (chunk) => { downloaded += chunk.length; };
const onEnd = () => {
cleanup();
if (!reconnectIfEndedEarly()) {
stream.end();
}
};
activeRequest = httpLib.request(parsed, (res) => {
// Needed for node v10, v12.
// istanbul ignore next
if (stream.destroyed) {
return;
}
if (redirectStatusCodes.has(res.statusCode)) {
if (redirects++ >= opts.maxRedirects) {
stream.emit('error', new Miniget.MinigetError('Too many redirects'));
}
else {
if (res.headers.location) {
url = res.headers.location;
}
else {
let err = new Miniget.MinigetError('Redirect status code given with no location', res.statusCode);
stream.emit('error', err);
cleanup();
return;
}
setTimeout(doDownload, parseInt(res.headers['retry-after'] || '0', 10) * 1000);
stream.emit('redirect', url);
}
cleanup();
return;
// Check for rate limiting.
}
else if (retryStatusCodes.has(res.statusCode)) {
if (!retryRequest({ retryAfter: parseInt(res.headers['retry-after'] || '0', 10) })) {
let err = new Miniget.MinigetError(`Status code: ${res.statusCode}`, res.statusCode);
stream.emit('error', err);
}
cleanup();
return;
}
else if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 400)) {
let err = new Miniget.MinigetError(`Status code: ${res.statusCode}`, res.statusCode);
if (res.statusCode >= 500) {
onError(err);
}
else {
stream.emit('error', err);
}
cleanup();
return;
}
activeDecodedStream = res;
if (opts.acceptEncoding && res.headers['content-encoding']) {
for (let enc of res.headers['content-encoding'].split(', ').reverse()) {
let fn = opts.acceptEncoding[enc];
if (fn) {
activeDecodedStream = activeDecodedStream.pipe(fn());
activeDecodedStream.on('error', onError);
}
}
}
if (!contentLength) {
contentLength = parseInt(`${res.headers['content-length']}`, 10);
acceptRanges = res.headers['accept-ranges'] === 'bytes' &&
contentLength > 0 && opts.maxReconnects > 0;
}
res.on('data', onData);
activeDecodedStream.on('end', onEnd);
activeDecodedStream.pipe(stream, { end: !acceptRanges });
activeResponse = res;
stream.emit('response', res);
res.on('error', onError);
forwardEvents(res, responseEvents);
});
activeRequest.on('error', onError);
activeRequest.on('close', onRequestClose);
forwardEvents(activeRequest, requestEvents);
if (stream.destroyed) {
streamDestroy(...destroyArgs);
}
stream.emit('request', activeRequest);
activeRequest.end();
};
stream.abort = (err) => {
console.warn('`MinigetStream#abort()` has been deprecated in favor of `MinigetStream#destroy()`');
stream.aborted = true;
stream.emit('abort');
stream.destroy(err);
};
let destroyArgs;
const streamDestroy = (err) => {
activeRequest.destroy(err);
activeDecodedStream === null || activeDecodedStream === void 0 ? void 0 : activeDecodedStream.unpipe(stream);
activeDecodedStream === null || activeDecodedStream === void 0 ? void 0 : activeDecodedStream.destroy();
clearTimeout(retryTimeout);
};
stream._destroy = (...args) => {
stream.destroyed = true;
if (activeRequest) {
streamDestroy(...args);
}
else {
destroyArgs = args;
}
};
stream.text = () => new Promise((resolve, reject) => {
let body = '';
stream.setEncoding('utf8');
stream.on('data', chunk => body += chunk);
stream.on('end', () => resolve(body));
stream.on('error', reject);
});
process.nextTick(doDownload);
return stream;
}
module.exports = Miniget;
//# sourceMappingURL=index.js.map