Replace fetch with Axios in client
Replace `.then` with await syntax where possible.
GitLab: #142
Change-Id: I6c132f49f152afa7e20919a1c70c539f2ad54878
diff --git a/client/package.json b/client/package.json
index 2bab75d..52106b2 100644
--- a/client/package.json
+++ b/client/package.json
@@ -55,7 +55,6 @@
"react-draggable": "^4.4.5",
"react-dropzone": "^14.2.3",
"react-emoji-render": "^1.2.4",
- "react-fetch-hook": "^1.9.5",
"react-i18next": "^11.18.6",
"react-modal": "^3.15.1",
"react-redux": "^8.0.2",
diff --git a/client/src/App.tsx b/client/src/App.tsx
index c202c37..f5c36a7 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -15,6 +15,7 @@
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
+import axios from 'axios';
import { useState } from 'react';
import { json, LoaderFunctionArgs, Outlet, redirect } from 'react-router-dom';
@@ -22,10 +23,10 @@
import { apiUrl } from './utils/constants';
export async function checkSetupStatus(): Promise<boolean> {
- const url = new URL('/setup/check', apiUrl);
- const response = await fetch(url);
- const { isSetupComplete } = await response.json();
- return isSetupComplete;
+ const { data } = await axios.get('/setup/check', {
+ baseURL: apiUrl,
+ });
+ return data.isSetupComplete;
}
export async function appLoader({ request }: LoaderFunctionArgs) {
diff --git a/client/src/components/AccountPreferences.tsx b/client/src/components/AccountPreferences.tsx
index f0c5d31..2086f14 100644
--- a/client/src/components/AccountPreferences.tsx
+++ b/client/src/components/AccountPreferences.tsx
@@ -39,7 +39,6 @@
import { useState } from 'react';
import { useAuthContext } from '../contexts/AuthProvider';
-import { apiUrl } from '../utils/constants';
import ConversationAvatar from './ConversationAvatar';
import ConversationsOverviewCard from './ConversationsOverviewCard';
import JamiIdCard from './JamiIdCard';
@@ -56,18 +55,8 @@
},
};
-type AccountPreferencesProps = {
- // TODO: Remove account prop after migration to new server
- account?: Account;
-};
-
-export default function AccountPreferences({ account: _account }: AccountPreferencesProps) {
- const authContext = useAuthContext(true);
- const account = _account ?? authContext?.account;
- const token = authContext?.token;
- if (!account || !token) {
- throw new Error('Account not defined');
- }
+export default function AccountPreferences() {
+ const { account, axiosInstance } = useAuthContext();
const devices: string[][] = [];
const accountDevices = account.getDevices();
@@ -82,40 +71,21 @@
const [details, setDetails] = useState(account.getDetails());
- const addModerator = () => {
+ const addModerator = async () => {
if (defaultModeratorUri) {
- fetch(new URL(`/default-moderators/${defaultModeratorUri}`, apiUrl), {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- method: 'PUT',
- });
+ await axiosInstance.put(`/default-moderators/${defaultModeratorUri}`);
setDefaultModeratorUri('');
}
};
- const removeModerator = (uri: string) =>
- fetch(new URL(`/default-moderators/${uri}`, apiUrl), {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- method: 'DELETE',
- });
+ const removeModerator = async (uri: string) => await axiosInstance.delete(`/default-moderators/${uri}`);
- const handleToggle = (key: keyof AccountDetails, value: boolean) => {
+ const handleToggle = async (key: keyof AccountDetails, value: boolean) => {
console.log(`handleToggle ${key} ${value}`);
const newDetails: Partial<AccountDetails> = {};
newDetails[key] = value ? 'true' : 'false';
console.log(newDetails);
- fetch(new URL('/account', apiUrl), {
- method: 'PATCH',
- headers: {
- Accept: 'application/json',
- Authorization: `Bearer ${token}`,
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(newDetails),
- });
+ await axiosInstance.patch('/account', newDetails);
setDetails({ ...account.updateDetails(newDetails) });
};
diff --git a/client/src/components/ContactList.jsx b/client/src/components/ContactList.jsx
index 3bc040b..578d3c9 100644
--- a/client/src/components/ContactList.jsx
+++ b/client/src/components/ContactList.jsx
@@ -23,7 +23,6 @@
import { useAuthContext } from '../contexts/AuthProvider';
import { useAppDispatch, useAppSelector } from '../redux/hooks';
-import { apiUrl } from '../utils/constants';
import ConversationAvatar from './ConversationAvatar';
const customStyles = {
@@ -38,7 +37,7 @@
};
export default function ContactList() {
- const { token } = useAuthContext();
+ const { axiosInstance } = useAuthContext();
const { accountId } = useAppSelector((state) => state.userInfo);
const dispatch = useAppDispatch();
@@ -59,22 +58,19 @@
const closeModalDetails = () => setModalDetailsIsOpen(false);
const closeModalDelete = () => setModalDeleteIsOpen(false);
- const getContactDetails = () => {
+ const getContactDetails = async () => {
const controller = new AbortController();
- fetch(new URL(`/contacts/${currentContact.id}`, apiUrl), {
- signal: controller.signal,
- headers: {
- Authorization: `Bearer ${token}`,
- },
- })
- .then((res) => res.json())
- .then((result) => {
- console.log('CONTACT LIST - DETAILS: ', result);
- })
- .catch((e) => console.log('ERROR GET CONTACT DETAILS: ', e));
+ try {
+ const data = await axiosInstance.get(`/contacts/${currentContact.id}`, {
+ signal: controller.signal,
+ });
+ console.log('CONTACT LIST - DETAILS: ', data);
+ } catch (e) {
+ console.log('ERROR GET CONTACT DETAILS: ', e);
+ }
};
- const removeOrBlock = (block = false) => {
+ const removeOrBlock = async (block = false) => {
console.log('REMOVE');
setBlockOrRemove(false);
const controller = new AbortController();
@@ -82,33 +78,29 @@
if (block) {
url += '/block';
}
- fetch(new URL(url, apiUrl), {
- signal: controller.signal,
- method: block ? 'POST' : 'DELETE',
- headers: {
- Authorization: `Bearer ${token}`,
- },
- })
- .then((res) => res.json())
- .catch((e) => console.log(`ERROR ${block ? 'blocking' : 'removing'} CONTACT : `, e));
+ try {
+ await axiosInstance(url, {
+ signal: controller.signal,
+ method: block ? 'POST' : 'DELETE',
+ });
+ } catch (e) {
+ console.log(`ERROR ${block ? 'blocking' : 'removing'} CONTACT : `, e);
+ }
closeModalDelete();
};
useEffect(() => {
const controller = new AbortController();
- fetch(new URL(`/contacts`, apiUrl), {
- signal: controller.signal,
- headers: {
- Authorization: `Bearer ${token}`,
- },
- })
- .then((res) => res.json())
- .then((result) => {
- console.log('CONTACTS: ', result);
- setContacts(result);
+ axiosInstance
+ .get(`/contacts`, {
+ signal: controller.signal,
+ })
+ .then(({ data }) => {
+ console.log('CONTACTS: ', data);
+ setContacts(data);
});
return () => controller.abort();
- }, [token]);
+ }, [axiosInstance]);
return (
<div className="rooms-list">
diff --git a/client/src/components/ConversationListItem.tsx b/client/src/components/ConversationListItem.tsx
index 5f10f6e..aeef7de 100644
--- a/client/src/components/ConversationListItem.tsx
+++ b/client/src/components/ConversationListItem.tsx
@@ -36,7 +36,6 @@
import { useAuthContext } from '../contexts/AuthProvider';
import { setRefreshFromSlice } from '../redux/appSlice';
import { useAppDispatch } from '../redux/hooks';
-import { apiUrl } from '../utils/constants';
import ConversationAvatar from './ConversationAvatar';
import {
AudioCallIcon,
@@ -89,7 +88,7 @@
};
export default function ConversationListItem({ conversation }: ConversationListItemProps) {
- const { token } = useAuthContext();
+ const { axiosInstance } = useAuthContext();
const { conversationId, contactId } = useParams();
const dispatch = useAppDispatch();
@@ -115,47 +114,36 @@
const closeModalDetails = () => setModalDetailsIsOpen(false);
const closeModalDelete = () => setModalDeleteIsOpen(false);
- const getContactDetails = () => {
+ const getContactDetails = async () => {
const controller = new AbortController();
- fetch(new URL(`/contacts/${userId}`, apiUrl), {
- signal: controller.signal,
- headers: {
- Authorization: `Bearer ${token}`,
- },
- })
- .then((res) => res.json())
- .then((result) => {
- console.log('CONTACT LIST - DETAILS: ', result);
- })
- .catch((e) => console.log('ERROR GET CONTACT DETAILS: ', e));
+ try {
+ const data = await axiosInstance.get(`/contacts/${userId}`, {
+ signal: controller.signal,
+ });
+ console.log('CONTACT LIST - DETAILS: ', data);
+ } catch (e) {
+ console.log('ERROR GET CONTACT DETAILS: ', e);
+ }
};
- const removeOrBlock = (block = false) => {
+ const removeOrBlock = async (block = false) => {
setBlockOrRemove(false);
- console.log('EEEH', conversation.getAccountId(), userId);
-
const controller = new AbortController();
let url = `/contacts/${userId}`;
if (block) {
url += '/block';
}
- fetch(new URL(url, apiUrl), {
- signal: controller.signal,
- method: block ? 'POST' : 'DELETE',
- headers: {
- Authorization: `Bearer ${token}`,
- },
- })
- .then((res) => res.json())
- .then(() => {
- console.log('propre');
- dispatch(setRefreshFromSlice());
- })
- .catch((e) => {
- console.log(`ERROR ${block ? 'blocking' : 'removing'} CONTACT : `, e);
- dispatch(setRefreshFromSlice());
+ try {
+ await axiosInstance(url, {
+ signal: controller.signal,
+ method: block ? 'POST' : 'DELETE',
});
+ dispatch(setRefreshFromSlice());
+ } catch (e) {
+ console.error(`Error ${block ? 'blocking' : 'removing'} contact : `, e);
+ dispatch(setRefreshFromSlice());
+ }
closeModalDelete();
};
diff --git a/client/src/components/ConversationsOverviewCard.tsx b/client/src/components/ConversationsOverviewCard.tsx
index 8a73cc2..84d022d 100644
--- a/client/src/components/ConversationsOverviewCard.tsx
+++ b/client/src/components/ConversationsOverviewCard.tsx
@@ -21,10 +21,9 @@
import { useNavigate } from 'react-router';
import { useAuthContext } from '../contexts/AuthProvider';
-import { apiUrl } from '../utils/constants';
export default function ConversationsOverviewCard() {
- const { token, account } = useAuthContext();
+ const { axiosInstance, account } = useAuthContext();
const navigate = useNavigate();
const [conversationCount, setConversationCount] = useState<number | undefined>();
@@ -33,19 +32,16 @@
useEffect(() => {
const controller = new AbortController();
- fetch(new URL('/conversations', apiUrl), {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- signal: controller.signal,
- })
- .then((res) => res.json())
- .then((result: Conversation[]) => {
- console.log(result);
- setConversationCount(result.length);
+ axiosInstance
+ .get<Conversation[]>('/conversations', {
+ signal: controller.signal,
+ })
+ .then(({ data }) => {
+ console.log(data);
+ setConversationCount(data.length);
});
return () => controller.abort(); // crash on React18
- }, [token, accountId]);
+ }, [axiosInstance, accountId]);
return (
<Card onClick={() => navigate(`/`)}>
diff --git a/client/src/components/UsernameChooser.jsx b/client/src/components/UsernameChooser.jsx
index 3c1f584..77c1d73 100644
--- a/client/src/components/UsernameChooser.jsx
+++ b/client/src/components/UsernameChooser.jsx
@@ -17,8 +17,8 @@
*/
import { SearchRounded } from '@mui/icons-material';
import { InputAdornment, TextField } from '@mui/material';
+import axios from 'axios';
import { useEffect, useState } from 'react';
-import usePromise from 'react-fetch-hook/usePromise';
import { apiUrl } from '../utils/constants.js';
@@ -26,17 +26,29 @@
export default function UsernameChooser({ setName, ...props }) {
const [query, setQuery] = useState('');
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState();
+ const [data, setData] = useState();
- const { isLoading, data, error } = usePromise(
- () =>
- isInputValid(query)
- ? fetch(new URL(`/ns/username/${query}`, apiUrl)).then((res) => {
- if (res.status === 200) return res.json();
- else throw res.status;
- })
- : new Promise((res, rej) => rej(400)),
- [query]
- );
+ useEffect(() => {
+ if (isInputValid(query)) {
+ setIsLoading(true);
+ axios
+ .get(`/ns/username/${query}`, {
+ baseURL: apiUrl,
+ })
+ .then((res) => {
+ setIsLoading(false);
+ if (res.status === 200) {
+ setData(res.data);
+ } else {
+ throw res.status;
+ }
+ });
+ } else {
+ setError(400);
+ }
+ }, [query]);
useEffect(() => {
if (!isLoading) {
diff --git a/client/src/contexts/AuthProvider.tsx b/client/src/contexts/AuthProvider.tsx
index ac829dd..440c4dc 100644
--- a/client/src/contexts/AuthProvider.tsx
+++ b/client/src/contexts/AuthProvider.tsx
@@ -15,9 +15,10 @@
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
+import axios, { AxiosInstance } from 'axios';
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 { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import ProcessingRequest from '../components/ProcessingRequest';
@@ -28,6 +29,7 @@
token: string;
account: Account;
logout: () => void;
+ axiosInstance: AxiosInstance;
}
const AuthContext = createContext<IAuthContext | undefined>(undefined);
@@ -42,6 +44,33 @@
navigate('/login');
}, [navigate]);
+ const axiosInstance = useMemo(() => {
+ if (!token) {
+ return;
+ }
+
+ const instance = axios.create({
+ baseURL: apiUrl,
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ instance.interceptors.response.use(
+ (res) => res,
+ (e) => {
+ switch (e.response?.status) {
+ case HttpStatusCode.Unauthorized:
+ logout();
+ break;
+ }
+ throw e;
+ }
+ );
+
+ return instance;
+ }, [token, logout]);
+
useEffect(() => {
const accessToken = localStorage.getItem('accessToken');
@@ -54,33 +83,14 @@
}, [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();
- });
+ if (!axiosInstance) {
+ return;
}
- }, [token, logout]);
- if (!token || !account) {
+ axiosInstance.get('/account').then(({ data }) => setAccount(Account.from(data)));
+ }, [axiosInstance, logout]);
+
+ if (!token || !account || !axiosInstance) {
return <ProcessingRequest open />;
}
@@ -90,6 +100,7 @@
token,
logout,
account,
+ axiosInstance,
}}
>
{children}
diff --git a/client/src/contexts/WebRTCProvider.tsx b/client/src/contexts/WebRTCProvider.tsx
index be5a045..86950f7 100644
--- a/client/src/contexts/WebRTCProvider.tsx
+++ b/client/src/contexts/WebRTCProvider.tsx
@@ -211,24 +211,21 @@
const sendWebRTCOffer = useCallback(async () => {
if (webRTCConnection && socket) {
- webRTCConnection
- .createOffer({
- offerToReceiveAudio: true,
- offerToReceiveVideo: true,
- })
- .then((sdp) => {
- socket.send({
- type: WebSocketMessageType.WebRTCOffer,
- data: {
- from: account.getId(),
- to: contactId,
- message: {
- sdp: sdp,
- },
- },
- });
- webRTCConnection.setLocalDescription(new RTCSessionDescription(sdp));
- });
+ const sdp = await webRTCConnection.createOffer({
+ offerToReceiveAudio: true,
+ offerToReceiveVideo: true,
+ });
+ socket.send({
+ type: WebSocketMessageType.WebRTCOffer,
+ data: {
+ from: account.getId(),
+ to: contactId,
+ message: {
+ sdp,
+ },
+ },
+ });
+ await webRTCConnection.setLocalDescription(new RTCSessionDescription(sdp));
}
}, [account, contactId, socket, webRTCConnection]);
diff --git a/client/src/locale/en/translation.json b/client/src/locale/en/translation.json
index 3185015..bd21509 100644
--- a/client/src/locale/en/translation.json
+++ b/client/src/locale/en/translation.json
@@ -55,6 +55,7 @@
"message_input_placeholder_three": "Write to {{member0}}, {{member1}} and {{member2}}",
"message_input_placeholder_four": "Write to {{member0}}, {{member1}}, {{member2}}, +1 other member",
"message_input_placeholder_more": "Write to {{member0}}, {{member1}}, {{member2}}, +{{excess}} other members",
+ "conversation_add_contact": "Add contact",
"login_username_not_found": "Username not found",
"login_invalid_password": "Incorrect password",
"login_form_title": "LOGIN",
diff --git a/client/src/locale/fr/translation.json b/client/src/locale/fr/translation.json
index b3098f4..e61c57a 100644
--- a/client/src/locale/fr/translation.json
+++ b/client/src/locale/fr/translation.json
@@ -55,6 +55,7 @@
"message_input_placeholder_three": "Écrire à {{member0}}, {{member1}} et {{member2}}",
"message_input_placeholder_four": "Écrire à {{member0}}, {{member1}}, {{member2}}, +1 autre membre",
"message_input_placeholder_more": "Écrire à {{member01}}, {{member1}}, {{member2}}, +{{excess}} autres membres",
+ "conversation_add_contact": "Ajouter le contact",
"login_username_not_found": "Nom d'utilisateur introuvable",
"login_invalid_password": "Mot de passe incorrect",
"login_form_title": "CONNEXION",
diff --git a/client/src/pages/AddContactPage.tsx b/client/src/pages/AddContactPage.tsx
index 2c2bfb2..442b405 100644
--- a/client/src/pages/AddContactPage.tsx
+++ b/client/src/pages/AddContactPage.tsx
@@ -17,40 +17,29 @@
*/
import GroupAddRounded from '@mui/icons-material/GroupAddRounded';
import { Box, Card, CardContent, Container, Fab, Typography } from '@mui/material';
+import { Trans } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { useAuthContext } from '../contexts/AuthProvider';
import { setRefreshFromSlice } from '../redux/appSlice';
import { useAppDispatch } from '../redux/hooks';
-import { apiUrl } from '../utils/constants';
type AddContactPageProps = {
contactId: string;
};
export default function AddContactPage({ contactId }: AddContactPageProps) {
- const { token } = useAuthContext();
+ const { axiosInstance } = useAuthContext();
const navigate = useNavigate();
const dispatch = useAppDispatch();
const handleClick = async () => {
- const response = await fetch(new URL(`/conversations`, apiUrl), {
- method: 'POST',
- headers: {
- Accept: 'application/json',
- Authorization: `Bearer ${token}`,
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ members: [contactId] }),
- }).then((res) => {
- dispatch(setRefreshFromSlice());
- return res.json();
- });
+ const { data } = await axiosInstance.put(`/contacts/${contactId}`);
+ dispatch(setRefreshFromSlice());
- console.log(response);
- if (response.conversationId) {
- navigate(`/conversation/${response.conversationId}`);
+ if (data.conversationId) {
+ navigate(`/conversation/${data.conversationId}`);
}
};
@@ -63,7 +52,7 @@
<Box style={{ textAlign: 'center', marginTop: 16 }}>
<Fab variant="extended" color="primary" onClick={handleClick}>
<GroupAddRounded />
- Add contact
+ <Trans key="conversation_add_contact" />
</Fab>
</Box>
</CardContent>
diff --git a/client/src/pages/JamiLogin.tsx b/client/src/pages/JamiLogin.tsx
index 5cd2917..256bb76 100644
--- a/client/src/pages/JamiLogin.tsx
+++ b/client/src/pages/JamiLogin.tsx
@@ -64,14 +64,14 @@
const accessToken = await loginUser(username, password);
setAccessToken(accessToken);
navigate('/settings', { replace: true });
- } catch (err) {
+ } catch (e) {
setIsLoggingInUser(false);
- if (err instanceof UsernameNotFound) {
+ if (e instanceof UsernameNotFound) {
setErrorAlertContent(t('login_username_not_found'));
- } else if (err instanceof InvalidPassword) {
+ } else if (e instanceof InvalidPassword) {
setErrorAlertContent(t('login_invalid_password'));
} else {
- throw err;
+ throw e;
}
}
}
diff --git a/client/src/pages/JamiRegistration.tsx b/client/src/pages/JamiRegistration.tsx
index a1024d5..62e59c1 100644
--- a/client/src/pages/JamiRegistration.tsx
+++ b/client/src/pages/JamiRegistration.tsx
@@ -74,14 +74,14 @@
const accessToken = await loginUser(username, password);
setAccessToken(accessToken);
navigate('/settings', { replace: true });
- } catch (err) {
+ } catch (e) {
setIsCreatingUser(false);
- if (err instanceof UsernameNotFound) {
+ if (e instanceof UsernameNotFound) {
setErrorAlertContent(t('login_username_not_found'));
- } else if (err instanceof InvalidPassword) {
+ } else if (e instanceof InvalidPassword) {
setErrorAlertContent(t('login_invalid_password'));
} else {
- throw err;
+ throw e;
}
}
};
diff --git a/client/src/pages/Messenger.tsx b/client/src/pages/Messenger.tsx
index 83d1352..fd8dc99 100644
--- a/client/src/pages/Messenger.tsx
+++ b/client/src/pages/Messenger.tsx
@@ -28,13 +28,12 @@
import { useAuthContext } from '../contexts/AuthProvider';
import { useAppSelector } from '../redux/hooks';
import { MessengerRouteParams } from '../router';
-import { apiUrl } from '../utils/constants';
import { useUrlParams } from '../utils/hooks';
import AddContactPage from './AddContactPage';
const Messenger = () => {
const { refresh } = useAppSelector((state) => state.userInfo);
- const { token, account } = useAuthContext();
+ const { account, axiosInstance } = useAuthContext();
const [conversations, setConversations] = useState<Conversation[] | undefined>(undefined);
const [searchQuery, setSearchQuery] = useState('');
@@ -47,51 +46,36 @@
const accountId = account.getId();
useEffect(() => {
- console.log('REFRESH CONVERSATIONS FROM MESSENGER');
const controller = new AbortController();
- fetch(new URL(`/conversations`, apiUrl), {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- signal: controller.signal,
- })
- .then((res) => res.json())
- .then((result: Conversation[]) => {
- console.log(result);
- setConversations(Object.values(result).map((c) => Conversation.from(accountId, c)));
+ axiosInstance
+ .get<Conversation[]>('/conversations', {
+ signal: controller.signal,
+ })
+ .then(({ data }) => {
+ setConversations(Object.values(data).map((c) => Conversation.from(accountId, c)));
});
// return () => controller.abort()
- }, [token, accountId, refresh]);
+ }, [axiosInstance, accountId, refresh]);
useEffect(() => {
if (!searchQuery) return;
const controller = new AbortController();
- fetch(new URL(`/ns/username/${searchQuery}`, apiUrl), {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- signal: controller.signal,
- })
- .then((response) => {
- if (response.status === 200) {
- return response.json();
- } else {
- throw new Error(response.status.toString());
- }
+ // TODO: Type properly https://git.jami.net/savoirfairelinux/jami-web/-/issues/92
+ axiosInstance
+ .get<{ state: number; address: string; username: string }>(`/ns/username/${searchQuery}`, {
+ signal: controller.signal,
})
- .then((response) => {
- console.log(response);
- const contact = new Contact(response.address);
- contact.setRegisteredName(response.username);
+ .then(({ data }) => {
+ const contact = new Contact(data.address);
+ contact.setRegisteredName(data.username);
setSearchResults(contact ? Conversation.fromSingleContact(accountId, contact) : undefined);
})
.catch(() => {
setSearchResults(undefined);
});
// return () => controller.abort() // crash on React18
- }, [accountId, searchQuery, token]);
+ }, [accountId, searchQuery, axiosInstance]);
- console.log('Messenger render');
return (
<Stack direction="row" height="100vh" width="100vw">
<Stack flexGrow={0} flexShrink={0} overflow="auto">
diff --git a/client/src/pages/SetupLogin.tsx b/client/src/pages/SetupLogin.tsx
index 79079be..4608a59 100644
--- a/client/src/pages/SetupLogin.tsx
+++ b/client/src/pages/SetupLogin.tsx
@@ -17,6 +17,7 @@
*/
import GroupAddRounded from '@mui/icons-material/GroupAddRounded';
import { Box, Card, CardContent, Container, Fab, Input, Typography } from '@mui/material';
+import axios from 'axios';
import { HttpStatusCode } from 'jami-web-common';
import { FormEvent, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -38,17 +39,15 @@
}, []);
const adminCreation = async (password: string) => {
- const url = new URL('/setup/admin/create', apiUrl);
-
let response: Response;
try {
- response = await fetch(url, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ password }),
- });
+ response = await axios.post(
+ '/setup/admin/create',
+ { password },
+ {
+ baseURL: apiUrl,
+ }
+ );
} catch (e) {
throw new Error(`Admin creation failed`);
}
@@ -59,18 +58,16 @@
};
const adminLogin = async (password: string) => {
- const url = new URL('/setup/admin/login', apiUrl);
-
let response: Response;
try {
- response = await fetch(url, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ password }),
- });
- } catch (err) {
+ response = await axios.post(
+ '/setup/admin/login',
+ { password },
+ {
+ baseURL: apiUrl,
+ }
+ );
+ } catch (e) {
throw new Error(`Admin login failed`);
}
diff --git a/client/src/services/Conversation.ts b/client/src/services/Conversation.ts
index d742730..92cc80d 100644
--- a/client/src/services/Conversation.ts
+++ b/client/src/services/Conversation.ts
@@ -16,59 +16,44 @@
* <https://www.gnu.org/licenses/>.
*/
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
-import axios from 'axios';
import { useAuthContext } from '../contexts/AuthProvider';
-import { apiUrl } from '../utils/constants';
export const useConversationQuery = (conversationId: string) => {
- const { token } = useAuthContext();
- return useQuery(['conversation', conversationId], () => fetchConversation(conversationId, token), {
- enabled: !!conversationId,
- });
+ const { axiosInstance } = useAuthContext();
+ return useQuery(
+ ['conversation', conversationId],
+ async () => {
+ const { data } = await axiosInstance.get(`/conversations/${conversationId}`);
+ return data;
+ },
+ {
+ enabled: !!conversationId,
+ }
+ );
};
export const useMessagesQuery = (conversationId: string) => {
- const { token } = useAuthContext();
- return useQuery(['messages', conversationId], () => fetchMessages(conversationId, token), {
- enabled: !!conversationId,
- });
+ const { axiosInstance } = useAuthContext();
+ return useQuery(
+ ['messages', conversationId],
+ async () => {
+ const { data } = await axiosInstance.get(`/conversations/${conversationId}/messages`);
+ return data;
+ },
+ {
+ enabled: !!conversationId,
+ }
+ );
};
export const useSendMessageMutation = (conversationId: string) => {
- const { token } = useAuthContext();
+ const { axiosInstance } = useAuthContext();
const queryClient = useQueryClient();
return useMutation(
- (message: string) =>
- axios.post(
- new URL(`/conversations/${conversationId}/messages`, apiUrl).toString(),
- { message },
- {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- }
- ),
+ (message: string) => axiosInstance.post(`/conversations/${conversationId}/messages`, { message }),
{
onSuccess: () => queryClient.invalidateQueries(['messages', conversationId]),
}
);
};
-
-const fetchConversation = (conversationId: string, token: string) =>
- axios
- .get(new URL(`/conversations/${conversationId}`, apiUrl).toString(), {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- })
- .then((result) => result.data);
-
-const fetchMessages = (conversationId: string, token: string) =>
- axios
- .get(new URL(`/conversations/${conversationId}/messages`, apiUrl).toString(), {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- })
- .then((result) => result.data);
diff --git a/client/src/utils/auth.ts b/client/src/utils/auth.ts
index 8404eee..3704fe4 100644
--- a/client/src/utils/auth.ts
+++ b/client/src/utils/auth.ts
@@ -15,6 +15,7 @@
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
+import axios from 'axios';
import { passwordStrength } from 'check-password-strength';
import { HttpStatusCode } from 'jami-web-common';
@@ -39,68 +40,58 @@
const idToStrengthValueCode: StrengthValueCode[] = ['too_weak', 'weak', 'medium', 'strong'];
export async function isNameRegistered(name: string): Promise<boolean> {
- const url = new URL(`/ns/username/${name}`, apiUrl);
- const response = await fetch(url);
-
- switch (response.status) {
- case HttpStatusCode.Ok:
- return true;
- case HttpStatusCode.NotFound:
- return false;
- default:
- throw new Error(await response.text());
+ try {
+ await axios.get(`/ns/username/${name}`, {
+ baseURL: apiUrl,
+ });
+ return true;
+ } catch (e: any) {
+ if (e.response?.status !== HttpStatusCode.NotFound) {
+ throw e;
+ }
+ return false;
}
}
export function checkPasswordStrength(password: string): PasswordCheckResult {
const strengthResult: PasswordStrengthResult = passwordStrength(password);
- const checkResult: PasswordCheckResult = {
+ return {
strong: strengthResult.id === PasswordStrength.Strong.valueOf(),
valueCode: idToStrengthValueCode[strengthResult.id] ?? 'default',
};
-
- return checkResult;
}
export async function registerUser(username: string, password: string): Promise<void> {
- const url = new URL('/auth/new-account', apiUrl);
- const response: Response = await fetch(url, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ username, password }),
- });
-
- if (response.status !== HttpStatusCode.Created) {
- throw new Error(await response.text());
- }
+ await axios.post(
+ '/auth/new-account',
+ { username, password },
+ {
+ baseURL: apiUrl,
+ }
+ );
}
export async function loginUser(username: string, password: string): Promise<string> {
- const url = new URL('/auth/login', apiUrl);
- const response = await fetch(url, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ username, password }),
- });
-
- switch (response.status) {
- case HttpStatusCode.Ok:
- break;
- case HttpStatusCode.NotFound:
- throw new UsernameNotFound();
- case HttpStatusCode.Unauthorized:
- throw new InvalidPassword();
- default:
- throw new Error(await response.text());
+ try {
+ const { data } = await axios.post(
+ '/auth/login',
+ { username, password },
+ {
+ baseURL: apiUrl,
+ }
+ );
+ return data.accessToken;
+ } catch (e: any) {
+ switch (e.response?.status) {
+ case HttpStatusCode.NotFound:
+ throw new UsernameNotFound();
+ case HttpStatusCode.Unauthorized:
+ throw new InvalidPassword();
+ default:
+ throw e;
+ }
}
-
- const data: { accessToken: string } = await response.json();
- return data.accessToken;
}
export function getAccessToken(): string {
diff --git a/client/src/utils/constants.ts b/client/src/utils/constants.ts
index f5c5423..56c738a 100644
--- a/client/src/utils/constants.ts
+++ b/client/src/utils/constants.ts
@@ -21,4 +21,10 @@
export const jamiLogoDefaultSize = '512px';
-export const apiUrl = new URL(import.meta.env.VITE_API_URL);
+const apiUrl: string = import.meta.env.VITE_API_URL;
+
+if (!apiUrl) {
+ throw new Error('VITE_API_URL not defined');
+}
+
+export { apiUrl };
diff --git a/package-lock.json b/package-lock.json
index 9c524bd..9114722 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -97,7 +97,6 @@
"react-draggable": "^4.4.5",
"react-dropzone": "^14.2.3",
"react-emoji-render": "^1.2.4",
- "react-fetch-hook": "^1.9.5",
"react-i18next": "^11.18.6",
"react-modal": "^3.15.1",
"react-redux": "^8.0.2",
@@ -14387,14 +14386,6 @@
"react-dom": ">=0.14.0"
}
},
- "node_modules/react-fetch-hook": {
- "version": "1.9.5",
- "resolved": "https://registry.npmjs.org/react-fetch-hook/-/react-fetch-hook-1.9.5.tgz",
- "integrity": "sha512-LhTwUk0iYQ4SUl2laQr5Jc2fk/K4B6ezntDWWBNdlag/cB3n2RYshofIL3bOghE5FEfkoDi6BqGD/YIwqW1/8w==",
- "peerDependencies": {
- "react": ">=16.8.0 <19.0.0"
- }
- },
"node_modules/react-i18next": {
"version": "11.18.6",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.6.tgz",
@@ -25648,7 +25639,6 @@
"react-draggable": "^4.4.5",
"react-dropzone": "^14.2.3",
"react-emoji-render": "^1.2.4",
- "react-fetch-hook": "^1.9.5",
"react-i18next": "^11.18.6",
"react-modal": "^3.15.1",
"react-redux": "^8.0.2",
@@ -28337,12 +28327,6 @@
"string-replace-to-array": "^1.0.1"
}
},
- "react-fetch-hook": {
- "version": "1.9.5",
- "resolved": "https://registry.npmjs.org/react-fetch-hook/-/react-fetch-hook-1.9.5.tgz",
- "integrity": "sha512-LhTwUk0iYQ4SUl2laQr5Jc2fk/K4B6ezntDWWBNdlag/cB3n2RYshofIL3bOghE5FEfkoDi6BqGD/YIwqW1/8w==",
- "requires": {}
- },
"react-i18next": {
"version": "11.18.6",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.6.tgz",
diff --git a/server/src/routers/contacts-router.ts b/server/src/routers/contacts-router.ts
index 5439fed..8d51d2b 100644
--- a/server/src/routers/contacts-router.ts
+++ b/server/src/routers/contacts-router.ts
@@ -39,8 +39,13 @@
});
contactsRouter.put('/:contactId', (req, res) => {
- jamid.addContact(res.locals.accountId, req.params.contactId);
- res.sendStatus(HttpStatusCode.NoContent);
+ const accountId = res.locals.accountId;
+ const contactId = req.params.contactId;
+
+ jamid.addContact(accountId, contactId);
+
+ const contactDetails = jamid.getContactDetails(accountId, contactId);
+ res.send(contactDetails);
});
contactsRouter.delete('/:contactId', (req, res) => {