Migrate downloadWithProgress to util

This commit is contained in:
Jerome Wu
2023-01-09 22:47:39 +08:00
parent 36c666aecf
commit ec183935fe
14 changed files with 110 additions and 225 deletions

View File

@@ -0,0 +1 @@
export const HeaderContentLength = "Content-Length";

View File

@@ -0,0 +1,6 @@
export const ERROR_RESPONSE_BODY_READER = new Error(
"failed to get response body reader"
);
export const ERROR_INCOMPLETED_DOWNLOAD = new Error(
"failed to complete download"
);

View File

@@ -1,3 +1,10 @@
import {
ERROR_RESPONSE_BODY_READER,
ERROR_INCOMPLETED_DOWNLOAD,
} from "./errors";
import { HeaderContentLength } from "./const";
import { ProgressCallback } from "./types";
export const readFromBlobOrFile = (blob: Blob | File): Promise<Uint8Array> =>
new Promise((resolve, reject) => {
const fileReader = new FileReader();
@@ -105,3 +112,79 @@ export const toBlobURL = async (
const blob = new Blob([buf], { type: mimeType });
return URL.createObjectURL(blob);
};
/**
* Download content of a URL with progress.
*
* Progress only works when Content-Length is provided by the server.
*
*/
export const downloadWithProgress = async (
url: string | URL,
cb: ProgressCallback
): Promise<ArrayBuffer> => {
const resp = await fetch(url);
let buf;
try {
// Set total to -1 to indicate that there is not Content-Type Header.
const total = parseInt(resp.headers.get(HeaderContentLength) || "-1");
const reader = resp.body?.getReader();
if (!reader) throw ERROR_RESPONSE_BODY_READER;
const chunks = [];
let received = 0;
for (;;) {
const { done, value } = await reader.read();
const delta = value ? value.length : 0;
if (done) {
if (total != -1 && total !== received) throw ERROR_INCOMPLETED_DOWNLOAD;
cb({ url, total, received, delta, done });
break;
}
chunks.push(value);
received += delta;
cb({ url, total, received, delta, done });
}
const data = new Uint8Array(received);
let position = 0;
for (const chunk of chunks) {
data.set(chunk, position);
position += chunk.length;
}
buf = data.buffer;
} catch (e) {
console.log(`failed to send download progress event: `, e);
// Fetch arrayBuffer directly when it is not possible to get progress.
buf = await resp.arrayBuffer();
cb({
url,
total: buf.byteLength,
received: buf.byteLength,
delta: 0,
done: true,
});
}
return buf;
};
/**
* Convert an URL to an Blob URL to avoid issues like CORS.
*/
export const toBlobURLWithProgress = async (
url: string,
/** mime type like `text/javascript` and `application/wasm` */
mimeType: string,
cb: ProgressCallback
): Promise<string> =>
URL.createObjectURL(
new Blob([await downloadWithProgress(url, cb)], {
type: mimeType,
})
);

View File

@@ -0,0 +1,9 @@
export interface DownloadProgressEvent {
url: string | URL;
total: number;
received: number;
delta: number;
done: boolean;
}
export type ProgressCallback = (event: DownloadProgressEvent) => void;