diff --git a/docs/js/adb/account-utils.js b/docs/js/adb/account-utils.js index 917d2e2..b7efd70 100644 --- a/docs/js/adb/account-utils.js +++ b/docs/js/adb/account-utils.js @@ -64,36 +64,117 @@ export async function deviceHasAccounts(adb) { export async function disableAccountApps(adb) { if (!adb) return []; + const disabled = new Set(); + const packagesToDisable = new Set([ + 'com.microsoft.office.officehubrow', + 'com.microsoft.office.word', + 'com.microsoft.office.excel', + 'com.microsoft.office.outlook', + 'com.microsoft.office.powerpoint', + 'com.microsoft.skydrive', + 'com.microsoft.appmanager', + 'com.facebook.katana', + 'com.facebook.orca', + 'com.whatsapp', + 'com.google.android.gm', + 'com.google.android.gsf.login', + 'com.google.android.gms', + 'com.samsung.android.mobileservice', + ]); + + const addPackage = pkg => { + if (!pkg) return; + const clean = pkg.trim(); + if (!clean || clean === 'android') return; + if (!/^[a-zA-Z0-9_.]+$/.test(clean)) return; + packagesToDisable.add(clean); + }; + + const accountTypePackageMap = { + 'com.google': ['com.google.android.gsf.login', 'com.google.android.gms'], + 'com.google.work': ['com.google.android.gm', 'com.google.android.gsf.login', 'com.google.android.gms'], + 'com.google.android.gm.exchange': ['com.google.android.gm'], + 'com.android.exchange': ['com.google.android.gm'], + 'com.microsoft.exchange': ['com.microsoft.office.outlook'], + 'com.microsoft.workaccount': ['com.microsoft.appmanager'], + 'com.facebook.auth.login': ['com.facebook.katana', 'com.facebook.orca'], + 'com.whatsapp': ['com.whatsapp'], + 'com.samsung.android.mobileservice': ['com.samsung.android.mobileservice'], + }; + + const commands = [ + 'cmd account list --user 0', + 'cmd account list', + 'dumpsys account', + ]; + + const outputs = []; + for (const cmd of commands) { + try { + const out = await adb.executeShellCommand(cmd); + if (out && out.trim()) outputs.push(out); + } catch (e) { + console.log(`command ${cmd} failed`, e?.message || e); + } + } + + const combined = outputs.join('\n\n'); + const componentRegex = /ComponentInfo\{([^\/}]+)\/[^}]+\}/g; + let match; + while ((match = componentRegex.exec(combined))) { + addPackage(match[1]); + } + + const packageRegex = /packageName=([a-zA-Z0-9_.]+)/g; + while ((match = packageRegex.exec(combined))) { + addPackage(match[1]); + } + + const authenticatorRegex = /AuthenticatorDescription \{([^}]+)\}/g; + while ((match = authenticatorRegex.exec(combined))) { + const block = match[1]; + const typeMatch = /type=([^,\s]+)/.exec(block); + const packageMatch = /packageName=([^,\s]+)/.exec(block); + if (packageMatch) addPackage(packageMatch[1]); + if (typeMatch && accountTypePackageMap[typeMatch[1]]) { + for (const pkg of accountTypePackageMap[typeMatch[1]]) addPackage(pkg); + } + } + + const typeRegex = /type[=:\s]+([^,\s]+)/g; + const accountTypes = new Set(); + while ((match = typeRegex.exec(combined))) { + const type = match[1]?.trim(); + if (type) accountTypes.add(type); + } + for (const type of accountTypes) { + const mapped = accountTypePackageMap[type]; + if (mapped) { + for (const pkg of mapped) addPackage(pkg); + } + } + const run = async pkg => { + if (disabled.has(pkg)) return; try { await adb.executeShellCommand(`pm disable-user --user 0 ${pkg}`); disabled.add(pkg); console.log('disabled', pkg); } catch (e) { - console.log('failed to disable', pkg); + console.log('failed to disable', pkg, e?.message || e); } }; - const manualPkgs = [ - 'com.microsoft.office.officehubrow', - 'com.microsoft.office.word', - 'com.microsoft.office.excel', - 'com.microsoft.office.outlook', - 'com.microsoft.office.powerpoint', - ]; - for (const pkg of manualPkgs) { + + for (const pkg of packagesToDisable) { await run(pkg); } - try { - const out = await adb.executeShellCommand('dumpsys account'); - const re = /ComponentInfo\{([^\/}]+)\/[^}]+\}/g; - let m; - while ((m = re.exec(out))) { - await run(m[1]); - } - } catch (e) { - console.log('dumpsys account error', e?.message || e); + + // Give the system a brief moment to acknowledge the disabled authenticators + if (disabled.size > 0) { + await new Promise(resolve => setTimeout(resolve, 1000)); } + return Array.from(disabled); } @@ -102,7 +183,7 @@ export async function reenablePackages(adb, packages) { console.log('reenablePackages', packages); for (const pkg of packages) { try { - await adb.executeShellCommand(`pm enable ${pkg}`); + await adb.executeShellCommand(`pm enable --user 0 ${pkg}`); console.log('reenabled', pkg); } catch (e) { console.log('failed to reenable', pkg); diff --git a/docs/js/apps.js b/docs/js/apps.js index ded80fe..ff5bef2 100644 --- a/docs/js/apps.js +++ b/docs/js/apps.js @@ -420,6 +420,7 @@ class JTechMDMInstaller { const isLocal = location.hostname === 'localhost' || location.hostname === '127.0.0.1'; try { if (isLocal) { + // Prefer dynamic API in dev to avoid 404 noise const res = await fetch('/api/apks'); if (!res.ok) throw new Error('/api/apks failed'); apks = await res.json(); @@ -429,6 +430,7 @@ class JTechMDMInstaller { apks = await res.json(); } } catch (err) { + // Fallback to the other source const res = await fetch(isLocal ? 'apks.json' : '/api/apks'); apks = await res.json(); } @@ -956,6 +958,11 @@ class JTechMDMInstaller { if (hasAccounts) { this.uiManager.logToConsole('Accounts detected - temporarily disabling account apps...', 'warning'); const disabled = await disableAccountApps(this.adbConnection); + if (disabled.length > 0) { + this.uiManager.logToConsole(`Temporarily disabled: ${disabled.join(', ')}`, 'info'); + } else { + this.uiManager.logToConsole('No specific account packages detected; proceeding with retry.', 'warning'); + } try { this.uiManager.logToConsole('Retrying device owner command...', 'info'); result = await this.adbConnection.executeShellCommand(command); @@ -965,6 +972,9 @@ class JTechMDMInstaller { } } finally { await reenablePackages(this.adbConnection, disabled); + if (disabled.length > 0) { + this.uiManager.logToConsole('Re-enabled previously disabled account packages.', 'info'); + } } if (!/success/i.test(result)) { // Surface a clear warning but keep full output visible above diff --git a/js/adb/account-utils.js b/js/adb/account-utils.js index 917d2e2..b7efd70 100644 --- a/js/adb/account-utils.js +++ b/js/adb/account-utils.js @@ -64,36 +64,117 @@ export async function deviceHasAccounts(adb) { export async function disableAccountApps(adb) { if (!adb) return []; + const disabled = new Set(); + const packagesToDisable = new Set([ + 'com.microsoft.office.officehubrow', + 'com.microsoft.office.word', + 'com.microsoft.office.excel', + 'com.microsoft.office.outlook', + 'com.microsoft.office.powerpoint', + 'com.microsoft.skydrive', + 'com.microsoft.appmanager', + 'com.facebook.katana', + 'com.facebook.orca', + 'com.whatsapp', + 'com.google.android.gm', + 'com.google.android.gsf.login', + 'com.google.android.gms', + 'com.samsung.android.mobileservice', + ]); + + const addPackage = pkg => { + if (!pkg) return; + const clean = pkg.trim(); + if (!clean || clean === 'android') return; + if (!/^[a-zA-Z0-9_.]+$/.test(clean)) return; + packagesToDisable.add(clean); + }; + + const accountTypePackageMap = { + 'com.google': ['com.google.android.gsf.login', 'com.google.android.gms'], + 'com.google.work': ['com.google.android.gm', 'com.google.android.gsf.login', 'com.google.android.gms'], + 'com.google.android.gm.exchange': ['com.google.android.gm'], + 'com.android.exchange': ['com.google.android.gm'], + 'com.microsoft.exchange': ['com.microsoft.office.outlook'], + 'com.microsoft.workaccount': ['com.microsoft.appmanager'], + 'com.facebook.auth.login': ['com.facebook.katana', 'com.facebook.orca'], + 'com.whatsapp': ['com.whatsapp'], + 'com.samsung.android.mobileservice': ['com.samsung.android.mobileservice'], + }; + + const commands = [ + 'cmd account list --user 0', + 'cmd account list', + 'dumpsys account', + ]; + + const outputs = []; + for (const cmd of commands) { + try { + const out = await adb.executeShellCommand(cmd); + if (out && out.trim()) outputs.push(out); + } catch (e) { + console.log(`command ${cmd} failed`, e?.message || e); + } + } + + const combined = outputs.join('\n\n'); + const componentRegex = /ComponentInfo\{([^\/}]+)\/[^}]+\}/g; + let match; + while ((match = componentRegex.exec(combined))) { + addPackage(match[1]); + } + + const packageRegex = /packageName=([a-zA-Z0-9_.]+)/g; + while ((match = packageRegex.exec(combined))) { + addPackage(match[1]); + } + + const authenticatorRegex = /AuthenticatorDescription \{([^}]+)\}/g; + while ((match = authenticatorRegex.exec(combined))) { + const block = match[1]; + const typeMatch = /type=([^,\s]+)/.exec(block); + const packageMatch = /packageName=([^,\s]+)/.exec(block); + if (packageMatch) addPackage(packageMatch[1]); + if (typeMatch && accountTypePackageMap[typeMatch[1]]) { + for (const pkg of accountTypePackageMap[typeMatch[1]]) addPackage(pkg); + } + } + + const typeRegex = /type[=:\s]+([^,\s]+)/g; + const accountTypes = new Set(); + while ((match = typeRegex.exec(combined))) { + const type = match[1]?.trim(); + if (type) accountTypes.add(type); + } + for (const type of accountTypes) { + const mapped = accountTypePackageMap[type]; + if (mapped) { + for (const pkg of mapped) addPackage(pkg); + } + } + const run = async pkg => { + if (disabled.has(pkg)) return; try { await adb.executeShellCommand(`pm disable-user --user 0 ${pkg}`); disabled.add(pkg); console.log('disabled', pkg); } catch (e) { - console.log('failed to disable', pkg); + console.log('failed to disable', pkg, e?.message || e); } }; - const manualPkgs = [ - 'com.microsoft.office.officehubrow', - 'com.microsoft.office.word', - 'com.microsoft.office.excel', - 'com.microsoft.office.outlook', - 'com.microsoft.office.powerpoint', - ]; - for (const pkg of manualPkgs) { + + for (const pkg of packagesToDisable) { await run(pkg); } - try { - const out = await adb.executeShellCommand('dumpsys account'); - const re = /ComponentInfo\{([^\/}]+)\/[^}]+\}/g; - let m; - while ((m = re.exec(out))) { - await run(m[1]); - } - } catch (e) { - console.log('dumpsys account error', e?.message || e); + + // Give the system a brief moment to acknowledge the disabled authenticators + if (disabled.size > 0) { + await new Promise(resolve => setTimeout(resolve, 1000)); } + return Array.from(disabled); } @@ -102,7 +183,7 @@ export async function reenablePackages(adb, packages) { console.log('reenablePackages', packages); for (const pkg of packages) { try { - await adb.executeShellCommand(`pm enable ${pkg}`); + await adb.executeShellCommand(`pm enable --user 0 ${pkg}`); console.log('reenabled', pkg); } catch (e) { console.log('failed to reenable', pkg); diff --git a/js/apps.js b/js/apps.js index 0d764e7..ff5bef2 100644 --- a/js/apps.js +++ b/js/apps.js @@ -958,6 +958,11 @@ class JTechMDMInstaller { if (hasAccounts) { this.uiManager.logToConsole('Accounts detected - temporarily disabling account apps...', 'warning'); const disabled = await disableAccountApps(this.adbConnection); + if (disabled.length > 0) { + this.uiManager.logToConsole(`Temporarily disabled: ${disabled.join(', ')}`, 'info'); + } else { + this.uiManager.logToConsole('No specific account packages detected; proceeding with retry.', 'warning'); + } try { this.uiManager.logToConsole('Retrying device owner command...', 'info'); result = await this.adbConnection.executeShellCommand(command); @@ -967,6 +972,9 @@ class JTechMDMInstaller { } } finally { await reenablePackages(this.adbConnection, disabled); + if (disabled.length > 0) { + this.uiManager.logToConsole('Re-enabled previously disabled account packages.', 'info'); + } } if (!/success/i.test(result)) { // Surface a clear warning but keep full output visible above