From 829b0cebfa55af67a5734b3618af2655d26c0061 Mon Sep 17 00:00:00 2001 From: Iason Paraskevopoulos Date: Wed, 28 Jan 2026 08:41:55 +0000 Subject: [PATCH] fix: livekit disconnect issue --- tauri/src/components/ui/call-center.tsx | 44 ++++++++++++++++++++++++- tauri/src/store/store.ts | 9 +++++ tauri/src/windows/main-window/app.tsx | 10 +++--- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/tauri/src/components/ui/call-center.tsx b/tauri/src/components/ui/call-center.tsx index 8a20da4..a183328 100644 --- a/tauri/src/components/ui/call-center.tsx +++ b/tauri/src/components/ui/call-center.tsx @@ -53,6 +53,14 @@ export function CallCenter() { return (
+ {/* Reconnecting Banner */} + {callTokens.isReconnecting && ( +
+
+ Reconnecting... +
+ )} +
{/* Call Timer */} {callTokens && ( @@ -594,7 +602,7 @@ function CameraIcon() { } function MediaDevicesSettings() { - const { callTokens, setCallTokens } = useStore(); + const { callTokens, setCallTokens, updateCallTokens, livekitUrl } = useStore(); const { state: roomState } = useRoomContext(); const { localParticipant } = useLocalParticipant(); const { isNoiseFilterPending, setNoiseFilterEnabled, isNoiseFilterEnabled } = useKrispNoiseFilter({ @@ -610,12 +618,46 @@ function MediaDevicesSettings() { const room = useRoomContext(); const [roomConnected, setRoomConnected] = useState(false); + useEffect(() => { room.on(RoomEvent.Connected, () => { setRoomConnected(true); }); }, [room]); + // Listen to connection state changes and handle reconnection + useEffect(() => { + const handleConnectionStateChange = async (state: ConnectionState) => { + console.log("Connection state changed:", state); + + if (state === ConnectionState.Disconnected && callTokens) { + // Room disconnected but we still have callTokens - try to reconnect + console.log("Room disconnected, attempting to reconnect..."); + updateCallTokens({ isReconnecting: true }); + + try { + if (!livekitUrl) { + throw new Error("LiveKit URL not available"); + } + await room.connect(livekitUrl, callTokens.audioToken); + } catch (error) { + console.error("Reconnection failed:", error); + updateCallTokens({ isReconnecting: false }); + } + } else if (state === ConnectionState.Connected && callTokens?.isReconnecting) { + // Successfully reconnected + console.log("Successfully reconnected!"); + updateCallTokens({ isReconnecting: false }); + } + }; + + room.on(RoomEvent.ConnectionStateChanged, handleConnectionStateChange); + + return () => { + room.off(RoomEvent.ConnectionStateChanged, handleConnectionStateChange); + }; + }, [room, callTokens, updateCallTokens, livekitUrl]); + useEffect(() => { if (!callTokens) return; diff --git a/tauri/src/store/store.ts b/tauri/src/store/store.ts index cbe9971..4dd9a7b 100644 --- a/tauri/src/store/store.ts +++ b/tauri/src/store/store.ts @@ -32,6 +32,8 @@ export type CallState = { krispToggle?: boolean; controllerSupportsAv1?: boolean; av1Enabled?: boolean; + // Reconnection state + isReconnecting?: boolean; } & TCallTokensMessage["payload"]; type State = { @@ -47,6 +49,7 @@ type State = { // Call tokens for LiveKit callTokens: CallState | null; customServerUrl: string | null; + livekitUrl: string | null; }; type Actions = { @@ -63,6 +66,7 @@ type Actions = { setCallTokens: (tokens: CallState | null) => void; updateCallTokens: (tokens: Partial) => void; setCustomServerUrl: (url: string | null) => void; + setLivekitUrl: (url: string | null) => void; }; const initialState: State = { @@ -76,6 +80,7 @@ const initialState: State = { calling: null, callTokens: null, customServerUrl: null, + livekitUrl: null, }; /** @@ -130,6 +135,10 @@ const useStore = create()( set((state) => { state.customServerUrl = url; }), + setLivekitUrl: (url) => + set((state) => { + state.livekitUrl = url; + }), setTab: (tab) => set((state) => { state.tab = tab; diff --git a/tauri/src/windows/main-window/app.tsx b/tauri/src/windows/main-window/app.tsx index 20d4337..af263d3 100644 --- a/tauri/src/windows/main-window/app.tsx +++ b/tauri/src/windows/main-window/app.tsx @@ -23,7 +23,6 @@ import { listen } from "@tauri-apps/api/event"; import Invite from "./invite"; import { sounds } from "@/constants/sounds"; import { useDisableNativeContextMenu } from "@/lib/hooks"; -import { validateAndSetAuthToken } from "@/lib/authUtils"; import { processDeepLinkUrl } from "@/lib/deepLinkUtils"; import { Rooms } from "./tabs/Rooms"; import { LiveKitRoom } from "@livekit/components-react"; @@ -44,6 +43,7 @@ function App() { setTab, setTeammates, setAuthToken, + setLivekitUrl, } = useStore(); const coreProcessCrashedRef = useRef(false); @@ -52,8 +52,8 @@ function App() { const { useQuery } = useAPI(); const [incomingCallerId, setIncomingCallerId] = useState(null); - const [livekitUrl, setLivekitUrl] = useState(""); const sentryMetadataRef = useRef(false); + const livekitUrl = useStore((s) => s.livekitUrl); const { error: userError } = useQuery("get", "/api/auth/user", undefined, { enabled: !!authToken, @@ -90,7 +90,7 @@ function App() { queryHash: `livekit-url-${authToken}`, }); - // Send LiveKit URL to Tauri backend when it's fetched + // Send LiveKit URL to Tauri backend and store when fetched useEffect(() => { const sendLivekitUrlToBackend = async () => { if (livekitUrlData?.url) { @@ -106,7 +106,7 @@ function App() { }; sendLivekitUrlToBackend(); - }, [livekitUrlData]); + }, [livekitUrlData, setLivekitUrl]); // Load stored token and custom server URL on app start useEffect(() => { @@ -389,7 +389,7 @@ function App() { ( - + {children} )}