From 43f8595cce8fe9a9af900094e23da4278da2997f Mon Sep 17 00:00:00 2001 From: Amr Ayman <48989143+ulite-Amr@users.noreply.github.com> Date: Fri, 21 Aug 2026 00:03:25 +0000 Subject: [PATCH 1/2] fix: fall back to internal storage when external storage is unavailable The app used to pick the external data directory unconditionally and then create the plugins directory without guarding against failures. On devices where the external filesystem cannot be created (e.g. Android/data dir is not creatable), the unhandled rejection aborted startup, leaving users stuck on the splash screen with a misleading "Update Android System WebView or Chrome" message. - Probe external storage with fs.stat() and fall back to internal storage - Wrap plugins directory creation in try/catch and fall back to internal - Request storage permissions only on SDK < 33, guarded by hasPermission - Guard editorManager.hasUnsavedFiles() access in exitAppMessage - Show an honest "storage is unavailable" startup message for storage errors --- src/lib/acode.js | 2 +- src/main.js | 66 +++++++++++++++++++++++++++++++++++++++++++----- www/index.html | 24 +++++++++++++++--- 3 files changed, 81 insertions(+), 11 deletions(-) diff --git a/src/lib/acode.js b/src/lib/acode.js index 6d9310d24..d5a0f2de6 100644 --- a/src/lib/acode.js +++ b/src/lib/acode.js @@ -718,7 +718,7 @@ class Acode { } get exitAppMessage() { - const numFiles = editorManager.hasUnsavedFiles(); + const numFiles = editorManager?.hasUnsavedFiles?.() ?? 0; if (numFiles) { return strings["unsaved files close app"]; } diff --git a/src/main.js b/src/main.js index 071757803..ebc287122 100644 --- a/src/main.js +++ b/src/main.js @@ -100,6 +100,20 @@ document.addEventListener("deviceready", onDeviceReady); document.addEventListener("backbutton", backButtonHandler); document.addEventListener("menubutton", menuButtonHandler); +async function ensurePermission(permission) { + try { + const granted = await helpers.promisify(system.hasPermission, permission); + if (!granted) { + await helpers.promisify(system.requestPermission, permission); + } + } catch (error) { + logger.log( + "error", + `Failed to request permission ${permission}: ${error.message || error}`, + ); + } +} + async function onDeviceReady() { await initEncodings(); // important to load encodings before anything else @@ -112,14 +126,37 @@ async function onDeviceReady() { dataDirectory, } = cordova.file; + async function resolveStorageDir(preferred, fallback) { + if (!preferred) return fallback; + const fs = fsOperation(preferred); + if (!fs) return fallback; + try { + await fs.stat(); + return preferred; + } catch (error) { + logger.log( + "warn", + `Storage dir unavailable (${preferred}), falling back to ${fallback}: ${error.message || error}`, + ); + return fallback; + } + } + window.app = document.body; window.root = tag.get("#root"); window.addedFolder = addedFolder; window.editorManager = null; window.toast = toast; window.ASSETS_DIRECTORY = Url.join(cordova.file.applicationDirectory, "www"); - window.DATA_STORAGE = externalDataDirectory || dataDirectory; - window.CACHE_STORAGE = externalCacheDirectory || cacheDirectory; + window.DATA_STORAGE = await resolveStorageDir( + externalDataDirectory, + dataDirectory, + ); + window.CACHE_STORAGE = await resolveStorageDir( + externalCacheDirectory, + cacheDirectory, + ); + window.PLUGIN_DIR = Url.join(DATA_STORAGE, "plugins"); window.KEYBINDING_FILE = Url.join(DATA_STORAGE, ".key-bindings.json"); window.log = logger.log.bind(logger); @@ -212,9 +249,11 @@ async function onDeviceReady() { await adRewards.init(); ensureAceCompatApi(); - system.requestPermission("android.permission.READ_EXTERNAL_STORAGE"); - system.requestPermission("android.permission.WRITE_EXTERNAL_STORAGE"); - system.requestPermission("android.permission.POST_NOTIFICATIONS"); + if (Number.isInteger(window.ANDROID_SDK_INT) && window.ANDROID_SDK_INT < 33) { + await ensurePermission("android.permission.READ_EXTERNAL_STORAGE"); + await ensurePermission("android.permission.WRITE_EXTERNAL_STORAGE"); + } + await ensurePermission("android.permission.POST_NOTIFICATIONS"); const { versionCode } = BuildInfo; @@ -227,7 +266,22 @@ async function onDeviceReady() { } if (!(await fsOperation(PLUGIN_DIR).exists())) { - await fsOperation(DATA_STORAGE).createDirectory("plugins"); + try { + await fsOperation(DATA_STORAGE).createDirectory("plugins"); + } catch (error) { + logger.log( + "error", + `Failed to create plugins directory, falling back to internal storage: ${error.message || error}`, + ); + window.DATA_STORAGE = dataDirectory; + window.CACHE_STORAGE = cacheDirectory; + window.PLUGIN_DIR = Url.join(window.DATA_STORAGE, "plugins"); + window.KEYBINDING_FILE = Url.join( + window.DATA_STORAGE, + ".key-bindings.json", + ); + await fsOperation(window.DATA_STORAGE).createDirectory("plugins"); + } } localStorage.versionCode = versionCode; diff --git a/www/index.html b/www/index.html index 07f170909..9c9cd88f4 100644 --- a/www/index.html +++ b/www/index.html @@ -141,12 +141,25 @@ ); } + function isStorageError(reason) { + if (!reason) return false; + return ( + reason.name === "FileError" || + reason.name === "NotFoundError" || + reason.name === "SecurityError" || + (typeof reason.code === "number" && reason.code >= 1 && reason.code <= 12) + ); + } + function handleStartupError(event) { if (!isStartupLoading()) return; var message = event && event.message ? event.message : "Startup error"; setStartupMessage( - "Acode failed to start. Update Android System WebView or Chrome. " + - message + isStorageError(event && event.error) + ? "Acode failed to start: storage is unavailable. Try restarting your device, or clearing the app data. " + + message + : "Acode failed to start. Update Android System WebView or Chrome. " + + message ); } @@ -156,8 +169,11 @@ var message = reason && reason.message ? reason.message : "Startup promise failed"; setStartupMessage( - "Acode failed to start. Update Android System WebView or Chrome. " + - message + isStorageError(reason) + ? "Acode failed to start: storage is unavailable. Try restarting your device, or clearing the app data. " + + message + : "Acode failed to start. Update Android System WebView or Chrome. " + + message ); } From 5b9145d776fffa0209887f2a1885ba8cb5b37abc Mon Sep 17 00:00:00 2001 From: ulite-Amr <48989143+ulite-Amr@users.noreply.github.com> Date: Fri, 21 Aug 2026 13:01:00 +0000 Subject: [PATCH 2/2] fix: detect storage errors via FileError instanceof instead of numeric code range --- www/index.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/index.html b/www/index.html index 9c9cd88f4..78fbbfd2b 100644 --- a/www/index.html +++ b/www/index.html @@ -147,7 +147,8 @@ reason.name === "FileError" || reason.name === "NotFoundError" || reason.name === "SecurityError" || - (typeof reason.code === "number" && reason.code >= 1 && reason.code <= 12) + (typeof window.FileError !== "undefined" && + reason instanceof window.FileError) ); }