Migrate downloadWithProgress to util
This commit is contained in:
1
packages/util/src/const.ts
Normal file
1
packages/util/src/const.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const HeaderContentLength = "Content-Length";
|
||||
6
packages/util/src/errors.ts
Normal file
6
packages/util/src/errors.ts
Normal 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"
|
||||
);
|
||||
@@ -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,
|
||||
})
|
||||
);
|
||||
|
||||
9
packages/util/src/types.ts
Normal file
9
packages/util/src/types.ts
Normal 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;
|
||||
Reference in New Issue
Block a user