First upload version 0.0.1

This commit is contained in:
Neyra
2026-02-05 15:27:49 +08:00
commit 8e9b7201ed
4182 changed files with 593136 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
import retry from "async-retry";
import { EventEmitter } from "eventemitter3";
import { AvailablePrograms } from "../../download-file/download-programs/switch-program.js";
import StatusCodeError from "./errors/status-code-error.js";
export declare const MIN_LENGTH_FOR_MORE_INFO_REQUEST: number;
export type BaseDownloadEngineFetchStreamOptions = {
retry?: retry.Options;
/**
* If true, the engine will retry the request if the server returns a status code between 500 and 599
*/
retryOnServerError?: boolean;
headers?: Record<string, string>;
/**
* If true, parallel download will be enabled even if the server does not return `accept-range` header, this is good when using cross-origin requests
*/
acceptRangeIsKnown?: boolean;
ignoreIfRangeWithQueryParams?: boolean;
} & ({
defaultFetchDownloadInfo?: {
length: number;
acceptRange: boolean;
};
} | {
/**
* Try different headers to see if any authentication is needed
*/
tryHeaders?: Record<string, string>[];
/**
* Delay between trying different headers
*/
tryHeadersDelay?: number;
});
export type DownloadInfoResponse = {
length: number;
acceptRange: boolean;
newURL?: string;
fileName?: string;
};
export type FetchSubState = {
url: string;
startChunk: number;
endChunk: number;
totalSize: number;
chunkSize: number;
rangeSupport?: boolean;
onProgress?: (length: number) => void;
};
export type BaseDownloadEngineFetchStreamEvents = {
paused: () => void;
resumed: () => void;
aborted: () => void;
errorCountIncreased: (errorCount: number, error: Error) => void;
};
export type WriteCallback = (data: Uint8Array[], position: number, index: number) => void;
export default abstract class BaseDownloadEngineFetchStream extends EventEmitter<BaseDownloadEngineFetchStreamEvents> {
readonly programType?: AvailablePrograms;
abstract readonly transferAction: string;
readonly options: Partial<BaseDownloadEngineFetchStreamOptions>;
state: FetchSubState;
paused?: Promise<void>;
aborted: boolean;
protected _pausedResolve?: () => void;
errorCount: {
value: number;
};
constructor(options?: Partial<BaseDownloadEngineFetchStreamOptions>);
protected get _startSize(): number;
protected get _endSize(): number;
protected initEvents(): void;
abstract withSubState(state: FetchSubState): this;
protected cloneState<Fetcher extends BaseDownloadEngineFetchStream>(state: FetchSubState, fetchStream: Fetcher): Fetcher;
fetchDownloadInfo(url: string): Promise<DownloadInfoResponse>;
protected abstract fetchDownloadInfoWithoutRetry(url: string): Promise<DownloadInfoResponse>;
fetchChunks(callback: WriteCallback): Promise<void>;
protected abstract fetchWithoutRetryChunks(callback: WriteCallback): Promise<void> | void;
close(): void | Promise<void>;
protected appendToURL(url: string): string;
protected retryOnServerError(error: Error): error is StatusCodeError;
}

View File

@@ -0,0 +1,139 @@
import retry from "async-retry";
import { retryAsyncStatementSimple } from "./utils/retry-async-statement.js";
import { EventEmitter } from "eventemitter3";
import HttpError from "./errors/http-error.js";
import StatusCodeError from "./errors/status-code-error.js";
import sleep from "sleep-promise";
export const MIN_LENGTH_FOR_MORE_INFO_REQUEST = 1024 * 1024 * 3; // 3MB
const DEFAULT_OPTIONS = {
retryOnServerError: true,
retry: {
retries: 150,
factor: 1.5,
minTimeout: 200,
maxTimeout: 5_000
},
tryHeadersDelay: 50
};
export default class BaseDownloadEngineFetchStream extends EventEmitter {
programType;
options = {};
state = null;
paused;
aborted = false;
_pausedResolve;
errorCount = { value: 0 };
constructor(options = {}) {
super();
this.options = { ...DEFAULT_OPTIONS, ...options };
this.initEvents();
}
get _startSize() {
return this.state.startChunk * this.state.chunkSize;
}
get _endSize() {
return Math.min(this.state.endChunk * this.state.chunkSize, this.state.totalSize);
}
initEvents() {
this.on("aborted", () => {
this.aborted = true;
this._pausedResolve?.();
});
this.on("paused", () => {
this.paused = new Promise((resolve) => {
this._pausedResolve = resolve;
});
});
this.on("resumed", () => {
this._pausedResolve?.();
this._pausedResolve = undefined;
this.paused = undefined;
});
}
cloneState(state, fetchStream) {
fetchStream.state = state;
fetchStream.errorCount = this.errorCount;
fetchStream.on("errorCountIncreased", this.emit.bind(this, "errorCountIncreased"));
this.on("aborted", fetchStream.emit.bind(fetchStream, "aborted"));
this.on("paused", fetchStream.emit.bind(fetchStream, "paused"));
this.on("resumed", fetchStream.emit.bind(fetchStream, "resumed"));
return fetchStream;
}
async fetchDownloadInfo(url) {
let throwErr = null;
const tryHeaders = "tryHeaders" in this.options && this.options.tryHeaders ? this.options.tryHeaders.slice() : [];
const fetchDownloadInfoCallback = async () => {
try {
return await this.fetchDownloadInfoWithoutRetry(url);
}
catch (error) {
if (error instanceof HttpError && !this.retryOnServerError(error)) {
if ("tryHeaders" in this.options && tryHeaders.length) {
this.options.headers = tryHeaders.shift();
await sleep(this.options.tryHeadersDelay ?? 0);
return await fetchDownloadInfoCallback();
}
throwErr = error;
return null;
}
this.errorCount.value++;
this.emit("errorCountIncreased", this.errorCount.value, error);
if (error instanceof StatusCodeError && error.retryAfter) {
await sleep(error.retryAfter * 1000);
return await fetchDownloadInfoCallback();
}
throw error;
}
};
const response = ("defaultFetchDownloadInfo" in this.options && this.options.defaultFetchDownloadInfo) || await retry(fetchDownloadInfoCallback, this.options.retry);
if (throwErr) {
throw throwErr;
}
return response;
}
async fetchChunks(callback) {
let lastStartLocation = this.state.startChunk;
let retryResolvers = retryAsyncStatementSimple(this.options.retry);
// eslint-disable-next-line no-constant-condition
while (true) {
try {
return await this.fetchWithoutRetryChunks(callback);
}
catch (error) {
if (error?.name === "AbortError")
return;
if (error instanceof HttpError && !this.retryOnServerError(error)) {
throw error;
}
this.errorCount.value++;
this.emit("errorCountIncreased", this.errorCount.value, error);
if (error instanceof StatusCodeError && error.retryAfter) {
await sleep(error.retryAfter * 1000);
continue;
}
if (lastStartLocation !== this.state.startChunk) {
lastStartLocation = this.state.startChunk;
retryResolvers = retryAsyncStatementSimple(this.options.retry);
}
await retryResolvers(error);
}
}
}
close() {
this.emit("aborted");
}
appendToURL(url) {
const parsed = new URL(url);
if (this.options.ignoreIfRangeWithQueryParams) {
const randomText = Math.random()
.toString(36);
parsed.searchParams.set("_ignore", randomText);
}
return parsed.href;
}
retryOnServerError(error) {
return Boolean(this.options.retryOnServerError) && error instanceof StatusCodeError &&
(error.statusCode >= 500 || error.statusCode === 429);
}
}
//# sourceMappingURL=base-download-engine-fetch-stream.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
import BaseDownloadEngineFetchStream, { DownloadInfoResponse, FetchSubState, WriteCallback } from "./base-download-engine-fetch-stream.js";
type GetNextChunk = () => Promise<ReadableStreamReadResult<Uint8Array>> | ReadableStreamReadResult<Uint8Array>;
export default class DownloadEngineFetchStreamFetch extends BaseDownloadEngineFetchStream {
private _fetchDownloadInfoWithHEAD;
transferAction: string;
withSubState(state: FetchSubState): this;
protected fetchWithoutRetryChunks(callback: WriteCallback): Promise<void>;
protected fetchDownloadInfoWithoutRetry(url: string): Promise<DownloadInfoResponse>;
protected fetchDownloadInfoWithoutRetryByMethod(url: string, method?: "HEAD" | "GET"): Promise<DownloadInfoResponse>;
protected fetchDownloadInfoWithoutRetryContentRange(url: string, response?: Response): Promise<number>;
chunkGenerator(callback: WriteCallback, getNextChunk: GetNextChunk): Promise<void>;
protected static convertHeadersToRecord(headers: Headers): {
[key: string]: string;
};
}
export {};

View File

@@ -0,0 +1,113 @@
import BaseDownloadEngineFetchStream, { MIN_LENGTH_FOR_MORE_INFO_REQUEST } from "./base-download-engine-fetch-stream.js";
import InvalidContentLengthError from "./errors/invalid-content-length-error.js";
import SmartChunkSplit from "./utils/smart-chunk-split.js";
import { parseContentDisposition } from "./utils/content-disposition.js";
import StatusCodeError from "./errors/status-code-error.js";
import { parseHttpContentRange } from "./utils/httpRange.js";
import { browserCheck } from "./utils/browserCheck.js";
export default class DownloadEngineFetchStreamFetch extends BaseDownloadEngineFetchStream {
_fetchDownloadInfoWithHEAD = false;
transferAction = "Downloading";
withSubState(state) {
const fetchStream = new DownloadEngineFetchStreamFetch(this.options);
return this.cloneState(state, fetchStream);
}
async fetchWithoutRetryChunks(callback) {
const headers = {
accept: "*/*",
...this.options.headers
};
if (this.state.rangeSupport) {
headers.range = `bytes=${this._startSize}-${this._endSize - 1}`;
}
const controller = new AbortController();
const response = await fetch(this.appendToURL(this.state.url), {
headers,
signal: controller.signal
});
if (response.status < 200 || response.status >= 300) {
throw new StatusCodeError(this.state.url, response.status, response.statusText, headers);
}
const contentLength = parseHttpContentRange(response.headers.get("content-range"))?.length ?? parseInt(response.headers.get("content-length"));
const expectedContentLength = this._endSize - this._startSize;
if (this.state.rangeSupport && contentLength !== expectedContentLength) {
throw new InvalidContentLengthError(expectedContentLength, contentLength);
}
this.on("aborted", () => {
controller.abort();
});
const reader = response.body.getReader();
return await this.chunkGenerator(callback, () => reader.read());
}
async fetchDownloadInfoWithoutRetry(url) {
if (this._fetchDownloadInfoWithHEAD) {
try {
return this.fetchDownloadInfoWithoutRetryByMethod(url, "HEAD");
}
catch (error) {
if (!(error instanceof StatusCodeError)) {
throw error;
}
this._fetchDownloadInfoWithHEAD = false;
}
}
return this.fetchDownloadInfoWithoutRetryByMethod(url, "GET");
}
async fetchDownloadInfoWithoutRetryByMethod(url, method = "HEAD") {
const response = await fetch(url, {
method: method,
headers: {
"Accept-Encoding": "identity",
...this.options.headers
}
});
if (response.status < 200 || response.status >= 300) {
throw new StatusCodeError(url, response.status, response.statusText, this.options.headers, DownloadEngineFetchStreamFetch.convertHeadersToRecord(response.headers));
}
const acceptRange = this.options.acceptRangeIsKnown ?? response.headers.get("accept-ranges") === "bytes";
const fileName = parseContentDisposition(response.headers.get("content-disposition"));
let length = parseInt(response.headers.get("content-length")) || 0;
if (response.headers.get("content-encoding") || browserCheck() && MIN_LENGTH_FOR_MORE_INFO_REQUEST < length) {
length = acceptRange ? await this.fetchDownloadInfoWithoutRetryContentRange(url, method === "GET" ? response : undefined) : 0;
}
return {
length,
acceptRange,
newURL: response.url,
fileName
};
}
async fetchDownloadInfoWithoutRetryContentRange(url, response) {
const responseGet = response ?? await fetch(url, {
method: "GET",
headers: {
accept: "*/*",
...this.options.headers,
range: "bytes=0-0"
}
});
const contentRange = responseGet.headers.get("content-range");
return parseHttpContentRange(contentRange)?.size || 0;
}
async chunkGenerator(callback, getNextChunk) {
const smartSplit = new SmartChunkSplit(callback, this.state);
// eslint-disable-next-line no-constant-condition
while (true) {
const { done, value } = await getNextChunk();
await this.paused;
if (done || this.aborted)
break;
smartSplit.addChunk(value);
this.state.onProgress?.(smartSplit.savedLength);
}
smartSplit.sendLeftovers();
}
static convertHeadersToRecord(headers) {
const headerObj = {};
headers.forEach((value, key) => {
headerObj[key] = value;
});
return headerObj;
}
}
//# sourceMappingURL=download-engine-fetch-stream-fetch.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"download-engine-fetch-stream-fetch.js","sourceRoot":"","sources":["../../../../../src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,11 @@
import BaseDownloadEngineFetchStream, { DownloadInfoResponse, FetchSubState, WriteCallback } from "./base-download-engine-fetch-stream.js";
export default class DownloadEngineFetchStreamLocalFile extends BaseDownloadEngineFetchStream {
transferAction: string;
private _fd;
private _fsPath;
withSubState(state: FetchSubState): this;
private _ensureFileOpen;
protected fetchWithoutRetryChunks(callback: WriteCallback): Promise<void>;
protected fetchDownloadInfoWithoutRetry(path: string): Promise<DownloadInfoResponse>;
close(): void;
}

View File

@@ -0,0 +1,54 @@
import fs from "fs/promises";
import { withLock } from "lifecycle-utils";
import retry from "async-retry";
import fsExtra from "fs-extra";
import BaseDownloadEngineFetchStream from "./base-download-engine-fetch-stream.js";
import SmartChunkSplit from "./utils/smart-chunk-split.js";
import streamResponse from "./utils/stream-response.js";
const OPEN_MODE = "r";
export default class DownloadEngineFetchStreamLocalFile extends BaseDownloadEngineFetchStream {
transferAction = "Copying";
_fd = null;
_fsPath = null;
withSubState(state) {
const fetchStream = new DownloadEngineFetchStreamLocalFile(this.options);
return this.cloneState(state, fetchStream);
}
async _ensureFileOpen(path) {
return await withLock(this, "_lock", async () => {
if (this._fd && this._fsPath === path) {
return this._fd;
}
this._fd?.close();
return await retry(async () => {
await fsExtra.ensureFile(path);
return this._fd = await fs.open(path, OPEN_MODE);
}, this.options.retry);
});
}
async fetchWithoutRetryChunks(callback) {
const file = await this._ensureFileOpen(this.state.url);
const stream = file.createReadStream({
start: this._startSize,
end: this._endSize - 1,
autoClose: true
});
return await streamResponse(stream, this, new SmartChunkSplit(callback, this.state), this.state.onProgress);
}
async fetchDownloadInfoWithoutRetry(path) {
const stat = await fs.stat(path);
if (!stat.isFile()) {
throw new Error("Path is a directory");
}
return {
length: stat.size,
acceptRange: true
};
}
close() {
super.close();
this._fd?.close();
this._fd = null;
}
}
//# sourceMappingURL=download-engine-fetch-stream-local-file.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"download-engine-fetch-stream-local-file.js","sourceRoot":"","sources":["../../../../../src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-local-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAC,QAAQ,EAAC,MAAM,iBAAiB,CAAC;AACzC,OAAO,KAAK,MAAM,aAAa,CAAC;AAChC,OAAO,OAAO,MAAM,UAAU,CAAC;AAC/B,OAAO,6BAAmF,MAAM,wCAAwC,CAAC;AACzI,OAAO,eAAe,MAAM,8BAA8B,CAAC;AAC3D,OAAO,cAAc,MAAM,4BAA4B,CAAC;AAExD,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB,MAAM,CAAC,OAAO,OAAO,kCAAmC,SAAQ,6BAA6B;IACzE,cAAc,GAAG,SAAS,CAAC;IACnC,GAAG,GAAsB,IAAI,CAAC;IAC9B,OAAO,GAAkB,IAAI,CAAC;IAE7B,YAAY,CAAC,KAAoB;QACtC,MAAM,WAAW,GAAG,IAAI,kCAAkC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzE,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,WAAW,CAAS,CAAC;IACvD,CAAC;IAEO,KAAK,CAAC,eAAe,CAAC,IAAY;QACtC,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE;YAC5C,IAAI,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;gBACpC,OAAO,IAAI,CAAC,GAAG,CAAC;YACpB,CAAC;YAED,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC;YAClB,OAAO,MAAM,KAAK,CAAC,KAAK,IAAI,EAAE;gBAC1B,MAAM,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;gBAC/B,OAAO,IAAI,CAAC,GAAG,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACrD,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;IACP,CAAC;IAEkB,KAAK,CAAC,uBAAuB,CAAC,QAAuB;QACpE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAExD,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC;YACjC,KAAK,EAAE,IAAI,CAAC,UAAU;YACtB,GAAG,EAAE,IAAI,CAAC,QAAQ,GAAG,CAAC;YACtB,SAAS,EAAE,IAAI;SAClB,CAAC,CAAC;QAEH,OAAO,MAAM,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,eAAe,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAChH,CAAC;IAEkB,KAAK,CAAC,6BAA6B,CAAC,IAAY;QAC/D,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO;YACH,MAAM,EAAE,IAAI,CAAC,IAAI;YACjB,WAAW,EAAE,IAAI;SACpB,CAAC;IACN,CAAC;IAEQ,KAAK;QACV,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC;QAClB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC;IACpB,CAAC;CACJ"}

View File

@@ -0,0 +1,18 @@
import BaseDownloadEngineFetchStream, { DownloadInfoResponse, FetchSubState, WriteCallback } from "./base-download-engine-fetch-stream.js";
import { AvailablePrograms } from "../../download-file/download-programs/switch-program.js";
export default class DownloadEngineFetchStreamXhr extends BaseDownloadEngineFetchStream {
private _fetchDownloadInfoWithHEAD;
readonly programType: AvailablePrograms;
transferAction: string;
withSubState(state: FetchSubState): this;
fetchBytes(url: string, start: number, end: number, onProgress?: (length: number) => void): Promise<Uint8Array>;
protected fetchBytesWithoutRetry(url: string, start: number, end: number, onProgress?: (length: number) => void): Promise<Uint8Array>;
fetchChunks(callback: WriteCallback): Promise<void>;
protected fetchWithoutRetryChunks(): Promise<void>;
protected _fetchChunksRangeSupport(callback: WriteCallback): Promise<void>;
protected _fetchChunksWithoutRange(callback: WriteCallback): Promise<void>;
protected fetchDownloadInfoWithoutRetry(url: string): Promise<DownloadInfoResponse>;
protected fetchDownloadInfoWithoutRetryByMethod(url: string, method?: "HEAD" | "GET"): Promise<DownloadInfoResponse>;
protected fetchDownloadInfoWithoutRetryContentRange(url: string, xhrResponse?: XMLHttpRequest): number | Promise<number>;
protected static convertXHRHeadersToRecord(xhr: XMLHttpRequest): Record<string, string>;
}

View File

@@ -0,0 +1,196 @@
import BaseDownloadEngineFetchStream, { MIN_LENGTH_FOR_MORE_INFO_REQUEST } from "./base-download-engine-fetch-stream.js";
import EmptyResponseError from "./errors/empty-response-error.js";
import StatusCodeError from "./errors/status-code-error.js";
import XhrError from "./errors/xhr-error.js";
import InvalidContentLengthError from "./errors/invalid-content-length-error.js";
import retry from "async-retry";
import { parseContentDisposition } from "./utils/content-disposition.js";
import { parseHttpContentRange } from "./utils/httpRange.js";
export default class DownloadEngineFetchStreamXhr extends BaseDownloadEngineFetchStream {
_fetchDownloadInfoWithHEAD = true;
programType = "chunks";
transferAction = "Downloading";
withSubState(state) {
const fetchStream = new DownloadEngineFetchStreamXhr(this.options);
return this.cloneState(state, fetchStream);
}
async fetchBytes(url, start, end, onProgress) {
return await retry(async () => {
return await this.fetchBytesWithoutRetry(url, start, end, onProgress);
}, this.options.retry);
}
fetchBytesWithoutRetry(url, start, end, onProgress) {
return new Promise((resolve, reject) => {
const headers = {
accept: "*/*",
...this.options.headers
};
if (this.state.rangeSupport) {
headers.range = `bytes=${start}-${end - 1}`;
}
const xhr = new XMLHttpRequest();
xhr.responseType = "arraybuffer";
xhr.open("GET", this.appendToURL(url), true);
for (const [key, value] of Object.entries(headers)) {
xhr.setRequestHeader(key, value);
}
xhr.onload = () => {
const contentLength = parseInt(xhr.getResponseHeader("content-length"));
if (this.state.rangeSupport && contentLength !== end - start) {
throw new InvalidContentLengthError(end - start, contentLength);
}
if (xhr.status >= 200 && xhr.status < 300) {
const arrayBuffer = xhr.response;
if (arrayBuffer) {
resolve(new Uint8Array(arrayBuffer));
}
else {
reject(new EmptyResponseError(url, headers));
}
}
else {
reject(new StatusCodeError(url, xhr.status, xhr.statusText, headers));
}
};
xhr.onerror = () => {
reject(new XhrError(`Failed to fetch ${url}`));
};
xhr.onprogress = (event) => {
if (event.lengthComputable) {
onProgress?.(event.loaded);
}
};
xhr.send();
this.on("aborted", () => {
xhr.abort();
});
});
}
async fetchChunks(callback) {
if (this.state.rangeSupport) {
return await this._fetchChunksRangeSupport(callback);
}
return await this._fetchChunksWithoutRange(callback);
}
fetchWithoutRetryChunks() {
throw new Error("Method not needed, use fetchChunks instead.");
}
async _fetchChunksRangeSupport(callback) {
while (this._startSize < this._endSize) {
await this.paused;
if (this.aborted)
return;
const chunk = await this.fetchBytes(this.state.url, this._startSize, this._endSize, this.state.onProgress);
callback([chunk], this._startSize, this.state.startChunk++);
}
}
async _fetchChunksWithoutRange(callback) {
const relevantContent = await (async () => {
const result = await this.fetchBytes(this.state.url, 0, this._endSize, this.state.onProgress);
return result.slice(this._startSize, this._endSize || result.length);
})();
let totalReceivedLength = 0;
let index = 0;
while (totalReceivedLength < relevantContent.byteLength) {
await this.paused;
if (this.aborted)
return;
const start = totalReceivedLength;
const end = Math.min(relevantContent.byteLength, start + this.state.chunkSize);
const chunk = relevantContent.slice(start, end);
totalReceivedLength += chunk.byteLength;
callback([chunk], index * this.state.chunkSize, index++);
}
}
fetchDownloadInfoWithoutRetry(url) {
if (this._fetchDownloadInfoWithHEAD) {
try {
return this.fetchDownloadInfoWithoutRetryByMethod(url, "HEAD");
}
catch (error) {
if (!(error instanceof StatusCodeError)) {
throw error;
}
this._fetchDownloadInfoWithHEAD = false;
}
}
return this.fetchDownloadInfoWithoutRetryByMethod(url, "GET");
}
async fetchDownloadInfoWithoutRetryByMethod(url, method = "HEAD") {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
const allHeaders = {
...this.options.headers
};
for (const [key, value] of Object.entries(allHeaders)) {
xhr.setRequestHeader(key, value);
}
xhr.onload = async () => {
if (xhr.status >= 200 && xhr.status < 300) {
const contentLength = parseInt(xhr.getResponseHeader("content-length"));
const length = MIN_LENGTH_FOR_MORE_INFO_REQUEST < contentLength ? await this.fetchDownloadInfoWithoutRetryContentRange(url, method === "GET" ? xhr : undefined) : 0;
const fileName = parseContentDisposition(xhr.getResponseHeader("content-disposition"));
const acceptRange = this.options.acceptRangeIsKnown ?? xhr.getResponseHeader("Accept-Ranges") === "bytes";
resolve({
length,
acceptRange,
newURL: xhr.responseURL,
fileName
});
}
else {
reject(new StatusCodeError(url, xhr.status, xhr.statusText, this.options.headers, DownloadEngineFetchStreamXhr.convertXHRHeadersToRecord(xhr)));
}
};
xhr.onerror = function () {
reject(new XhrError(`Failed to fetch ${url}`));
};
xhr.send();
});
}
fetchDownloadInfoWithoutRetryContentRange(url, xhrResponse) {
const getSize = (xhr) => {
const contentRange = xhr.getResponseHeader("Content-Range");
return parseHttpContentRange(contentRange)?.size || 0;
};
if (xhrResponse) {
return getSize(xhrResponse);
}
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
const allHeaders = {
accept: "*/*",
...this.options.headers,
range: "bytes=0-0"
};
for (const [key, value] of Object.entries(allHeaders)) {
xhr.setRequestHeader(key, value);
}
xhr.onload = () => {
resolve(getSize(xhr));
};
xhr.onerror = () => {
reject(new XhrError(`Failed to fetch ${url}`));
};
xhr.send();
});
}
static convertXHRHeadersToRecord(xhr) {
const headersString = xhr.getAllResponseHeaders();
const headersArray = headersString.trim()
.split(/[\r\n]+/);
const headersObject = {};
headersArray.forEach(line => {
const parts = line.split(": ");
const key = parts.shift();
const value = parts.join(": ");
if (key) {
headersObject[key] = value;
}
});
return headersObject;
}
}
//# sourceMappingURL=download-engine-fetch-stream-xhr.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
import FetchStreamError from "./fetch-stream-error.js";
export default class EmptyResponseError extends FetchStreamError {
readonly headers: {
[key: string]: string | string[];
};
constructor(url: string, headers: {
[key: string]: string | string[];
});
}

View File

@@ -0,0 +1,9 @@
import FetchStreamError from "./fetch-stream-error.js";
export default class EmptyResponseError extends FetchStreamError {
headers;
constructor(url, headers) {
super("Empty response error: " + url);
this.headers = headers;
}
}
//# sourceMappingURL=empty-response-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"empty-response-error.js","sourceRoot":"","sources":["../../../../../../src/download/download-engine/streams/download-engine-fetch-stream/errors/empty-response-error.ts"],"names":[],"mappings":"AAAA,OAAO,gBAAgB,MAAM,yBAAyB,CAAC;AAEvD,MAAM,CAAC,OAAO,OAAO,kBAAmB,SAAQ,gBAAgB;IACnB;IAAzC,YAAY,GAAW,EAAkB,OAA6C;QAClF,KAAK,CAAC,wBAAwB,GAAG,GAAG,CAAC,CAAC;QADD,YAAO,GAAP,OAAO,CAAsC;IAEtF,CAAC;CACJ"}

View File

@@ -0,0 +1,3 @@
import IpullError from "../../../../../errors/ipull-error.js";
export default class FetchStreamError extends IpullError {
}

View File

@@ -0,0 +1,4 @@
import IpullError from "../../../../../errors/ipull-error.js";
export default class FetchStreamError extends IpullError {
}
//# sourceMappingURL=fetch-stream-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"fetch-stream-error.js","sourceRoot":"","sources":["../../../../../../src/download/download-engine/streams/download-engine-fetch-stream/errors/fetch-stream-error.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,sCAAsC,CAAC;AAE9D,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,UAAU;CAAG"}

View File

@@ -0,0 +1,6 @@
import FetchStreamError from "./fetch-stream-error.js";
/**
* Represents an error that return from the server (will not retry)
*/
export default class HttpError extends FetchStreamError {
}

View File

@@ -0,0 +1,7 @@
import FetchStreamError from "./fetch-stream-error.js";
/**
* Represents an error that return from the server (will not retry)
*/
export default class HttpError extends FetchStreamError {
}
//# sourceMappingURL=http-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"http-error.js","sourceRoot":"","sources":["../../../../../../src/download/download-engine/streams/download-engine-fetch-stream/errors/http-error.ts"],"names":[],"mappings":"AAAA,OAAO,gBAAgB,MAAM,yBAAyB,CAAC;AAEvD;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,gBAAgB;CAEtD"}

View File

@@ -0,0 +1,4 @@
import HttpError from "./http-error.js";
export default class InvalidContentLengthError extends HttpError {
constructor(expectedLength: number, gotLength: number | string);
}

View File

@@ -0,0 +1,7 @@
import HttpError from "./http-error.js";
export default class InvalidContentLengthError extends HttpError {
constructor(expectedLength, gotLength) {
super(`Expected ${expectedLength} bytes, but got ${gotLength} bytes. If you on browser try to set "ignoreIfRangeWithQueryParams" to true, this will add a "_ignore" query parameter to the URL to avoid chrome "if-range" header.`);
}
}
//# sourceMappingURL=invalid-content-length-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"invalid-content-length-error.js","sourceRoot":"","sources":["../../../../../../src/download/download-engine/streams/download-engine-fetch-stream/errors/invalid-content-length-error.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,iBAAiB,CAAC;AAExC,MAAM,CAAC,OAAO,OAAO,yBAA0B,SAAQ,SAAS;IAC5D,YAAY,cAAsB,EAAE,SAA0B;QAC1D,KAAK,CAAC,YAAY,cAAc,mBAAmB,SAAS,sKAAsK,CAAC,CAAC;IACxO,CAAC;CACJ"}

View File

@@ -0,0 +1,4 @@
import FetchStreamError from "./fetch-stream-error.js";
export default class PathNotAFileError extends FetchStreamError {
constructor(path: string);
}

View File

@@ -0,0 +1,7 @@
import FetchStreamError from "./fetch-stream-error.js";
export default class PathNotAFileError extends FetchStreamError {
constructor(path) {
super(`Path is not a file: ${path}`);
}
}
//# sourceMappingURL=path-not-a-file-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"path-not-a-file-error.js","sourceRoot":"","sources":["../../../../../../src/download/download-engine/streams/download-engine-fetch-stream/errors/path-not-a-file-error.ts"],"names":[],"mappings":"AAAA,OAAO,gBAAgB,MAAM,yBAAyB,CAAC;AAEvD,MAAM,CAAC,OAAO,OAAO,iBAAkB,SAAQ,gBAAgB;IAC3D,YAAY,IAAY;QACpB,KAAK,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;CACJ"}

View File

@@ -0,0 +1,18 @@
import HttpError from "./http-error.js";
export default class StatusCodeError extends HttpError {
readonly url: string;
readonly statusCode: number;
readonly statusText: string;
headers?: {
[key: string]: string | string[];
} | undefined;
responseHeaders?: {
[key: string]: string;
} | undefined;
constructor(url: string, statusCode: number, statusText: string, headers?: {
[key: string]: string | string[];
} | undefined, responseHeaders?: {
[key: string]: string;
} | undefined);
get retryAfter(): number | undefined;
}

View File

@@ -0,0 +1,32 @@
import HttpError from "./http-error.js";
export default class StatusCodeError extends HttpError {
url;
statusCode;
statusText;
headers;
responseHeaders;
constructor(url, statusCode, statusText, headers, responseHeaders) {
super();
this.url = url;
this.statusCode = statusCode;
this.statusText = statusText;
this.headers = headers;
this.responseHeaders = responseHeaders;
this.message = `Url: ${url}, Status code: ${statusCode}, status text: ${statusText}`;
}
get retryAfter() {
const retryAfter = this.responseHeaders?.["retry-after"];
if (retryAfter) {
const number = parseInt(retryAfter, 10);
if (isNaN(number)) {
return new Date(retryAfter).getTime() - Date.now();
}
return number;
}
else if (this.responseHeaders?.["ratelimit-reset"]) {
return parseInt(this.responseHeaders["ratelimit-reset"], 10) * 1000;
}
return;
}
}
//# sourceMappingURL=status-code-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"status-code-error.js","sourceRoot":"","sources":["../../../../../../src/download/download-engine/streams/download-engine-fetch-stream/errors/status-code-error.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,iBAAiB,CAAC;AAExC,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,SAAS;IAE9B;IACA;IACA;IACT;IACA;IALX,YACoB,GAAW,EACX,UAAkB,EAClB,UAAkB,EAC3B,OAA8C,EAC9C,eAA2C;QAElD,KAAK,EAAE,CAAC;QANQ,QAAG,GAAH,GAAG,CAAQ;QACX,eAAU,GAAV,UAAU,CAAQ;QAClB,eAAU,GAAV,UAAU,CAAQ;QAC3B,YAAO,GAAP,OAAO,CAAuC;QAC9C,oBAAe,GAAf,eAAe,CAA4B;QAGlD,IAAI,CAAC,OAAO,GAAG,QAAQ,GAAG,kBAAkB,UAAU,kBAAkB,UAAU,EAAE,CAAC;IACzF,CAAC;IAED,IAAI,UAAU;QACV,MAAM,UAAU,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC,aAAa,CAAC,CAAC;QACzD,IAAI,UAAU,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;YACxC,IAAI,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;gBAChB,OAAO,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvD,CAAC;YACD,OAAO,MAAM,CAAC;QAClB,CAAC;aAAM,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC,iBAAiB,CAAC,EAAE,CAAC;YACnD,OAAO,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,iBAAiB,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC;QACxE,CAAC;QAED,OAAO;IACX,CAAC;CACJ"}

View File

@@ -0,0 +1,3 @@
import FetchStreamError from "./fetch-stream-error.js";
export default class XhrError extends FetchStreamError {
}

View File

@@ -0,0 +1,4 @@
import FetchStreamError from "./fetch-stream-error.js";
export default class XhrError extends FetchStreamError {
}
//# sourceMappingURL=xhr-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"xhr-error.js","sourceRoot":"","sources":["../../../../../../src/download/download-engine/streams/download-engine-fetch-stream/errors/xhr-error.ts"],"names":[],"mappings":"AAAA,OAAO,gBAAgB,MAAM,yBAAyB,CAAC;AAEvD,MAAM,CAAC,OAAO,OAAO,QAAS,SAAQ,gBAAgB;CAAG"}

View File

@@ -0,0 +1 @@
export declare function browserCheck(): boolean;

View File

@@ -0,0 +1,4 @@
export function browserCheck() {
return typeof window !== "undefined" && typeof window.document !== "undefined";
}
//# sourceMappingURL=browserCheck.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"browserCheck.js","sourceRoot":"","sources":["../../../../../../src/download/download-engine/streams/download-engine-fetch-stream/utils/browserCheck.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,YAAY;IACxB,OAAO,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,WAAW,CAAC;AACnF,CAAC"}

View File

@@ -0,0 +1 @@
export declare function parseContentDisposition(header?: string | null): string | undefined;

View File

@@ -0,0 +1,12 @@
import { parse } from "@tinyhttp/content-disposition";
export function parseContentDisposition(header) {
if (!header) {
return undefined;
}
try {
return String(parse(header).parameters.filename || "") || undefined;
}
catch { }
return undefined;
}
//# sourceMappingURL=content-disposition.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"content-disposition.js","sourceRoot":"","sources":["../../../../../../src/download/download-engine/streams/download-engine-fetch-stream/utils/content-disposition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAC,MAAM,+BAA+B,CAAC;AAEpD,MAAM,UAAU,uBAAuB,CAAC,MAAsB;IAC1D,IAAI,CAAC,MAAM,EAAE,CAAC;QACV,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,IAAI,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,QAAQ,IAAI,EAAE,CAAC,IAAI,SAAS,CAAC;IACxE,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IAEV,OAAO,SAAS,CAAC;AACrB,CAAC"}

View File

@@ -0,0 +1,6 @@
export declare function parseHttpContentRange(value?: string | null): {
start: number;
end: number;
size: number;
length: number;
} | null;

View File

@@ -0,0 +1,22 @@
export function parseHttpContentRange(value) {
try {
if (!value)
return null;
const parts = value.split(" ")[1].split("/");
const range = parts[0].split("-");
const size = parseInt(parts[1]);
const start = parseInt(range[0]);
const end = parseInt(range[1]);
const length = end - start + 1;
return {
start,
end,
size,
length
};
}
catch {
return null;
}
}
//# sourceMappingURL=httpRange.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"httpRange.js","sourceRoot":"","sources":["../../../../../../src/download/download-engine/streams/download-engine-fetch-stream/utils/httpRange.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,qBAAqB,CAAC,KAAqB;IACvD,IAAI,CAAC;QACD,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAEhC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,MAAM,GAAG,GAAG,GAAG,KAAK,GAAG,CAAC,CAAC;QAE/B,OAAO;YACH,KAAK;YACL,GAAG;YACH,IAAI;YACJ,MAAM;SACT,CAAC;IACN,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC"}

View File

@@ -0,0 +1,7 @@
import retry from "async-retry";
export declare function promiseWithResolvers<Resolve = void>(): {
promise: Promise<Resolve>;
resolve: (value: Resolve) => void;
reject: (reason: unknown) => void;
};
export declare function retryAsyncStatementSimple(options?: retry.Options): (reason?: Error) => Promise<void>;

View File

@@ -0,0 +1,43 @@
import retry from "async-retry";
export function promiseWithResolvers() {
let resolve;
let reject;
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
return { promise, resolve: resolve, reject: reject };
}
function retryAsyncStatement(options) {
const resolvers = promiseWithResolvers();
const waitResolvers = promiseWithResolvers();
const promiseWithRetry = retry(async () => {
try {
waitResolvers.resolve();
Object.assign(waitResolvers, promiseWithResolvers());
await resolvers.promise;
}
catch (error) {
Object.assign(resolvers, promiseWithResolvers());
throw error;
}
}, options);
promiseWithRetry.catch((reason) => {
waitResolvers.reject(reason);
});
promiseWithRetry.then((value) => {
waitResolvers.resolve(value);
});
return {
waitResolvers,
resolvers
};
}
export function retryAsyncStatementSimple(options) {
const retryState = retryAsyncStatement(options);
return (reason = new Error()) => {
retryState.resolvers.reject(reason);
return retryState.waitResolvers.promise;
};
}
//# sourceMappingURL=retry-async-statement.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"retry-async-statement.js","sourceRoot":"","sources":["../../../../../../src/download/download-engine/streams/download-engine-fetch-stream/utils/retry-async-statement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,aAAa,CAAC;AAEhC,MAAM,UAAU,oBAAoB;IAChC,IAAI,OAAiC,CAAC;IACtC,IAAI,MAAiC,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAU,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE;QACvD,OAAO,GAAG,QAAQ,CAAC;QACnB,MAAM,GAAG,OAAO,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,OAAO,EAAC,OAAO,EAAE,OAAO,EAAE,OAAQ,EAAE,MAAM,EAAE,MAAO,EAAC,CAAC;AACzD,CAAC;AAED,SAAS,mBAAmB,CAAC,OAAuB;IAChD,MAAM,SAAS,GAAG,oBAAoB,EAAE,CAAC;IACzC,MAAM,aAAa,GAAG,oBAAoB,EAAE,CAAC;IAE7C,MAAM,gBAAgB,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE;QACtC,IAAI,CAAC;YACD,aAAa,CAAC,OAAO,EAAE,CAAC;YACxB,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,oBAAoB,EAAE,CAAC,CAAC;YACrD,MAAM,SAAS,CAAC,OAAO,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,oBAAoB,EAAE,CAAC,CAAC;YACjD,MAAM,KAAK,CAAC;QAChB,CAAC;IACL,CAAC,EAAE,OAAO,CAAC,CAAC;IAEZ,gBAAgB,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE;QAC9B,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,gBAAgB,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;QAC5B,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,OAAO;QACH,aAAa;QACb,SAAS;KACZ,CAAC;AACN,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,OAAuB;IAC7D,MAAM,UAAU,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAEhD,OAAO,CAAC,MAAM,GAAG,IAAI,KAAK,EAAE,EAAE,EAAE;QAC5B,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACpC,OAAO,UAAU,CAAC,aAAa,CAAC,OAAO,CAAC;IAC5C,CAAC,CAAC;AACN,CAAC"}

View File

@@ -0,0 +1,17 @@
import { WriteCallback } from "../base-download-engine-fetch-stream.js";
export type SmartChunkSplitOptions = {
chunkSize: number;
startChunk: number;
};
export default class SmartChunkSplit {
private readonly _callback;
private readonly _options;
private _bytesWriteLocation;
private _bytesLeftovers;
private _chunks;
constructor(_callback: WriteCallback, _options: SmartChunkSplitOptions);
addChunk(data: Uint8Array): void;
get savedLength(): number;
sendLeftovers(): void;
private _sendChunk;
}

View File

@@ -0,0 +1,45 @@
export default class SmartChunkSplit {
_callback;
_options;
_bytesWriteLocation;
_bytesLeftovers = 0;
_chunks = [];
constructor(_callback, _options) {
this._options = _options;
this._callback = _callback;
this._bytesWriteLocation = _options.startChunk * _options.chunkSize;
}
addChunk(data) {
this._chunks.push(data);
this._sendChunk();
}
get savedLength() {
return this._bytesLeftovers + this._chunks.reduce((acc, chunk) => acc + chunk.length, 0);
}
sendLeftovers() {
if (this.savedLength > 0) {
this._callback(this._chunks, this._bytesWriteLocation, this._options.startChunk++);
}
}
_sendChunk() {
while (this.savedLength >= this._options.chunkSize) {
if (this._chunks.length === 0) {
this._callback([], this._bytesWriteLocation, this._options.startChunk++);
this._bytesWriteLocation += this._options.chunkSize;
this._bytesLeftovers -= this._options.chunkSize;
}
let sendLength = this._bytesLeftovers;
for (let i = 0; i < this._chunks.length; i++) {
sendLength += this._chunks[i].byteLength;
if (sendLength >= this._options.chunkSize) {
const sendChunks = this._chunks.splice(0, i + 1);
this._callback(sendChunks, this._bytesWriteLocation, this._options.startChunk++);
this._bytesWriteLocation += sendLength - this._bytesLeftovers;
this._bytesLeftovers = sendLength - this._options.chunkSize;
break;
}
}
}
}
}
//# sourceMappingURL=smart-chunk-split.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"smart-chunk-split.js","sourceRoot":"","sources":["../../../../../../src/download/download-engine/streams/download-engine-fetch-stream/utils/smart-chunk-split.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,OAAO,OAAO,eAAe;IACf,SAAS,CAAgB;IACzB,QAAQ,CAAyB;IAC1C,mBAAmB,CAAS;IAC5B,eAAe,GAAW,CAAC,CAAC;IAC5B,OAAO,GAAiB,EAAE,CAAC;IAEnC,YAAmB,SAAwB,EAAE,QAAgC;QACzE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,mBAAmB,GAAG,QAAQ,CAAC,UAAU,GAAG,QAAQ,CAAC,SAAS,CAAC;IACxE,CAAC;IAEM,QAAQ,CAAC,IAAgB;QAC5B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,IAAI,CAAC,UAAU,EAAE,CAAC;IACtB,CAAC;IAED,IAAW,WAAW;QAClB,OAAO,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC7F,CAAC;IAEM,aAAa;QAChB,IAAI,IAAI,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;YACvB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,mBAAmB,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QACvF,CAAC;IACL,CAAC;IAEO,UAAU;QACd,OAAO,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;YACjD,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC5B,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,mBAAmB,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;gBACzE,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;gBACpD,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;YACpD,CAAC;YAED,IAAI,UAAU,GAAG,IAAI,CAAC,eAAe,CAAC;YACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC3C,UAAU,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;gBACzC,IAAI,UAAU,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;oBACxC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;oBAEjD,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,mBAAmB,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;oBACjF,IAAI,CAAC,mBAAmB,IAAI,UAAU,GAAG,IAAI,CAAC,eAAe,CAAC;oBAC9D,IAAI,CAAC,eAAe,GAAG,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;oBAC5D,MAAM;gBACV,CAAC;YACL,CAAC;QACL,CAAC;IACL,CAAC;CACJ"}

View File

@@ -0,0 +1,12 @@
import SmartChunkSplit from "./smart-chunk-split.js";
import BaseDownloadEngineFetchStream from "../base-download-engine-fetch-stream.js";
type IStreamResponse = {
on(event: "data", listener: (chunk: Uint8Array) => void): IStreamResponse;
on(event: "close", listener: () => void): IStreamResponse;
on(event: "error", listener: (error: Error) => void): IStreamResponse;
pause(): void;
resume(): void;
destroy(): void;
};
export default function streamResponse(stream: IStreamResponse, downloadEngine: BaseDownloadEngineFetchStream, smartSplit: SmartChunkSplit, onProgress?: (leftOverLength: number) => void): Promise<void>;
export {};

View File

@@ -0,0 +1,31 @@
import { promiseWithResolvers } from "./retry-async-statement.js";
export default async function streamResponse(stream, downloadEngine, smartSplit, onProgress) {
const { promise, resolve, reject } = promiseWithResolvers();
stream.on("data", (chunk) => {
smartSplit.addChunk(chunk);
onProgress?.(smartSplit.savedLength);
});
stream.on("close", () => {
smartSplit.sendLeftovers();
resolve();
});
stream.on("error", (error) => {
reject(error);
});
const pause = stream.pause.bind(stream);
const resume = stream.resume.bind(stream);
const close = stream.destroy.bind(stream);
downloadEngine.on("paused", pause);
downloadEngine.on("resumed", resume);
downloadEngine.on("aborted", close);
try {
await promise;
}
finally {
downloadEngine.off("paused", pause);
downloadEngine.off("resumed", resume);
downloadEngine.off("aborted", close);
stream.destroy();
}
}
//# sourceMappingURL=stream-response.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"stream-response.js","sourceRoot":"","sources":["../../../../../../src/download/download-engine/streams/download-engine-fetch-stream/utils/stream-response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,oBAAoB,EAAC,MAAM,4BAA4B,CAAC;AAahE,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,cAAc,CAAC,MAAuB,EAAE,cAA6C,EAAE,UAA2B,EAAE,UAA6C;IAC3L,MAAM,EAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAC,GAAG,oBAAoB,EAAE,CAAC;IAE1D,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;QACxB,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC3B,UAAU,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACpB,UAAU,CAAC,aAAa,EAAE,CAAC;QAC3B,OAAO,EAAE,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;QACzB,MAAM,CAAC,KAAK,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAE1C,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACnC,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACrC,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAEpC,IAAI,CAAC;QACD,MAAM,OAAO,CAAC;IAClB,CAAC;YAAS,CAAC;QACP,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QACpC,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACtC,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACrC,MAAM,CAAC,OAAO,EAAE,CAAC;IACrB,CAAC;AACL,CAAC"}