From 1b810a5f0a44de873ad49f64a63a8d54c03f8d8c Mon Sep 17 00:00:00 2001 From: Mike Swanson Date: Mon, 29 Dec 2025 21:24:12 -0700 Subject: [PATCH] Native viewer: use guruconnect:// protocol with fallback to download --- server/static/dashboard.html | 95 ++++++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 27 deletions(-) diff --git a/server/static/dashboard.html b/server/static/dashboard.html index b3c2093..e2784fc 100644 --- a/server/static/dashboard.html +++ b/server/static/dashboard.html @@ -596,14 +596,17 @@
Full keyboard capture including Win key, Alt+Tab, and Ctrl+Alt+Del.
- @@ -927,24 +930,26 @@ // Connect modal state let connectSessionId = null; let connectMachineName = null; + let protocolCheckTimeout = null; const connectModal = document.getElementById("connectModal"); const connectClose = document.getElementById("connectClose"); const connectWeb = document.getElementById("connectWeb"); const connectNative = document.getElementById("connectNative"); - const nativeCommandContainer = document.getElementById("nativeCommandContainer"); - const nativeCommandText = document.getElementById("nativeCommandText"); + const nativeNotInstalled = document.getElementById("nativeNotInstalled"); const connectMachineNameEl = document.getElementById("connectMachineName"); connectClose.addEventListener("click", () => { connectModal.classList.remove("active"); - nativeCommandContainer.style.display = "none"; + nativeNotInstalled.style.display = "none"; + if (protocolCheckTimeout) clearTimeout(protocolCheckTimeout); }); connectModal.addEventListener("click", (e) => { if (e.target === connectModal) { connectModal.classList.remove("active"); - nativeCommandContainer.style.display = "none"; + nativeNotInstalled.style.display = "none"; + if (protocolCheckTimeout) clearTimeout(protocolCheckTimeout); } }); @@ -953,27 +958,63 @@ const viewerUrl = "/viewer.html?session_id=" + connectSessionId; window.open(viewerUrl, "viewer_" + connectSessionId, "width=1280,height=800,menubar=no,toolbar=no,location=no,status=no"); connectModal.classList.remove("active"); - nativeCommandContainer.style.display = "none"; + nativeNotInstalled.style.display = "none"; } }); connectNative.addEventListener("click", () => { - if (connectSessionId) { - const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; - const serverUrl = protocol + "//" + window.location.host + "/ws/viewer"; - const cmd = `guruconnect-viewer.exe -i ${connectSessionId} -s ${serverUrl}`; - nativeCommandText.textContent = cmd; - nativeCommandContainer.style.display = "block"; - } + launchNativeViewer(); }); - function copyNativeCommand() { - const cmd = nativeCommandText.textContent; - navigator.clipboard.writeText(cmd).then(() => { - const btn = document.querySelector(".copy-btn"); - btn.textContent = "Copied!"; - setTimeout(() => btn.textContent = "Copy", 2000); - }); + function launchNativeViewer() { + if (!connectSessionId) return; + + const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; + const serverUrl = encodeURIComponent(protocol + "//" + window.location.host + "/ws/viewer"); + const protocolUrl = `guruconnect://view/${connectSessionId}?server=${serverUrl}`; + + // Try to launch the protocol handler + // We use a hidden iframe to avoid navigation issues + const iframe = document.createElement("iframe"); + iframe.style.display = "none"; + document.body.appendChild(iframe); + + // Set up timeout to detect if protocol handler didn't work + let launched = false; + const startTime = Date.now(); + + // If window loses focus quickly, protocol likely worked + const blurHandler = () => { + if (Date.now() - startTime < 2000) { + launched = true; + connectModal.classList.remove("active"); + nativeNotInstalled.style.display = "none"; + } + }; + window.addEventListener("blur", blurHandler, { once: true }); + + // Try to launch + try { + iframe.contentWindow.location.href = protocolUrl; + } catch (e) { + // Some browsers throw on custom protocols + } + + // Fallback: also try window.location for browsers that block iframe protocols + protocolCheckTimeout = setTimeout(() => { + window.removeEventListener("blur", blurHandler); + document.body.removeChild(iframe); + + if (!launched) { + // Protocol handler probably not installed, show download option + nativeNotInstalled.style.display = "block"; + } + }, 1500); + } + + function retryNativeViewer() { + nativeNotInstalled.style.display = "none"; + launchNativeViewer(); } function connectToMachine(sessionId) { @@ -981,7 +1022,7 @@ connectSessionId = sessionId; connectMachineName = machine?.agent_name || sessionId.slice(0, 8); connectMachineNameEl.textContent = connectMachineName; - nativeCommandContainer.style.display = "none"; + nativeNotInstalled.style.display = "none"; connectModal.classList.add("active"); }