Autarch/web/static/js/hardware-direct.js
DigiJ ffe47c51b5 Initial public release — AUTARCH v1.0.0
Full security platform with web dashboard, 16 Flask blueprints, 26 modules,
autonomous AI agent, WebUSB hardware support, and Archon Android companion app.

Includes Hash Toolkit, debug console, anti-stalkerware shield, Metasploit/RouterSploit
integration, WireGuard VPN, OSINT reconnaissance, and multi-backend LLM support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:57:32 -08:00

818 lines
30 KiB
JavaScript

/**
* AUTARCH Hardware Direct Mode
* Browser-based device access via WebUSB (ADB/Fastboot) and Web Serial (ESP32)
*
* Dependencies (loaded before this file):
* - web/static/js/lib/adb-bundle.js → window.YumeAdb
* - web/static/js/lib/fastboot-bundle.js → window.Fastboot
* - web/static/js/lib/esptool-bundle.js → window.EspTool
*/
var HWDirect = (function() {
'use strict';
// ── Browser Capability Detection ─────────────────────────────
var supported = {
webusb: typeof navigator !== 'undefined' && !!navigator.usb,
webserial: typeof navigator !== 'undefined' && !!navigator.serial,
};
// ── State ────────────────────────────────────────────────────
var adbDevice = null; // Adb instance (ya-webadb)
var adbTransport = null; // AdbDaemonTransport
var adbUsbDevice = null; // AdbDaemonWebUsbDevice (for disconnect)
var adbDeviceInfo = {}; // Cached device props
var fbDevice = null; // FastbootDevice instance
var espLoader = null; // ESPLoader instance
var espTransport = null; // Web Serial Transport
var espPort = null; // SerialPort reference
var espMonitorReader = null;
var espMonitorRunning = false;
// ── ADB Credential Store (IndexedDB) ─────────────────────────
// ADB requires RSA key authentication. Keys are stored in IndexedDB
// so the user only needs to authorize once per browser.
var DB_NAME = 'autarch_adb_keys';
var STORE_NAME = 'keys';
function _openKeyDB() {
return new Promise(function(resolve, reject) {
var req = indexedDB.open(DB_NAME, 1);
req.onupgradeneeded = function(e) {
e.target.result.createObjectStore(STORE_NAME, { keyPath: 'id' });
};
req.onsuccess = function(e) { resolve(e.target.result); };
req.onerror = function(e) { reject(e.target.error); };
});
}
function _saveKey(id, privateKey) {
return _openKeyDB().then(function(db) {
return new Promise(function(resolve, reject) {
var tx = db.transaction(STORE_NAME, 'readwrite');
tx.objectStore(STORE_NAME).put({ id: id, key: privateKey });
tx.oncomplete = function() { resolve(); };
tx.onerror = function(e) { reject(e.target.error); };
});
});
}
function _loadKeys() {
return _openKeyDB().then(function(db) {
return new Promise(function(resolve, reject) {
var tx = db.transaction(STORE_NAME, 'readonly');
var req = tx.objectStore(STORE_NAME).getAll();
req.onsuccess = function() { resolve(req.result || []); };
req.onerror = function(e) { reject(e.target.error); };
});
});
}
// Web Crypto RSA key generation for ADB authentication
var credentialStore = {
generateKey: async function() {
var keyPair = await crypto.subtle.generateKey(
{ name: 'RSASSA-PKCS1-v1_5', modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-1' },
true, ['sign']
);
var exported = await crypto.subtle.exportKey('pkcs8', keyPair.privateKey);
var keyBuffer = new Uint8Array(exported);
var keyId = 'adb_key_' + Date.now();
await _saveKey(keyId, Array.from(keyBuffer));
return { buffer: keyBuffer, name: 'autarch@browser' };
},
iterateKeys: async function*() {
var keys = await _loadKeys();
for (var i = 0; i < keys.length; i++) {
yield { buffer: new Uint8Array(keys[i].key), name: 'autarch@browser' };
}
}
};
// ══════════════════════════════════════════════════════════════
// ADB (WebUSB)
// ══════════════════════════════════════════════════════════════
/**
* Get list of already-paired ADB devices (no permission prompt).
* Returns array of {serial, name, raw}.
*/
async function adbGetDevices() {
if (!supported.webusb) return [];
var manager = YumeAdb.AdbDaemonWebUsbDeviceManager.BROWSER;
if (!manager) return [];
try {
var devices = await manager.getDevices();
return devices.map(function(d) {
return { serial: d.serial || '', name: d.name || '', raw: d };
});
} catch (e) {
console.error('adbGetDevices:', e);
return [];
}
}
/**
* Request user to select an ADB device (shows browser USB picker).
* Returns {serial, name, raw} or null.
*/
async function adbRequestDevice() {
if (!supported.webusb) throw new Error('WebUSB not supported in this browser');
var manager = YumeAdb.AdbDaemonWebUsbDeviceManager.BROWSER;
if (!manager) throw new Error('WebUSB manager not available');
var device = await manager.requestDevice();
if (!device) return null;
return { serial: device.serial || '', name: device.name || '', raw: device };
}
/**
* Connect to an ADB device (from adbGetDevices or adbRequestDevice).
* Performs USB connection + ADB authentication.
* The device screen will show "Allow USB debugging?" on first connect.
*/
async function adbConnect(deviceObj) {
if (adbDevice) {
await adbDisconnect();
}
var usbDev = deviceObj.raw;
var connection;
try {
connection = await usbDev.connect();
} catch (e) {
var errMsg = e.message || String(e);
var errLow = errMsg.toLowerCase();
// Windows: USB driver conflict — ADB server or another app owns the device
if (errLow.includes('already in used') || errLow.includes('already in use') ||
errLow.includes('in use by another') || errLow.includes('access denied')) {
// Try forcing a release and retrying once
try { await usbDev.close(); } catch (_) {}
try {
connection = await usbDev.connect();
} catch (e2) {
throw new Error(
'USB device is claimed by another program (usually the ADB server).\n\n' +
'Fix: open a terminal and run:\n' +
' adb kill-server\n\n' +
'Then close Android Studio, scrcpy, or any other ADB tool, and click Connect again.'
);
}
} else if (errLow.includes('permission') || errLow.includes('not allowed') ||
errLow.includes('failed to open')) {
throw new Error(
'USB permission denied. On Linux, ensure udev rules are installed:\n' +
' sudo bash -c "echo \'SUBSYSTEM==\"usb\", ATTR{idVendor}==\"18d1\", MODE=\"0666\"\' > /etc/udev/rules.d/99-android.rules"\n' +
' sudo udevadm control --reload-rules'
);
} else {
throw new Error(
'USB connect failed: ' + errMsg + '\n\n' +
'Make sure the device screen is unlocked and USB debugging is enabled in Developer Options.'
);
}
}
try {
adbTransport = await YumeAdb.AdbDaemonTransport.authenticate({
serial: deviceObj.serial,
connection: connection,
credentialStore: credentialStore,
});
} catch (e) {
var msg = e.message || String(e);
if (msg.toLowerCase().includes('auth') || msg.toLowerCase().includes('denied')) {
throw new Error('ADB auth denied — tap "Allow USB Debugging?" on the device screen, then click Connect again.');
}
throw new Error('ADB authentication failed: ' + msg);
}
adbDevice = new YumeAdb.Adb(adbTransport);
adbUsbDevice = usbDev;
adbDeviceInfo = {};
return true;
}
/**
* Run a shell command on the connected ADB device.
* Returns {stdout, stderr, exitCode} or {output} for legacy protocol.
*/
async function adbShell(cmd) {
if (!adbDevice) throw new Error('No ADB device connected');
try {
// Prefer shell v2 protocol (separate stdout/stderr + exit code)
if (adbDevice.subprocess && adbDevice.subprocess.shellProtocol) {
var result = await adbDevice.subprocess.shellProtocol.spawnWaitText(cmd);
return {
stdout: result.stdout || '',
stderr: result.stderr || '',
exitCode: result.exitCode,
output: result.stdout || ''
};
}
// Fallback: none protocol (mixed output)
if (adbDevice.subprocess && adbDevice.subprocess.noneProtocol) {
var output = await adbDevice.subprocess.noneProtocol.spawnWaitText(cmd);
return { output: output, stdout: output, stderr: '', exitCode: 0 };
}
throw new Error('No subprocess protocol available');
} catch (e) {
return { output: '', stdout: '', stderr: e.message, exitCode: -1, error: e.message };
}
}
/**
* Get device info (model, brand, android version, etc.).
* Returns object with property key-value pairs.
*/
async function adbGetInfo() {
if (!adbDevice) throw new Error('No ADB device connected');
var props = [
['model', 'ro.product.model'],
['brand', 'ro.product.brand'],
['device', 'ro.product.device'],
['manufacturer', 'ro.product.manufacturer'],
['android_version', 'ro.build.version.release'],
['sdk', 'ro.build.version.sdk'],
['build', 'ro.build.display.id'],
['security_patch', 'ro.build.version.security_patch'],
['cpu_abi', 'ro.product.cpu.abi'],
['serialno', 'ro.serialno'],
];
var info = {};
for (var i = 0; i < props.length; i++) {
try {
var result = await adbShell('getprop ' + props[i][1]);
info[props[i][0]] = (result.stdout || result.output || '').trim();
} catch (e) {
info[props[i][0]] = '';
}
}
// Battery info
try {
var batt = await adbShell('dumpsys battery');
var battOut = batt.stdout || batt.output || '';
var levelMatch = battOut.match(/level:\s*(\d+)/);
var statusMatch = battOut.match(/status:\s*(\d+)/);
if (levelMatch) info.battery = levelMatch[1] + '%';
if (statusMatch) {
var statuses = {1:'Unknown', 2:'Charging', 3:'Discharging', 4:'Not charging', 5:'Full'};
info.battery_status = statuses[statusMatch[1]] || 'Unknown';
}
} catch (e) {}
// Storage info
try {
var df = await adbShell('df /data');
var dfOut = df.stdout || df.output || '';
var lines = dfOut.trim().split('\n');
if (lines.length >= 2) {
var parts = lines[1].trim().split(/\s+/);
if (parts.length >= 4) {
info.storage_total = parts[1];
info.storage_used = parts[2];
info.storage_free = parts[3];
}
}
} catch (e) {}
adbDeviceInfo = info;
return info;
}
/**
* Reboot ADB device to specified mode.
*/
async function adbReboot(mode) {
if (!adbDevice) throw new Error('No ADB device connected');
if (!adbDevice.power) throw new Error('Power commands not available');
switch (mode) {
case 'system': await adbDevice.power.reboot(); break;
case 'bootloader': await adbDevice.power.bootloader(); break;
case 'recovery': await adbDevice.power.recovery(); break;
case 'fastboot': await adbDevice.power.fastboot(); break;
case 'sideload': await adbDevice.power.sideload(); break;
default: await adbDevice.power.reboot(); break;
}
adbDevice = null;
adbTransport = null;
return { success: true };
}
/**
* Install APK on connected device.
* @param {Blob|File} blob - APK file
*/
async function adbInstall(blob) {
if (!adbDevice) throw new Error('No ADB device connected');
// Push to temp location then pm install
var tmpPath = '/data/local/tmp/autarch_install.apk';
await adbPush(blob, tmpPath);
var result = await adbShell('pm install -r ' + tmpPath);
await adbShell('rm ' + tmpPath);
return result;
}
/**
* Push a file to the device.
* @param {Blob|File|Uint8Array} data - File data
* @param {string} remotePath - Destination path on device
*/
async function adbPush(data, remotePath) {
if (!adbDevice) throw new Error('No ADB device connected');
var sync = await adbDevice.sync();
try {
var bytes;
if (data instanceof Uint8Array) {
bytes = data;
} else if (data instanceof Blob) {
var ab = await data.arrayBuffer();
bytes = new Uint8Array(ab);
} else {
throw new Error('Unsupported data type');
}
var stream = new ReadableStream({
start: function(controller) {
controller.enqueue(bytes);
controller.close();
}
});
await sync.write({
filename: remotePath,
file: stream,
permission: 0o644,
mtime: Math.floor(Date.now() / 1000),
});
return { success: true };
} finally {
await sync.dispose();
}
}
/**
* Pull a file from the device.
* Returns Blob.
*/
async function adbPull(remotePath) {
if (!adbDevice) throw new Error('No ADB device connected');
var sync = await adbDevice.sync();
try {
var readable = sync.read(remotePath);
var reader = readable.getReader();
var chunks = [];
while (true) {
var result = await reader.read();
if (result.done) break;
chunks.push(result.value);
}
var totalLen = chunks.reduce(function(s, c) { return s + c.length; }, 0);
var combined = new Uint8Array(totalLen);
var offset = 0;
for (var i = 0; i < chunks.length; i++) {
combined.set(chunks[i], offset);
offset += chunks[i].length;
}
return new Blob([combined]);
} finally {
await sync.dispose();
}
}
/**
* Get logcat output.
*/
async function adbLogcat(lines) {
lines = lines || 100;
return await adbShell('logcat -d -t ' + lines);
}
/**
* Disconnect from ADB device.
*/
async function adbDisconnect() {
if (adbDevice) {
try { await adbDevice.close(); } catch (e) {}
}
if (adbUsbDevice) {
// Release the USB interface so Windows won't block the next connect()
try { await adbUsbDevice.close(); } catch (e) {}
}
adbDevice = null;
adbTransport = null;
adbUsbDevice = null;
adbDeviceInfo = {};
}
/**
* Check if ADB device is connected.
*/
function adbIsConnected() {
return adbDevice !== null;
}
/**
* Get a human-readable label for the connected ADB device.
* Returns "Model (serial)" or "Connected device" if info not yet fetched.
*/
function adbGetDeviceLabel() {
if (!adbDevice) return 'Not connected';
var model = adbDeviceInfo.model || '';
var serial = adbUsbDevice ? (adbUsbDevice.serial || '') : '';
if (model && serial) return model + ' (' + serial + ')';
if (model) return model;
if (serial) return serial;
return 'Connected device';
}
// ══════════════════════════════════════════════════════════════
// FASTBOOT (WebUSB)
// ══════════════════════════════════════════════════════════════
/**
* Connect to a fastboot device (shows browser USB picker).
*/
async function fbConnect() {
if (!supported.webusb) throw new Error('WebUSB not supported');
if (fbDevice) await fbDisconnect();
fbDevice = new Fastboot.FastbootDevice();
await fbDevice.connect();
return true;
}
/**
* Get fastboot device info (getvar queries).
*/
async function fbGetInfo() {
if (!fbDevice) throw new Error('No fastboot device connected');
var vars = ['product', 'variant', 'serialno', 'secure', 'unlocked',
'current-slot', 'max-download-size', 'battery-voltage',
'battery-soc-ok', 'hw-revision', 'version-bootloader',
'version-baseband', 'off-mode-charge'];
var info = {};
for (var i = 0; i < vars.length; i++) {
try {
info[vars[i]] = await fbDevice.getVariable(vars[i]);
} catch (e) {
info[vars[i]] = '';
}
}
return info;
}
/**
* Flash a partition.
* @param {string} partition - Partition name (boot, recovery, system, etc.)
* @param {Blob|File} blob - Firmware image
* @param {function} progressCb - Called with (progress: 0-1)
*/
async function fbFlash(partition, blob, progressCb) {
if (!fbDevice) throw new Error('No fastboot device connected');
var callback = progressCb || function() {};
await fbDevice.flashBlob(partition, blob, function(progress) {
callback(progress);
});
return { success: true };
}
/**
* Reboot fastboot device.
*/
async function fbReboot(mode) {
if (!fbDevice) throw new Error('No fastboot device connected');
switch (mode) {
case 'bootloader': await fbDevice.reboot('bootloader'); break;
case 'recovery': await fbDevice.reboot('recovery'); break;
default: await fbDevice.reboot(); break;
}
fbDevice = null;
return { success: true };
}
/**
* OEM unlock the bootloader.
*/
async function fbOemUnlock() {
if (!fbDevice) throw new Error('No fastboot device connected');
await fbDevice.runCommand('oem unlock');
return { success: true };
}
/**
* Flash a factory image ZIP (PixelFlasher-style).
* Parses the ZIP, identifies partitions, flashes in sequence.
* @param {Blob|File} zipBlob - Factory image ZIP file
* @param {object} options - Flash options
* @param {function} progressCb - Called with {stage, partition, progress, message}
*/
async function fbFactoryFlash(zipBlob, options, progressCb) {
if (!fbDevice) throw new Error('No fastboot device connected');
options = options || {};
var callback = progressCb || function() {};
callback({ stage: 'init', message: 'Reading factory image ZIP...' });
try {
// Use fastboot.js built-in factory flash support
await fbDevice.flashFactoryZip(zipBlob, !options.wipeData, function(action, item, progress) {
if (action === 'unpack') {
callback({ stage: 'unpack', partition: item, progress: progress,
message: 'Unpacking: ' + item });
} else if (action === 'flash') {
callback({ stage: 'flash', partition: item, progress: progress,
message: 'Flashing: ' + item + ' (' + Math.round(progress * 100) + '%)' });
} else if (action === 'reboot') {
callback({ stage: 'reboot', message: 'Rebooting...' });
}
});
callback({ stage: 'done', message: 'Factory flash complete' });
return { success: true };
} catch (e) {
callback({ stage: 'error', message: 'Flash failed: ' + e.message });
return { success: false, error: e.message };
}
}
/**
* Disconnect from fastboot device.
*/
async function fbDisconnect() {
if (fbDevice) {
try { await fbDevice.disconnect(); } catch (e) {}
}
fbDevice = null;
}
function fbIsConnected() {
return fbDevice !== null;
}
// ══════════════════════════════════════════════════════════════
// ESP32 (Web Serial)
// ══════════════════════════════════════════════════════════════
/**
* Request a serial port (shows browser serial picker).
* Returns port reference for later use.
*/
async function espRequestPort() {
if (!supported.webserial) throw new Error('Web Serial not supported');
espPort = await navigator.serial.requestPort({
filters: [
{ usbVendorId: 0x10C4 }, // Silicon Labs CP210x
{ usbVendorId: 0x1A86 }, // QinHeng CH340
{ usbVendorId: 0x0403 }, // FTDI
{ usbVendorId: 0x303A }, // Espressif USB-JTAG
]
});
return espPort;
}
/**
* Connect to ESP32 and detect chip.
* @param {number} baud - Baud rate (default 115200)
*/
async function espConnect(baud) {
if (!espPort) throw new Error('No serial port selected. Call espRequestPort() first.');
baud = baud || 115200;
if (espTransport) await espDisconnect();
await espPort.open({ baudRate: baud });
espTransport = new EspTool.Transport(espPort);
espLoader = new EspTool.ESPLoader({
transport: espTransport,
baudrate: baud,
romBaudrate: 115200,
});
// Connect and detect chip
await espLoader.main();
return {
success: true,
chip: espLoader.chipName || 'Unknown',
};
}
/**
* Get detected chip info.
*/
function espGetChipInfo() {
if (!espLoader) return null;
return {
chip: espLoader.chipName || 'Unknown',
features: espLoader.chipFeatures || [],
mac: espLoader.macAddr || '',
};
}
/**
* Flash firmware to ESP32.
* @param {Array} fileArray - [{data: Uint8Array|string, address: number}, ...]
* @param {function} progressCb - Called with (fileIndex, written, total)
*/
async function espFlash(fileArray, progressCb) {
if (!espLoader) throw new Error('No ESP32 connected. Call espConnect() first.');
var callback = progressCb || function() {};
await espLoader.writeFlash({
fileArray: fileArray,
flashSize: 'keep',
flashMode: 'keep',
flashFreq: 'keep',
eraseAll: false,
compress: true,
reportProgress: function(fileIndex, written, total) {
callback(fileIndex, written, total);
},
});
return { success: true };
}
/**
* Start serial monitor on the connected port.
* @param {number} baud - Baud rate (default 115200)
* @param {function} outputCb - Called with each line of output
*/
async function espMonitorStart(baud, outputCb) {
if (!espPort) throw new Error('No serial port selected');
baud = baud || 115200;
// If loader is connected, disconnect it first but keep port open
if (espLoader) {
try { await espLoader.hardReset(); } catch (e) {}
espLoader = null;
espTransport = null;
}
// Close and reopen at monitor baud rate
try { await espPort.close(); } catch (e) {}
await espPort.open({ baudRate: baud });
espMonitorRunning = true;
var decoder = new TextDecoderStream();
espPort.readable.pipeTo(decoder.writable).catch(function(e) {
if (espMonitorRunning) console.error('espMonitor pipeTo:', e);
});
espMonitorReader = decoder.readable.getReader();
(async function readLoop() {
try {
while (espMonitorRunning) {
var result = await espMonitorReader.read();
if (result.done) break;
if (result.value && outputCb) {
outputCb(result.value);
}
}
} catch (e) {
if (espMonitorRunning) {
outputCb('[Monitor error: ' + e.message + ']');
}
}
})();
return { success: true };
}
/**
* Send data to serial monitor.
*/
async function espMonitorSend(data) {
if (!espPort || !espPort.writable) throw new Error('No serial monitor active');
var writer = espPort.writable.getWriter();
try {
var encoded = new TextEncoder().encode(data + '\n');
await writer.write(encoded);
} finally {
writer.releaseLock();
}
}
/**
* Stop serial monitor.
*/
async function espMonitorStop() {
espMonitorRunning = false;
if (espMonitorReader) {
try { await espMonitorReader.cancel(); } catch (e) {}
espMonitorReader = null;
}
}
/**
* Disconnect ESP32 and close serial port.
*/
async function espDisconnect() {
espMonitorRunning = false;
if (espMonitorReader) {
try { await espMonitorReader.cancel(); } catch (e) {}
espMonitorReader = null;
}
espLoader = null;
espTransport = null;
if (espPort) {
try { await espPort.close(); } catch (e) {}
espPort = null;
}
}
function espIsConnected() {
return espLoader !== null || espMonitorRunning;
}
// ══════════════════════════════════════════════════════════════
// Utility
// ══════════════════════════════════════════════════════════════
/**
* Read a local file as Uint8Array (from <input type="file">).
*/
function readFileAsBytes(file) {
return new Promise(function(resolve, reject) {
var reader = new FileReader();
reader.onload = function() { resolve(new Uint8Array(reader.result)); };
reader.onerror = function() { reject(reader.error); };
reader.readAsArrayBuffer(file);
});
}
/**
* Read a local file as text.
*/
function readFileAsText(file) {
return new Promise(function(resolve, reject) {
var reader = new FileReader();
reader.onload = function() { resolve(reader.result); };
reader.onerror = function() { reject(reader.error); };
reader.readAsText(file);
});
}
/**
* Download data as a file to the user's machine.
*/
function downloadBlob(blob, filename) {
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
// ── Public API ────────────────────────────────────────────────
return {
supported: supported,
// ADB
adbGetDevices: adbGetDevices,
adbRequestDevice: adbRequestDevice,
adbConnect: adbConnect,
adbShell: adbShell,
adbGetInfo: adbGetInfo,
adbReboot: adbReboot,
adbInstall: adbInstall,
adbPush: adbPush,
adbPull: adbPull,
adbLogcat: adbLogcat,
adbDisconnect: adbDisconnect,
adbIsConnected: adbIsConnected,
adbGetDeviceLabel: adbGetDeviceLabel,
// Fastboot
fbConnect: fbConnect,
fbGetInfo: fbGetInfo,
fbFlash: fbFlash,
fbReboot: fbReboot,
fbOemUnlock: fbOemUnlock,
fbFactoryFlash: fbFactoryFlash,
fbDisconnect: fbDisconnect,
fbIsConnected: fbIsConnected,
// ESP32
espRequestPort: espRequestPort,
espConnect: espConnect,
espGetChipInfo: espGetChipInfo,
espFlash: espFlash,
espMonitorStart: espMonitorStart,
espMonitorSend: espMonitorSend,
espMonitorStop: espMonitorStop,
espDisconnect: espDisconnect,
espIsConnected: espIsConnected,
// Utility
readFileAsBytes: readFileAsBytes,
readFileAsText: readFileAsText,
downloadBlob: downloadBlob,
};
})();