From 890abfdaff667411a4ffb7b35aa1c36554fdc7c0 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Wed, 27 Jan 2021 16:54:13 -0800 Subject: [PATCH] Update web installer for fastboot.js updates Some of the API and paths have changed, so this needs to be updated accordingly. The most prominent change is the removal of BlobStore and downloading support from fastboot.js, because a fastboot library should not be responsible for downloading files. --- process_static | 2 +- static/install/web.html | 4 +- static/js/web-install.js | 149 +++++++++++++++++++++++++++++++++------ validate_static | 4 +- 4 files changed, 132 insertions(+), 27 deletions(-) diff --git a/process_static b/process_static index fcd7efd9..874e72fe 100755 --- a/process_static +++ b/process_static @@ -7,7 +7,7 @@ export PATH="$PWD/node_modules/.bin:$PATH" rm -rf static_tmp cp -a static static_tmp -rm -rf static_tmp/js/fastboot/{!(dist),dist/!(fastboot.min.js|libs)} +rm -rf static_tmp/js/fastboot/{!(dist),dist/!(fastboot.min.mjs|libs)} for file in static_tmp/**/*.@(json|webmanifest); do json_reformat -m < "$file" | sponge "$file" diff --git a/static/install/web.html b/static/install/web.html index a7a03698..ca3963c6 100644 --- a/static/install/web.html +++ b/static/install/web.html @@ -26,8 +26,8 @@ - - + +
diff --git a/static/js/web-install.js b/static/js/web-install.js index aeef9e12..4bf0fb60 100644 --- a/static/js/web-install.js +++ b/static/js/web-install.js @@ -1,11 +1,98 @@ // @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT -import { FastbootDevice, FactoryImages } from "./fastboot/dist/fastboot.min.js?0"; +import * as fastboot from "./fastboot/dist/fastboot.min.mjs?0"; const RELEASES_URL = "https://releases.grapheneos.org"; -let device = new FastbootDevice(); -let lastReleaseZip = null; +const CACHE_DB_NAME = "BlobStore"; +const CACHE_DB_VERSION = 1; + +class BlobStore { + constructor() { + this.db = null; + } + + async _wrapReq(request, onUpgrade = null) { + return new Promise((resolve, reject) => { + request.onsuccess = () => { + resolve(request.result); + }; + request.oncomplete = () => { + resolve(request.result); + }; + request.onerror = (event) => { + reject(event); + }; + + if (onUpgrade !== null) { + request.onupgradeneeded = onUpgrade; + } + }); + } + + async init() { + if (this.db === null) { + this.db = await this._wrapReq( + indexedDB.open(CACHE_DB_NAME, CACHE_DB_VERSION), + (event) => { + let db = event.target.result; + db.createObjectStore("files", { keyPath: "name" }); + /* no index needed for such a small database */ + } + ); + } + } + + async saveFile(name, blob) { + this.db.transaction(["files"], "readwrite").objectStore("files").add({ + name: name, + blob: blob, + }); + } + + async loadFile(name) { + try { + let obj = await this._wrapReq( + this.db.transaction("files").objectStore("files").get(name) + ); + return obj.blob; + } catch (error) { + return null; + } + } + + async close() { + 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) { + 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(); + console.log("File downloaded, saving..."); + await this.saveFile(filename, blob); + console.log("File saved"); + } else { + console.log( + `Loaded ${filename} from blob store, skipping download` + ); + } + + return blob; + } +} + +let device = new fastboot.FastbootDevice(); +let blobStore = new BlobStore(); async function ensureConnected(setProgress) { console.log(device.device); @@ -23,36 +110,50 @@ async function unlockBootloader(setProgress) { return "Bootloader unlocked."; } -async function downloadRelease(setProgress) { - await ensureConnected(setProgress); - - setProgress("Getting device model..."); +async function getLatestRelease() { let product = await device.getVariable("product"); - setProgress("Finding latest release..."); + let metadataResp = await fetch(`${RELEASES_URL}/${product}-stable`); let metadata = await metadataResp.text(); let releaseId = metadata.split(" ")[0]; + return `${product}-factory-${releaseId}.zip`; +} + +async function downloadRelease(setProgress) { + await ensureConnected(setProgress); + + setProgress("Finding latest release..."); + let latestZip = await getLatestRelease(); + // Download and cache the zip as a blob - setProgress(`Downloading ${releaseId} release for ${product}...`); - lastReleaseZip = `${product}-factory-${releaseId}.zip`; - await FactoryImages.downloadZip(`${RELEASES_URL}/${lastReleaseZip}`); - return `Downloaded ${releaseId} release for ${product}.`; + setProgress(`Downloading ${latestZip}...`); + await blobStore.init(); + await blobStore.download(`${RELEASES_URL}/${latestZip}`); + return `Downloaded ${latestZip} release.`; } async function flashRelease(setProgress) { await ensureConnected(setProgress); + // Need to do this again because the user may not have clicked download if + // it was cached + setProgress("Finding latest release..."); + let latestZip = await getLatestRelease(); + await blobStore.init(); + let blob = await blobStore.loadFile(latestZip); + if (blob === null) { + throw new Error("You need to download a release first!"); + } + setProgress("Flashing release..."); - await FactoryImages.flashZip(device, lastReleaseZip, (action, partition) => { - let userPartition = partition == "avb_custom_key" ? "verified boot key" : partition; - if (action == "unpack") { - setProgress(`Unpacking image: ${userPartition}`); - } else { - setProgress(`Flashing image: ${userPartition}`); - } + await fastboot.FactoryImages.flashZip(device, blob, true, (action, item) => { + let userAction = fastboot.FactoryImages.USER_ACTION_MAP[action]; + let userItem = item === "avb_custom_key" ? "verified boot key" : item; + setProgress(`${userAction} ${userItem}...`); }); - return `Flashed ${lastReleaseZip} to device.`; + + return `Flashed ${latestZip} to device.`; } async function lockBootloader(setProgress) { @@ -83,9 +184,13 @@ function addButtonHook(id, callback) { }; } -FactoryImages.configureZip({ +// This doesn't really hurt, and because this page is exclusively for web install, +// we can tolerate extra logging in the console in case something goes wrong. +fastboot.setDebugMode(true); + +fastboot.FactoryImages.configureZip({ workerScripts: { - inflate: ["/js/fastboot/dist/libs/z-worker-pako.js", "pako_inflate.min.js"], + inflate: ["/js/fastboot/dist/vendor/z-worker-pako.js", "pako_inflate.min.js"], }, }); diff --git a/validate_static b/validate_static index c7267cd7..5ee272bb 100755 --- a/validate_static +++ b/validate_static @@ -7,13 +7,13 @@ export PATH="$PWD/node_modules/.bin:$PATH" rm -rf static_tmp cp -a static static_tmp -rm -rf static_tmp/js/fastboot/{!(dist),dist/!(fastboot.min.js|libs)} +rm -rf static_tmp/js/fastboot/{!(dist),dist/!(fastboot.min.mjs|libs)} for file in static_tmp/**/*.@(json|webmanifest); do json_verify < "$file" >/dev/null done xmllint --noout static_tmp/**/*.@(html|svg|xml) -eslint static_tmp/**/!(fastboot.min|z-worker-pako|pako_inflate.min).js +eslint static_tmp/**/!(fastboot.min.m|z-worker-pako.|pako_inflate.min.)js stylelint static_tmp/**/*.css validatornu --Werror --also-check-css --also-check-svg static_tmp/**/*.@(css|html|svg)