import { useEffect, useRef, useState } from 'react' import Guacamole from 'guacamole-common-js' import { api, getToken, type Integration } from '../lib/api' const TEXT_SECONDARY = '#7A7D85' export default function RemoteDesktop() { const [hosts, setHosts] = useState([]) const [hostId, setHostId] = useState(null) const [status, setStatus] = useState<'idle' | 'connecting' | 'connected' | 'error'>('idle') const [errorMessage, setErrorMessage] = useState('') const displayRef = useRef(null) const clientRef = useRef(null) useEffect(() => { api.listIntegrations().then(({ integrations }) => { setHosts(integrations.filter((i) => i.type === 'remote_desktop')) }) }, []) useEffect(() => { return () => { clientRef.current?.disconnect() } }, []) function connect(id: number) { clientRef.current?.disconnect() if (displayRef.current) displayRef.current.innerHTML = '' setStatus('connecting') setErrorMessage('') const token = getToken() const proto = window.location.protocol === 'https:' ? 'wss' : 'ws' // Guacamole.WebSocketTunnel appends its own "?" query string on connect(), // so the tunnel URL itself must not already contain one. const tunnel = new Guacamole.WebSocketTunnel(`${proto}://${window.location.host}/api/guacamole`) const client = new Guacamole.Client(tunnel) clientRef.current = client client.onerror = (err: { message?: string }) => { setStatus('error') setErrorMessage(err?.message ?? 'Connection failed') } client.onstatechange = (state: number) => { if (state === 3) setStatus('connected') } const display = client.getDisplay().getElement() displayRef.current?.appendChild(display) client.connect(`token=${encodeURIComponent(token ?? '')}&integrationId=${id}`) } function handleSelect(id: number) { setHostId(id) connect(id) } const host = hosts.find((h) => h.id === hostId) return (

Remote Desktops

{hosts.length === 0 && (

No remote desktop integrations configured. Add one in Settings → Integrations.

)} {hosts.map((h) => ( ))}

{host ? host.name : 'Select a remote desktop'}

{status === 'connecting' && 'Connecting…'} {status === 'connected' && 'Connected'} {status === 'error' && `Error: ${errorMessage}`}

) }