bugfix(web installer): disable buttons depending on the state of the installer
Currently clicking some web installer buttons, like 'Lock bootloader', can interrupt flashing and some, like 'Flash release', can cause multiple instances of the "flashing" process to initiate. This commit adds support for enabling/disabling web installer buttons and disables buttons that might interrupt flashing while flashing. Clicking the 'Download release' button while a download is in progress can cause multiple downloads to initiate, so disable the button during downloads and enable it afterwards.
This commit is contained in:
parent
4e61bcf551
commit
0f37181876
@ -7,7 +7,18 @@ const RELEASES_URL = "https://releases.grapheneos.org";
|
||||
const CACHE_DB_NAME = "BlobStore";
|
||||
const CACHE_DB_VERSION = 1;
|
||||
|
||||
let safeToLeave = true;
|
||||
const Buttons = {
|
||||
UNLOCK_BOOTLOADER: "unlock-bootloader",
|
||||
DOWNLOAD_RELEASE: "download-release",
|
||||
FLASH_RELEASE: "flash-release",
|
||||
LOCK_BOOTLOADER: "lock-bootloader",
|
||||
REMOVE_CUSTOM_KEY: "remove-custom-key"
|
||||
};
|
||||
|
||||
const InstallerState = {
|
||||
DOWNLOADING_RELEASE: 0x1,
|
||||
INSTALLING_RELEASE: 0x2
|
||||
};
|
||||
|
||||
// This wraps XHR because getting progress updates with fetch() is overly complicated.
|
||||
function fetchBlobWithProgress(url, onProgress) {
|
||||
@ -29,6 +40,12 @@ function fetchBlobWithProgress(url, onProgress) {
|
||||
});
|
||||
}
|
||||
|
||||
function setButtonState({ id, enabled }) {
|
||||
const button = document.getElementById(`${id}-button`);
|
||||
button.disabled = !enabled;
|
||||
return button;
|
||||
}
|
||||
|
||||
class BlobStore {
|
||||
constructor() {
|
||||
this.db = null;
|
||||
@ -106,8 +123,39 @@ class BlobStore {
|
||||
}
|
||||
}
|
||||
|
||||
class ButtonController {
|
||||
#map;
|
||||
|
||||
constructor() {
|
||||
this.#map = new Map();
|
||||
}
|
||||
|
||||
setEnabled(...ids) {
|
||||
ids.forEach((id) => {
|
||||
// Only enable button if it won't be disabled.
|
||||
if (!this.#map.has(id)) {
|
||||
this.#map.set(id, /* enabled = */ true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setDisabled(...ids) {
|
||||
ids.forEach((id) => this.#map.set(id, /* enabled = */ false));
|
||||
}
|
||||
|
||||
applyState() {
|
||||
this.#map.forEach((enabled, id) => {
|
||||
setButtonState({ id, enabled });
|
||||
});
|
||||
this.#map.clear();
|
||||
}
|
||||
}
|
||||
|
||||
let installerState = 0;
|
||||
|
||||
let device = new fastboot.FastbootDevice();
|
||||
let blobStore = new BlobStore();
|
||||
let buttonController = new ButtonController();
|
||||
|
||||
async function ensureConnected(setProgress) {
|
||||
if (!device.isConnected) {
|
||||
@ -170,7 +218,7 @@ async function downloadRelease(setProgress) {
|
||||
let [latestZip,] = await getLatestRelease();
|
||||
|
||||
// Download and cache the zip as a blob
|
||||
safeToLeave = false;
|
||||
setInstallerState({ state: InstallerState.DOWNLOADING_RELEASE, active: true });
|
||||
setProgress(`Downloading ${latestZip}...`);
|
||||
await blobStore.init();
|
||||
try {
|
||||
@ -178,7 +226,7 @@ async function downloadRelease(setProgress) {
|
||||
setProgress(`Downloading ${latestZip}...`, progress);
|
||||
});
|
||||
} finally {
|
||||
safeToLeave = true;
|
||||
setInstallerState({ state: InstallerState.DOWNLOADING_RELEASE, active: false });
|
||||
}
|
||||
setProgress(`Downloaded ${latestZip} release.`, 1.0);
|
||||
}
|
||||
@ -225,7 +273,7 @@ async function flashRelease(setProgress) {
|
||||
}
|
||||
|
||||
setProgress("Flashing release...");
|
||||
safeToLeave = false;
|
||||
setInstallerState({ state: InstallerState.INSTALLING_RELEASE, active: true });
|
||||
try {
|
||||
await device.flashFactoryZip(blob, true, reconnectCallback,
|
||||
(action, item, progress) => {
|
||||
@ -257,7 +305,7 @@ async function flashRelease(setProgress) {
|
||||
await device.runCommand("erase:dpm_b");
|
||||
}
|
||||
} finally {
|
||||
safeToLeave = true;
|
||||
setInstallerState({ state: InstallerState.INSTALLING_RELEASE, active: false });
|
||||
}
|
||||
|
||||
return `Flashed ${latestZip} to device.`;
|
||||
@ -316,8 +364,7 @@ function addButtonHook(id, callback) {
|
||||
}
|
||||
};
|
||||
|
||||
let button = document.getElementById(`${id}-button`);
|
||||
button.disabled = false;
|
||||
let button = setButtonState({ id, enabled: true });
|
||||
button.onclick = async () => {
|
||||
try {
|
||||
let finalStatus = await callback(statusCallback);
|
||||
@ -333,6 +380,45 @@ function addButtonHook(id, callback) {
|
||||
};
|
||||
}
|
||||
|
||||
function setInstallerState({ state, active }) {
|
||||
if (active) {
|
||||
installerState |= state;
|
||||
} else {
|
||||
installerState &= ~state;
|
||||
}
|
||||
invalidateInstallerState();
|
||||
}
|
||||
|
||||
function isInstallerStateActive(state) {
|
||||
return (installerState & state) === state;
|
||||
}
|
||||
|
||||
function invalidateInstallerState() {
|
||||
if (isInstallerStateActive(InstallerState.DOWNLOADING_RELEASE)) {
|
||||
buttonController.setDisabled(Buttons.DOWNLOAD_RELEASE);
|
||||
} else {
|
||||
buttonController.setEnabled(Buttons.DOWNLOAD_RELEASE);
|
||||
}
|
||||
|
||||
let disableWhileInstalling = [
|
||||
Buttons.DOWNLOAD_RELEASE,
|
||||
Buttons.FLASH_RELEASE,
|
||||
Buttons.LOCK_BOOTLOADER,
|
||||
Buttons.REMOVE_CUSTOM_KEY,
|
||||
];
|
||||
if (isInstallerStateActive(InstallerState.INSTALLING_RELEASE)) {
|
||||
buttonController.setDisabled(...disableWhileInstalling);
|
||||
} else {
|
||||
buttonController.setEnabled(...disableWhileInstalling);
|
||||
}
|
||||
|
||||
buttonController.applyState();
|
||||
}
|
||||
|
||||
function safeToLeave() {
|
||||
return installerState === 0;
|
||||
}
|
||||
|
||||
// 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.setDebugLevel(2);
|
||||
@ -344,18 +430,18 @@ fastboot.configureZip({
|
||||
});
|
||||
|
||||
if ("usb" in navigator) {
|
||||
addButtonHook("unlock-bootloader", unlockBootloader);
|
||||
addButtonHook("download-release", downloadRelease);
|
||||
addButtonHook("flash-release", flashRelease);
|
||||
addButtonHook("lock-bootloader", lockBootloader);
|
||||
addButtonHook("remove-custom-key", eraseNonStockKey);
|
||||
addButtonHook(Buttons.UNLOCK_BOOTLOADER, unlockBootloader);
|
||||
addButtonHook(Buttons.DOWNLOAD_RELEASE, downloadRelease);
|
||||
addButtonHook(Buttons.FLASH_RELEASE, flashRelease);
|
||||
addButtonHook(Buttons.LOCK_BOOTLOADER, lockBootloader);
|
||||
addButtonHook(Buttons.REMOVE_CUSTOM_KEY, eraseNonStockKey);
|
||||
} else {
|
||||
console.log("WebUSB unavailable");
|
||||
}
|
||||
|
||||
// This will create an alert box to stop the user from leaving the page during actions
|
||||
window.addEventListener("beforeunload", event => {
|
||||
if (!safeToLeave) {
|
||||
if (!safeToLeave()) {
|
||||
console.log("User tried to leave the page whilst unsafe to leave!");
|
||||
event.returnValue = "";
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user