Add wrapper comp to WebRtcProvider, CallProvider
In `CallProvider` and `WebRtcProvider`, add a wrapper component that show a loading page if certain variables aren't set (webSocket, webRtcConnection).
This makes it so that we do not need to check that they are undefined in the actual code.
In `WebRtcProvider`, put some listeners in their separate `useEffect` to avoid re-binding them unecessarily.
Change-Id: I25f0ea57455bf3eb7705b69793fe3f2949c52916
diff --git a/client/src/contexts/CallProvider.tsx b/client/src/contexts/CallProvider.tsx
index e602508..e99399d 100644
--- a/client/src/contexts/CallProvider.tsx
+++ b/client/src/contexts/CallProvider.tsx
@@ -19,13 +19,14 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { Navigate, useNavigate } from 'react-router-dom';
+import LoadingPage from '../components/Loading';
import { useUrlParams } from '../hooks/useUrlParams';
import { CallRouteParams } from '../router';
import { callTimeoutMs } from '../utils/constants';
import { SetState, WithChildren } from '../utils/utils';
import { ConversationContext } from './ConversationProvider';
import { WebRtcContext } from './WebRtcProvider';
-import { WebSocketContext } from './WebSocketProvider';
+import { IWebSocketContext, WebSocketContext } from './WebSocketProvider';
export type CallRole = 'caller' | 'receiver';
@@ -87,12 +88,33 @@
export const CallContext = createContext<ICallContext>(defaultCallContext);
export default ({ children }: WithChildren) => {
+ const webSocket = useContext(WebSocketContext);
+ const { webRtcConnection } = useContext(WebRtcContext);
+
+ if (!webSocket || !webRtcConnection) {
+ return <LoadingPage />;
+ }
+
+ return (
+ <CallProvider webSocket={webSocket} webRtcConnection={webRtcConnection}>
+ {children}
+ </CallProvider>
+ );
+};
+
+const CallProvider = ({
+ children,
+ webSocket,
+ webRtcConnection,
+}: WithChildren & {
+ webSocket: IWebSocketContext;
+ webRtcConnection: RTCPeerConnection;
+}) => {
const {
queryParams: { role: callRole },
state: routeState,
} = useUrlParams<CallRouteParams>();
- const webSocket = useContext(WebSocketContext);
- const { webRtcConnection, remoteStreams, sendWebRtcOffer, isConnected } = useContext(WebRtcContext);
+ const { remoteStreams, sendWebRtcOffer, isConnected } = useContext(WebRtcContext);
const { conversationId, conversation } = useContext(ConversationContext);
const navigate = useNavigate();
@@ -153,7 +175,7 @@
}, []);
useEffect(() => {
- if (localStream && webRtcConnection) {
+ if (localStream) {
for (const track of localStream.getTracks()) {
webRtcConnection.addTrack(track, localStream);
}
@@ -191,10 +213,6 @@
);
useEffect(() => {
- if (!webSocket) {
- return;
- }
-
if (callRole === 'caller' && callStatus === CallStatus.Default) {
const callBegin: CallBegin = {
contactId: contactUri,
@@ -221,10 +239,6 @@
}, []);
useEffect(() => {
- if (!webSocket || !webRtcConnection) {
- return;
- }
-
if (callRole === 'caller' && callStatus === CallStatus.Ringing) {
const callAcceptListener = (data: CallAction) => {
console.info('Received event on CallAccept', data);
@@ -254,10 +268,6 @@
}, [callRole, webSocket, webRtcConnection, sendWebRtcOffer, callStatus, conversationId]);
const quitCall = useCallback(() => {
- if (!webRtcConnection) {
- throw new Error('Could not quit call: webRtcConnection is not defined');
- }
-
const localTracks = localStream?.getTracks();
if (localTracks) {
for (const track of localTracks) {
@@ -270,10 +280,6 @@
}, [webRtcConnection, localStream, navigate, conversationId]);
useEffect(() => {
- if (!webSocket) {
- return;
- }
-
const callEndListener = (data: CallAction) => {
console.info('Received event on CallEnd', data);
if (data.conversationId !== conversationId) {
@@ -302,10 +308,6 @@
const acceptCall = useCallback(
(withVideoOn: boolean) => {
- if (!webSocket) {
- throw new Error('Could not accept call');
- }
-
const callAccept: CallAction = {
contactId: contactUri,
conversationId,
@@ -320,10 +322,6 @@
);
const endCall = useCallback(() => {
- if (!webSocket) {
- throw new Error('Could not end call');
- }
-
const callEnd: CallAction = {
contactId: contactUri,
conversationId,
diff --git a/client/src/contexts/WebRtcProvider.tsx b/client/src/contexts/WebRtcProvider.tsx
index 13a30a2..397bd83 100644
--- a/client/src/contexts/WebRtcProvider.tsx
+++ b/client/src/contexts/WebRtcProvider.tsx
@@ -19,10 +19,11 @@
import { WebRtcIceCandidate, WebRtcSdp, WebSocketMessageType } from 'jami-web-common';
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
+import LoadingPage from '../components/Loading';
import { WithChildren } from '../utils/utils';
import { useAuthContext } from './AuthProvider';
import { ConversationContext } from './ConversationProvider';
-import { WebSocketContext } from './WebSocketProvider';
+import { IWebSocketContext, WebSocketContext } from './WebSocketProvider';
interface IWebRtcContext {
isConnected: boolean;
@@ -44,14 +45,8 @@
export default ({ children }: WithChildren) => {
const { account } = useAuthContext();
- const webSocket = useContext(WebSocketContext);
- const { conversation, conversationId } = useContext(ConversationContext);
const [webRtcConnection, setWebRtcConnection] = useState<RTCPeerConnection | undefined>();
- const [remoteStreams, setRemoteStreams] = useState<readonly MediaStream[]>();
- const [isConnected, setIsConnected] = useState(false);
-
- // TODO: This logic will have to change to support multiple people in a call
- const contactUri = useMemo(() => conversation.getFirstMember().contact.getUri(), [conversation]);
+ const webSocket = useContext(WebSocketContext);
useEffect(() => {
if (!webRtcConnection && account) {
@@ -75,12 +70,34 @@
}
}, [account, webRtcConnection]);
+ if (!webRtcConnection || !webSocket) {
+ return <LoadingPage />;
+ }
+
+ return (
+ <WebRtcProvider webRtcConnection={webRtcConnection} webSocket={webSocket}>
+ {children}
+ </WebRtcProvider>
+ );
+};
+
+const WebRtcProvider = ({
+ children,
+ webRtcConnection,
+ webSocket,
+}: WithChildren & {
+ webRtcConnection: RTCPeerConnection;
+ webSocket: IWebSocketContext;
+}) => {
+ const { conversation, conversationId } = useContext(ConversationContext);
+ const [remoteStreams, setRemoteStreams] = useState<readonly MediaStream[]>();
+ const [isConnected, setIsConnected] = useState(false);
+
+ // TODO: This logic will have to change to support multiple people in a call
+ const contactUri = useMemo(() => conversation.getFirstMember().contact.getUri(), [conversation]);
+
const sendWebRtcOffer = useCallback(
async (sdp: RTCSessionDescriptionInit) => {
- if (!webRtcConnection || !webSocket) {
- throw new Error('Could not send WebRTC offer');
- }
-
const webRtcOffer: WebRtcSdp = {
contactId: contactUri,
conversationId: conversationId,
@@ -96,10 +113,6 @@
const sendWebRtcAnswer = useCallback(
(sdp: RTCSessionDescriptionInit) => {
- if (!webRtcConnection || !webSocket) {
- throw new Error('Could not send WebRTC answer');
- }
-
const webRtcAnswer: WebRtcSdp = {
contactId: contactUri,
conversationId: conversationId,
@@ -109,14 +122,12 @@
console.info('Sending WebRtcAnswer', webRtcAnswer);
webSocket.send(WebSocketMessageType.WebRtcAnswer, webRtcAnswer);
},
- [contactUri, conversationId, webRtcConnection, webSocket]
+ [contactUri, conversationId, webSocket]
);
- useEffect(() => {
- if (!webSocket || !webRtcConnection) {
- return;
- }
+ /* WebSocket Listeners */
+ useEffect(() => {
const webRtcOfferListener = async (data: WebRtcSdp) => {
console.info('Received event on WebRtcOffer', data);
if (data.conversationId !== conversationId) {
@@ -143,9 +154,17 @@
await webRtcConnection.setRemoteDescription(new RTCSessionDescription(data.sdp));
};
+ webSocket.bind(WebSocketMessageType.WebRtcOffer, webRtcOfferListener);
+ webSocket.bind(WebSocketMessageType.WebRtcAnswer, webRtcAnswerListener);
+ return () => {
+ webSocket.unbind(WebSocketMessageType.WebRtcOffer, webRtcOfferListener);
+ webSocket.unbind(WebSocketMessageType.WebRtcAnswer, webRtcAnswerListener);
+ };
+ }, [webSocket, webRtcConnection, sendWebRtcAnswer, conversationId]);
+
+ useEffect(() => {
const webRtcIceCandidateListener = async (data: WebRtcIceCandidate) => {
- console.info('Received event on WebRtcIceCandidate', data);
if (data.conversationId !== conversationId) {
console.warn('Wrong incoming conversationId, ignoring action');
return;
@@ -154,28 +173,17 @@
await webRtcConnection.addIceCandidate(data.candidate);
};
- webSocket.bind(WebSocketMessageType.WebRtcOffer, webRtcOfferListener);
- webSocket.bind(WebSocketMessageType.WebRtcAnswer, webRtcAnswerListener);
webSocket.bind(WebSocketMessageType.WebRtcIceCandidate, webRtcIceCandidateListener);
return () => {
- webSocket.unbind(WebSocketMessageType.WebRtcOffer, webRtcOfferListener);
- webSocket.unbind(WebSocketMessageType.WebRtcAnswer, webRtcAnswerListener);
webSocket.unbind(WebSocketMessageType.WebRtcIceCandidate, webRtcIceCandidateListener);
};
- }, [webSocket, webRtcConnection, sendWebRtcAnswer, conversationId]);
+ }, [webRtcConnection, webSocket, conversationId]);
+
+ /* WebRTC Listeners */
useEffect(() => {
- if (!webRtcConnection || !webSocket) {
- return;
- }
-
const iceCandidateEventListener = (event: RTCPeerConnectionIceEvent) => {
- console.info('Received WebRTC event on icecandidate', event);
- if (!contactUri) {
- throw new Error('Could not handle WebRTC event on icecandidate: contactUri is not defined');
- }
-
if (event.candidate) {
const webRtcIceCandidate: WebRtcIceCandidate = {
contactId: contactUri,
@@ -183,32 +191,38 @@
candidate: event.candidate,
};
- console.info('Sending WebRtcIceCandidate', webRtcIceCandidate);
webSocket.send(WebSocketMessageType.WebRtcIceCandidate, webRtcIceCandidate);
}
};
+ webRtcConnection.addEventListener('icecandidate', iceCandidateEventListener);
+
+ return () => {
+ webRtcConnection.removeEventListener('icecandidate', iceCandidateEventListener);
+ };
+ }, [webRtcConnection, webSocket, contactUri, conversationId]);
+
+ useEffect(() => {
const trackEventListener = (event: RTCTrackEvent) => {
console.info('Received WebRTC event on track', event);
setRemoteStreams(event.streams);
};
- const iceConnectionStateChangeEventListener = () => {
+ const iceConnectionStateChangeEventListener = (event: Event) => {
+ console.info(`Received WebRTC event on iceconnectionstatechange: ${webRtcConnection.iceConnectionState}`, event);
setIsConnected(
webRtcConnection.iceConnectionState === 'connected' || webRtcConnection.iceConnectionState === 'completed'
);
};
- webRtcConnection.addEventListener('icecandidate', iceCandidateEventListener);
webRtcConnection.addEventListener('track', trackEventListener);
webRtcConnection.addEventListener('iceconnectionstatechange', iceConnectionStateChangeEventListener);
return () => {
- webRtcConnection.removeEventListener('icecandidate', iceCandidateEventListener);
webRtcConnection.removeEventListener('track', trackEventListener);
webRtcConnection.removeEventListener('iceconnectionstatechange', iceConnectionStateChangeEventListener);
};
- }, [webRtcConnection, webSocket, contactUri, conversationId]);
+ }, [webRtcConnection]);
return (
<WebRtcContext.Provider