feat(update): App laedt Updates per HTTP mit Fortschrittsanzeige statt als 65-MB-WebSocket-Block
Bisher kam die APK als EINE WebSocket-Nachricht (~65 MB Base64) mit starrem 60-s-Timeout — ueber den externen Proxy (Heim-Upload → Hetzner → Handy) zu knapp, Abbruch ohne Fortschritt. RVS: GET/HEAD /update/latest.apk — nur mit gueltigem Raum-Token im Header X-RVS-Token (sonst 403), Content-Length, Range/206 zum Fortsetzen. Version im Dateinamen wird ohne angehaengten Punkt erkannt. App: Download per HTTP (react-native-fs) mit Fortschritt in % und MB, Abbrechen-Knopf, Abbruch nur bei 30 s ohne Daten statt fester Gesamtzeit, 3 Versuche, Groessen-Check gegen Content-Length. Alter RVS (404): Fallback auf den WebSocket-Weg mit 5 min Timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+46
-1
@@ -403,6 +403,51 @@ function handleHttpRequest(req, res) {
|
||||
return;
|
||||
}
|
||||
|
||||
// App-Update als normaler HTTP-Download (statt eines riesigen WS-Blocks):
|
||||
// Content-Length fuer die Fortschrittsanzeige, Range zum Fortsetzen.
|
||||
// Nur fuer Geraete mit gueltigem Raum-Token (Header X-RVS-Token).
|
||||
if ((req.method === "GET" || req.method === "HEAD") && pathname === "/update/latest.apk") {
|
||||
const tok = String(req.headers["x-rvs-token"] || "");
|
||||
if (!tok || !rooms.has(tok)) {
|
||||
res.writeHead(403, { "Content-Type": "text/plain" });
|
||||
res.end("Forbidden\n");
|
||||
return;
|
||||
}
|
||||
const apk = getLatestAPK();
|
||||
if (!apk) {
|
||||
res.writeHead(404, { "Content-Type": "text/plain" });
|
||||
res.end("Keine APK verfuegbar\n");
|
||||
return;
|
||||
}
|
||||
const size = fs.statSync(apk.path).size;
|
||||
const headers = {
|
||||
"Content-Type": "application/vnd.android.package-archive",
|
||||
"Content-Disposition": `attachment; filename="ARIA-v${apk.version}.apk"`,
|
||||
"Accept-Ranges": "bytes",
|
||||
"Cache-Control": "no-store",
|
||||
"X-APK-Version": apk.version,
|
||||
};
|
||||
let start = 0, end = size - 1, status = 200;
|
||||
const m = /^bytes=(\d*)-(\d*)$/.exec(String(req.headers.range || ""));
|
||||
if (m && (m[1] || m[2])) {
|
||||
if (m[1]) { start = parseInt(m[1], 10); if (m[2]) end = Math.min(parseInt(m[2], 10), size - 1); }
|
||||
else { start = Math.max(0, size - parseInt(m[2], 10)); }
|
||||
if (start > end || start >= size) {
|
||||
res.writeHead(416, { "Content-Range": `bytes */${size}` });
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
status = 206;
|
||||
headers["Content-Range"] = `bytes ${start}-${end}/${size}`;
|
||||
}
|
||||
headers["Content-Length"] = end - start + 1;
|
||||
res.writeHead(status, headers);
|
||||
if (req.method === "HEAD") { res.end(); return; }
|
||||
log(`APK-Download (HTTP) v${apk.version}${status === 206 ? ` ab Byte ${start}` : ""}`);
|
||||
fs.createReadStream(apk.path, { start, end }).pipe(res);
|
||||
return;
|
||||
}
|
||||
|
||||
// Health-Endpoint
|
||||
if (req.method === "GET" && pathname === "/health") {
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
@@ -743,7 +788,7 @@ function getLatestAPK() {
|
||||
.filter(f => f.endsWith(".apk"))
|
||||
.map(f => {
|
||||
// ARIA-v0.0.2.3.apk oder ARIA-Cockpit-release.apk
|
||||
const match = f.match(/(\d+\.\d+\.\d+[\.\d]*)/);
|
||||
const match = f.match(/(\d+(?:\.\d+){2,})/);
|
||||
return { file: f, path: path.join(UPDATES_DIR, f), version: match ? match[1] : null };
|
||||
})
|
||||
.filter(f => f.version)
|
||||
|
||||
Reference in New Issue
Block a user