({
chatId,
activated,
}: UseDesktopConnectionOptions)
| 80 | }; |
| 81 | |
| 82 | export function useDesktopConnection({ |
| 83 | chatId, |
| 84 | activated, |
| 85 | }: UseDesktopConnectionOptions): UseDesktopConnectionResult { |
| 86 | const [status, setStatus] = useState<DesktopConnectionStatus>("idle"); |
| 87 | const [hasConnected, setHasConnected] = useState(false); |
| 88 | const [remoteClipboardText, setRemoteClipboardText] = useState<string | null>( |
| 89 | null, |
| 90 | ); |
| 91 | |
| 92 | // rfbRef provides synchronous access for cleanup and event |
| 93 | // handlers. rfbInstance (state) provides reactivity so consumers |
| 94 | // re-render when the RFB instance changes. |
| 95 | const [rfbInstance, setRfbInstance] = useState<RFB | null>(null); |
| 96 | const rfbRef = useRef<RFB | null>(null); |
| 97 | |
| 98 | const offscreenContainerRef = useRef<HTMLElement | null>(null); |
| 99 | const reconnectAttemptRef = useRef(0); |
| 100 | const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); |
| 101 | const reconnectStableTimerRef = useRef<ReturnType<typeof setTimeout> | null>( |
| 102 | null, |
| 103 | ); |
| 104 | const connectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); |
| 105 | // Monotonically increasing counter. Incremented at the start of |
| 106 | // every doConnect() and inside the cleanup. Event handlers |
| 107 | // capture the current value and bail when it no longer matches, |
| 108 | // which prevents async noVNC callbacks from a previous session |
| 109 | // (e.g. a phantom "disconnect" event fired after cleanupRfb) |
| 110 | // from scheduling unwanted reconnect timers. |
| 111 | const generationRef = useRef(0); |
| 112 | |
| 113 | // Populated by the lifecycle effect so reconnect() can tear |
| 114 | // down and restart the connection from outside the effect. |
| 115 | // Cleared on cleanup so calls after unmount are no-ops. |
| 116 | const restartRef = useRef<(() => void) | null>(null); |
| 117 | |
| 118 | const reconnect = () => { |
| 119 | restartRef.current?.(); |
| 120 | }; |
| 121 | const { copyToClipboard: syncRemoteClipboardToLocal } = useClipboard({ |
| 122 | onError: () => { |
| 123 | toast.error( |
| 124 | "Failed to sync the remote clipboard to your local clipboard.", |
| 125 | ); |
| 126 | }, |
| 127 | }); |
| 128 | |
| 129 | const attach = (container: HTMLElement) => { |
| 130 | const screen = offscreenContainerRef.current; |
| 131 | if (screen && screen.parentElement !== container) { |
| 132 | container.appendChild(screen); |
| 133 | } |
| 134 | }; |
| 135 | // Single lifecycle effect that owns the entire connection. |
| 136 | // Connects on mount, tears down on unmount, and resets when |
| 137 | // chatId changes. |
| 138 | // |
| 139 | // All connection logic (doConnect, cleanupRfb, timer helpers) |
no test coverage detected