Improve error handling when `RTCPeerConnection` throws (#289)

* fix(WebRTC): improve error handling during peer connection creation and add connection error overlay

* refactor: update peer connection state handling and improve type definitions across components
This commit is contained in:
Adam Shiervani 2025-03-25 14:54:04 +01:00 committed by GitHub
parent 3b711db781
commit a3580b5465
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 34 additions and 25 deletions

View File

@ -36,7 +36,7 @@ export default function DashboardNavbar({
picture,
kvmName,
}: NavbarProps) {
const peerConnectionState = useRTCStore(state => state.peerConnection?.connectionState);
const peerConnectionState = useRTCStore(state => state.peerConnectionState);
const setUser = useUserStore(state => state.setUser);
const navigate = useNavigate();
const onLogout = useCallback(async () => {

View File

@ -9,19 +9,22 @@ const PeerConnectionStatusMap = {
failed: "Connection failed",
closed: "Closed",
new: "Connecting",
};
} as Record<RTCPeerConnectionState | "error" | "closing", string>;
export type PeerConnections = keyof typeof PeerConnectionStatusMap;
type StatusProps = Record<PeerConnections, {
type StatusProps = Record<
PeerConnections,
{
statusIndicatorClassName: string;
}>;
}
>;
export default function PeerConnectionStatusCard({
state,
title,
}: {
state?: PeerConnections;
state?: RTCPeerConnectionState | null;
title?: string;
}) {
if (!state) return null;

View File

@ -8,11 +8,14 @@ import { HidState } from "@/hooks/stores";
type USBStates = HidState["usbState"];
type StatusProps = Record<USBStates, {
type StatusProps = Record<
USBStates,
{
icon: React.FC<{ className: string | undefined }>;
iconClassName: string;
statusIndicatorClassName: string;
}>;
}
>;
const USBStateMap: Record<USBStates, string> = {
configured: "Connected",
@ -27,9 +30,8 @@ export default function USBStateStatus({
peerConnectionState,
}: {
state: USBStates;
peerConnectionState?: RTCPeerConnectionState;
peerConnectionState: RTCPeerConnectionState | null;
}) {
const StatusCardProps: StatusProps = {
configured: {
icon: ({ className }) => (

View File

@ -126,7 +126,7 @@ export default function KvmIdRoute() {
const setIsTurnServerInUse = useRTCStore(state => state.setTurnServerInUse);
const peerConnection = useRTCStore(state => state.peerConnection);
const peerConnectionState = useRTCStore(state => state.peerConnectionState);
const setPeerConnectionState = useRTCStore(state => state.setPeerConnectionState);
const setMediaMediaStream = useRTCStore(state => state.setMediaStream);
const setPeerConnection = useRTCStore(state => state.setPeerConnection);
@ -153,6 +153,7 @@ export default function KvmIdRoute() {
// However, the onconnectionstatechange event doesn't fire when close() is called manually
// So we need to explicitly update our state to maintain consistency
// I don't know why this is happening, but this is the best way I can think of to handle it
// ALSO, this will render the connection error overlay linking to docs
setPeerConnectionState("closed");
},
[peerConnection, setPeerConnectionState],
@ -255,12 +256,19 @@ export default function KvmIdRoute() {
setStartedConnectingAt(new Date());
setConnectedAt(null);
const pc = new RTCPeerConnection({
let pc: RTCPeerConnection;
try {
pc = new RTCPeerConnection({
// We only use STUN or TURN servers if we're in the cloud
...(isInCloud && iceConfig?.iceServers
? { iceServers: [iceConfig?.iceServers] }
: {}),
});
} catch (e) {
console.error(`Error creating peer connection: ${e}`);
closePeerConnection();
return;
}
// Set up event listeners and data channels
pc.onconnectionstatechange = () => {
@ -296,8 +304,10 @@ export default function KvmIdRoute() {
setPeerConnection(pc);
} catch (e) {
console.error(`Error creating offer: ${e}`);
closePeerConnection();
}
}, [
closePeerConnection,
iceConfig?.iceServers,
sdp,
setDiskChannel,
@ -315,9 +325,8 @@ export default function KvmIdRoute() {
if (location.pathname.includes("other-session")) return;
// If we're already connected or connecting, we don't need to connect
if (
["connected", "connecting", "new"].includes(peerConnection?.connectionState ?? "")
) {
// We have to use the state from the store, because the peerConnection.connectionState doesnt trigger a value change, if called manually from .close()
if (["connected", "connecting", "new"].includes(peerConnectionState ?? "")) {
return;
}
@ -331,12 +340,7 @@ export default function KvmIdRoute() {
connectWebRTC();
}, 3000);
return () => clearInterval(interval);
}, [
connectWebRTC,
connectionFailed,
location.pathname,
peerConnection?.connectionState,
]);
}, [connectWebRTC, connectionFailed, location.pathname, peerConnectionState]);
// On boot, if the connection state is undefined, we connect to the WebRTC
useEffect(() => {