Add AuthProvider to provide account and token

When logged in, the token and account info can be retrieved using
`useAuthContext` hook.
Fix jamid.node path in jamid.ts
Fix some eslint warnings.

Change-Id: I1ea4d537693df807b4ea67a277addfecfc749e4a
diff --git a/Dockerfile b/Dockerfile
index be62653..0f6c3cf 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -11,6 +11,7 @@
 
 # Create a symlink to the daemon node app
 RUN ln -s /daemon/bin/nodejs/build/Release/jamid.node jamid.node
+RUN mkdir server && ln -s /daemon/bin/nodejs/build/Release/jamid.node server/jamid.node
 
 COPY package*.json ./
 COPY client/package*.json client/
diff --git a/README.md b/README.md
index 34fd4fc..c63bec7 100644
--- a/README.md
+++ b/README.md
@@ -23,10 +23,11 @@
 
 To build the dring.node Javascript interface to talk to the daemon api go to the daemon repo and use ./configure --with-nodejs then execute make -j4 to build the daemon
 
-Create a symbolic link to `jamid.node` at the root of jami-web:
+Create a symbolic link to `jamid.node` at the root of jami-web and inside `server/`:
 
 ```bash
 ln -s daemon/bin/nodejs/build/Release/jamid.node jamid.node
+ln -s daemon/bin/nodejs/build/Release/jamid.node server/jamid.node
 ```
 
 Then, start the servers:
diff --git a/client/src/components/AccountPreferences.tsx b/client/src/components/AccountPreferences.tsx
index 2eedc97..8ca098e 100644
--- a/client/src/components/AccountPreferences.tsx
+++ b/client/src/components/AccountPreferences.tsx
@@ -35,11 +35,11 @@
   Typography,
 } from '@mui/material';
 import { motion } from 'framer-motion';
-import { Account } from 'jami-web-common';
-import { AccountDetails } from 'jami-web-common';
+import { Account, AccountDetails } from 'jami-web-common';
 import { useState } from 'react';
 
 import authManager from '../AuthManager';
+import { useAuthContext } from '../contexts/AuthProvider';
 import ConversationAvatar from './ConversationAvatar';
 import ConversationsOverviewCard from './ConversationsOverviewCard';
 import JamiIdCard from './JamiIdCard';
@@ -57,10 +57,17 @@
 };
 
 type AccountPreferencesProps = {
-  account: Account;
+  // TODO: Remove account prop after migration to new server
+  account?: Account;
 };
 
-export default function AccountPreferences({ account }: AccountPreferencesProps) {
+export default function AccountPreferences({ account: _account }: AccountPreferencesProps) {
+  const authContext = useAuthContext(true);
+  const account = _account ?? authContext?.account;
+  if (!account) {
+    throw new Error('Account not defined');
+  }
+
   const devices: string[][] = [];
   const accountDevices = account.getDevices();
   for (const i in accountDevices) devices.push([i, accountDevices[i]]);
@@ -241,7 +248,7 @@
                     </ListItemAvatar>
                     <ListItemText primary={moderator.getDisplayName()} />
                     <ListItemSecondaryAction>
-                      <IconButton onClick={(e) => removeModerator(moderator.getUri())} size="large">
+                      <IconButton onClick={() => removeModerator(moderator.getUri())} size="large">
                         <DeleteRounded />
                       </IconButton>
                     </ListItemSecondaryAction>
diff --git a/client/src/components/Button.tsx b/client/src/components/Button.tsx
index 9264355..c33def2 100644
--- a/client/src/components/Button.tsx
+++ b/client/src/components/Button.tsx
@@ -213,7 +213,7 @@
   <IconButton {...props} disableRipple={true}>
     <Icon fontSize="inherit" />
   </IconButton>
-))(({ theme }) => ({
+))(() => ({
   color: '#7E7E7E',
   fontSize: '25px',
   height: '36px',
@@ -278,7 +278,7 @@
   <IconButton {...props} disableRipple={true}>
     {emoji}
   </IconButton>
-))(({ theme }) => ({
+))(() => ({
   color: 'white',
   fontSize: '20px',
   height: '20px',
@@ -288,7 +288,7 @@
 type SelectEmojiButtonProps = {
   onEmojiSelected: (emoji: string) => void;
 };
-export const SelectEmojiButton = ({ onEmojiSelected, ...props }: SelectEmojiButtonProps) => {
+export const SelectEmojiButton = ({ onEmojiSelected }: SelectEmojiButtonProps) => {
   const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
 
   const handleOpenEmojiPicker = useCallback(
diff --git a/client/src/components/ContactList.jsx b/client/src/components/ContactList.jsx
index 56f5f9c..c35c4f0 100644
--- a/client/src/components/ContactList.jsx
+++ b/client/src/components/ContactList.jsx
@@ -37,7 +37,7 @@
 };
 
 export default function ContactList() {
-  const { accountId, account } = useAppSelector((state) => state.userInfo);
+  const { accountId } = useAppSelector((state) => state.userInfo);
   const dispatch = useAppDispatch();
 
   const [contacts, setContacts] = useState([]);
diff --git a/client/src/components/ConversationListItem.tsx b/client/src/components/ConversationListItem.tsx
index b432a45..9a928e7 100644
--- a/client/src/components/ConversationListItem.tsx
+++ b/client/src/components/ConversationListItem.tsx
@@ -92,8 +92,8 @@
   const [modalDetailsIsOpen, setModalDetailsIsOpen] = useState(false);
   const [modalDeleteIsOpen, setModalDeleteIsOpen] = useState(false);
   const [blockOrRemove, setBlockOrRemove] = useState(true);
-  const [userId, setUserId] = useState(conversation?.getFirstMember()?.contact.getUri());
-  const [isSwarm, setIsSwarm] = useState(true);
+  const [userId] = useState(conversation?.getFirstMember()?.contact.getUri());
+  const [isSwarm] = useState(true);
 
   const navigateUrlPrefix = `/deprecated-account/${conversation.getAccountId()}`;
 
@@ -134,7 +134,7 @@
         method: 'DELETE',
       })
       .then((res) => res.json())
-      .then((result) => {
+      .then(() => {
         console.log('propre');
         dispatch(setRefreshFromSlice());
       })
diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx
index 2a00e7f..445dbae 100644
--- a/client/src/components/Header.tsx
+++ b/client/src/components/Header.tsx
@@ -19,9 +19,12 @@
 import { MouseEvent, useState } from 'react';
 import { useNavigate, useParams } from 'react-router-dom';
 
+import { useAuthContext } from '../contexts/AuthProvider';
 import { setAccessToken } from '../utils/auth';
 
 export default function Header() {
+  const authContext = useAuthContext(true);
+
   const navigate = useNavigate();
   const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
   const handleClick = (event: MouseEvent<HTMLButtonElement>) => setAnchorEl(event.currentTarget);
@@ -31,10 +34,12 @@
   const goToContacts = () => navigate(`/contacts`);
   const goToAccountSettings = () => navigate(`/deprecated-account/${params.accountId}/settings`);
 
-  const logout = () => {
+  const deprecatedLogout = () => {
     setAccessToken('');
-    navigate('/', { replace: true });
+    navigate('/deprecated-account', { replace: true });
   };
+  // TODO: Remove deprecated_logout once migration to new server is complete
+  const logout = authContext?.logout ?? deprecatedLogout;
 
   return (
     <Box>
diff --git a/client/src/contexts/AuthProvider.tsx b/client/src/contexts/AuthProvider.tsx
new file mode 100644
index 0000000..50b1723
--- /dev/null
+++ b/client/src/contexts/AuthProvider.tsx
@@ -0,0 +1,107 @@
+/*
+ * 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 { Account } from 'jami-web-common/dist/Account';
+import { HttpStatusCode } from 'jami-web-common/dist/enums/http-status-code';
+import { createContext, useCallback, useContext, useEffect, useState } from 'react';
+import { Outlet, useNavigate } from 'react-router-dom';
+
+import ProcessingRequest from '../components/ProcessingRequest';
+import { apiUrl } from '../utils/constants';
+
+interface IAuthContext {
+  token: string;
+  account: Account;
+  logout: () => void;
+}
+
+const AuthContext = createContext<IAuthContext | undefined>(undefined);
+
+export default () => {
+  const [token, setToken] = useState<string | undefined>();
+  const [account, setAccount] = useState<Account | undefined>();
+  const navigate = useNavigate();
+
+  const logout = useCallback(() => {
+    localStorage.removeItem('accessToken');
+    navigate('/');
+  }, [navigate]);
+
+  useEffect(() => {
+    const accessToken = localStorage.getItem('accessToken');
+
+    if (!accessToken) {
+      console.warn('Missing authentication JWT. Redirecting to login page...');
+      logout();
+    } else {
+      setToken(accessToken);
+    }
+  }, [logout]);
+
+  useEffect(() => {
+    if (token) {
+      const getAccount = async () => {
+        const url = new URL('/account', apiUrl);
+        const response = await fetch(url, {
+          method: 'GET',
+          headers: {
+            Authorization: `Bearer ${token}`,
+          },
+        });
+
+        if (response.status === HttpStatusCode.Ok) {
+          const serializedAccount = await response.json();
+          const account = Account.from(serializedAccount);
+          setAccount(account);
+        } else {
+          throw new Error(response.statusText);
+        }
+      };
+
+      getAccount().catch((e) => {
+        console.error('Error while retrieving account: ', e);
+        logout();
+      });
+    }
+  }, [token, logout]);
+
+  if (!token || !account) {
+    return <ProcessingRequest open />;
+  }
+
+  return (
+    <AuthContext.Provider
+      value={{
+        token,
+        logout,
+        account,
+      }}
+    >
+      <Outlet />
+    </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/index.tsx b/client/src/index.tsx
index e95a74d..968e62b 100644
--- a/client/src/index.tsx
+++ b/client/src/index.tsx
@@ -29,15 +29,16 @@
 
 import App from './App';
 import ContactList from './components/ContactList';
+import AuthProvider from './contexts/AuthProvider';
 import { SocketProvider } from './contexts/Socket';
 import AccountSelection from './pages/AccountSelection';
 import AccountSettings from './pages/AccountSettings';
 import CallInterface from './pages/CallInterface';
 import DeprecatedAccountSettings from './pages/DeprecatedAccountSettings';
-import Home from './pages/Home';
 import JamiMessenger from './pages/JamiMessenger';
 import Messenger from './pages/Messenger';
 import ServerSetup from './pages/ServerSetup';
+import Welcome from './pages/Welcome';
 import { store } from './redux/store';
 import defaultTheme from './themes/Default';
 import { ThemeDemonstrator } from './themes/ThemeDemonstrator';
@@ -55,11 +56,13 @@
 const router = createBrowserRouter(
   createRoutesFromElements(
     <Route path="/" element={<App />}>
-      <Route index element={<Home />} />
+      <Route index element={<Welcome />} />
       <Route path="theme" element={<ThemeDemonstrator />} />
-      <Route path="account" element={<JamiMessenger />} />
-      <Route path="settings" element={<AccountSettings />} />
-      <Route path="contacts" element={<ContactList />} />
+      <Route element={<AuthProvider />}>
+        <Route path="account" element={<JamiMessenger />} />
+        <Route path="settings" element={<AccountSettings />} />
+        <Route path="contacts" element={<ContactList />} />
+      </Route>
       <Route path="setup" element={<ServerSetup />} />
       {/* TODO: Remove this block after migration to new server*/}
       <Route path="deprecated-account" element={<AccountSelection />} />
diff --git a/client/src/pages/AccountSelection.tsx b/client/src/pages/AccountSelection.tsx
index 708f1eb..f8daf0a 100644
--- a/client/src/pages/AccountSelection.tsx
+++ b/client/src/pages/AccountSelection.tsx
@@ -36,7 +36,7 @@
 const AccountSelection = () => {
   const navigate = useNavigate();
   const [loaded, setLoaded] = useState(false);
-  const [error, setError] = useState(false);
+  const [, setError] = useState(false);
   const [accounts, setAccounts] = useState<Account[]>([]);
 
   authManager.authenticate('admin', 'admin');
diff --git a/client/src/pages/AccountSettings.tsx b/client/src/pages/AccountSettings.tsx
index b09fb65..f7b5e4f 100644
--- a/client/src/pages/AccountSettings.tsx
+++ b/client/src/pages/AccountSettings.tsx
@@ -16,67 +16,16 @@
  * <https://www.gnu.org/licenses/>.
  */
 import { Container } from '@mui/material';
-import { Account, HttpStatusCode } from 'jami-web-common';
-import { useEffect } from 'react';
-import { useNavigate } from 'react-router-dom';
 
 import AccountPreferences from '../components/AccountPreferences';
 import Header from '../components/Header';
-import ProcessingRequest from '../components/ProcessingRequest';
-import { setAccount } from '../redux/appSlice';
-import { useAppDispatch, useAppSelector } from '../redux/hooks';
-import { getAccessToken, setAccessToken } from '../utils/auth';
-import { apiUrl } from '../utils/constants';
 
 export default function AccountSettings() {
-  const dispatch = useAppDispatch();
-  const navigate = useNavigate();
-
-  const { account } = useAppSelector((state) => state.userInfo);
-  const accessToken = getAccessToken();
-
-  useEffect(() => {
-    if (accessToken) {
-      const getAccount = async () => {
-        const url = new URL('/account', apiUrl);
-        let response: Response;
-
-        try {
-          response = await fetch(url, {
-            method: 'GET',
-            mode: 'cors',
-            headers: {
-              Authorization: `Bearer ${accessToken}`,
-            },
-            referrerPolicy: 'no-referrer',
-          });
-        } catch (err) {
-          setAccessToken('');
-          dispatch(setAccount(undefined));
-          navigate('/', { replace: true });
-          return;
-        }
-
-        if (response.status === HttpStatusCode.Ok) {
-          const serializedAccount = await response.json();
-          const account = Account.from(serializedAccount);
-          dispatch(setAccount(account));
-        } else if (response.status === HttpStatusCode.Unauthorized) {
-          setAccessToken('');
-          dispatch(setAccount(undefined));
-          navigate('/', { replace: true });
-        }
-      };
-
-      getAccount();
-    }
-  }, [accessToken, dispatch, navigate]);
-
   // TODO: Improve component and sub-components UI.
   return (
     <Container maxWidth="sm">
       <Header />
-      {account ? <AccountPreferences account={account} /> : <ProcessingRequest open={true} />}
+      <AccountPreferences />
     </Container>
   );
 }
diff --git a/client/src/pages/DeprecatedAccountSettings.tsx b/client/src/pages/DeprecatedAccountSettings.tsx
index 4a37cd9..f69334d 100644
--- a/client/src/pages/DeprecatedAccountSettings.tsx
+++ b/client/src/pages/DeprecatedAccountSettings.tsx
@@ -23,7 +23,7 @@
 import authManager from '../AuthManager';
 import AccountPreferences from '../components/AccountPreferences';
 import Header from '../components/Header';
-import { setAccount, setAccountId } from '../redux/appSlice';
+import { setAccountId } from '../redux/appSlice';
 import { useAppDispatch } from '../redux/hooks';
 
 type AccountSettingsProps = {
@@ -55,7 +55,6 @@
         console.log(result);
         const account = Account.from(result);
         account.setDevices(result.devices);
-        dispatch(setAccount(account));
         setLocalAccount(account);
       })
       .catch((e) => console.log(e));
diff --git a/client/src/pages/Messenger.tsx b/client/src/pages/Messenger.tsx
index 0d13d5a..c38294a 100644
--- a/client/src/pages/Messenger.tsx
+++ b/client/src/pages/Messenger.tsx
@@ -83,7 +83,7 @@
         contact.setRegisteredName(response.name);
         setSearchResults(contact ? Conversation.fromSingleContact(accountId, contact) : undefined);
       })
-      .catch((e) => {
+      .catch(() => {
         setSearchResults(undefined);
       });
     // return () => controller.abort() // crash on React18
diff --git a/client/src/pages/Home.tsx b/client/src/pages/Welcome.tsx
similarity index 95%
rename from client/src/pages/Home.tsx
rename to client/src/pages/Welcome.tsx
index cdebb42..59899ea 100644
--- a/client/src/pages/Home.tsx
+++ b/client/src/pages/Welcome.tsx
@@ -27,7 +27,7 @@
 
 const borderRadius = 30;
 
-export default function Home() {
+export default function Welcome() {
   const theme: Theme = useTheme();
   const [isRegistrationDisplayed, setIsRegistrationDisplayed] = useState<boolean>(false);
 
@@ -90,9 +90,7 @@
                 sx={{ mt: theme.typography.pxToRem(30), mb: theme.typography.pxToRem(20) }}
               />
             )}
-            <Box className="home-child" sx={{ height: `${isMobile ? 'auto' : '100%'}` }}>
-              {child}
-            </Box>
+            <Box sx={{ height: `${isMobile ? 'auto' : '100%'}` }}>{child}</Box>
           </Grid>
         </Grid>
       </Paper>
diff --git a/client/src/redux/appSlice.ts b/client/src/redux/appSlice.ts
index b04d5db..895415b 100644
--- a/client/src/redux/appSlice.ts
+++ b/client/src/redux/appSlice.ts
@@ -16,14 +16,12 @@
  * <https://www.gnu.org/licenses/>.
  */
 import { createSlice, PayloadAction } from '@reduxjs/toolkit';
-import { Account } from 'jami-web-common';
 
 // Define a type for the slice state
 export interface AppState {
   // TODO: Remove accountId when account endpoints available.
   // Left for backwards compatibility, not necessary, included in token.
   accountId: string;
-  account?: Account;
   // TODO : Evaluate need for this when WebSocket will be available.
   refresh: boolean;
 }
@@ -31,7 +29,6 @@
 // Define the initial state using that type
 const initialState: AppState = {
   accountId: '',
-  account: undefined,
   refresh: true,
 };
 
@@ -43,16 +40,13 @@
     setAccountId: (state, action: PayloadAction<string>) => {
       state.accountId = action.payload;
     },
-    setAccount: (state, action: PayloadAction<Account | undefined>) => {
-      state.account = action.payload;
-    },
     setRefreshFromSlice: (state) => {
       state.refresh = !state.refresh;
     },
   },
 });
 
-export const { setAccountId, setAccount, setRefreshFromSlice } = userInfoSlice.actions;
+export const { setAccountId, setRefreshFromSlice } = userInfoSlice.actions;
 
 // Other code such as selectors can use the imported `RootState` type
 // export const selectCount = (state: RootState) => state.app.value;