fix: 审查修复—cleartext+package.json main+dead HTML+comment
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
|
||||||
<application
|
<application android:usesCleartextTraffic="true"
|
||||||
android:label="Yuzu GCA v0.1.0"
|
android:label="Yuzu GCA v0.1.0"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width">
|
|
||||||
<style>
|
|
||||||
body{font-family:monospace;background:#0f172a;color:#e2e8f0;padding:16px;margin:0}
|
|
||||||
h1{font-size:18px;color:#38bdf8}
|
|
||||||
.btn{display:block;width:100%;padding:14px;margin:8px 0;border:none;border-radius:8px;font-size:16px;cursor:pointer}
|
|
||||||
.btn-check{background:#1e40af;color:#fff}
|
|
||||||
.btn-download{background:#15803d;color:#fff}
|
|
||||||
.log{background:#1e293b;padding:12px;border-radius:8px;margin-top:12px;max-height:300px;overflow-y:auto;font-size:12px;white-space:pre-wrap}
|
|
||||||
.info{color:#94a3b8;font-size:12px;margin:4px 0}
|
|
||||||
</style></head><body>
|
|
||||||
<h1>Yuzu GCA</h1>
|
|
||||||
<div class="info">version <span id="ver">v0.1.0-dav-android-5</span></div>
|
|
||||||
<button class="btn btn-check" onclick="checkUpdate()">检查更新</button>
|
|
||||||
<button class="btn btn-download" onclick="applyUpdate()" id="btnApply" disabled>下载更新</button>
|
|
||||||
<div id="apkLink" style="display:none;margin-top:8px;font-size:12px;color:#38bdf8"></div>
|
|
||||||
<div class="log" id="log"></div>
|
|
||||||
<script>
|
|
||||||
var dlUrl='',dlVer='',dlSha='';
|
|
||||||
var channel=localStorage.getItem('gca_channel')||'stable';
|
|
||||||
function log(m){var l=document.getElementById('log');l.innerText+='[V5]'+m+'\n';l.scrollTop=l.scrollHeight}
|
|
||||||
|
|
||||||
function checkUpdate(){
|
|
||||||
log('channel: '+channel);
|
|
||||||
var manifestUrl=Android.fetchText('version.txt').trim();
|
|
||||||
if(!manifestUrl.startsWith('http')){log('ERR: invalid version.txt');return}
|
|
||||||
var j=JSON.parse(Android.fetchManifest(channel));
|
|
||||||
if(j.error){log('ERR: '+j.error);return}
|
|
||||||
var h5=j.h5;log('latest: '+h5.latest+' min: '+h5.min_version);
|
|
||||||
var apk=j.apk;if(apk){var m=(document.getElementById('ver').innerText||'').match(/(\d+)$/);if(m){var curCode=parseInt(m[0]);if(apk.versionCode>curCode){log('APK UPDATE:v'+apk.version);document.getElementById('apkLink').innerHTML='<a href=\"'+apk.url+'\">Download APK v'+apk.version+'</a>';document.getElementById('apkLink').style.display='block'}}}
|
|
||||||
var v=(h5.min_version||'').replace(/[^0-9]/g,''),my=(document.getElementById('ver').innerText||'').replace(/[^0-9]/g,'');
|
|
||||||
if(parseInt(my||'0')<parseInt(v||'0')){log('ERR: APK too old (need v'+h5.min_version+')');return}
|
|
||||||
dlUrl=h5.url;dlSha=h5.sha256||'';
|
|
||||||
if(!dlSha||/^(0{64}|TBD)/.test(dlSha)){log('ERR: channel no build');return}
|
|
||||||
if(h5.latest!==document.getElementById('ver').innerText&&h5.latest!=='v'+document.getElementById('ver').innerText){
|
|
||||||
dlVer=h5.latest;document.getElementById('btnApply').disabled=false;log('*** NEW VERSION ***')}
|
|
||||||
else log('up to date')
|
|
||||||
}
|
|
||||||
function applyUpdate(){
|
|
||||||
log('downloading...');
|
|
||||||
var p=dlUrl.split('/h5/');var rel=p.length>1?'h5/'+p[1]:dlUrl;
|
|
||||||
var t=Android.fetchText(rel);
|
|
||||||
if(typeof t!=='string'||t.startsWith('ERROR:')){log('ERR: '+t);return}
|
|
||||||
var h=Android.sha256(t);
|
|
||||||
if(h!==dlSha){log('SHA256 MISMATCH!');return}
|
|
||||||
Android.saveCache(t);log('OK! restart app to apply')
|
|
||||||
}
|
|
||||||
</script></body></html>
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width">
|
|
||||||
<style>
|
|
||||||
body{font-family:monospace;background:#0f172a;color:#e2e8f0;padding:16px;margin:0}
|
|
||||||
h1{font-size:18px;color:#38bdf8}
|
|
||||||
.btn{display:block;width:100%;padding:14px;margin:8px 0;border:none;border-radius:8px;font-size:16px;cursor:pointer}
|
|
||||||
.btn-check{background:#1e40af;color:#fff}
|
|
||||||
.btn-download{background:#15803d;color:#fff}
|
|
||||||
.log{background:#1e293b;padding:12px;border-radius:8px;margin-top:12px;max-height:300px;overflow-y:auto;font-size:12px;white-space:pre-wrap}
|
|
||||||
.info{color:#94a3b8;font-size:12px;margin:4px 0}
|
|
||||||
</style></head><body>
|
|
||||||
<h1>Yuzu GCA</h1>
|
|
||||||
<div class="info">version <span id="ver">v0.1.0-dav-android-5</span></div>
|
|
||||||
<button class="btn btn-check" onclick="checkUpdate()">检查更新</button>
|
|
||||||
<button class="btn btn-download" onclick="applyUpdate()" id="btnApply" disabled>下载更新</button>
|
|
||||||
<div class="log" id="log"></div>
|
|
||||||
<script>
|
|
||||||
var dlUrl='',dlVer='',dlSha='';
|
|
||||||
var channel=localStorage.getItem('gca_channel')||'stable';
|
|
||||||
function log(m){document.getElementById('log').innerText+=m+'\n'}
|
|
||||||
|
|
||||||
function checkUpdate(){
|
|
||||||
log('channel: '+channel);
|
|
||||||
var manifestUrl=Android.fetchText('version.txt').trim();
|
|
||||||
if(!manifestUrl.startsWith('http')){log('ERR: invalid version.txt');return}
|
|
||||||
var j=JSON.parse(Android.fetchManifest(channel));
|
|
||||||
if(j.error){log('ERR: '+j.error);return}
|
|
||||||
var h5=j.h5;log('latest: '+h5.latest+' min: '+h5.min_version);
|
|
||||||
var v=(h5.min_version||'').replace(/[^0-9]/g,''),my=(document.getElementById('ver').innerText||'').replace(/[^0-9]/g,'');
|
|
||||||
if(parseInt(my||'0')<parseInt(v||'0')){log('ERR: APK too old (need v'+h5.min_version+')');return}
|
|
||||||
dlUrl=h5.url;dlSha=h5.sha256||'';
|
|
||||||
if(!dlSha||/^0{64}$/.test(dlSha)){log('ERR: channel no build');return}
|
|
||||||
if(h5.latest!==document.getElementById('ver').innerText&&h5.latest!=='v'+document.getElementById('ver').innerText){
|
|
||||||
dlVer=h5.latest;document.getElementById('btnApply').disabled=false;log('*** NEW VERSION ***')}
|
|
||||||
else log('up to date')
|
|
||||||
}
|
|
||||||
function applyUpdate(){
|
|
||||||
log('downloading...');
|
|
||||||
var p=dlUrl.split('/h5/');var rel=p.length>1?'h5/'+p[1]:dlUrl;
|
|
||||||
var t=Android.fetchText(rel);
|
|
||||||
if(typeof t!=='string'||t.startsWith('ERROR:')){log('ERR: '+t);return}
|
|
||||||
var h=Android.sha256(t);
|
|
||||||
if(h!==dlSha){log('SHA256 MISMATCH!');return}
|
|
||||||
Android.saveCache(t);log('OK! restart app to apply')
|
|
||||||
}
|
|
||||||
</script></body></html>
|
|
||||||
Binary file not shown.
@@ -1,76 +1,23 @@
|
|||||||
package dev.yuzu.gca
|
package dev.yuzu.gca
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import java.io.File
|
|
||||||
import java.net.HttpURLConnection
|
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal MainActivity 鈥?loads React Native dev server.
|
||||||
|
* Run `npx expo start ` and APK connects to it.
|
||||||
|
* No OTA, no Bridge, no complexity.
|
||||||
|
*/
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
WebView(this).apply {
|
||||||
val cacheFile = File(filesDir, "gca_hot.html")
|
|
||||||
val wv = WebView(this).apply {
|
|
||||||
settings.javaScriptEnabled = true
|
settings.javaScriptEnabled = true
|
||||||
settings.domStorageEnabled = true
|
settings.domStorageEnabled = true
|
||||||
settings.allowFileAccess = true
|
// Load Expo dev server on local network
|
||||||
addJavascriptInterface(Bridge(this@MainActivity), "Android")
|
// Change to your PC's LAN IP (e.g. 192.168.x.x)
|
||||||
|
loadUrl("http://10.0.2.2:8081")
|
||||||
if (cacheFile.exists()) {
|
}.also { setContentView(it) }
|
||||||
val html = cacheFile.readText()
|
|
||||||
loadDataWithBaseURL("file:///android_asset/", html, "text/html", "UTF-8", null)
|
|
||||||
Log.i(TAG, "Loaded cached HTML (${cacheFile.length()}B)")
|
|
||||||
} else {
|
|
||||||
val html = assets.open("frame.html").bufferedReader().readText()
|
|
||||||
loadDataWithBaseURL("file:///android_asset/", html, "text/html", "utf-8", null)
|
|
||||||
Log.i(TAG, "Loaded frame.html (${html.length}B)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setContentView(wv)
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class Bridge(private val ctx: MainActivity) {
|
|
||||||
@android.webkit.JavascriptInterface
|
|
||||||
fun saveCache(html: String) {
|
|
||||||
if (html.length > 500_000) { Log.w(TAG, "Cache too large"); return }
|
|
||||||
File(ctx.filesDir, "gca_hot.html").writeText(html)
|
|
||||||
Log.i(TAG, "Saved ${html.length}B")
|
|
||||||
}
|
|
||||||
|
|
||||||
@android.webkit.JavascriptInterface
|
|
||||||
fun clearCache() { File(ctx.filesDir, "gca_hot.html").delete() }
|
|
||||||
|
|
||||||
@android.webkit.JavascriptInterface
|
|
||||||
fun sha256(input: String): String {
|
|
||||||
val md = java.security.MessageDigest.getInstance("SHA-256")
|
|
||||||
return md.digest(input.toByteArray(Charsets.UTF_8)).joinToString("") { "%02x".format(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@android.webkit.JavascriptInterface
|
|
||||||
fun fetchManifest(channel: String): String {
|
|
||||||
val url = "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/ota/manifest.json"
|
|
||||||
return try {
|
|
||||||
val conn = URL(url).openConnection() as HttpURLConnection
|
|
||||||
conn.connectTimeout = 10000; conn.readTimeout = 10000
|
|
||||||
val raw = conn.inputStream.bufferedReader().readText()
|
|
||||||
org.json.JSONObject(raw).getJSONObject(channel).toString()
|
|
||||||
} catch (e: Exception) { """{"error":"${e.message}"}""" }
|
|
||||||
}
|
|
||||||
|
|
||||||
@android.webkit.JavascriptInterface
|
|
||||||
fun fetchText(path: String): String {
|
|
||||||
val safe = path.replace("..", "").replace("..", "").replace("\\", "/")
|
|
||||||
val url = "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/ota/$safe"
|
|
||||||
return try {
|
|
||||||
URL(url).openConnection().apply { connectTimeout = 10000; readTimeout = 10000 }
|
|
||||||
.getInputStream().bufferedReader().readText()
|
|
||||||
} catch (e: Exception) { "ERROR: ${e.message}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object { private const val TAG = "GCA" }
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,43 +1,4 @@
|
|||||||
var React = require("react");
|
import { registerRootComponent } from 'expo';
|
||||||
var View = require("react-native").View;
|
import App from './App';
|
||||||
var Text = require("react-native").Text;
|
|
||||||
var AppRegistry = require("react-native").AppRegistry;
|
|
||||||
var Updates = require("expo-updates");
|
|
||||||
|
|
||||||
function App() {
|
registerRootComponent(App);
|
||||||
var [msg, setMsg] = React.useState("checking...");
|
|
||||||
|
|
||||||
React.useEffect(function() {
|
|
||||||
async function check() {
|
|
||||||
try {
|
|
||||||
var rv = Updates.runtimeVersion || "empty";
|
|
||||||
var ch = Updates.channel || "default";
|
|
||||||
var id = Updates.updateId || "none";
|
|
||||||
var emb = Updates.isEmbeddedLaunch ? "embedded" : "not-embedded";
|
|
||||||
var info = "rv=" + rv + " ch=" + ch + " id=" + id + " emb=" + emb;
|
|
||||||
|
|
||||||
var result = await Updates.checkForUpdateAsync();
|
|
||||||
if (result.isAvailable) {
|
|
||||||
info += "\nUPDATE AVAILABLE! downloading...";
|
|
||||||
await Updates.fetchUpdateAsync();
|
|
||||||
info += "\ndownloaded. Restart app.";
|
|
||||||
await Updates.reloadAsync();
|
|
||||||
} else {
|
|
||||||
info += "\nno update available";
|
|
||||||
}
|
|
||||||
setMsg(info);
|
|
||||||
} catch(e) {
|
|
||||||
setMsg("Error: " + e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
check();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return React.createElement(View, {style: {flex:1,backgroundColor:"#0f172a",alignItems:"center",justifyContent:"center"}},
|
|
||||||
React.createElement(Text, {style: {fontSize:28,fontWeight:"800",color:"#e2e8f0"}}, "Yuzu GCA"),
|
|
||||||
React.createElement(Text, {style: {fontSize:14,color:"#64748b",marginTop:8}}, "v0.1.0-dav-android-10"),
|
|
||||||
React.createElement(Text, {style: {fontSize:10,color:"#3b82f6",marginTop:8,textAlign:"center"}}, msg)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
AppRegistry.registerComponent("main", function() { return App; });
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@yuzu-gca/client-android",
|
"name": "@yuzu-gca/client-android",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "expo-router/entry",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"android": "expo start --android",
|
"android": "expo start --android",
|
||||||
|
|||||||
Reference in New Issue
Block a user