keepme / src /hooks /useWebRTC.ts
narinder1231's picture
fix useWebRTC hook to handle events correctly
e586605
import { useState, useRef, useCallback, useEffect } from "react";
import { ICreateConversationResponse } from "../interfaces/conversation";
import { createConnection, createConversation } from "../services/api/chatService";
import { conversationWebSocket } from "../services/websockets/conversation";
export const useWebRTC = () => {
const [isConnected, setIsConnected] = useState(false);
const [transcript, setTranscript] = useState("");
const [remoteStream, setRemoteStream] = useState<MediaStream | null>(null);
const [callStatus, setCallStatus] = useState("");
const peerConnectionRef = useRef<RTCPeerConnection | null>(null);
const dataChannelRef = useRef<RTCDataChannel | null>(null);
const socketRef = useRef<WebSocket | null>(null);
const [error, setError] = useState<string | null>(null);
const mediaStreamRef = useRef<MediaStream | null>(null);
const isConnectedRef = useRef(isConnected);
useEffect(() => {
isConnectedRef.current = isConnected;
}, [isConnected]);
const endCall = useCallback(() => {
if (peerConnectionRef.current) {
peerConnectionRef.current.close();
peerConnectionRef.current = null;
}
if (socketRef.current) {
socketRef.current.close();
socketRef.current = null;
}
if (mediaStreamRef.current) {
mediaStreamRef.current.getTracks().forEach((track) => track.stop());
mediaStreamRef.current = null;
}
if (dataChannelRef.current) {
dataChannelRef.current.close();
dataChannelRef.current = null;
}
setIsConnected(false);
setCallStatus("Disconnected");
setRemoteStream(null);
setTranscript("");
}, []);
const startCall = useCallback(async () => {
try {
const peerConnection = new RTCPeerConnection();
peerConnectionRef.current = peerConnection;
peerConnection.onconnectionstatechange = () => {
switch (peerConnection.connectionState) {
case "connected":
setIsConnected(true);
setCallStatus("Connected");
break;
case "disconnected":
case "failed":
case "closed":
endCall();
break;
default:
break;
}
};
peerConnection.ontrack = (event) => {
setRemoteStream(event.streams[0]);
};
const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaStreamRef.current = mediaStream;
peerConnection.addTrack(mediaStream.getTracks()[0], mediaStream);
const dataChannel = peerConnection.createDataChannel("response");
dataChannelRef.current = dataChannel;
dataChannel.onmessage = (event) => {
if (socketRef.current?.readyState === WebSocket.OPEN) {
socketRef.current.send(event.data);
}
};
const sessionResponse = await createConversation({ modality: "voice" });
const sessionData: ICreateConversationResponse = await sessionResponse.data;
const conversationId = sessionData.conversation_id;
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
const webrtcResponse = await createConnection(conversationId, {
conversation_id: conversationId,
offer: { sdp: offer.sdp, type: offer.type },
});
const responseData = await webrtcResponse.data;
const ephemeralKey = responseData.ephemeral_key;
const socket = conversationWebSocket({
conversationId,
modality: "voice",
});
socketRef.current = socket;
socket.onopen = () => {
socket.send(
JSON.stringify({
type: "headers",
headers: { Authorization: `Bearer ${ephemeralKey}` },
})
);
};
socket.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.transcript) {
setTranscript((prev) => `${prev}\n${data.transcript}`);
} else if (dataChannelRef.current?.readyState === "open") {
dataChannelRef.current.send(JSON.stringify(data));
}
} catch {
setError("Error handling message");
}
};
socket.onclose = (_event) => {
if (isConnectedRef.current) endCall();
};
await peerConnection.setRemoteDescription(new RTCSessionDescription(responseData.answer));
} catch {
setError("Call setup failed");
endCall();
}
}, [endCall]);
return {
startCall,
endCall,
isConnected,
transcript,
remoteStream,
callStatus,
error,
};
};