← Commit history

sandbox-embed banner + Detect responsiveness + em-dash cleanup

John Lauer ·fa2f5fb5b1 ·2mo ago ·parent c8b7e81
1 file changed +33−6
web-control-demo.html+33−6
@@ -61,6 +61,13 @@     &middot; ab: <span id="ver" class="pill">unknown</span>     &middot; web control: <span id="wc" class="pill">?</span>   </div>+  <div id="sandbox-warn" style="display:none; margin:10px 0; padding:10px 14px; border:1px solid #f85149; border-radius:8px; background:rgba(248,81,73,.08); color:#f85149; font-size:14px;">+    <b>This embed cannot reach Adom Bridge.</b> The page is running in a sandboxed view (origin <code>null</code>)+    whose security policy blocks every fetch to <code>127.0.0.1</code>, so ALL buttons here will fail with+    &ldquo;Failed to fetch&rdquo;. Open the page directly instead:+    <a href="http://127.0.0.1:24681/" target="_blank" rel="noopener" style="color:#58a6ff">http://127.0.0.1:24681/</a>+    (any real browser tab works).+  </div>    <div class="bar">     <label for="target">Run on machine:</label>@@ -163,6 +170,16 @@ for (const w of mine) {   var dot = document.getElementById("dot");   var sel = document.getElementById("target");   document.getElementById("origin").textContent = location.origin || "(null)";+  // A sandboxed embed (wiki readme iframe, an in-app webview) runs at origin+  // "null" and its CSP blocks EVERY loopback fetch - the whole page is dead+  // there. Say so up front instead of letting each button fail cryptically.+  var SANDBOXED = !location.origin || location.origin === "null";+  function sandboxWarn() { document.getElementById("sandbox-warn").style.display = "block"; }+  if (SANDBOXED) sandboxWarn();+  // Any fetch that dies with the browser's opaque TypeError (CSP block, no+  // listener, mixed-content) escalates the banner too - the single most+  // common cause is the sandboxed-embed case above.+  function fetchDied(e) { if (/failed to fetch|networkerror/i.test(String(e))) sandboxWarn(); }   function logLine(m) { log.textContent += "\n" + m; log.scrollTop = log.scrollHeight; }   function chosenTarget() { return sel.value || null; }   // WHO is "this machine": ab 2.0.53+ reports its relay client name at@@ -205,7 +222,7 @@ for (const w of mine) {     logLine("GET " + AB + path);     fetch(AB + path).then(function (r) { return r.json().then(function (j) { return { s: r.status, j: j }; }); })       .then(function (res) { logLine("  <- HTTP " + res.s); if (res.s === 200) setRes(el, "ok", "OK", summarize ? summarize(res.j) : ""); else setRes(el, "err", "HTTP " + res.s, ""); })-      .catch(function (e) { setRes(el, "err", "unreachable", String(e)); });+      .catch(function (e) { fetchDied(e); setRes(el, "err", "unreachable", SANDBOXED ? "sandboxed embed blocks loopback - open http://127.0.0.1:24681 directly (see banner)" : String(e)); });   }   function doVerb(el, command, args, target, summarize) {     var body = { command: command }; if (args) body.args = args;@@ -221,7 +238,7 @@ for (const w of mine) {         else if (res.j && res.j.errorCode === "web_control_disabled") setRes(el, "err", "Refused: web control OFF", "re-enable in Settings &rarr; Web Control");         else setRes(el, "err", "Not approved", (res.j && (res.j.error || res.j.errorCode)) || ("HTTP " + res.s));       })-      .catch(function (e) { setRes(el, "err", "blocked", String(e)); });+      .catch(function (e) { fetchDied(e); setRes(el, "err", "blocked", SANDBOXED ? "sandboxed embed blocks loopback - open http://127.0.0.1:24681 directly (see banner)" : String(e)); });   }   // POST that returns the flattened JSON. Optional target.   function postVerb(command, args, target) {@@ -503,7 +520,7 @@ for (const w of mine) {       var want = pickTitle.toLowerCase();       var picked = withImg.filter(function (s) { return ((s.title || s.label || "") + "").toLowerCase().indexOf(want) >= 0; });       if (picked.length) { withImg = picked; }-      else clog(c, '  (no window titled like "' + pickTitle + '" — showing all ' + withImg.length + ')');+      else clog(c, '  (no window titled like "' + pickTitle + '" - showing all ' + withImg.length + ')');     }     clog(c, "Rendering " + withImg.length + " window(s).");     withImg.forEach(function (s) { addShot(c.card, s.image || s.base64 || s.png, s.title || s.label || s.window || "window"); });@@ -650,7 +667,7 @@ for (const w of mine) {         var on = wc.enabled !== false; el.textContent = on ? "ON" : "OFF"; el.style.color = on ? "#2ea043" : "#f85149";         setRes(cDetect.res, "ok", "Running: v" + (s.version || "?"), "web control " + (on ? "ON" : "OFF"));       });-    }).catch(function (e) { dot.style.background = "#484f58"; setRes(cDetect.res, "err", "not reachable", String(e)); });+    }).catch(function (e) { fetchDied(e); dot.style.background = "#484f58"; setRes(cDetect.res, "err", "not reachable", SANDBOXED ? "sandboxed embed blocks loopback - open http://127.0.0.1:24681 directly (see banner)" : String(e)); });   };   var cStatus = card("grp-read", "read", "Read status");   cStatus.btn.onclick = function () { doGet(cStatus.res, "/status", function (s) { var st = n(s.bridgeUpdates && s.bridgeUpdates.staleCount); return "v" + s.version + " - web control " + ((s.webControl && s.webControl.enabled === false) ? "OFF" : "ON") + (st ? (" - " + st + " bridge update(s)") : ""); }); };@@ -798,7 +815,7 @@ for (const w of mine) {     { label: "Library (" + PART.name + " in '" + PART.kicadLib + "')", cmd: "kicad_show_library", args: function () { return { libraryName: PART.kicadLib }; }, stage: stageKicadPart, pick: "symbol editor", note: "the library holding the symbol + footprint + 3D model" },     { label: "Schematic (just the " + PART.name + ")", cmd: "kicad_show_schematic", args: function () { return { filePath: (PART.kicadProject || {}).schPath }; }, stage: stageKicadProject, pick: "schematic editor", note: "the generated single-component schematic (kicad_make_part_project)" },     { label: "2D board (part placed, tight edge cuts)", cmd: "kicad_show_2d_board", args: function () { return { filePath: (PART.kicadProject || {}).pcbPath }; }, stage: stageKicadProject, pick: "pcb editor", note: "the part-dominant board" },-    { label: "3D board (real 3D body on the board)", cmd: "kicad_show_3d_board", args: function () { return { filePath: (PART.kicadProject || {}).pcbPath }; }, stage: stageKicadProject, pick: "3d viewer", note: "the populated board in 3D — the whole ladder underneath it" },+    { label: "3D board (real 3D body on the board)", cmd: "kicad_show_3d_board", args: function () { return { filePath: (PART.kicadProject || {}).pcbPath }; }, stage: stageKicadProject, pick: "3d viewer", note: "the populated board in 3D - the whole ladder underneath it" },     { label: "Project (top of the tree)", cmd: "kicad_show_project", args: function () { return { filePath: (PART.kicadProject || {}).proPath }; }, stage: stageKicadProject, pick: "kicad 10", note: "the Project Manager: schematic + board hang off it" }   ].forEach(function (s) {     var cc = card("grp-kicad", "verb", s.label);@@ -939,6 +956,10 @@ for (const w of mine) {    // ---- target picker ----   document.getElementById("refresh-targets").onclick = function () {+    var btn = this, pill = document.getElementById("target-count");+    // The user must SEE the click did something, instantly - and see the+    // failure in the pill, not buried in the collapsed log (John, 2026-08-16).+    btn.disabled = true; pill.style.color = ""; pill.textContent = "detecting ... (approve the Adom Bridge prompt if one appears)";     logLine("POST /command {targets}");     // Resolve the self name first so the dedupe + "self" label are right even     // when Detect was never clicked (an old ab just returns no machine block).@@ -946,6 +967,7 @@ for (const w of mine) {       .then(function () { return fetch(AB + "/command", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ command: "targets" }) }); })       .then(function (r) { return r.json(); })       .then(function (j) {+        btn.disabled = false;         // The direct API answers with clients[]; the relay-CLI shape is targets[].         var d = j.data || j; var list = d.targets || d.clients || [];         // The relay lists THIS machine by name too - that row is redundant with@@ -959,7 +981,12 @@ for (const w of mine) {         if (peers.length >= 1) { var a = document.createElement("option"); a.value = "all"; a.textContent = "ALL machines (fan out)"; sel.appendChild(a); }         document.getElementById("target-count").textContent = selfLabel() + (peers.length ? " + " + peers.length + " more" : " only");       })-      .catch(function (e) { logLine("targets failed: " + e); });+      .catch(function (e) {+        btn.disabled = false; fetchDied(e);+        pill.style.color = "#f85149";+        pill.textContent = SANDBOXED ? "blocked: sandboxed embed (see banner)" : ("detect failed: " + String(e).slice(0, 80));+        logLine("targets failed: " + e);+      });   }; })(); </script>