UNPKG

kernelsu-alt

Version:

Library for KernelSU and KernelSU-Next module WebUI

287 lines (262 loc) 9.09 kB
/** * Imported from https://www.npmjs.com/package/kernelsu * Modified version by KOWX712 * Added KernelSU-Next package manager API */ let callbackCounter = 0; function getUniqueCallbackName(prefix) { return `${prefix}_callback_${Date.now()}_${callbackCounter++}`; } /** * Execute shell command with ksu.exec * @param {string} command - The command to execute * @param {Object} [options={}] - Options object containing: * - cwd <string> - Current working directory of the child process * - env {Object} - Environment key-value pairs * @returns {Promise<Object>} Resolves with: * - errno {number} - Exit code of the command * - stdout {string} - Standard output from the command * - stderr {string} - Standard error from the command */ export function exec(command, options = {}) { return new Promise((resolve, reject) => { const callbackFuncName = getUniqueCallbackName("exec"); window[callbackFuncName] = (errno, stdout, stderr) => { resolve({ errno, stdout, stderr }); cleanup(callbackFuncName); }; function cleanup(successName) { delete window[successName]; } try { if (typeof ksu !== 'undefined') { ksu.exec(command, JSON.stringify(options), callbackFuncName); } else { resolve({ errno: 1, stdout: "", stderr: "ksu is not defined" }); } } catch (error) { reject(error); cleanup(callbackFuncName); } }); } /** * Standard I/O stream for a child process. * @class */ class Stdio { constructor() { this.listeners = {}; } on(event, listener) { if (!this.listeners[event]) { this.listeners[event] = []; } this.listeners[event].push(listener); } emit(event, ...args) { if (this.listeners[event]) { this.listeners[event].forEach(listener => listener(...args)); } } } /** * Spawn shell process with ksu.spawn * @param {string} command - The command to execute * @param {string[]} [args=[]] - Array of arguments to pass to the command * @param {Object} [options={}] - Options object containing: * - cwd <string> - Current working directory of the child process * - env {Object} - Environment key-value pairs * @returns {Object} A child process object with: * - stdout: Stream for standard output * - stderr: Stream for standard error * - stdin: Stream for standard input * - on(event, listener): Attach event listener ('exit', 'error') * - emit(event, ...args): Emit events internally */ export function spawn(command, args = [], options = {}) { const child = { listeners: {}, stdout: new Stdio(), stderr: new Stdio(), stdin: new Stdio(), on(event, listener) { if (!this.listeners[event]) this.listeners[event] = []; this.listeners[event].push(listener); }, emit(event, ...args) { if (this.listeners[event]) { this.listeners[event].forEach(listener => listener(...args)); } } }; const callbackName = getUniqueCallbackName("spawn"); window[callbackName] = child; child.on("exit", () => delete window[callbackName]); try { if (typeof ksu !== 'undefined') { ksu.spawn(command, JSON.stringify(args), JSON.stringify(options), callbackName); } else { setTimeout(() => { child.stderr.emit("data", "ksu is not defined"); child.emit("exit", 1); }, 0); } } catch (error) { child.emit("error", error); delete window[callbackName]; } return child; } /** * Request the WebView enter/exit full screen. * @param {Boolean} isFullScreen - full screen state */ export function fullScreen(isFullScreen) { if (typeof ksu !== 'undefined') { ksu.fullScreen(isFullScreen); } } /** * Show android toast message * @param {string} message - The message to display in toast * @returns {void} */ export function toast(message) { if (typeof ksu !== 'undefined') { ksu.toast(message); } else { console.log(message); } } /** * List installed packages * @param {string} [type="all"] - The type of packages to list: "user", "system", or "all". * @returns {Promise<string[]>} A promise that resolves to an array of package names. */ export async function listPackages(type = "all") { const pkgTypes = { all: () => globalThis.ksu?.listAllPackages(), user: () => globalThis.ksu?.listUserPackages(), system: () => globalThis.ksu?.listSystemPackages(), }; const pkgType = pkgTypes[type]; if (typeof pkgType === 'function') { try { return JSON.parse(pkgType()); } catch (error) { // Fallback to pm list package } } return new Promise((resolve, reject) => { const pmArgs = { all: [], user: ['-3'], system: ['-s'], }; if (!(type in pmArgs)) { return reject(new Error(`Unknown listPackages type: ${type}`)); } let pkgs = []; let stderr = ''; const pm = spawn('pm', ['list', 'packages', ...pmArgs[type]]); pm.stdout.on('data', (data) => { if (data.trim() !== '') { pkgs.push(data.trim().replace(/^package:/, '')); } }); pm.stderr.on('data', (data) => (stderr += data)); pm.on('exit', (code) => { if (code !== 0) { return reject(new Error(`pm process exited with code ${code}: ${stderr.trim()}`)); } resolve(pkgs); }); pm.on('error', (error) => { reject(error); }); }); } /** * @typedef {object} PackagesInfo * @property {string} packageName - Package name of the application. * @property {string} versionName - Version of the application. * @property {number} versionCode - Version code of the application. * @property {string} appLabel - Display name of the application. * @property {boolean} isSystem - Whether the application is a system app. * @property {number} uid - UID of the application. */ /** * Retrieves detailed information for one or more packages. * @param {string|string[]} pkg - A single package name or an array of package names. * @returns {Promise<PackagesInfo|PackagesInfo[]>} Resolves with: * - a single package information object if one package is provided * - {} * - an array of package information objects if multiple packages are provided * - [{},{}] */ export function getPackagesInfo(pkg) { return new Promise((resolve, reject) => { if (!pkg) { return resolve([]); } if (typeof globalThis.ksu?.getPackagesInfo !== 'function') { return reject(new Error("ksu.getPackagesInfo is not available.")); } const pkgs = Array.isArray(pkg) ? pkg : [pkg]; if (pkgs.length === 0) { return resolve([]); } try { const infoJson = ksu.getPackagesInfo(JSON.stringify(pkgs)); const result = JSON.parse(infoJson); if (!Array.isArray(pkg) && result.length === 1) { resolve(result[0]); } else { resolve(result); } } catch (error) { reject(new Error(`Failed to get package info: ${error.message}`)); } }); } /** * @typedef {object} PackagesIcon * @property {string} packageName - Package name of the application. * @property {string} icon - Ready-to-use base64 image for src. */ /** * Retrieves base64 icon for one or more packages. * @param {string|string[]} pkg - A single package name or an array of package names. * @param {number} [size=100] - The dimension of the icon to retrieve. * @returns {Promise<PackagesIcon|PackagesIcon[]>} Resolves with: * - a single icon object if one package is provided * - {} * - an array of icon objects if multiple packages are provided * - [{},{}] */ export function getPackagesIcon(pkg, size = 100) { return new Promise((resolve, reject) => { if (!pkg) { return resolve([]); } if (typeof globalThis.ksu?.getPackagesIcons !== 'function') { return reject(new Error("ksu.getPackagesIcons is not available.")); } const pkgs = Array.isArray(pkg) ? pkg : [pkg]; if (pkgs.length === 0) { return resolve([]); } try { const iconJson = ksu.getPackagesIcons(JSON.stringify(pkgs), size); const result = JSON.parse(iconJson); if (!Array.isArray(pkg) && result.length === 1) { resolve(result[0]); } else { resolve(result); } } catch (error) { reject(new Error(`Failed to get package icon: ${error.message}`)); } }); }