From b779376f5bf00c267d2467da5fb5cfe0d414170a Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Tue, 29 Sep 2026 00:53:18 +1000 Subject: [PATCH] Devices: fixes from review round 22 - An upload's answer arriving after a switch opens nothing; the APK alternatives dialog installs on the headset the APK was checked for. - A handshake that goes silent (e.g. a jump host's forward hanging) moves on to the next address. Co-Authored-By: Claude Opus 5.5 (1M context) --- ui/frame_link.py | 2 +- ui/index.html | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/ui/frame_link.py b/ui/frame_link.py index 1867355..4a050b9 100644 --- a/ui/frame_link.py +++ b/ui/frame_link.py @@ -835,7 +835,7 @@ class Link: proc.kill() self.fail(step, self.explain(f"Timed out talking to {alias}") or "The headset took too long to answer.", f"Timed out talking to {alias}") - return "next" if step in ("ssh", "identity") else "stop" + return "next" # silence is about this address (or a jump host's forward to it) try: line = lines.get(timeout=min(left, 0.25)) except queue.Empty: diff --git a/ui/index.html b/ui/index.html index f281d4c..2dcb0a3 100644 --- a/ui/index.html +++ b/ui/index.html @@ -1549,10 +1549,16 @@ function upload(file, mode, dev = window.connDevice) { const bar = $("prog").firstElementChild; $("prog").style.display = "block"; bar.style.width = "0"; xhr.upload.onprogress = e => { if (e.lengthComputable) bar.style.width = (100 * e.loaded / e.total) + "%"; }; + const gen = devGen; xhr.onload = () => { $("prog").style.display = "none"; let data; try { data = JSON.parse(xhr.responseText); } catch { data = { error: `HTTP ${xhr.status}` }; } - if (data.apk?.blocker && xhr.status >= 300) checkApkAlternatives(data.apk); + if (gen !== devGen) { // the app switched headsets meanwhile: nothing to offer about this one here + const err = new Error(data.error || "Switched headsets"); + err.stale = true; + return xhr.status < 300 ? resolve(data) : reject(err); + } + if (data.apk?.blocker && xhr.status >= 300) checkApkAlternatives(data.apk, dev); xhr.status < 300 ? resolve(data) : reject(new Error(data.error)); }; xhr.onerror = () => { $("prog").style.display = "none"; reject(new Error("network error")); }; @@ -1560,7 +1566,7 @@ function upload(file, mode, dev = window.connDevice) { }); } let apkLookup = 0; -async function checkApkAlternatives(apk) { +async function checkApkAlternatives(apk, dev = window.connDevice) { const lookup = ++apkLookup; $("apkAltReason").textContent = apk.blocker; $("apkAltVersions").textContent = "Checking F-Droid for older versions…"; @@ -1571,12 +1577,12 @@ async function checkApkAlternatives(apk) { if (apk.version_code != null) query.set("code", apk.version_code); try { const result = await api(`/api/apk-versions?${query}`); - if (lookup === apkLookup && $("apkAltDlg").open) showApkAlternatives(apk.blocker, result); + if (lookup === apkLookup && $("apkAltDlg").open) showApkAlternatives(apk.blocker, result, dev); } catch (e) { if (lookup === apkLookup) $("apkAltVersions").textContent = e.message; } } -function showApkAlternatives(reason, result) { +function showApkAlternatives(reason, result, dev = window.connDevice) { $("apkAltReason").textContent = reason; $("apkAltNote").textContent = `${result.versions.length} of ${result.total} compatible versions. ${result.note}`; $("apkAltErrors").textContent = result.errors.join(" · "); @@ -1591,9 +1597,10 @@ function showApkAlternatives(reason, result) { const v = result.versions[+b.dataset.version]; if (installing.has(result.package)) return; b.disabled = true; b.textContent = "Installing…"; + // For the headset the APK was checked against: refused (409) if the app has switched since. const res = await runJob(`Install ${result.package} ${v.version}`, result.package, () => api("/api/android", { action: "install", package: result.package, url: v.url - })); + }, dev)); if (res) $("apkAltDlg").close(); else { b.disabled = false; b.textContent = "Install"; } await loadAndroid(); if (cat.apps) { const y = window.scrollY; filterCatalog(); window.scrollTo(0, y); }