fix(migration): show warning instead of error when URLs migrated to different target

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 <noreply@anthropic.com>
This commit is contained in:
Tobias Gesellchen
2026-05-25 22:19:45 +02:00
co-authored by Claude Sonnet 4.6
parent 7d9f3d6a39
commit de239d8396
+36 -4
View File
@@ -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"};
}