diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca137bc..9b22258 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,13 +26,13 @@ jobs: - name: Update dependency run: | - curl -Ls https://cdn.jsdelivr.net/npm/marked/marked.min.js > module/webui/scripts/marked.min.js + curl -Ls https://cdn.jsdelivr.net/npm/marked/marked.min.js > module/webui/scripts/assets/marked.min.js # Commit if found changes - if ! git diff --exit-code module/webui/scripts/marked.min.js > /dev/null; then + if ! git diff --exit-code module/webui/scripts/assets/marked.min.js > /dev/null; then git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" - git add module/webui/scripts/marked.min.js + git add module/webui/scripts/assets/marked.min.js git commit -m "deps: update marked.min.js" git push fi diff --git a/module/webui/index.html b/module/webui/index.html index e75e859..5a8fbea 100644 --- a/module/webui/index.html +++ b/module/webui/index.html @@ -19,7 +19,7 @@ - + diff --git a/module/webui/scripts/applist.js b/module/webui/scripts/applist.js index 036f96e..75148ea 100644 --- a/module/webui/scripts/applist.js +++ b/module/webui/scripts/applist.js @@ -1,4 +1,5 @@ -import { basePath, exec, hideFloatingBtn, appsWithExclamation, appsWithQuestion, toast } from './main.js'; +import { exec, toast } from './assets/kernelsu.js'; +import { basePath, hideFloatingBtn, appsWithExclamation, appsWithQuestion } from './main.js'; const appTemplate = document.getElementById('app-template').content; const modeOverlay = document.querySelector('.mode-overlay'); diff --git a/module/webui/scripts/assets/kernelsu.js b/module/webui/scripts/assets/kernelsu.js new file mode 100644 index 0000000..ed09323 --- /dev/null +++ b/module/webui/scripts/assets/kernelsu.js @@ -0,0 +1,111 @@ +/** + * Imported from https://www.npmjs.com/package/kernelsu + * Slightly modified version by KOWX712 + * Added full description + * Simplified spawn function + * Handle error on toast + */ + +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 - Current working directory of the child process + * - env {Object} - Environment key-value pairs + * @returns {Promise} 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 { + ksu.exec(command, JSON.stringify(options), callbackFuncName); + } catch (error) { + reject(error); + cleanup(callbackFuncName); + } + }); +} + +/** + * 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 - 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)); + } + } + }; + function Stdio() { + this.listeners = {}; + } + Stdio.prototype.on = function(event, listener) { + if (!this.listeners[event]) { + this.listeners[event] = []; + } + this.listeners[event].push(listener); + }; + Stdio.prototype.emit = function(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 { + ksu.spawn(command, JSON.stringify(args), JSON.stringify(options), callbackName); + } catch (error) { + child.emit("error", error); + delete window[callbackName]; + } + return child; +} + +/** + * Show android toast message + * @param {string} message - The message to display in toast + * @returns {void} + */ +export function toast(message) { + try { + ksu.toast(message); + } catch (error) { + console.error("Error displaying toast:", error); + } +} diff --git a/module/webui/scripts/marked.min.js b/module/webui/scripts/assets/marked.min.js similarity index 100% rename from module/webui/scripts/marked.min.js rename to module/webui/scripts/assets/marked.min.js diff --git a/module/webui/scripts/boot_hash.js b/module/webui/scripts/boot_hash.js index 5940185..b362f52 100644 --- a/module/webui/scripts/boot_hash.js +++ b/module/webui/scripts/boot_hash.js @@ -1,4 +1,5 @@ -import { exec, showPrompt } from './main.js'; +import { exec } from './assets/kernelsu.js'; +import { showPrompt } from './main.js'; const bootHashOverlay = document.getElementById('boot-hash-overlay'); const bootHash = document.querySelector('.boot-hash-card'); diff --git a/module/webui/scripts/main.js b/module/webui/scripts/main.js index e238d7c..3e3ee2e 100644 --- a/module/webui/scripts/main.js +++ b/module/webui/scripts/main.js @@ -1,3 +1,4 @@ +import { exec, toast } from './assets/kernelsu.js'; import { appListContainer, fetchAppList } from './applist.js'; import { loadTranslations, setupLanguageMenu, translations } from './language.js'; import { setupSystemAppMenu } from './menu_option.js'; @@ -310,42 +311,3 @@ document.addEventListener('DOMContentLoaded', async () => { document.getElementById("refresh").addEventListener("click", refreshAppList); document.querySelector('.uninstall-container').classList.remove('hidden-uninstall'); }); - -/** - * Execute shell command with ksu.exec - * @param {string} command - The command to execute - * @param {Object} [options={}] - Options object containing: - * - cwd - Current working directory of the child process - * - env {Object} - Environment key-value pairs - * @returns {Promise} 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 = `exec_callback_${Date.now()}_${e++}`; - window[callbackFuncName] = (errno, stdout, stderr) => { - resolve({ errno, stdout, stderr }); - cleanup(callbackFuncName); - }; - function cleanup(successName) { - delete window[successName]; - } - try { - ksu.exec(command, JSON.stringify(options), callbackFuncName); - } catch (error) { - reject(error); - cleanup(callbackFuncName); - } - }); -} - -// Function to toast message -export function toast(message) { - try { - ksu.toast(message); - } catch (error) { - console.error("Failed to show toast:", error); - } -} \ No newline at end of file diff --git a/module/webui/scripts/menu_option.js b/module/webui/scripts/menu_option.js index e594ec2..8711026 100644 --- a/module/webui/scripts/menu_option.js +++ b/module/webui/scripts/menu_option.js @@ -1,4 +1,5 @@ -import { basePath, exec, showPrompt, toast, applyRippleEffect, refreshAppList } from './main.js'; +import { exec, toast } from './assets/kernelsu.js'; +import { basePath, showPrompt, applyRippleEffect, refreshAppList } from './main.js'; // Function to check or uncheck all app function toggleCheckboxes(shouldCheck) { diff --git a/module/webui/scripts/security_patch.js b/module/webui/scripts/security_patch.js index 371ac64..b212be4 100644 --- a/module/webui/scripts/security_patch.js +++ b/module/webui/scripts/security_patch.js @@ -1,4 +1,5 @@ -import { basePath, exec, showPrompt } from './main.js'; +import { exec } from './assets/kernelsu.js'; +import { basePath, showPrompt } from './main.js'; const overlay = document.getElementById('security-patch-overlay'); const overlayContent = document.querySelector('.security-patch-card'); diff --git a/module/webui/scripts/update.js b/module/webui/scripts/update.js index 1ab98be..652e112 100644 --- a/module/webui/scripts/update.js +++ b/module/webui/scripts/update.js @@ -1,4 +1,5 @@ -import { basePath, exec, showPrompt, noConnection, linkRedirect } from './main.js'; +import { exec } from './assets/kernelsu.js'; +import { basePath, showPrompt, noConnection, linkRedirect } from './main.js'; import { updateCard } from './applist.js'; const updateMenu = document.querySelector('.update-overlay');