diff --git a/app/src/controller.manager.js b/app/src/controller.manager.js index bfe32a731f9603d3f4be2d111ef9e11af8ab6ec9..6c10606e323d3124399fbcc712a6f2c0edcd2a35 100644 --- a/app/src/controller.manager.js +++ b/app/src/controller.manager.js @@ -33,7 +33,6 @@ export class Controller { WDebug.log("Controller Manager Next", next); if (next) { - //K1ZFP check this if (next.mode) { //if next step require another mode [adb|fastboot|bootloader] if (this.deviceManager.isConnected() && !this.inInMode(next.mode)) { @@ -138,12 +137,10 @@ export class Controller { case Command.CMD_TYPE.reboot: try { await this.deviceManager.reboot(cmd.mode); + return true; } catch (e) { - console.error(e); - //K1ZFP TODO - return false; + throw new Error(`Reboot to ${cmd.mode} failed: ${e.message || e}`); } - return true; case Command.CMD_TYPE.connect: { const proposal = "Proposal: Check connection and that no other program is using the phone and retry."; @@ -162,14 +159,19 @@ export class Controller { } case Command.CMD_TYPE.erase: return this.deviceManager.erase(cmd.partition); - case Command.CMD_TYPE.flash: - return this.deviceManager.flash( + case Command.CMD_TYPE.flash: { + const FLASH_COOLDOWN_MS = 5000; // Pause after flash to let device stabilize + const result = await this.deviceManager.flash( cmd.file, cmd.partition, (done, total) => { this.view.onInstalling(cmd.file, done, total); }, ); + // Small delay between flash operations to prevent overwhelming the device + await new Promise((resolve) => setTimeout(resolve, FLASH_COOLDOWN_MS)); + return result; + } case Command.CMD_TYPE.unlock: { //check if unlocked to avoid unnecessary command let isUnlocked = false; @@ -191,13 +193,16 @@ export class Controller { ); if (!isUnlocked) { try { - this.deviceManager.unlock(cmd.command); // Do not await thus display unlocking screen + await this.deviceManager.unlock(cmd.command); } catch (e) { //on some device, check unlocked does not work but when we try the command, it throws an error with "already unlocked" if (e.bootloaderMessage?.includes("already")) { WDebug.log("device already unlocked"); } else if (e.bootloaderMessage?.includes("not allowed")) { - WDebug.log("device unlock is not allowed"); + WDebug.log("device unlock is not allowed"); + throw new Error(`Unlock not allowed: ${e.message || e}`); + } else { + throw e; } } } else { @@ -228,13 +233,15 @@ export class Controller { } if (!isLocked) { try { - this.deviceManager.lock(cmd.command); // Do not await thus display unlocking screen + await this.deviceManager.lock(cmd.command); + isLocked = true; } catch (e) { //on some device, check unlocked does not work but when we try the command, it throws an error with "already locked" if (e.bootloaderMessage?.includes("already")) { + WDebug.log("device already locked"); isLocked = true; } else { - console.error(e); //K1ZFP TODO + throw new Error(`Lock failed: ${e.message || e}`); } } } @@ -246,16 +253,14 @@ export class Controller { await this.deviceManager.sideload(cmd.file); return true; } catch (e) { - console.error(e); // K1ZFP TODO - return false; + throw new Error(`Sideload ${cmd.file} failed: ${e.message || e}`); } case Command.CMD_TYPE.format: try { - this.deviceManager.for(cmd.partition); + return this.deviceManager.format(cmd.partition); } catch (e) { - console.error(e); // K1ZFP TODO + throw new Error(`Format ${cmd.partition} failed: ${e.message || e}`); } - return true; case Command.CMD_TYPE.delay: await new Promise((resolve) => setTimeout(resolve, cmd.partition)); return true; @@ -269,8 +274,8 @@ export class Controller { async onDeviceConnected() { const productName = this.deviceManager.getProductName(); - const wasAlreadyConnected = this.deviceManager.wasAlreadyConnected(); - if (!wasAlreadyConnected) { + if (this.deviceManager.isFirstConnection()) { + this.deviceManager.markAsConnected(); this.view.updateData("product-name", productName); this.model = productName; WDebug.log("ControllerManager Model:", this.model); diff --git a/app/src/controller/device.manager.js b/app/src/controller/device.manager.js index 74aff737139366989a321faacb6fa9f4f8c7ecf9..cb0188f6fcec3c61bc63802a019eb6aa610a313e 100644 --- a/app/src/controller/device.manager.js +++ b/app/src/controller/device.manager.js @@ -34,12 +34,12 @@ export class DeviceManager { await this.downloader.init(); } - wasAlreadyConnected() { - if (this.wasConnected == false) { - this.wasConnected = true; - return false; - } - return true; + isFirstConnection() { + return !this.wasConnected; + } + + markAsConnected() { + this.wasConnected = true; } setResources(folder, steps) { diff --git a/app/src/controller/device/adb.class.js b/app/src/controller/device/adb.class.js index 04be40556d3f9e7f350b3b5a992ebb3b9ea59eb0..1a18a24e8bea6bc2b7c85d4377b05f0911da4c63 100644 --- a/app/src/controller/device/adb.class.js +++ b/app/src/controller/device/adb.class.js @@ -13,12 +13,12 @@ export class ADB extends Device { this.webusb = null; } - async isConnected() { + isConnected() { if (!this.device) { return false; } try { - return this.device.getDevice(); + return !!this.device.getDevice(); } catch { return false; } diff --git a/app/src/controller/device/bootloader.class.js b/app/src/controller/device/bootloader.class.js index 5c34fd353e107b5df4a779ba927a70097aeb9890..d8ecb9349ac768b74f8e635b2ca9cbd850b9a28c 100644 --- a/app/src/controller/device/bootloader.class.js +++ b/app/src/controller/device/bootloader.class.js @@ -44,10 +44,34 @@ export class Bootloader extends Device { } async connect() { - try { - await this.device.connect(); - } catch (e) { - throw new Error("Cannot connect Bootloader", `${e.message || e}`); + const MAX_CONNECT_ATTEMPTS = 3; + const CONNECT_RETRY_DELAY = 2000; // 2 seconds + + for (let attempt = 1; attempt <= MAX_CONNECT_ATTEMPTS; attempt++) { + try { + WDebug.log(`Connecting to bootloader (attempt ${attempt}/${MAX_CONNECT_ATTEMPTS})...`); + await this.device.connect(); + WDebug.log(`Successfully connected to bootloader on attempt ${attempt}`); + return; + } catch (e) { + const errorMsg = e.message || String(e); + WDebug.log(`Bootloader connection attempt ${attempt} failed: ${errorMsg}`); + + // If this is the last attempt, throw the error + if (attempt === MAX_CONNECT_ATTEMPTS) { + throw new Error( + `Cannot connect to bootloader after ${MAX_CONNECT_ATTEMPTS} attempts. ` + + `The device may not be in bootloader mode yet. ` + + `Please ensure the device is in bootloader/fastboot mode and try again. ` + + `Error: ${errorMsg}` + ); + } + + // Wait before retry, with increasing delay + const delay = CONNECT_RETRY_DELAY * attempt; + WDebug.log(`Waiting ${delay}ms before retry...`); + await new Promise((resolve) => setTimeout(resolve, delay)); + } } } @@ -72,8 +96,19 @@ export class Bootloader extends Device { ); } - async flashBlob(partition, blob, onProgress) { + async flashBlob(partition, blob, onProgress, attempt = 1) { + const MAX_ATTEMPTS = 3; + const RETRY_DELAY_MS = 3000; // Wait before retry to let device stabilize + + // Pre-flash check: ensure device is still connected + if (!this.device.isConnected) { + throw new Error(`Device disconnected before flashing ${partition}`); + } + try { + WDebug.log( + `Flashing ${partition} (${(blob.size / 1024 / 1024).toFixed(1)} MB)...`, + ); await this.device.flashBlob(partition, blob, (progress) => { onProgress(progress * blob.size, blob.size, partition); }); @@ -81,8 +116,27 @@ export class Bootloader extends Device { return true; } catch (e) { if (e instanceof TimeoutError) { - WDebug.log("Timeout on flashblob >" + partition); - return await this.flashBlob(partition, blob, onProgress); + WDebug.log( + `Timeout on flashblob > ${partition} (attempt ${attempt}/${MAX_ATTEMPTS})`, + ); + if (attempt < MAX_ATTEMPTS) { + // Wait before retry to allow device to recover + WDebug.log(`Waiting ${RETRY_DELAY_MS}ms before retry...`); + await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS)); + + // Check if device is still connected before retry + if (!this.device.isConnected) { + throw new Error( + `Device disconnected during flash of ${partition}. Please reconnect and try again.`, + ); + } + + return await this.flashBlob(partition, blob, onProgress, attempt + 1); + } + throw new Error( + `Bootloader timeout: flashing ${partition} failed after ${MAX_ATTEMPTS} attempts. ` + + `Try using a different USB port or cable.`, + ); } else { console.log("flashBlob error", e); throw new Error(`Bootloader error: ${e.message || e}`); @@ -100,7 +154,7 @@ export class Bootloader extends Device { const unlocked = await this.device.getVariable(variable); return !(!unlocked || unlocked === "no"); } catch (e) { - console.error(e); // K1ZFP TODO + console.error("isUnlocked check failed:", e); throw e; } } @@ -113,7 +167,7 @@ export class Bootloader extends Device { const unlocked = await this.device.getVariable(variable); return !unlocked || unlocked === "no"; } catch (e) { - console.error(e); //K1ZFP TODO + console.error("isLocked check failed:", e); throw e; } } @@ -124,7 +178,7 @@ export class Bootloader extends Device { if (command) { await this.device.runCommand(command); } else { - throw Error("no unlock command configured"); //K1ZFP TODO + throw new Error("No unlock command configured for this device"); } } @@ -133,7 +187,7 @@ export class Bootloader extends Device { await this.device.runCommand(command); return !(await this.isUnlocked()); } else { - throw Error("no lock command configured"); //K1ZFP TODO + throw new Error("No lock command configured for this device"); } } } diff --git a/app/src/controller/device/recovery.class.js b/app/src/controller/device/recovery.class.js index 6746c61573fb497bc4c401b2b62ec91a21ad7cbf..85c4bde42c095a6df35d22d05e4400b2d5e9599d 100644 --- a/app/src/controller/device/recovery.class.js +++ b/app/src/controller/device/recovery.class.js @@ -8,17 +8,16 @@ import { ADB } from "./adb.class.js"; export class Recovery extends Device { constructor(device) { super(device); - this.webusb = null; this.count = 0; - this.adbWebBackend = null; + this.adbDaemonWebUsbDevice = null; } - async isConnected() { + isConnected() { if (!this.device) { return false; } try { - return this.device.getDevice(); + return !!this.device.getDevice(); } catch { return false; } @@ -101,9 +100,9 @@ export class Recovery extends Device { for (const interface_ of configuration.interfaces) { for (const alternate of interface_.alternates) { if ( - alternate.interfaceSubclass === WebUsbDeviceFilter.subclassCode && alternate.interfaceClass === WebUsbDeviceFilter.classCode && - alternate.interfaceSubclass === WebUsbDeviceFilter.subclassCode + alternate.interfaceSubclass === WebUsbDeviceFilter.subclassCode && + alternate.interfaceProtocol === WebUsbDeviceFilter.protocolCode ) { if ( ((_a = this.adbDaemonWebUsbDevice.configuration) === null || @@ -211,11 +210,11 @@ export class Recovery extends Device { } getProductName() { - return this.webusb.name; + return this.adbDaemonWebUsbDevice?.productName; } getSerialNumber() { - return this.webusb.product; + return this.adbDaemonWebUsbDevice?.serialNumber; } async adbOpen(blob) { diff --git a/app/src/controller/downloader.manager.js b/app/src/controller/downloader.manager.js index 4a2937cf281aab51fa2c56d634b09db9ee48af7e..385272cc8d64ae8ac0411e0b01f15384951db07d 100644 --- a/app/src/controller/downloader.manager.js +++ b/app/src/controller/downloader.manager.js @@ -22,7 +22,6 @@ export class Downloader { this.db = await this.openDBStore(); await this.clearDBStore(); - this.quota = await navigator.storage.estimate(); } /* @@ -80,15 +79,15 @@ export class Downloader { if (file.unzip) { const zipReader = new ZipReader(new BlobReader(blob)); const filesEntries = await zipReader.getEntries(); - for (let i = 0; i < filesEntries.length; i++) { + for (let j = 0; j < filesEntries.length; j++) { const unzippedEntry = await this.getFileFromZip( - filesEntries[i], + filesEntries[j], (value, total) => { - onUnzipProgress(value, total, filesEntries[i].filename); + onUnzipProgress(value, total, filesEntries[j].filename); }, ); let filename = this.getMappedName( - filesEntries[i].filename, + filesEntries[j].filename, file.mapping, ); if (filesRequired.includes(filename)) {