Redirect user from call page when not in call

When a user tries to access a call page while not in a call, redirect the user to the home page.

Misc changes:

- Add route state to the call route that includes the CallStatus.
- CallProvider redirects to home if the callStatus isn't set (meaning
  the user isn't in a call).
- Remove `beginCall` function in `ConversationProvider`. Added `useStartCall` hook that redirects the user to the call page. The `CallProvider` automatically sends the `BeginCall` message when the user reaches the page for the first time.
- Reorder functions in CallProvider to have `useEffect` functions at the top

GitLab: #164
Change-Id: I6cec1b9f31cb308d92a69112f5b38d1bdf79e05f
diff --git a/client/src/contexts/CallProvider.tsx b/client/src/contexts/CallProvider.tsx
index a17e738..de2a6d7 100644
--- a/client/src/contexts/CallProvider.tsx
+++ b/client/src/contexts/CallProvider.tsx
@@ -29,6 +29,7 @@
 export type CallRole = 'caller' | 'receiver';
 
 export enum CallStatus {
+  Default,
   Ringing,
   Connecting,
   InCall,
@@ -65,7 +66,7 @@
   isVideoOn: false,
   setVideoStatus: () => {},
   callRole: 'caller',
-  callStatus: CallStatus.Ringing,
+  callStatus: CallStatus.Default,
 
   acceptCall: () => {},
 };
@@ -75,6 +76,7 @@
 export default ({ children }: WithChildren) => {
   const {
     queryParams: { role: callRole },
+    state: routeState,
   } = useUrlParams<CallRouteParams>();
   const webSocket = useContext(WebSocketContext);
   const { webRtcConnection, remoteStreams, sendWebRtcOffer, isConnected } = useContext(WebRtcContext);
@@ -87,9 +89,11 @@
 
   const [isAudioOn, setIsAudioOn] = useState(false);
   const [isVideoOn, setIsVideoOn] = useState(false);
-  const [callStatus, setCallStatus] = useState(CallStatus.Ringing);
+  const [callStatus, setCallStatus] = useState(routeState?.callStatus);
 
-  // TODO: This logic will have to change to support multiple people in a call
+  // TODO: This logic will have to change to support multiple people in a call. Could we move this logic to the server?
+  //       The client could make a single request with the conversationId, and the server would be tasked with sending
+  //       all the individual requests to the members of the conversation.
   const contactUri = useMemo(() => conversation.getFirstMember().contact.getUri(), [conversation]);
 
   useEffect(() => {
@@ -140,35 +144,22 @@
     }
   }, [localStream, webRtcConnection]);
 
-  const setAudioStatus = useCallback(
-    (isOn: boolean) => {
-      if (!localStream) {
-        return;
-      }
+  useEffect(() => {
+    if (!webSocket) {
+      return;
+    }
 
-      for (const track of localStream.getAudioTracks()) {
-        track.enabled = isOn;
-      }
+    if (callRole === 'caller' && callStatus === CallStatus.Default) {
+      const callBegin: CallAction = {
+        contactId: contactUri,
+        conversationId,
+      };
 
-      setIsAudioOn(isOn);
-    },
-    [localStream]
-  );
-
-  const setVideoStatus = useCallback(
-    (isOn: boolean) => {
-      if (!localStream) {
-        return;
-      }
-
-      for (const track of localStream.getVideoTracks()) {
-        track.enabled = isOn;
-      }
-
-      setIsVideoOn(isOn);
-    },
-    [localStream]
-  );
+      console.info('Sending CallBegin', callBegin);
+      webSocket.send(WebSocketMessageType.CallBegin, callBegin);
+      setCallStatus(CallStatus.Ringing);
+    }
+  }, [webSocket, callRole, callStatus, contactUri, conversationId]);
 
   useEffect(() => {
     if (!webSocket || !webRtcConnection) {
@@ -220,8 +211,38 @@
     setCallStatus(CallStatus.Connecting);
   }, [webSocket, contactUri, conversationId]);
 
-  if (!callRole) {
-    console.error('Call role not defined. Redirecting...');
+  const setAudioStatus = useCallback(
+    (isOn: boolean) => {
+      if (!localStream) {
+        return;
+      }
+
+      for (const track of localStream.getAudioTracks()) {
+        track.enabled = isOn;
+      }
+
+      setIsAudioOn(isOn);
+    },
+    [localStream]
+  );
+
+  const setVideoStatus = useCallback(
+    (isOn: boolean) => {
+      if (!localStream) {
+        return;
+      }
+
+      for (const track of localStream.getVideoTracks()) {
+        track.enabled = isOn;
+      }
+
+      setIsVideoOn(isOn);
+    },
+    [localStream]
+  );
+
+  if (!callRole || callStatus === undefined) {
+    console.error('Invalid route. Redirecting...');
     return <Navigate to={'/'} />;
   }