diff --git a/packages/client-android/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/packages/client-android/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 21a2fcc..95483c9 100644 Binary files a/packages/client-android/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/packages/client-android/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/packages/client-android/android/app/src/main/java/dev/yuzu/gca/MainActivity.kt b/packages/client-android/android/app/src/main/java/dev/yuzu/gca/MainActivity.kt index dea4bfa..419eabe 100644 --- a/packages/client-android/android/app/src/main/java/dev/yuzu/gca/MainActivity.kt +++ b/packages/client-android/android/app/src/main/java/dev/yuzu/gca/MainActivity.kt @@ -3,8 +3,11 @@ package dev.yuzu.gca import android.os.Bundle import android.util.Log import android.webkit.WebView +import android.webkit.WebViewClient import androidx.appcompat.app.AppCompatActivity import java.io.File +import java.net.HttpURLConnection +import java.net.URL class MainActivity : AppCompatActivity() { @@ -20,10 +23,17 @@ class MainActivity : AppCompatActivity() { settings.javaScriptEnabled = true settings.domStorageEnabled = true settings.allowFileAccess = true - settings.allowFileAccessFromFileURLs = true - settings.allowUniversalAccessFromFileURLs = true - settings.mixedContentMode = android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW addJavascriptInterface(Bridge(this@MainActivity), "Android") + webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView, url: String) { + view.evaluateJavascript(""" + var meta = document.createElement('meta'); + meta.httpEquiv = 'Content-Security-Policy'; + meta.content = "default-src 'none'; script-src 'unsafe-inline'; connect-src 'none'"; + document.head.appendChild(meta); + """.trimIndent(), null) + } + } if (cacheFile.exists()) { val html = cacheFile.readText() @@ -47,17 +57,57 @@ class MainActivity : AppCompatActivity() { ctx.cacheFile.writeText(html) Log.i(TAG, "Hot-update HTML saved (${html.length}B)") } + @android.webkit.JavascriptInterface fun clearCache() { ctx.cacheFile.delete() Log.i(TAG, "Cache cleared") } + @android.webkit.JavascriptInterface fun sha256(input: String): String { val md = java.security.MessageDigest.getInstance("SHA-256") val digest = md.digest(input.toByteArray(Charsets.UTF_8)) return digest.joinToString("") { "%02x".format(it) } } + + /** Fetch manifest JSON from Gitea — proxy to avoid CORS in WebView */ + @android.webkit.JavascriptInterface + fun fetchManifest(channel: String): String { + val url = "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/ota/h5/manifest-$channel.json" + Log.i(TAG, "Bridge fetching $url") + return try { + val conn = URL(url).openConnection() as HttpURLConnection + conn.connectTimeout = 10_000 + conn.readTimeout = 10_000 + conn.instanceFollowRedirects = true + val code = conn.responseCode + if (code != 200) throw Exception("HTTP $code") + conn.inputStream.bufferedReader().readText() + } catch (e: Exception) { + Log.e(TAG, "Bridge fetch failed: ${e.message}") + """{"error":"${e.message}"}""" + } + } + + /** Fetch text file from Gitea — proxy for version.txt etc */ + @android.webkit.JavascriptInterface + fun fetchText(path: String): String { + val url = "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/ota/$path" + Log.i(TAG, "Bridge fetching $url") + return try { + val conn = URL(url).openConnection() as HttpURLConnection + conn.connectTimeout = 10_000 + conn.readTimeout = 10_000 + conn.instanceFollowRedirects = true + val code = conn.responseCode + if (code != 200) throw Exception("HTTP $code") + conn.inputStream.bufferedReader().readText() + } catch (e: Exception) { + Log.e(TAG, "Bridge fetchText($path) failed: ${e.message}") + "ERROR: ${e.message}" + } + } } companion object { private const val TAG = "GCA-H5" }