From de239d8396fb25bf2b4c05570ca657e83fd0a7ff Mon Sep 17 00:00:00 2001 From: Tobias Gesellchen Date: Mon, 25 May 2026 22:03:53 +0200 Subject: [PATCH] fix(migration): show warning instead of error when URLs migrated to different target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When isXMLMigrated and isTelnetMigrated both return false, the UI fell through to the ❌ "Original (Bose cloud)" catch-all even if the speaker's on-device URLs clearly point to a non-Bose host. This happened when the service's Settings Target Domain and the URL written to the speaker had drifted — e.g. migrated with http://spotify:8000 but Settings URL is an IP address, or vice versa. Add isMigratedToOtherTarget() that checks parsed_current_config: if at least one URL field is set and none contain a known Bose cloud hostname, the speaker has been migrated, just not to the *current* Settings Target Domain. - urlConfigVerdict now returns ⚠️ "Migrated (URL mismatch)" in this case, showing the actual margeServerUrl and noting that the speaker must be able to reach the service there - The top-level migration status badge shows ⚠️ orange instead of ❌ red - The apply plan path is unchanged: it will re-point the speaker to the current Settings Target Domain, which is one valid resolution path Related to #408 Co-Authored-By: Claude Sonnet 4.6 --- pkg/service/handlers/web/js/script.js | 40 ++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/pkg/service/handlers/web/js/script.js b/pkg/service/handlers/web/js/script.js index 85e9bb1..7331633 100644 --- a/pkg/service/handlers/web/js/script.js +++ b/pkg/service/handlers/web/js/script.js @@ -1933,8 +1933,11 @@ async function showSummary(deviceId) { fillPlanURLInputs(defaultServiceURLs(targetUrl, {soundcorkMode: soundcork})); const migrationStatus = document.getElementById("migration-status"); - migrationStatus.innerText = summary.is_migrated ? "✅ Migrated to AfterTouch" : "❌ Not Migrated"; - migrationStatus.style.color = summary.is_migrated ? "green" : "red"; + const otherTarget = isMigratedToOtherTarget(summary); + migrationStatus.innerText = summary.is_migrated ? "✅ Migrated to AfterTouch" + : otherTarget ? "⚠️ Migrated (URL mismatch)" + : "❌ Not Migrated"; + migrationStatus.style.color = summary.is_migrated ? "green" : otherTarget ? "orange" : "red"; migrationStatus.style.fontWeight = "bold"; // Trust CA Now button now lives inside the state card's CA / TLS @@ -3736,6 +3739,23 @@ function formatURLPair(xmlVal, telVal) { return `xml: ${xmlVal} • live: ${telVal}`; } +// isMigratedToOtherTarget returns true when the speaker's on-device URLs have +// been changed away from Bose cloud but don't match the Settings Target Domain — +// i.e. the speaker was previously migrated to a different AfterTouch hostname +// (or the Settings Target Domain drifted after migration). In this state +// xml_migrated and telnet_migrated are both false (they only match the *current* +// Settings Target Domain), yet labelling the speaker "Original (Bose cloud)" +// would be factually wrong. +function isMigratedToOtherTarget(summary) { + if (summary.xml_migrated || summary.telnet_migrated) return false; + const boseDomains = ["streaming.bose.com", "stats.bose.com", "updates.bose.com", "bmx.bose.com"]; + const cfg = summary.parsed_current_config; + if (!cfg) return false; + const urls = [cfg.margeServerUrl, cfg.statsServerUrl, cfg.swUpdateUrl, cfg.bmxRegistryUrl].filter(Boolean); + if (urls.length === 0) return false; + return !urls.some(u => boseDomains.some(d => u.includes(d))); +} + // urlConfigVerdict reports the URL-flip axis in the context of DNS // interception, because "URLs still point at Bose" is only a problem // if nothing else is redirecting them. When the DNS hook (or, less @@ -3751,8 +3771,20 @@ function urlConfigVerdict(summary) { if (xml) return {icon: "✅", text: "AfterTouch URLs", note: "(XML only — telnet runtime may still hold the old URLs)"}; if (tel) return {icon: "✅", text: "AfterTouch URLs", note: "(telnet runtime — reboot to persist into the on-disk XML)"}; - // Neither URL-flip mechanism is active. Whether that's OK depends - // on whether DNS interception is doing the redirect. + // Neither URL-flip mechanism matched the Settings Target Domain. + // Before falling back to "Original (Bose cloud)", check whether the + // device's URLs already point somewhere that is clearly not Bose cloud + // — e.g. the speaker was migrated to "http://spotify:8000" but the + // Settings Target Domain is now an IP address (or vice versa). The + // speaker is effectively migrated; "Original (Bose cloud)" would be + // misleading and alarming. + if (isMigratedToOtherTarget(summary)) { + const margeUrl = (summary.parsed_current_config || {}).margeServerUrl || "unknown"; + if (dns) return {icon: "✅", text: "Migrated (URL mismatch)", note: `— device has ${margeUrl}, also intercepted via DNS`}; + return {icon: "⚠️", text: "Migrated (URL mismatch)", note: `— device has ${margeUrl}; the speaker must be able to reach the service there. Update Settings Target Domain to match, or apply the plan to change the device URL`}; + } + + // URLs are genuinely pointing at Bose cloud (or the config is unreadable). if (dns) return {icon: "✅", text: "Original (Bose cloud)", note: "— intercepted via DNS, device reaches AfterTouch"}; return {icon: "❌", text: "Original (Bose cloud)", note: "— not intercepted, device will reach the real Bose cloud"}; }