Remove non-null assertion in ConversationProvider
- Add `createOptionalContext` that is used by `AuthContext` and `ConversationContext` to create a context with a hook
that can be used to retrieve its value and throw an error if it's undefined.
- In `router.tsx`, put `Messenger` inside `ConversationProvider`.
- In `ConversationListItem`, use the conversationId from the `ConversationContext ` instead of the url params.
- Fix bug in `CallInterface` with fullscreen.
- Remove unecessary useEffect dependency in `NotificationManager`.
Change-Id: Ib5f0ae6a0a34cdbdb02f871e36194376d945230d
diff --git a/client/src/components/CallChatDrawer.tsx b/client/src/components/CallChatDrawer.tsx
index d2040d7..ef5cf04 100644
--- a/client/src/components/CallChatDrawer.tsx
+++ b/client/src/components/CallChatDrawer.tsx
@@ -19,7 +19,7 @@
import { useContext } from 'react';
import { CallContext } from '../contexts/CallProvider';
-import { ConversationContext } from '../contexts/ConversationProvider';
+import { useConversationContext } from '../contexts/ConversationProvider';
import ChatInterface from '../pages/ChatInterface';
import { CloseButton } from './Button';
@@ -45,7 +45,7 @@
const CallChatDrawerHeader = () => {
const { setIsChatShown } = useContext(CallContext);
- const { conversation } = useContext(ConversationContext);
+ const { conversation } = useConversationContext();
// TODO: Improve this to support multiple members
const contact = conversation.getFirstMember().contact;
diff --git a/client/src/components/ConversationListItem.tsx b/client/src/components/ConversationListItem.tsx
index ebf0c93..41cfbe4 100644
--- a/client/src/components/ConversationListItem.tsx
+++ b/client/src/components/ConversationListItem.tsx
@@ -22,13 +22,12 @@
import { useNavigate } from 'react-router-dom';
import { useAuthContext } from '../contexts/AuthProvider';
+import { useConversationContext } from '../contexts/ConversationProvider';
import { MessengerContext } from '../contexts/MessengerProvider';
import { useStartCall } from '../hooks/useStartCall';
-import { useUrlParams } from '../hooks/useUrlParams';
import { Conversation } from '../models/Conversation';
import { setRefreshFromSlice } from '../redux/appSlice';
import { useAppDispatch } from '../redux/hooks';
-import { ConversationRouteParams } from '../router';
import ContextMenu, { ContextMenuHandler, useContextMenuHandler } from './ContextMenu';
import ConversationAvatar from './ConversationAvatar';
import { ConfirmationDialog, DialogContentList, InfosDialog, useDialogHandler } from './Dialog';
@@ -48,13 +47,12 @@
};
export default function ConversationListItem({ conversation }: ConversationListItemProps) {
- const {
- urlParams: { conversationId },
- } = useUrlParams<ConversationRouteParams>();
+ const conversationContext = useConversationContext(true);
+ const conversationId = conversationContext?.conversationId;
const contextMenuHandler = useContextMenuHandler();
const { newContactId, setNewContactId } = useContext(MessengerContext);
- const pathId = conversationId || newContactId;
+ const pathId = conversationId ?? newContactId;
const isSelected = conversation.getDisplayUri() === pathId;
const navigate = useNavigate();
diff --git a/client/src/components/ConversationView.tsx b/client/src/components/ConversationView.tsx
index 0319574..075ca6e 100644
--- a/client/src/components/ConversationView.tsx
+++ b/client/src/components/ConversationView.tsx
@@ -16,11 +16,11 @@
* <https://www.gnu.org/licenses/>.
*/
import { Divider, Stack, Typography } from '@mui/material';
-import { useContext, useMemo } from 'react';
+import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useAuthContext } from '../contexts/AuthProvider';
-import { ConversationContext } from '../contexts/ConversationProvider';
+import { useConversationContext } from '../contexts/ConversationProvider';
import { useStartCall } from '../hooks/useStartCall';
import { ConversationMember } from '../models/Conversation';
import ChatInterface from '../pages/ChatInterface';
@@ -43,7 +43,7 @@
const ConversationHeader = () => {
const { account } = useAuthContext();
- const { conversation, conversationId } = useContext(ConversationContext);
+ const { conversation, conversationId } = useConversationContext();
const { t } = useTranslation();
const members = conversation.getMembers();
diff --git a/client/src/contexts/AuthProvider.tsx b/client/src/contexts/AuthProvider.tsx
index 06bf1bb..d0d88cc 100644
--- a/client/src/contexts/AuthProvider.tsx
+++ b/client/src/contexts/AuthProvider.tsx
@@ -17,10 +17,11 @@
*/
import axios, { AxiosInstance } from 'axios';
import { HttpStatusCode } from 'jami-web-common';
-import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import ProcessingRequest from '../components/ProcessingRequest';
+import { createOptionalContext } from '../hooks/createOptionalContext';
import { Account } from '../models/Account';
import { apiUrl } from '../utils/constants';
import { WithChildren } from '../utils/utils';
@@ -33,7 +34,9 @@
axiosInstance: AxiosInstance;
}
-const AuthContext = createContext<IAuthContext | undefined>(undefined);
+const optionalAuthContext = createOptionalContext<IAuthContext>('AuthContext');
+const AuthContext = optionalAuthContext.Context;
+export const useAuthContext = optionalAuthContext.useOptionalContext;
export default ({ children }: WithChildren) => {
const [token, setToken] = useState<string | undefined>();
@@ -109,13 +112,3 @@
</AuthContext.Provider>
);
};
-
-export function useAuthContext(dontThrowIfUndefined: true): IAuthContext | undefined;
-export function useAuthContext(): IAuthContext;
-export function useAuthContext(dontThrowIfUndefined?: true) {
- const authContext = useContext(AuthContext);
- if (!authContext && !dontThrowIfUndefined) {
- throw new Error('AuthContext is not provided');
- }
- return authContext;
-}
diff --git a/client/src/contexts/CallProvider.tsx b/client/src/contexts/CallProvider.tsx
index 13e0d24..39aad07 100644
--- a/client/src/contexts/CallProvider.tsx
+++ b/client/src/contexts/CallProvider.tsx
@@ -25,7 +25,7 @@
import { CallRouteParams } from '../router';
import { callTimeoutMs } from '../utils/constants';
import { SetState, WithChildren } from '../utils/utils';
-import { ConversationContext } from './ConversationProvider';
+import { useConversationContext } from './ConversationProvider';
import { MediaDevicesInfo, MediaInputKind, WebRtcContext } from './WebRtcProvider';
import { IWebSocketContext, WebSocketContext } from './WebSocketProvider';
@@ -124,7 +124,7 @@
const { state: routeState } = useUrlParams<CallRouteParams>();
const { localStream, sendWebRtcOffer, iceConnectionState, closeConnection, getMediaDevices, updateLocalStream } =
useContext(WebRtcContext);
- const { conversationId, conversation } = useContext(ConversationContext);
+ const { conversationId, conversation } = useConversationContext();
const navigate = useNavigate();
const [mediaDevices, setMediaDevices] = useState(defaultCallContext.mediaDevices);
diff --git a/client/src/contexts/ConversationProvider.tsx b/client/src/contexts/ConversationProvider.tsx
index 25137c0..b927278 100644
--- a/client/src/contexts/ConversationProvider.tsx
+++ b/client/src/contexts/ConversationProvider.tsx
@@ -16,9 +16,10 @@
* <https://www.gnu.org/licenses/>.
*/
import { ConversationView, WebSocketMessageType } from 'jami-web-common';
-import { createContext, useContext, useEffect, useState } from 'react';
+import { useContext, useEffect, useState } from 'react';
import LoadingPage from '../components/Loading';
+import { createOptionalContext } from '../hooks/createOptionalContext';
import { useUrlParams } from '../hooks/useUrlParams';
import { Conversation } from '../models/Conversation';
import { ConversationRouteParams } from '../router';
@@ -27,12 +28,14 @@
import { useAuthContext } from './AuthProvider';
import { WebSocketContext } from './WebSocketProvider';
-interface IConversationProvider {
+interface IConversationContext {
conversationId: string;
conversation: Conversation;
}
-export const ConversationContext = createContext<IConversationProvider>(undefined!);
+const optionalConversationContext = createOptionalContext<IConversationContext>('ConversationContext');
+const ConversationContext = optionalConversationContext.Context;
+export const useConversationContext = optionalConversationContext.useOptionalContext;
export default ({ children }: WithChildren) => {
const {
diff --git a/client/src/contexts/WebRtcProvider.tsx b/client/src/contexts/WebRtcProvider.tsx
index 4cd6263..59999b9 100644
--- a/client/src/contexts/WebRtcProvider.tsx
+++ b/client/src/contexts/WebRtcProvider.tsx
@@ -22,7 +22,7 @@
import LoadingPage from '../components/Loading';
import { WithChildren } from '../utils/utils';
import { useAuthContext } from './AuthProvider';
-import { ConversationContext } from './ConversationProvider';
+import { useConversationContext } from './ConversationProvider';
import { IWebSocketContext, WebSocketContext } from './WebSocketProvider';
export type MediaDevicesInfo = Record<MediaDeviceKind, MediaDeviceInfo[]>;
@@ -99,7 +99,7 @@
webRtcConnection: RTCPeerConnection;
webSocket: IWebSocketContext;
}) => {
- const { conversation, conversationId } = useContext(ConversationContext);
+ const { conversation, conversationId } = useConversationContext();
const [localStream, setLocalStream] = useState<MediaStream>();
const [remoteStreams, setRemoteStreams] = useState<readonly MediaStream[]>();
const [iceConnectionState, setIceConnectionState] = useState<RTCIceConnectionState | undefined>();
diff --git a/client/src/hooks/createOptionalContext.ts b/client/src/hooks/createOptionalContext.ts
new file mode 100644
index 0000000..73c4258
--- /dev/null
+++ b/client/src/hooks/createOptionalContext.ts
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { createContext, useContext } from 'react';
+
+export const createOptionalContext = <T>(displayName: string) => {
+ const Context = createContext<T | undefined>(undefined);
+ Context.displayName = displayName;
+
+ function useOptionalContext(noThrow: true): T | undefined;
+ function useOptionalContext(noThrow?: false): T;
+ function useOptionalContext(noThrow?: boolean) {
+ const value = useContext(Context);
+ if (value === undefined && !noThrow) {
+ throw new Error(`The context ${Context.displayName} is not provided`);
+ }
+ return value;
+ }
+
+ return {
+ Context,
+ useOptionalContext,
+ };
+};
diff --git a/client/src/managers/NotificationManager.tsx b/client/src/managers/NotificationManager.tsx
index 63eccc6..907efb5 100644
--- a/client/src/managers/NotificationManager.tsx
+++ b/client/src/managers/NotificationManager.tsx
@@ -19,7 +19,6 @@
import { useContext, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
-import { useAuthContext } from '../contexts/AuthProvider';
import { CallStatus } from '../contexts/CallProvider';
import { WebSocketContext } from '../contexts/WebSocketProvider';
import { WithChildren } from '../utils/utils';
@@ -30,7 +29,6 @@
export default ({ children }: WithChildren) => {
const webSocket = useContext(WebSocketContext);
const navigate = useNavigate();
- const { axiosInstance } = useAuthContext();
useEffect(() => {
if (!webSocket) {
@@ -53,7 +51,7 @@
return () => {
webSocket.unbind(WebSocketMessageType.CallBegin, callBeginListener);
};
- }, [webSocket, navigate, axiosInstance]);
+ }, [webSocket, navigate]);
return <>{children}</>;
};
diff --git a/client/src/pages/CallInterface.tsx b/client/src/pages/CallInterface.tsx
index 54c9b10..f21c49c 100644
--- a/client/src/pages/CallInterface.tsx
+++ b/client/src/pages/CallInterface.tsx
@@ -47,7 +47,7 @@
} from '../components/CallButtons';
import CallChatDrawer from '../components/CallChatDrawer';
import { CallContext, CallStatus } from '../contexts/CallProvider';
-import { ConversationContext } from '../contexts/ConversationProvider';
+import { useConversationContext } from '../contexts/ConversationProvider';
import { WebRtcContext } from '../contexts/WebRtcProvider';
import { VideoElementWithSinkId } from '../utils/utils';
import { CallPending } from './CallPending';
@@ -63,7 +63,7 @@
if (isFullscreen && document.fullscreenElement === null) {
callInterfaceRef.current.requestFullscreen();
- } else if (!isFullscreen && document.fullscreenEnabled !== null) {
+ } else if (!isFullscreen && document.fullscreenElement !== null) {
document.exitFullscreen();
}
}, [isFullscreen]);
@@ -187,7 +187,7 @@
const CallInterfaceInformation = () => {
const { callStartTime } = useContext(CallContext);
- const { conversation } = useContext(ConversationContext);
+ const { conversation } = useConversationContext();
const [elapsedTime, setElapsedTime] = useState(0);
const memberName = useMemo(() => conversation.getFirstMember().contact.getRegisteredName(), [conversation]);
diff --git a/client/src/pages/CallPending.tsx b/client/src/pages/CallPending.tsx
index 4bdbef4..2cd57c6 100644
--- a/client/src/pages/CallPending.tsx
+++ b/client/src/pages/CallPending.tsx
@@ -29,13 +29,13 @@
} from '../components/CallButtons';
import ConversationAvatar from '../components/ConversationAvatar';
import { CallContext, CallStatus } from '../contexts/CallProvider';
-import { ConversationContext } from '../contexts/ConversationProvider';
+import { useConversationContext } from '../contexts/ConversationProvider';
import { WebRtcContext } from '../contexts/WebRtcProvider';
import { VideoElementWithSinkId } from '../utils/utils';
export const CallPending = () => {
const { localStream } = useContext(WebRtcContext);
- const { conversation } = useContext(ConversationContext);
+ const { conversation } = useConversationContext();
const { callRole } = useContext(CallContext);
const localVideoRef = useRef<VideoElementWithSinkId | null>(null);
@@ -148,7 +148,7 @@
export const CallPendingCallerInterface = () => {
const { callStatus } = useContext(CallContext);
const { t } = useTranslation();
- const { conversation } = useContext(ConversationContext);
+ const { conversation } = useConversationContext();
const memberName = useMemo(() => conversation.getFirstMember().contact.getRegisteredName(), [conversation]);
let title = t('loading');
@@ -182,7 +182,7 @@
const { callStatus } = useContext(CallContext);
const { t } = useTranslation();
- const { conversation } = useContext(ConversationContext);
+ const { conversation } = useConversationContext();
const memberName = useMemo(() => conversation.getFirstMember().contact.getRegisteredName(), [conversation]);
let title = t('loading');
diff --git a/client/src/pages/ChatInterface.tsx b/client/src/pages/ChatInterface.tsx
index f6a637a..4ae9ce6 100644
--- a/client/src/pages/ChatInterface.tsx
+++ b/client/src/pages/ChatInterface.tsx
@@ -24,14 +24,14 @@
import LoadingPage from '../components/Loading';
import MessageList from '../components/MessageList';
import SendMessageForm from '../components/SendMessageForm';
-import { ConversationContext } from '../contexts/ConversationProvider';
+import { useConversationContext } from '../contexts/ConversationProvider';
import { WebSocketContext } from '../contexts/WebSocketProvider';
import { useMessagesQuery, useSendMessageMutation } from '../services/conversationQueries';
import { FileHandler } from '../utils/files';
const ChatInterface = () => {
const webSocket = useContext(WebSocketContext);
- const { conversationId, conversation } = useContext(ConversationContext);
+ const { conversationId, conversation } = useConversationContext();
const [messages, setMessages] = useState<Message[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(false);
diff --git a/client/src/router.tsx b/client/src/router.tsx
index 7bbddd4..8ed3403 100644
--- a/client/src/router.tsx
+++ b/client/src/router.tsx
@@ -79,11 +79,11 @@
<Route
path="conversation/:conversationId"
element={
- <Messenger>
- <ConversationProvider>
+ <ConversationProvider>
+ <Messenger>
<Outlet />
- </ConversationProvider>
- </Messenger>
+ </Messenger>
+ </ConversationProvider>
}
>
<Route index element={<ConversationView />} />