diff --git a/static/install/web.html b/static/install/web.html
index 21c6f2b1..14ff279d 100644
--- a/static/install/web.html
+++ b/static/install/web.html
@@ -163,7 +163,11 @@
-
+
+
+
+
+
diff --git a/static/js/web-install.js b/static/js/web-install.js
index 2db0ddef..9a5f8ef9 100644
--- a/static/js/web-install.js
+++ b/static/js/web-install.js
@@ -7,6 +7,26 @@ const RELEASES_URL = "https://releases.grapheneos.org";
const CACHE_DB_NAME = "BlobStore";
const CACHE_DB_VERSION = 1;
+// This wraps XHR because getting progress updates with fetch() is overly complicated.
+function fetchBlobWithProgress(url, onProgress) {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", url);
+ xhr.responseType = "blob";
+ xhr.send();
+
+ return new Promise((resolve, reject) => {
+ xhr.onload = () => {
+ resolve(xhr.response);
+ };
+ xhr.onprogress = (event) => {
+ onProgress(event.loaded / event.total);
+ };
+ xhr.onerror = () => {
+ reject(`${xhr.status} ${xhr.statusText}`);
+ };
+ });
+}
+
class BlobStore {
constructor() {
this.db = null;
@@ -65,19 +85,12 @@ class BlobStore {
this.db.close();
}
- /**
- * Downloads the file from the given URL and saves it to this BlobStore.
- *
- * @param {string} url - URL of the file to download.
- * @returns {blob} Blob containing the downloaded data.
- */
- async download(url) {
+ async download(url, onProgress = () => {}) {
let filename = url.split("/").pop();
let blob = await this.loadFile(filename);
if (blob === null) {
console.log(`Downloading ${url}`);
- let resp = await fetch(new Request(url));
- blob = await resp.blob();
+ let blob = await fetchBlobWithProgress(url, onProgress);
console.log("File downloaded, saving...");
await this.saveFile(filename, blob);
console.log("File saved");
@@ -144,8 +157,10 @@ async function downloadRelease(setProgress) {
// Download and cache the zip as a blob
setProgress(`Downloading ${latestZip}...`);
await blobStore.init();
- await blobStore.download(`${RELEASES_URL}/${latestZip}`);
- return `Downloaded ${latestZip} release.`;
+ await blobStore.download(`${RELEASES_URL}/${latestZip}`, (progress) => {
+ setProgress(`Downloading ${latestZip}...`, progress);
+ });
+ setProgress(`Downloaded ${latestZip} release.`, 1.0);
}
async function reconnectCallback() {
@@ -238,7 +253,9 @@ function addButtonHook(id, callback) {
button.onclick = async () => {
try {
let finalStatus = await callback(statusCallback);
- statusCallback(finalStatus);
+ if (finalStatus !== undefined) {
+ statusCallback(finalStatus);
+ }
} catch (error) {
statusCallback(`Error: ${error.message}`);
statusField.className = "error-text";