Loading app/index.html +1 −2 Original line number Diff line number Diff line Loading @@ -10,8 +10,7 @@ <link rel="stylesheet" href="/css/styles.css" /> <link rel="stylesheet" href="/css/loader.css" /> <title>/e/OS Installer</title> <script type="module" src="/src/vue/view.manager.js"></script> <script type="module" src="/src/before-leave-app.js"></script> <script type="module" src="/src/vue/listener.manager.js"></script> </head> <body id="body"> <div id="process-ctn"> Loading app/src/before-leave-app.jsdeleted 100644 → 0 +0 −3 Original line number Diff line number Diff line window.addEventListener("beforeunload", function (event) { event.preventDefault(); }); app/src/controller/controller.manager.js +132 −37 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ import { DeviceManager } from "./device.manager.js"; import { COMMAND } from "./enums/command.enum.js"; import { Step } from "./utils/step.class.js"; import { DebugManager } from "./debug.manager.js"; /* * Class to manage process * Check and display the steps, interact with deviceManager Loading @@ -21,16 +22,33 @@ export class ControllerManager { this.downloadChoiceEnabled = false; } /** * Initializes controller dependencies and binds the view. * * @param {object} view UI view manager that receives state updates. * @returns {Promise<void>} Resolves when dependencies are initialized. */ async init(view) { this.deviceManager = new DeviceManager(); await this.deviceManager.init(); this.view = view; } /** * Enables or disables manual download choice. * * @param {boolean} enabled Whether user-driven download selection is enabled. * @returns {void} */ setDownloadChoiceEnabled(enabled) { this.downloadChoiceEnabled = enabled; } /** * Advances to the next installer step, handling mode changes and connections. * * @returns {Promise<void>} Resolves when the next step is started. */ async next() { let current = this.steps[this.currentIndex]; let next = this.steps[this.currentIndex + 1]; Loading Loading @@ -82,6 +100,13 @@ export class ControllerManager { } } /** * Executes commands for the current step. * * @param {string} stepName Step identifier expected to be active. * @param {HTMLElement} [loader] Loader element hidden after successful connect. * @returns {Promise<void>} Resolves when the step has been processed. */ async executeStep(stepName, loader) { const current = this.steps[this.currentIndex]; let this_command; Loading Loading @@ -126,19 +151,21 @@ export class ControllerManager { } /** * Checks whether the connected device already matches a mode. * * @param mode * @returns {boolean} * Check if device is connected to a mode * @param {string} mode Target device mode. * @returns {boolean} True when device is already in the requested mode. */ inInMode(mode) { return this.deviceManager.isInMode(mode); } /* * run a command this throw new error if something went wwrong. error should contain a proposal to solve the issue. /** * Runs a parsed command by dispatching to the appropriate handler. * * @param {import("./device/command.class.js").Command} cmd Command metadata. * @param {HTMLElement} [loader] Optional loading element used by connect step. * @returns {Promise<boolean>} True when the command succeeds. */ async runCommand(cmd, loader) { DebugManager.log("ControllerManager run command:", cmd); Loading Loading @@ -173,6 +200,11 @@ export class ControllerManager { } } /** * Runs the download step, triggering file download progress callbacks. * * @returns {Promise<boolean>} True when all files are downloaded successfully. */ async runDownloadCommand() { try { await this.deviceManager.downloadAll( Loading @@ -196,6 +228,12 @@ export class ControllerManager { } } /** * Reboots the device to the mode specified in the command. * * @param {import("./device/command.class.js").Command} cmd Reboot command. * @returns {Promise<boolean>} True when reboot command succeeds. */ async runRebootCommand(cmd) { try { await this.deviceManager.reboot(cmd.mode); Loading @@ -205,6 +243,13 @@ export class ControllerManager { } } /** * Connects to the device mode requested by the command. * * @param {import("./device/command.class.js").Command} cmd Connect command. * @param {HTMLElement} [loader] Optional loading element hidden after connect. * @returns {Promise<boolean>} True when connection succeeds. */ async runConnectCommand(cmd, loader) { const proposal = "Proposal: Check connection and that no other program is using the phone and retry."; Loading @@ -222,6 +267,12 @@ export class ControllerManager { } } /** * Flashes one file to one partition and applies a post-flash cooldown. * * @param {import("./device/command.class.js").Command} cmd Flash command. * @returns {Promise<boolean>} True when flashing succeeds. */ async runFlashCommand(cmd) { const FLASH_COOLDOWN_MS = this.resources?.flash_cooldown_ms ?? 2500; const result = await this.deviceManager.flash( Loading @@ -238,6 +289,12 @@ export class ControllerManager { return result; } /** * Unlocks the bootloader if needed and can skip ahead for goto steps. * * @param {import("./device/command.class.js").Command} cmd Unlock command. * @returns {Promise<boolean>} True when unlock flow completes. */ async runUnlockCommand(cmd) { let isUnlocked = false; let gotoStep = ""; Loading Loading @@ -287,6 +344,12 @@ export class ControllerManager { return true; } /** * Locks the bootloader if currently unlocked. * * @param {import("./device/command.class.js").Command} cmd Lock command. * @returns {Promise<boolean>} True when lock flow completes. */ async runLockCommand(cmd) { let isLocked = false; if (cmd.partition) { Loading @@ -308,6 +371,12 @@ export class ControllerManager { return true; } /** * Connects to recovery and performs sideload. * * @param {import("./device/command.class.js").Command} cmd Sideload command. * @returns {Promise<boolean>} True when sideload succeeds. */ async runSideloadCommand(cmd) { try { await this.deviceManager.connect("recovery"); Loading @@ -318,6 +387,12 @@ export class ControllerManager { } } /** * Formats a partition requested by the command. * * @param {import("./device/command.class.js").Command} cmd Format command. * @returns {Promise<boolean>} True when format command succeeds. */ async runFormatCommand(cmd) { try { return this.deviceManager.format(cmd.partition); Loading @@ -326,17 +401,34 @@ export class ControllerManager { } } /** * Waits for a command-defined delay. * * @param {import("./device/command.class.js").Command} cmd Delay command. * @returns {Promise<boolean>} True after delay completes. */ async runDelayCommand(cmd) { await new Promise((resolve) => setTimeout(resolve, cmd.partition)); return true; } /** * Forwards unknown commands to the active device implementation. * * @param {import("./device/command.class.js").Command} cmd Unknown command. * @returns {Promise<boolean>} True when forwarded command finishes. */ async runUnknownCommand(cmd) { DebugManager.log(`try unknown command ${cmd.command}`); await this.deviceManager.runCommand(cmd.command); return true; } /** * Handles device connection: updates UI, loads resources and validates version. * * @returns {Promise<void>} Resolves when device data is loaded. */ async onDeviceConnected() { const productName = this.deviceManager.getProductName(); if (this.deviceManager.isFirstConnection()) { Loading @@ -359,6 +451,13 @@ export class ControllerManager { } } } /** * Validates the connected Android version against required version. * * @param {string|number} versionRequired Minimum required Android version. * @returns {Promise<void>} Resolves when version is valid. */ async checkAndroidVersion(versionRequired) { const android = await this.deviceManager.getAndroidVersion(); DebugManager.log("current android version:", android); Loading @@ -369,6 +468,12 @@ export class ControllerManager { } } } /** * Fetches the device resource JSON based on the connected device model. * * @returns {Promise<object>} Resource object or throws device-model-not-supported. */ async getResources() { let resources = null; try { Loading Loading @@ -417,40 +522,13 @@ export class ControllerManager { throw new Error("Cannot find device resource", id); } } catch { const id = "model " + this.deviceManager.adb.banner.model + " " + "product " + this.deviceManager.adb.banner.product + " " + "name " + this.deviceManager.adb.getProductName() + " " + "device " + this.deviceManager.adb.banner.device; const id = `model ${this.deviceManager.adb.banner.model} product ${this.deviceManager.adb.banner.product} name ${this.deviceManager.adb.getProductName()} device ${this.deviceManager.adb.banner.device}`; throw new Error("Error on getting device resource", id); } } if (model.includes("A015")) { try { this_model = "tetris"; } catch { const id = "model " + this.deviceManager.adb.banner.model + " " + "product " + this.deviceManager.adb.banner.product + " " + "name " + this.deviceManager.adb.getProductName() + " " + "device " + this.deviceManager.adb.banner.device; throw new Error("Error on getting devcice resource", id); } } resources = await (await fetch(`resources/${this_model}.json`)).json(); Loading Loading @@ -485,6 +563,12 @@ export class ControllerManager { return resources; } /** * Applies device resources and extends installer steps. * * @param {object} resources Device resource payload from JSON file. * @returns {void} */ setResources(resources) { this.resources = resources; if (this.resources.steps) { Loading @@ -507,10 +591,21 @@ export class ControllerManager { }); } /** * Sets a local zip selected by the user. * * @param {File} file Local zip file. * @returns {void} */ setLocalZip(file) { this.deviceManager.setLocalZipFile(file); } /** * Clears the previously set local zip file. * * @returns {void} */ clearLocalZip() { this.deviceManager.clearLocalZipFile(); } Loading app/src/controller/debug.manager.js +14 −0 Original line number Diff line number Diff line /** * Centralized debug logger used by controller components. */ export class DebugManager { constructor() {} /** * Writes a debug message to the console. * * @param {...any} args Values to log. */ static log(...args) { console.log("[DEBUG]", ...args); } /** * Writes an error message to the console. * * @param {...any} args Values to log as an error. */ static error(...args) { console.error("[ERROR]", ...args); } Loading app/src/controller/device.manager.js +145 −9 Original line number Diff line number Diff line Loading @@ -7,9 +7,12 @@ import { DebugManager } from "./debug.manager.js"; import { MODE } from "./enums/mode.enum.js"; /** * wrap device functions * */ * Facade over ADB, bootloader, recovery and download operations. */ export class DeviceManager { /** * Creates a new DeviceManager and initializes all sub-managers. */ constructor() { this.model = ""; this.rom = undefined; Loading @@ -24,18 +27,39 @@ export class DeviceManager { this.wasConnected = false; } /** * Sets a local ROM zip file to be used instead of remote download. * * @param {File} file Local zip file selected by the user. * @returns {void} */ setLocalZipFile(file) { this.downloader.setLocalZip(file); } /** * Clears the previously set local zip file. * * @returns {void} */ clearLocalZipFile() { this.downloader.clearLocalZip(); } /** * Returns whether a local zip file has been set. * * @returns {boolean} True when a local zip file is available. */ hasLocalZipFile() { return this.downloader.hasLocalZip(); } /** * Initializes all device sub-managers. * * @returns {Promise<void>} Resolves when all managers are ready. */ async init() { await this.bootloader.init(); await this.adb.init(); Loading @@ -43,14 +67,32 @@ export class DeviceManager { await this.downloader.init(); } /** * Returns whether a device has not yet been connected in this session. * * @returns {boolean} True when no connection has occurred yet. */ isFirstConnection() { return !this.wasConnected; } /** * Marks the device as connected for the current session. * * @returns {void} */ markAsConnected() { this.wasConnected = true; } /** * Stores resource metadata and optional behavior flags. * * @param {Array<object>} folder Resource file descriptors. * @param {Array<object>} steps Installer steps used to collect required files. * @param {object} [options] Optional controller flags. * @returns {void} */ setResources(folder, steps, options) { this.folder = folder; this.files = steps Loading @@ -65,12 +107,30 @@ export class DeviceManager { } } /** * Reads bootloader lock state variable. * * @param {string} variable Fastboot variable name to query. * @returns {Promise<boolean>} True when variable indicates unlocked state. */ async getUnlocked(variable) { return this.bootloader.isUnlocked(variable); } /** * Returns the Android version of the connected device. * * @returns {Promise<string>} Android version string. */ async getAndroidVersion() { return await this.device.getAndroidVersion(); } /** * Checks whether the device is still detected on the USB bus. * * @returns {Promise<boolean>} True when device appears in USB device list. */ async isDetected() { const serial = this.serialNumber; if (serial) { Loading @@ -81,12 +141,10 @@ export class DeviceManager { } /** * @param mode * @returns {any} * * We set the device to the mode manager we went to connect to * And we connect the device * Sets the active device implementation for a given mode. * * @param {string} mode Device mode (`adb`, `bootloader`, or `recovery`). * @returns {Promise<void>} Resolves after active mode is set. */ async setMode(mode) { switch (mode) { Loading @@ -101,6 +159,13 @@ export class DeviceManager { break; } } /** * Connects to the requested mode. * * @param {string} mode Target mode to connect. * @returns {Promise<any>} Result returned by the selected device connector. */ async connect(mode) { await this.setMode(mode); try { Loading @@ -111,9 +176,10 @@ export class DeviceManager { } /** * @param mode * @returns {boolean} * Checks whether the active device is currently in a given mode. * * @param {string} mode Target mode. * @returns {boolean} True when active device matches mode. */ isInMode(mode) { switch (mode) { Loading @@ -127,17 +193,34 @@ export class DeviceManager { return false; } /** * Erases a fastboot partition. * * @param {string} partition Partition name. * @returns {Promise<boolean>} True when erase command succeeds. */ async erase(partition) { await this.bootloader.runCommand(`erase:${partition}`); return true; } /** * Formats a partition via fastboot (not universally supported). * * @returns {boolean} Always true; actual format is not implemented. */ format() { return true; // return this.bootloader.runCommand(`format ${argument}`); // the fastboot format md_udc is not supported evne by the official fastboot program } /** * Sends a bootloader unlock command. * * @param {string} command Fastboot unlock command. * @returns {Promise<boolean>} True when unlock command completes. */ async unlock(command) { // Unlock requires physical confirmation on the device (volume keys + // power button), so use a generous 5-minute timeout instead of the Loading @@ -146,12 +229,26 @@ export class DeviceManager { return true; } /** * Sends a bootloader lock command. * * @param {string} command Fastboot lock command. * @returns {Promise<boolean>} True when lock command completes. */ async lock(command) { // Lock may also require physical confirmation on the device. await this.bootloader.runCommand(command, 300_000); return true; } /** * Flashes a downloaded file to a partition. * * @param {string} file Filename in downloader cache. * @param {string} partition Target partition. * @param {(done:number,total:number)=>void} onProgress Flash progress callback. * @returns {Promise<boolean>} True when flashing succeeds. */ async flash(file, partition, onProgress) { let blob = await this.downloader.getFile(file); if (!blob) { Loading @@ -166,14 +263,30 @@ export class DeviceManager { return flashed; } /** * Returns the product name of the connected device. * * @returns {string|undefined} Product name string or undefined. */ getProductName() { return this.device.getProductName(); } /** * Returns the serial number of the connected device. * * @returns {string|undefined} Serial number string or undefined. */ getSerialNumber() { return this.device.getSerialNumber(); } /** * Reboots the connected device to a target mode. * * @param {string} mode Target reboot mode. * @returns {Promise<any>} Reboot result from the active device. */ async reboot(mode) { const res = await this.device.reboot(mode); if (res) { Loading @@ -182,6 +295,12 @@ export class DeviceManager { return res; } /** * Sideloads a zip file through recovery. * * @param {string} file Filename in downloader cache. * @returns {Promise<any>} Sideload result from the active device. */ async sideload(file) { let blob = await this.downloader.getFile(file); if (!blob) { Loading @@ -191,6 +310,12 @@ export class DeviceManager { return await this.device.sideload(blob); } /** * Runs a raw command against the active device implementation. * * @param {string} command Device command. * @returns {Promise<any>} Command result from active device. */ async runCommand(command) { try { return this.device.runCommand(command); Loading @@ -204,6 +329,9 @@ export class DeviceManager { * Some USB host controllers (e.g., AMD Ryzen) are slow to re-enumerate * devices after a mode switch, especially Mediatek bootloader devices. * Resolves when a device appears or after timeout (does not reject). * * @param {number} [timeoutMs=30000] Max wait before resolving. * @returns {Promise<void>} Resolves when device appears or timeout expires. */ waitForDeviceOnBus(timeoutMs = 30000) { const startTime = Date.now(); Loading Loading @@ -257,6 +385,14 @@ export class DeviceManager { }); } /** * Downloads all required files or ingests them from a local zip. * * @param {(loaded:number,total:number,name:string)=>void} onProgress Download callback. * @param {(loaded:number,total:number,name:string)=>void} onUnzip Unzip callback. * @param {(loaded:number,total:number,name:string)=>void} onVerify Verification callback. * @returns {Promise<void>} Resolves when all required files are available. */ async downloadAll(onProgress, onUnzip, onVerify) { try { if (this.downloader.hasLocalZip()) { Loading Loading
app/index.html +1 −2 Original line number Diff line number Diff line Loading @@ -10,8 +10,7 @@ <link rel="stylesheet" href="/css/styles.css" /> <link rel="stylesheet" href="/css/loader.css" /> <title>/e/OS Installer</title> <script type="module" src="/src/vue/view.manager.js"></script> <script type="module" src="/src/before-leave-app.js"></script> <script type="module" src="/src/vue/listener.manager.js"></script> </head> <body id="body"> <div id="process-ctn"> Loading
app/src/before-leave-app.jsdeleted 100644 → 0 +0 −3 Original line number Diff line number Diff line window.addEventListener("beforeunload", function (event) { event.preventDefault(); });
app/src/controller/controller.manager.js +132 −37 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ import { DeviceManager } from "./device.manager.js"; import { COMMAND } from "./enums/command.enum.js"; import { Step } from "./utils/step.class.js"; import { DebugManager } from "./debug.manager.js"; /* * Class to manage process * Check and display the steps, interact with deviceManager Loading @@ -21,16 +22,33 @@ export class ControllerManager { this.downloadChoiceEnabled = false; } /** * Initializes controller dependencies and binds the view. * * @param {object} view UI view manager that receives state updates. * @returns {Promise<void>} Resolves when dependencies are initialized. */ async init(view) { this.deviceManager = new DeviceManager(); await this.deviceManager.init(); this.view = view; } /** * Enables or disables manual download choice. * * @param {boolean} enabled Whether user-driven download selection is enabled. * @returns {void} */ setDownloadChoiceEnabled(enabled) { this.downloadChoiceEnabled = enabled; } /** * Advances to the next installer step, handling mode changes and connections. * * @returns {Promise<void>} Resolves when the next step is started. */ async next() { let current = this.steps[this.currentIndex]; let next = this.steps[this.currentIndex + 1]; Loading Loading @@ -82,6 +100,13 @@ export class ControllerManager { } } /** * Executes commands for the current step. * * @param {string} stepName Step identifier expected to be active. * @param {HTMLElement} [loader] Loader element hidden after successful connect. * @returns {Promise<void>} Resolves when the step has been processed. */ async executeStep(stepName, loader) { const current = this.steps[this.currentIndex]; let this_command; Loading Loading @@ -126,19 +151,21 @@ export class ControllerManager { } /** * Checks whether the connected device already matches a mode. * * @param mode * @returns {boolean} * Check if device is connected to a mode * @param {string} mode Target device mode. * @returns {boolean} True when device is already in the requested mode. */ inInMode(mode) { return this.deviceManager.isInMode(mode); } /* * run a command this throw new error if something went wwrong. error should contain a proposal to solve the issue. /** * Runs a parsed command by dispatching to the appropriate handler. * * @param {import("./device/command.class.js").Command} cmd Command metadata. * @param {HTMLElement} [loader] Optional loading element used by connect step. * @returns {Promise<boolean>} True when the command succeeds. */ async runCommand(cmd, loader) { DebugManager.log("ControllerManager run command:", cmd); Loading Loading @@ -173,6 +200,11 @@ export class ControllerManager { } } /** * Runs the download step, triggering file download progress callbacks. * * @returns {Promise<boolean>} True when all files are downloaded successfully. */ async runDownloadCommand() { try { await this.deviceManager.downloadAll( Loading @@ -196,6 +228,12 @@ export class ControllerManager { } } /** * Reboots the device to the mode specified in the command. * * @param {import("./device/command.class.js").Command} cmd Reboot command. * @returns {Promise<boolean>} True when reboot command succeeds. */ async runRebootCommand(cmd) { try { await this.deviceManager.reboot(cmd.mode); Loading @@ -205,6 +243,13 @@ export class ControllerManager { } } /** * Connects to the device mode requested by the command. * * @param {import("./device/command.class.js").Command} cmd Connect command. * @param {HTMLElement} [loader] Optional loading element hidden after connect. * @returns {Promise<boolean>} True when connection succeeds. */ async runConnectCommand(cmd, loader) { const proposal = "Proposal: Check connection and that no other program is using the phone and retry."; Loading @@ -222,6 +267,12 @@ export class ControllerManager { } } /** * Flashes one file to one partition and applies a post-flash cooldown. * * @param {import("./device/command.class.js").Command} cmd Flash command. * @returns {Promise<boolean>} True when flashing succeeds. */ async runFlashCommand(cmd) { const FLASH_COOLDOWN_MS = this.resources?.flash_cooldown_ms ?? 2500; const result = await this.deviceManager.flash( Loading @@ -238,6 +289,12 @@ export class ControllerManager { return result; } /** * Unlocks the bootloader if needed and can skip ahead for goto steps. * * @param {import("./device/command.class.js").Command} cmd Unlock command. * @returns {Promise<boolean>} True when unlock flow completes. */ async runUnlockCommand(cmd) { let isUnlocked = false; let gotoStep = ""; Loading Loading @@ -287,6 +344,12 @@ export class ControllerManager { return true; } /** * Locks the bootloader if currently unlocked. * * @param {import("./device/command.class.js").Command} cmd Lock command. * @returns {Promise<boolean>} True when lock flow completes. */ async runLockCommand(cmd) { let isLocked = false; if (cmd.partition) { Loading @@ -308,6 +371,12 @@ export class ControllerManager { return true; } /** * Connects to recovery and performs sideload. * * @param {import("./device/command.class.js").Command} cmd Sideload command. * @returns {Promise<boolean>} True when sideload succeeds. */ async runSideloadCommand(cmd) { try { await this.deviceManager.connect("recovery"); Loading @@ -318,6 +387,12 @@ export class ControllerManager { } } /** * Formats a partition requested by the command. * * @param {import("./device/command.class.js").Command} cmd Format command. * @returns {Promise<boolean>} True when format command succeeds. */ async runFormatCommand(cmd) { try { return this.deviceManager.format(cmd.partition); Loading @@ -326,17 +401,34 @@ export class ControllerManager { } } /** * Waits for a command-defined delay. * * @param {import("./device/command.class.js").Command} cmd Delay command. * @returns {Promise<boolean>} True after delay completes. */ async runDelayCommand(cmd) { await new Promise((resolve) => setTimeout(resolve, cmd.partition)); return true; } /** * Forwards unknown commands to the active device implementation. * * @param {import("./device/command.class.js").Command} cmd Unknown command. * @returns {Promise<boolean>} True when forwarded command finishes. */ async runUnknownCommand(cmd) { DebugManager.log(`try unknown command ${cmd.command}`); await this.deviceManager.runCommand(cmd.command); return true; } /** * Handles device connection: updates UI, loads resources and validates version. * * @returns {Promise<void>} Resolves when device data is loaded. */ async onDeviceConnected() { const productName = this.deviceManager.getProductName(); if (this.deviceManager.isFirstConnection()) { Loading @@ -359,6 +451,13 @@ export class ControllerManager { } } } /** * Validates the connected Android version against required version. * * @param {string|number} versionRequired Minimum required Android version. * @returns {Promise<void>} Resolves when version is valid. */ async checkAndroidVersion(versionRequired) { const android = await this.deviceManager.getAndroidVersion(); DebugManager.log("current android version:", android); Loading @@ -369,6 +468,12 @@ export class ControllerManager { } } } /** * Fetches the device resource JSON based on the connected device model. * * @returns {Promise<object>} Resource object or throws device-model-not-supported. */ async getResources() { let resources = null; try { Loading Loading @@ -417,40 +522,13 @@ export class ControllerManager { throw new Error("Cannot find device resource", id); } } catch { const id = "model " + this.deviceManager.adb.banner.model + " " + "product " + this.deviceManager.adb.banner.product + " " + "name " + this.deviceManager.adb.getProductName() + " " + "device " + this.deviceManager.adb.banner.device; const id = `model ${this.deviceManager.adb.banner.model} product ${this.deviceManager.adb.banner.product} name ${this.deviceManager.adb.getProductName()} device ${this.deviceManager.adb.banner.device}`; throw new Error("Error on getting device resource", id); } } if (model.includes("A015")) { try { this_model = "tetris"; } catch { const id = "model " + this.deviceManager.adb.banner.model + " " + "product " + this.deviceManager.adb.banner.product + " " + "name " + this.deviceManager.adb.getProductName() + " " + "device " + this.deviceManager.adb.banner.device; throw new Error("Error on getting devcice resource", id); } } resources = await (await fetch(`resources/${this_model}.json`)).json(); Loading Loading @@ -485,6 +563,12 @@ export class ControllerManager { return resources; } /** * Applies device resources and extends installer steps. * * @param {object} resources Device resource payload from JSON file. * @returns {void} */ setResources(resources) { this.resources = resources; if (this.resources.steps) { Loading @@ -507,10 +591,21 @@ export class ControllerManager { }); } /** * Sets a local zip selected by the user. * * @param {File} file Local zip file. * @returns {void} */ setLocalZip(file) { this.deviceManager.setLocalZipFile(file); } /** * Clears the previously set local zip file. * * @returns {void} */ clearLocalZip() { this.deviceManager.clearLocalZipFile(); } Loading
app/src/controller/debug.manager.js +14 −0 Original line number Diff line number Diff line /** * Centralized debug logger used by controller components. */ export class DebugManager { constructor() {} /** * Writes a debug message to the console. * * @param {...any} args Values to log. */ static log(...args) { console.log("[DEBUG]", ...args); } /** * Writes an error message to the console. * * @param {...any} args Values to log as an error. */ static error(...args) { console.error("[ERROR]", ...args); } Loading
app/src/controller/device.manager.js +145 −9 Original line number Diff line number Diff line Loading @@ -7,9 +7,12 @@ import { DebugManager } from "./debug.manager.js"; import { MODE } from "./enums/mode.enum.js"; /** * wrap device functions * */ * Facade over ADB, bootloader, recovery and download operations. */ export class DeviceManager { /** * Creates a new DeviceManager and initializes all sub-managers. */ constructor() { this.model = ""; this.rom = undefined; Loading @@ -24,18 +27,39 @@ export class DeviceManager { this.wasConnected = false; } /** * Sets a local ROM zip file to be used instead of remote download. * * @param {File} file Local zip file selected by the user. * @returns {void} */ setLocalZipFile(file) { this.downloader.setLocalZip(file); } /** * Clears the previously set local zip file. * * @returns {void} */ clearLocalZipFile() { this.downloader.clearLocalZip(); } /** * Returns whether a local zip file has been set. * * @returns {boolean} True when a local zip file is available. */ hasLocalZipFile() { return this.downloader.hasLocalZip(); } /** * Initializes all device sub-managers. * * @returns {Promise<void>} Resolves when all managers are ready. */ async init() { await this.bootloader.init(); await this.adb.init(); Loading @@ -43,14 +67,32 @@ export class DeviceManager { await this.downloader.init(); } /** * Returns whether a device has not yet been connected in this session. * * @returns {boolean} True when no connection has occurred yet. */ isFirstConnection() { return !this.wasConnected; } /** * Marks the device as connected for the current session. * * @returns {void} */ markAsConnected() { this.wasConnected = true; } /** * Stores resource metadata and optional behavior flags. * * @param {Array<object>} folder Resource file descriptors. * @param {Array<object>} steps Installer steps used to collect required files. * @param {object} [options] Optional controller flags. * @returns {void} */ setResources(folder, steps, options) { this.folder = folder; this.files = steps Loading @@ -65,12 +107,30 @@ export class DeviceManager { } } /** * Reads bootloader lock state variable. * * @param {string} variable Fastboot variable name to query. * @returns {Promise<boolean>} True when variable indicates unlocked state. */ async getUnlocked(variable) { return this.bootloader.isUnlocked(variable); } /** * Returns the Android version of the connected device. * * @returns {Promise<string>} Android version string. */ async getAndroidVersion() { return await this.device.getAndroidVersion(); } /** * Checks whether the device is still detected on the USB bus. * * @returns {Promise<boolean>} True when device appears in USB device list. */ async isDetected() { const serial = this.serialNumber; if (serial) { Loading @@ -81,12 +141,10 @@ export class DeviceManager { } /** * @param mode * @returns {any} * * We set the device to the mode manager we went to connect to * And we connect the device * Sets the active device implementation for a given mode. * * @param {string} mode Device mode (`adb`, `bootloader`, or `recovery`). * @returns {Promise<void>} Resolves after active mode is set. */ async setMode(mode) { switch (mode) { Loading @@ -101,6 +159,13 @@ export class DeviceManager { break; } } /** * Connects to the requested mode. * * @param {string} mode Target mode to connect. * @returns {Promise<any>} Result returned by the selected device connector. */ async connect(mode) { await this.setMode(mode); try { Loading @@ -111,9 +176,10 @@ export class DeviceManager { } /** * @param mode * @returns {boolean} * Checks whether the active device is currently in a given mode. * * @param {string} mode Target mode. * @returns {boolean} True when active device matches mode. */ isInMode(mode) { switch (mode) { Loading @@ -127,17 +193,34 @@ export class DeviceManager { return false; } /** * Erases a fastboot partition. * * @param {string} partition Partition name. * @returns {Promise<boolean>} True when erase command succeeds. */ async erase(partition) { await this.bootloader.runCommand(`erase:${partition}`); return true; } /** * Formats a partition via fastboot (not universally supported). * * @returns {boolean} Always true; actual format is not implemented. */ format() { return true; // return this.bootloader.runCommand(`format ${argument}`); // the fastboot format md_udc is not supported evne by the official fastboot program } /** * Sends a bootloader unlock command. * * @param {string} command Fastboot unlock command. * @returns {Promise<boolean>} True when unlock command completes. */ async unlock(command) { // Unlock requires physical confirmation on the device (volume keys + // power button), so use a generous 5-minute timeout instead of the Loading @@ -146,12 +229,26 @@ export class DeviceManager { return true; } /** * Sends a bootloader lock command. * * @param {string} command Fastboot lock command. * @returns {Promise<boolean>} True when lock command completes. */ async lock(command) { // Lock may also require physical confirmation on the device. await this.bootloader.runCommand(command, 300_000); return true; } /** * Flashes a downloaded file to a partition. * * @param {string} file Filename in downloader cache. * @param {string} partition Target partition. * @param {(done:number,total:number)=>void} onProgress Flash progress callback. * @returns {Promise<boolean>} True when flashing succeeds. */ async flash(file, partition, onProgress) { let blob = await this.downloader.getFile(file); if (!blob) { Loading @@ -166,14 +263,30 @@ export class DeviceManager { return flashed; } /** * Returns the product name of the connected device. * * @returns {string|undefined} Product name string or undefined. */ getProductName() { return this.device.getProductName(); } /** * Returns the serial number of the connected device. * * @returns {string|undefined} Serial number string or undefined. */ getSerialNumber() { return this.device.getSerialNumber(); } /** * Reboots the connected device to a target mode. * * @param {string} mode Target reboot mode. * @returns {Promise<any>} Reboot result from the active device. */ async reboot(mode) { const res = await this.device.reboot(mode); if (res) { Loading @@ -182,6 +295,12 @@ export class DeviceManager { return res; } /** * Sideloads a zip file through recovery. * * @param {string} file Filename in downloader cache. * @returns {Promise<any>} Sideload result from the active device. */ async sideload(file) { let blob = await this.downloader.getFile(file); if (!blob) { Loading @@ -191,6 +310,12 @@ export class DeviceManager { return await this.device.sideload(blob); } /** * Runs a raw command against the active device implementation. * * @param {string} command Device command. * @returns {Promise<any>} Command result from active device. */ async runCommand(command) { try { return this.device.runCommand(command); Loading @@ -204,6 +329,9 @@ export class DeviceManager { * Some USB host controllers (e.g., AMD Ryzen) are slow to re-enumerate * devices after a mode switch, especially Mediatek bootloader devices. * Resolves when a device appears or after timeout (does not reject). * * @param {number} [timeoutMs=30000] Max wait before resolving. * @returns {Promise<void>} Resolves when device appears or timeout expires. */ waitForDeviceOnBus(timeoutMs = 30000) { const startTime = Date.now(); Loading Loading @@ -257,6 +385,14 @@ export class DeviceManager { }); } /** * Downloads all required files or ingests them from a local zip. * * @param {(loaded:number,total:number,name:string)=>void} onProgress Download callback. * @param {(loaded:number,total:number,name:string)=>void} onUnzip Unzip callback. * @param {(loaded:number,total:number,name:string)=>void} onVerify Verification callback. * @returns {Promise<void>} Resolves when all required files are available. */ async downloadAll(onProgress, onUnzip, onVerify) { try { if (this.downloader.hasLocalZip()) { Loading