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_NAME = "BlobStore";
|
||||||
const CACHE_DB_VERSION = 1;
|
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.
|
// This wraps XHR because getting progress updates with fetch() is overly complicated.
|
||||||
function fetchBlobWithProgress(url, onProgress) {
|
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 {
|
class BlobStore {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.db = null;
|
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 device = new fastboot.FastbootDevice();
|
||||||
let blobStore = new BlobStore();
|
let blobStore = new BlobStore();
|
||||||
|
let buttonController = new ButtonController();
|
||||||
|
|
||||||
async function ensureConnected(setProgress) {
|
async function ensureConnected(setProgress) {
|
||||||
if (!device.isConnected) {
|
if (!device.isConnected) {
|
||||||
@ -170,7 +218,7 @@ async function downloadRelease(setProgress) {
|
|||||||
let [latestZip,] = await getLatestRelease();
|
let [latestZip,] = await getLatestRelease();
|
||||||
|
|
||||||
// Download and cache the zip as a blob
|
// Download and cache the zip as a blob
|
||||||
safeToLeave = false;
|
setInstallerState({ state: InstallerState.DOWNLOADING_RELEASE, active: true });
|
||||||
setProgress(`Downloading ${latestZip}...`);
|
setProgress(`Downloading ${latestZip}...`);
|
||||||
await blobStore.init();
|
await blobStore.init();
|
||||||
try {
|
try {
|
||||||
@ -178,7 +226,7 @@ async function downloadRelease(setProgress) {
|
|||||||
setProgress(`Downloading ${latestZip}...`, progress);
|
setProgress(`Downloading ${latestZip}...`, progress);
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
safeToLeave = true;
|
setInstallerState({ state: InstallerState.DOWNLOADING_RELEASE, active: false });
|
||||||
}
|
}
|
||||||
setProgress(`Downloaded ${latestZip} release.`, 1.0);
|
setProgress(`Downloaded ${latestZip} release.`, 1.0);
|
||||||
}
|
}
|
||||||
@ -225,7 +273,7 @@ async function flashRelease(setProgress) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setProgress("Flashing release...");
|
setProgress("Flashing release...");
|
||||||
safeToLeave = false;
|
setInstallerState({ state: InstallerState.INSTALLING_RELEASE, active: true });
|
||||||
try {
|
try {
|
||||||
await device.flashFactoryZip(blob, true, reconnectCallback,
|
await device.flashFactoryZip(blob, true, reconnectCallback,
|
||||||
(action, item, progress) => {
|
(action, item, progress) => {
|
||||||
@ -257,7 +305,7 @@ async function flashRelease(setProgress) {
|
|||||||
await device.runCommand("erase:dpm_b");
|
await device.runCommand("erase:dpm_b");
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
safeToLeave = true;
|
setInstallerState({ state: InstallerState.INSTALLING_RELEASE, active: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
return `Flashed ${latestZip} to device.`;
|
return `Flashed ${latestZip} to device.`;
|
||||||
@ -316,8 +364,7 @@ function addButtonHook(id, callback) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let button = document.getElementById(`${id}-button`);
|
let button = setButtonState({ id, enabled: true });
|
||||||
button.disabled = false;
|
|
||||||
button.onclick = async () => {
|
button.onclick = async () => {
|
||||||
try {
|
try {
|
||||||
let finalStatus = await callback(statusCallback);
|
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,
|
// 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.
|
// we can tolerate extra logging in the console in case something goes wrong.
|
||||||
fastboot.setDebugLevel(2);
|
fastboot.setDebugLevel(2);
|
||||||
@ -344,18 +430,18 @@ fastboot.configureZip({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if ("usb" in navigator) {
|
if ("usb" in navigator) {
|
||||||
addButtonHook("unlock-bootloader", unlockBootloader);
|
addButtonHook(Buttons.UNLOCK_BOOTLOADER, unlockBootloader);
|
||||||
addButtonHook("download-release", downloadRelease);
|
addButtonHook(Buttons.DOWNLOAD_RELEASE, downloadRelease);
|
||||||
addButtonHook("flash-release", flashRelease);
|
addButtonHook(Buttons.FLASH_RELEASE, flashRelease);
|
||||||
addButtonHook("lock-bootloader", lockBootloader);
|
addButtonHook(Buttons.LOCK_BOOTLOADER, lockBootloader);
|
||||||
addButtonHook("remove-custom-key", eraseNonStockKey);
|
addButtonHook(Buttons.REMOVE_CUSTOM_KEY, eraseNonStockKey);
|
||||||
} else {
|
} else {
|
||||||
console.log("WebUSB unavailable");
|
console.log("WebUSB unavailable");
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will create an alert box to stop the user from leaving the page during actions
|
// This will create an alert box to stop the user from leaving the page during actions
|
||||||
window.addEventListener("beforeunload", event => {
|
window.addEventListener("beforeunload", event => {
|
||||||
if (!safeToLeave) {
|
if (!safeToLeave()) {
|
||||||
console.log("User tried to leave the page whilst unsafe to leave!");
|
console.log("User tried to leave the page whilst unsafe to leave!");
|
||||||
event.returnValue = "";
|
event.returnValue = "";
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user