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"}

View File

@@ -0,0 +1,4 @@
export default abstract class BaseDownloadEngineWriteStream {
abstract write(cursor: number, buffer: Uint8Array): Promise<void> | void;
close(): void | Promise<void>;
}

View File

@@ -0,0 +1,5 @@
export default class BaseDownloadEngineWriteStream {
close() {
}
}
//# sourceMappingURL=base-download-engine-write-stream.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"base-download-engine-write-stream.js","sourceRoot":"","sources":["../../../../../src/download/download-engine/streams/download-engine-write-stream/base-download-engine-write-stream.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,OAAgB,6BAA6B;IAGvD,KAAK;IACL,CAAC;CACJ"}

View File

@@ -0,0 +1,22 @@
import retry from "async-retry";
import { DownloadFile } from "../../types.js";
import BaseDownloadEngineWriteStream from "./base-download-engine-write-stream.js";
type DownloadEngineWriteStreamOptionsBrowser = {
retry?: retry.Options;
file?: DownloadFile;
};
export type DownloadEngineWriteStreamBrowserWriter = (cursor: number, buffer: Uint8Array, options: DownloadEngineWriteStreamOptionsBrowser) => Promise<void> | void;
export default class DownloadEngineWriteStreamBrowser extends BaseDownloadEngineWriteStream {
protected readonly _writer?: DownloadEngineWriteStreamBrowserWriter;
readonly options: DownloadEngineWriteStreamOptionsBrowser;
protected _memory: Uint8Array;
protected _bytesWritten: number;
get writerClosed(): boolean | 0 | undefined;
constructor(_writer?: DownloadEngineWriteStreamBrowserWriter, options?: DownloadEngineWriteStreamOptionsBrowser);
protected _ensureBuffer(length: number): Uint8Array;
write(cursor: number, buffer: Uint8Array): void | Promise<void>;
get result(): Uint8Array;
resultAsBlobURL(): string;
resultAsText(): string;
}
export {};

View File

@@ -0,0 +1,52 @@
import BaseDownloadEngineWriteStream from "./base-download-engine-write-stream.js";
import WriterIsClosedError from "./errors/writer-is-closed-error.js";
import WriterNotDefineError from "./errors/writer-not-define-error.js";
export default class DownloadEngineWriteStreamBrowser extends BaseDownloadEngineWriteStream {
_writer;
options = {};
_memory = new Uint8Array(0);
_bytesWritten = 0;
get writerClosed() {
return this.options.file?.totalSize && this._bytesWritten === this.options.file.totalSize;
}
constructor(_writer, options = {}) {
super();
this.options = options;
this._writer = _writer;
}
_ensureBuffer(length) {
if (this._memory.length >= length) {
return this._memory;
}
if (!this.options.file) {
throw new WriterNotDefineError("Writer & file is not defined, please provide a writer or file");
}
const newSize = Math.max(length, this.options.file.totalSize);
const newMemory = new Uint8Array(newSize);
newMemory.set(this._memory);
return this._memory = newMemory;
}
write(cursor, buffer) {
if (this.writerClosed) {
throw new WriterIsClosedError();
}
if (!this._writer) {
this._ensureBuffer(cursor + buffer.byteLength)
.set(buffer, cursor);
this._bytesWritten += buffer.byteLength;
return;
}
return this._writer(cursor, buffer, this.options);
}
get result() {
return this._memory;
}
resultAsBlobURL() {
const blob = new Blob([this._memory]);
return URL.createObjectURL(blob);
}
resultAsText() {
return new TextDecoder().decode(this._memory);
}
}
//# sourceMappingURL=download-engine-write-stream-browser.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"download-engine-write-stream-browser.js","sourceRoot":"","sources":["../../../../../src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-browser.ts"],"names":[],"mappings":"AAEA,OAAO,6BAA6B,MAAM,wCAAwC,CAAC;AACnF,OAAO,mBAAmB,MAAM,oCAAoC,CAAC;AACrE,OAAO,oBAAoB,MAAM,qCAAqC,CAAC;AASvE,MAAM,CAAC,OAAO,OAAO,gCAAiC,SAAQ,6BAA6B;IACpE,OAAO,CAA0C;IACpD,OAAO,GAA4C,EAAE,CAAC;IAE5D,OAAO,GAAe,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IACxC,aAAa,GAAG,CAAC,CAAC;IAE5B,IAAW,YAAY;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,IAAI,IAAI,CAAC,aAAa,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC;IAC9F,CAAC;IAED,YAAmB,OAAgD,EAAE,UAAmD,EAAE;QACtH,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC3B,CAAC;IAES,aAAa,CAAC,MAAc;QAClC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,MAAM,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC,OAAO,CAAC;QACxB,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,oBAAoB,CAAC,+DAA+D,CAAC,CAAC;QACpG,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC9D,MAAM,SAAS,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC;QAC1C,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAE5B,OAAO,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;IACpC,CAAC;IAEM,KAAK,CAAC,MAAc,EAAE,MAAkB;QAC3C,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,MAAM,IAAI,mBAAmB,EAAE,CAAC;QACpC,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAChB,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC;iBACzC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACzB,IAAI,CAAC,aAAa,IAAI,MAAM,CAAC,UAAU,CAAC;YACxC,OAAO;QACX,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,IAAW,MAAM;QACb,OAAO,IAAI,CAAC,OAAO,CAAC;IACxB,CAAC;IAEM,eAAe;QAClB,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QACtC,OAAO,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IAEM,YAAY;QACf,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAClD,CAAC;CACJ"}

View File

@@ -0,0 +1,22 @@
import retry from "async-retry";
import BaseDownloadEngineWriteStream from "./base-download-engine-write-stream.js";
export type DownloadEngineWriteStreamOptionsNodeJS = {
retry?: retry.Options;
mode: string;
};
export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineWriteStream {
path: string;
finalPath: string;
private _fd;
private _fileWriteFinished;
readonly options: DownloadEngineWriteStreamOptionsNodeJS;
fileSize: number;
constructor(path: string, finalPath: string, options?: Partial<DownloadEngineWriteStreamOptionsNodeJS>);
private _ensureFileOpen;
write(cursor: number, buffer: Uint8Array): Promise<void>;
ftruncate(size?: number): Promise<void>;
saveMedataAfterFile(data: any): Promise<void>;
loadMetadataAfterFileWithoutRetry(): Promise<any>;
private _writeWithoutRetry;
close(): Promise<void>;
}

View File

@@ -0,0 +1,104 @@
import fs from "fs/promises";
import fsExtra from "fs-extra";
import retry from "async-retry";
import { withLock } from "lifecycle-utils";
import BaseDownloadEngineWriteStream from "./base-download-engine-write-stream.js";
import WriterIsClosedError from "./errors/writer-is-closed-error.js";
const DEFAULT_OPTIONS = {
mode: "r+"
};
const NOT_ENOUGH_SPACE_ERROR_CODE = "ENOSPC";
export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineWriteStream {
path;
finalPath;
_fd = null;
_fileWriteFinished = false;
options;
fileSize = 0;
constructor(path, finalPath, options = {}) {
super();
this.path = path;
this.finalPath = finalPath;
this.options = { ...DEFAULT_OPTIONS, ...options };
}
async _ensureFileOpen() {
return await withLock(this, "_lock", async () => {
if (this._fd) {
return this._fd;
}
return await retry(async () => {
await fsExtra.ensureFile(this.path);
return this._fd = await fs.open(this.path, this.options.mode);
}, this.options.retry);
});
}
async write(cursor, buffer) {
let throwError = false;
await retry(async () => {
try {
return await this._writeWithoutRetry(cursor, buffer);
}
catch (error) {
if (error?.code === NOT_ENOUGH_SPACE_ERROR_CODE) {
throwError = error;
return;
}
throw error;
}
}, this.options.retry);
if (throwError) {
throw throwError;
}
}
async ftruncate(size = this.fileSize) {
this._fileWriteFinished = true;
await retry(async () => {
const fd = await this._ensureFileOpen();
await fd.truncate(size);
}, this.options.retry);
}
async saveMedataAfterFile(data) {
if (this._fileWriteFinished) {
throw new WriterIsClosedError();
}
const jsonString = JSON.stringify(data);
const encoder = new TextEncoder();
const uint8Array = encoder.encode(jsonString);
await this.write(this.fileSize, uint8Array);
}
async loadMetadataAfterFileWithoutRetry() {
if (!await fsExtra.pathExists(this.path)) {
return;
}
const fd = await this._ensureFileOpen();
try {
const state = await fd.stat();
const metadataSize = state.size - this.fileSize;
if (metadataSize <= 0) {
return;
}
const metadataBuffer = Buffer.alloc(metadataSize);
await fd.read(metadataBuffer, 0, metadataSize, this.fileSize);
const decoder = new TextDecoder();
const metadataString = decoder.decode(metadataBuffer);
try {
return JSON.parse(metadataString);
}
catch { }
}
finally {
this._fd = null;
await fd.close();
}
}
async _writeWithoutRetry(cursor, buffer) {
const fd = await this._ensureFileOpen();
const { bytesWritten } = await fd.write(buffer, 0, buffer.length, cursor);
return bytesWritten;
}
async close() {
await this._fd?.close();
this._fd = null;
}
}
//# sourceMappingURL=download-engine-write-stream-nodejs.js.map

View File

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

View File

@@ -0,0 +1,4 @@
import IpullError from "../../../../../errors/ipull-error.js";
export default class WriterError extends IpullError {
constructor(message?: string);
}

View File

@@ -0,0 +1,7 @@
import IpullError from "../../../../../errors/ipull-error.js";
export default class WriterError extends IpullError {
constructor(message = "Writer error") {
super(message);
}
}
//# sourceMappingURL=writer-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"writer-error.js","sourceRoot":"","sources":["../../../../../../src/download/download-engine/streams/download-engine-write-stream/errors/writer-error.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,sCAAsC,CAAC;AAE9D,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,UAAU;IAC/C,YAAY,OAAO,GAAG,cAAc;QAChC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnB,CAAC;CACJ"}

View File

@@ -0,0 +1,4 @@
import WriterError from "./writer-error.js";
export default class WriterIsClosedError extends WriterError {
constructor(message?: string);
}

View File

@@ -0,0 +1,7 @@
import WriterError from "./writer-error.js";
export default class WriterIsClosedError extends WriterError {
constructor(message = "Writer is closed") {
super(message);
}
}
//# sourceMappingURL=writer-is-closed-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"writer-is-closed-error.js","sourceRoot":"","sources":["../../../../../../src/download/download-engine/streams/download-engine-write-stream/errors/writer-is-closed-error.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,mBAAmB,CAAC;AAE5C,MAAM,CAAC,OAAO,OAAO,mBAAoB,SAAQ,WAAW;IACxD,YAAY,OAAO,GAAG,kBAAkB;QACpC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnB,CAAC;CACJ"}

View File

@@ -0,0 +1,4 @@
import WriterError from "./writer-error.js";
export default class WriterNotDefineError extends WriterError {
constructor(message?: string);
}

View File

@@ -0,0 +1,7 @@
import WriterError from "./writer-error.js";
export default class WriterNotDefineError extends WriterError {
constructor(message = "Writer is not defined") {
super(message);
}
}
//# sourceMappingURL=writer-not-define-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"writer-not-define-error.js","sourceRoot":"","sources":["../../../../../../src/download/download-engine/streams/download-engine-write-stream/errors/writer-not-define-error.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,mBAAmB,CAAC;AAE5C,MAAM,CAAC,OAAO,OAAO,oBAAqB,SAAQ,WAAW;IACzD,YAAY,OAAO,GAAG,uBAAuB;QACzC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnB,CAAC;CACJ"}