Native viewer: use guruconnect:// protocol with fallback to download

This commit is contained in:
2025-12-29 21:24:12 -07:00
parent 0c3435fa99
commit 1b810a5f0a

View File

@@ -596,14 +596,17 @@
<div class="connect-option-desc">Full keyboard capture including Win key, Alt+Tab, and Ctrl+Alt+Del.</div> <div class="connect-option-desc">Full keyboard capture including Win key, Alt+Tab, and Ctrl+Alt+Del.</div>
</div> </div>
</div> </div>
<div id="nativeCommandContainer" style="display: none;"> <div id="nativeNotInstalled" style="display: none; margin-top: 16px; padding: 16px; background: hsla(45, 93%, 47%, 0.1); border: 1px solid hsla(45, 93%, 47%, 0.3); border-radius: 8px;">
<div class="native-command"> <p style="margin-bottom: 12px; color: hsl(45, 93%, 55%);">
<button class="copy-btn" onclick="copyNativeCommand()">Copy</button> <strong>Native viewer not installed</strong>
<code id="nativeCommandText"></code>
</div>
<p style="margin-top: 12px; font-size: 12px; color: hsl(var(--muted-foreground));">
Run this command in PowerShell or CMD where guruconnect-viewer.exe is located.
</p> </p>
<p style="font-size: 13px; margin-bottom: 12px; color: hsl(var(--muted-foreground));">
Download and run GuruConnect to register the protocol handler, then click "Try Again".
</p>
<div style="display: flex; gap: 8px;">
<a href="/downloads/guruconnect.exe" class="btn btn-primary" style="text-decoration: none;">Download GuruConnect</a>
<button class="btn btn-outline" onclick="retryNativeViewer()">Try Again</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -927,24 +930,26 @@
// Connect modal state // Connect modal state
let connectSessionId = null; let connectSessionId = null;
let connectMachineName = null; let connectMachineName = null;
let protocolCheckTimeout = null;
const connectModal = document.getElementById("connectModal"); const connectModal = document.getElementById("connectModal");
const connectClose = document.getElementById("connectClose"); const connectClose = document.getElementById("connectClose");
const connectWeb = document.getElementById("connectWeb"); const connectWeb = document.getElementById("connectWeb");
const connectNative = document.getElementById("connectNative"); const connectNative = document.getElementById("connectNative");
const nativeCommandContainer = document.getElementById("nativeCommandContainer"); const nativeNotInstalled = document.getElementById("nativeNotInstalled");
const nativeCommandText = document.getElementById("nativeCommandText");
const connectMachineNameEl = document.getElementById("connectMachineName"); const connectMachineNameEl = document.getElementById("connectMachineName");
connectClose.addEventListener("click", () => { connectClose.addEventListener("click", () => {
connectModal.classList.remove("active"); connectModal.classList.remove("active");
nativeCommandContainer.style.display = "none"; nativeNotInstalled.style.display = "none";
if (protocolCheckTimeout) clearTimeout(protocolCheckTimeout);
}); });
connectModal.addEventListener("click", (e) => { connectModal.addEventListener("click", (e) => {
if (e.target === connectModal) { if (e.target === connectModal) {
connectModal.classList.remove("active"); 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; const viewerUrl = "/viewer.html?session_id=" + connectSessionId;
window.open(viewerUrl, "viewer_" + connectSessionId, "width=1280,height=800,menubar=no,toolbar=no,location=no,status=no"); window.open(viewerUrl, "viewer_" + connectSessionId, "width=1280,height=800,menubar=no,toolbar=no,location=no,status=no");
connectModal.classList.remove("active"); connectModal.classList.remove("active");
nativeCommandContainer.style.display = "none"; nativeNotInstalled.style.display = "none";
} }
}); });
connectNative.addEventListener("click", () => { connectNative.addEventListener("click", () => {
if (connectSessionId) { launchNativeViewer();
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";
}
}); });
function copyNativeCommand() { function launchNativeViewer() {
const cmd = nativeCommandText.textContent; if (!connectSessionId) return;
navigator.clipboard.writeText(cmd).then(() => {
const btn = document.querySelector(".copy-btn"); const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
btn.textContent = "Copied!"; const serverUrl = encodeURIComponent(protocol + "//" + window.location.host + "/ws/viewer");
setTimeout(() => btn.textContent = "Copy", 2000); 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) { function connectToMachine(sessionId) {
@@ -981,7 +1022,7 @@
connectSessionId = sessionId; connectSessionId = sessionId;
connectMachineName = machine?.agent_name || sessionId.slice(0, 8); connectMachineName = machine?.agent_name || sessionId.slice(0, 8);
connectMachineNameEl.textContent = connectMachineName; connectMachineNameEl.textContent = connectMachineName;
nativeCommandContainer.style.display = "none"; nativeNotInstalled.style.display = "none";
connectModal.classList.add("active"); connectModal.classList.add("active");
} }