Transform CallProvider into 'CallManager' hook, review WebSocket messages for calls

- These changes allowed to remove more cascading effects. It is now possible to reactivate StrictMode. Downside is we lost the 'optional context' of CallProvider: the call logic will be loaded even if there is no call.
- The WebSocket messages have been changed so the client does not have to know the conversation members before a call. Previously, the client had to fetch the conversation members for a call, which was causing cascading effects.
- Accidentally, moving the handling of conversation members to the server added some logic for calls with more than two participants, but it is still not ready to work.

* CallProvider.tsx will be renamed in next commit in order to make it easier to track its file history

Change-Id: Iae711009adafce065ac3defc1c91c7ca0f37898c
diff --git a/client/src/contexts/CallManagerProvider.tsx b/client/src/contexts/CallManagerProvider.tsx
index ec68765..8f44a1b 100644
--- a/client/src/contexts/CallManagerProvider.tsx
+++ b/client/src/contexts/CallManagerProvider.tsx
@@ -15,117 +15,28 @@
  * License along with this program.  If not, see
  * <https://www.gnu.org/licenses/>.
  */
-import { CallBegin, ConversationInfos, WebSocketMessageType } from 'jami-web-common';
-import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
-import { useTranslation } from 'react-i18next';
-import { useNavigate } from 'react-router-dom';
-
 import { RemoteVideoOverlay } from '../components/VideoOverlay';
+import { createOptionalContext } from '../hooks/createOptionalContext';
 import { useUrlParams } from '../hooks/useUrlParams';
-import { ConversationMember } from '../models/conversation-member';
 import { ConversationRouteParams } from '../router';
-import { useConversationInfosQuery, useMembersQuery } from '../services/conversationQueries';
-import { SetState, WithChildren } from '../utils/utils';
-import { AlertSnackbarContext } from './AlertSnackbarProvider';
-import CallProvider, { CallRole } from './CallProvider';
-import { useWebSocketContext } from './WebSocketProvider';
+import { WithChildren } from '../utils/utils';
+import { ICallManager, useCallManager } from './CallProvider';
 
-export type CallData = {
-  conversationId: string;
-  role: CallRole;
-  withVideoOn?: boolean;
-};
-
-type ICallManagerContext = {
-  callData: CallData | undefined;
-  callConversationInfos: ConversationInfos | undefined;
-  callMembers: ConversationMember[] | undefined;
-  startCall: SetState<CallData | undefined>;
-  exitCall: () => void;
-};
-
-const defaultCallManagerContext: ICallManagerContext = {
-  callData: undefined,
-  callConversationInfos: undefined,
-  callMembers: undefined,
-  startCall: () => {},
-  exitCall: () => {},
-};
-
-export const CallManagerContext = createContext<ICallManagerContext>(defaultCallManagerContext);
-CallManagerContext.displayName = 'CallManagerContext';
+const optionalCallManagerContext = createOptionalContext<ICallManager>('CallManagerContext');
+export const useCallManagerContext = optionalCallManagerContext.useOptionalContext;
 
 export default ({ children }: WithChildren) => {
-  const [callData, setCallData] = useState<CallData>();
-  const { setAlertContent } = useContext(AlertSnackbarContext);
-  const webSocket = useWebSocketContext();
-  const navigate = useNavigate();
-  const { data: conversationInfos } = useConversationInfosQuery(callData?.conversationId);
-  const { data: members } = useMembersQuery(callData?.conversationId);
   const { urlParams } = useUrlParams<ConversationRouteParams>();
-  const { t } = useTranslation();
+  const callManager = useCallManager();
 
-  const failStartCall = useCallback(() => {
-    throw new Error('Cannot start call: Already in a call');
-  }, []);
-
-  const startCall = !callData ? setCallData : failStartCall;
-
-  const exitCall = useCallback(() => {
-    if (!callData) {
-      return;
-    }
-
-    setCallData(undefined);
-    // TODO: write in chat that the call ended
-  }, [callData]);
-
-  useEffect(() => {
-    const callBeginListener = ({ conversationId, withVideoOn }: CallBegin) => {
-      if (callData) {
-        // TODO: Currently, we display a notification if already in a call.
-        //       In the future, we should handle receiving a call while already in another.
-        setAlertContent({
-          messageI18nKey: 'missed_incoming_call',
-          messageI18nContext: { conversationId },
-          severity: 'info',
-          alertOpen: true,
-        });
-        return;
-      }
-
-      startCall({ conversationId: conversationId, role: 'receiver', withVideoOn });
-      navigate(`/conversation/${conversationId}`);
-    };
-
-    webSocket.bind(WebSocketMessageType.CallBegin, callBeginListener);
-
-    return () => {
-      webSocket.unbind(WebSocketMessageType.CallBegin, callBeginListener);
-    };
-  }, [webSocket, navigate, startCall, callData, setAlertContent, t]);
-
-  const value = useMemo(
-    () => ({
-      startCall,
-      callData,
-      callConversationInfos: conversationInfos,
-      callMembers: members,
-      exitCall,
-    }),
-    [startCall, callData, conversationInfos, members, exitCall]
-  );
+  const { callData } = callManager;
 
   return (
-    <>
-      <CallManagerContext.Provider value={value}>
-        <CallProvider>
-          {callData && callData.conversationId !== urlParams.conversationId && (
-            <RemoteVideoOverlay callConversationId={callData.conversationId} />
-          )}
-          {children}
-        </CallProvider>
-      </CallManagerContext.Provider>
-    </>
+    <optionalCallManagerContext.Context.Provider value={callManager}>
+      {callData && callData.conversationId !== urlParams.conversationId && (
+        <RemoteVideoOverlay callConversationId={callData.conversationId} />
+      )}
+      {children}
+    </optionalCallManagerContext.Context.Provider>
   );
 };