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)