Added exponential backoff to reconnection

Also made the number of reconnect attempts settable
Doesn't attempt a reconnection if we intentionally disconnect
Make sure the fire-and-forget for TURN activity doesn't result in unhandled promise rejection.
This commit is contained in:
Marc Brooks 2025-09-30 17:40:37 -05:00
parent 0984ca7e40
commit 69f429d0a5
No known key found for this signature in database
GPG Key ID: 583A6AF2D6AE1DC6
1 changed files with 20 additions and 9 deletions

View File

@ -146,6 +146,7 @@ export default function KvmIdRoute() {
const { otaState, setOtaState, setModalView } = useUpdateStore(); const { otaState, setOtaState, setModalView } = useUpdateStore();
const [loadingMessage, setLoadingMessage] = useState("Connecting to device..."); const [loadingMessage, setLoadingMessage] = useState("Connecting to device...");
const cleanupAndStopReconnecting = useCallback( const cleanupAndStopReconnecting = useCallback(
function cleanupAndStopReconnecting() { function cleanupAndStopReconnecting() {
console.log("Closing peer connection"); console.log("Closing peer connection");
@ -182,11 +183,11 @@ export default function KvmIdRoute() {
pc: RTCPeerConnection, pc: RTCPeerConnection,
remoteDescription: RTCSessionDescriptionInit, remoteDescription: RTCSessionDescriptionInit,
) { ) {
setLoadingMessage("Setting remote description"); setLoadingMessage("Setting remote description type:" + remoteDescription.type);
try { try {
await pc.setRemoteDescription(new RTCSessionDescription(remoteDescription)); await pc.setRemoteDescription(new RTCSessionDescription(remoteDescription));
console.log("[setRemoteSessionDescription] Remote description set successfully"); console.log("[setRemoteSessionDescription] Remote description set successfully to: " + remoteDescription.sdp);
setLoadingMessage("Establishing secure connection..."); setLoadingMessage("Establishing secure connection...");
} catch (error) { } catch (error) {
console.error( console.error(
@ -231,9 +232,15 @@ export default function KvmIdRoute() {
const ignoreOffer = useRef(false); const ignoreOffer = useRef(false);
const isSettingRemoteAnswerPending = useRef(false); const isSettingRemoteAnswerPending = useRef(false);
const makingOffer = useRef(false); const makingOffer = useRef(false);
const reconnectAttemptsRef = useRef(20);
const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:"; const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const reconnectInterval = (attempt: number) => {
// Exponential backoff with a max of 10 seconds between attempts
return Math.min(500 * 2 ** attempt, 10000);
}
const { sendMessage, getWebSocket } = useWebSocket( const { sendMessage, getWebSocket } = useWebSocket(
isOnDevice isOnDevice
? `${wsProtocol}//${window.location.host}/webrtc/signaling/client` ? `${wsProtocol}//${window.location.host}/webrtc/signaling/client`
@ -241,17 +248,16 @@ export default function KvmIdRoute() {
{ {
heartbeat: true, heartbeat: true,
retryOnError: true, retryOnError: true,
reconnectAttempts: 15, reconnectAttempts: reconnectAttemptsRef.current,
reconnectInterval: 1000, reconnectInterval: reconnectInterval,
onReconnectStop: () => { onReconnectStop: (attempt: number) => {
console.debug("Reconnect stopped"); console.debug("Reconnect stopped after ", attempt, "attempts");
cleanupAndStopReconnecting(); cleanupAndStopReconnecting();
}, },
shouldReconnect(event) { shouldReconnect(event) {
console.debug("[Websocket] shouldReconnect", event); console.debug("[Websocket] shouldReconnect", event);
// TODO: Why true? return !connectionFailed; // we always want to try to reconnect unless we're explicitly stopped
return true;
}, },
onClose(event) { onClose(event) {
@ -284,6 +290,7 @@ export default function KvmIdRoute() {
*/ */
const parsedMessage = JSON.parse(message.data); const parsedMessage = JSON.parse(message.data);
if (parsedMessage.type === "device-metadata") { if (parsedMessage.type === "device-metadata") {
const { deviceVersion } = parsedMessage.data; const { deviceVersion } = parsedMessage.data;
console.debug("[Websocket] Received device-metadata message"); console.debug("[Websocket] Received device-metadata message");
@ -300,10 +307,12 @@ export default function KvmIdRoute() {
console.log("[Websocket] Device is using new signaling"); console.log("[Websocket] Device is using new signaling");
isLegacySignalingEnabled.current = false; isLegacySignalingEnabled.current = false;
} }
setupPeerConnection(); setupPeerConnection();
} }
if (!peerConnection) return; if (!peerConnection) return;
if (parsedMessage.type === "answer") { if (parsedMessage.type === "answer") {
console.debug("[Websocket] Received answer"); console.debug("[Websocket] Received answer");
const readyForOffer = const readyForOffer =
@ -594,7 +603,9 @@ export default function KvmIdRoute() {
api.POST(`${CLOUD_API}/webrtc/turn_activity`, { api.POST(`${CLOUD_API}/webrtc/turn_activity`, {
bytesReceived: bytesReceivedDelta, bytesReceived: bytesReceivedDelta,
bytesSent: bytesSentDelta, bytesSent: bytesSentDelta,
}); }).catch(()=>{
// we don't care about errors here, but we don't want unhandled promise rejections
});
}, 10000); }, 10000);
const { setNetworkState} = useNetworkStateStore(); const { setNetworkState} = useNetworkStateStore();