diff --git a/src/components/app-wrapper.jsx b/src/components/app-wrapper.jsx index 93794b11f0..3e655808a9 100644 --- a/src/components/app-wrapper.jsx +++ b/src/components/app-wrapper.jsx @@ -43,6 +43,7 @@ import { importParamsFr, exportParamsEn, exportParamsFr, + NotificationsProvider, } from '@gridsuite/commons-ui'; import { IntlProvider } from 'react-intl'; import { BrowserRouter } from 'react-router-dom'; @@ -79,6 +80,7 @@ import { MAP_BASEMAP_CARTO, MAP_BASEMAP_CARTO_NOLABEL, } from '../utils/config-params'; +import useNotificationsUrlGenerator from 'hooks/use-notifications-url-generator'; let lightTheme = createTheme({ components: { @@ -327,6 +329,8 @@ const AppWrapperWithRedux = () => { const theme = useSelector((state) => state[PARAM_THEME]); + const urlMapper = useNotificationsUrlGenerator(); + return ( @@ -335,7 +339,9 @@ const AppWrapperWithRedux = () => { - + + + diff --git a/src/components/app.jsx b/src/components/app.jsx index 1be4615fee..3a4b72bb76 100644 --- a/src/components/app.jsx +++ b/src/components/app.jsx @@ -20,6 +20,7 @@ import { getPreLoginPath, initializeAuthenticationProd, useSnackMessage, + useNotificationsListener, } from '@gridsuite/commons-ui'; import PageNotFound from './page-not-found'; import { FormattedMessage } from 'react-intl'; @@ -55,7 +56,6 @@ import { getComputedLanguage } from '../utils/language'; import AppTopBar from './app-top-bar'; import { StudyContainer } from './study-container'; import { fetchValidateUser } from '../services/user-admin'; -import { connectNotificationsWsUpdateConfig } from '../services/config-notification'; import { fetchConfigParameter, fetchConfigParameters } from '../services/config'; import { fetchDefaultParametersValues, fetchIdpSettings } from '../services/utils'; import { getOptionalServices } from '../services/study'; @@ -87,6 +87,7 @@ import { setOptionalServices, setParamsLoaded, } from '../redux/actions'; +import { NOTIFICATIONS_URL_KEYS } from './utils/notificationsProvider-utils'; const noUserManager = { instance: null, error: null }; @@ -270,10 +271,8 @@ const App = () => { [dispatch, tablesNamesIndexes, tablesDefinitionIndexes] ); - const connectNotificationsUpdateConfig = useCallback(() => { - const ws = connectNotificationsWsUpdateConfig(); - - ws.onmessage = function (event) { + const updateConfig = useCallback( + (event) => { let eventData = JSON.parse(event.data); if (eventData.headers && eventData.headers['parameterName']) { fetchConfigParameter(eventData.headers['parameterName']) @@ -290,12 +289,13 @@ const App = () => { }) ); } - }; - ws.onerror = function (event) { - console.error('Unexpected Notification WebSocket error', event); - }; - return ws; - }, [updateParams, snackError, dispatch]); + }, + [dispatch, snackError, updateParams] + ); + + useNotificationsListener(NOTIFICATIONS_URL_KEYS.CONFIG, { + listenerCallbackMessage: updateConfig, + }); // Can't use lazy initializer because useRouteMatch is a hook const [initialMatchSilentRenewCallbackUrl] = useState( @@ -419,13 +419,8 @@ const App = () => { headerId: 'paramsRetrievingError', }) ); - - const ws = connectNotificationsUpdateConfig(); - return function () { - ws.close(); - }; } - }, [user, dispatch, updateParams, connectNotificationsUpdateConfig, snackError]); + }, [user, dispatch, updateParams, snackError]); const onChangeTab = useCallback((newTabIndex) => { setTabIndex(newTabIndex); diff --git a/src/components/utils/notificationsProvider-utils.ts b/src/components/utils/notificationsProvider-utils.ts new file mode 100644 index 0000000000..4d8423e5d6 --- /dev/null +++ b/src/components/utils/notificationsProvider-utils.ts @@ -0,0 +1,11 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +export enum NOTIFICATIONS_URL_KEYS { + CONFIG = 'CONFIG', +} + +export const PREFIX_CONFIG_NOTIFICATION_WS = import.meta.env.VITE_WS_GATEWAY + '/config-notification'; diff --git a/src/hooks/use-notifications-url-generator.ts b/src/hooks/use-notifications-url-generator.ts new file mode 100644 index 0000000000..ff2161b5c5 --- /dev/null +++ b/src/hooks/use-notifications-url-generator.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +import { NOTIFICATIONS_URL_KEYS, PREFIX_CONFIG_NOTIFICATION_WS } from 'components/utils/notificationsProvider-utils'; +import { useCallback, useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'redux/reducer'; +import { getUrlWithToken, getWsBase } from 'services/utils'; +import { APP_NAME } from 'utils/config-params'; + +const useNotificationsUrlGenerator = () => { + // The websocket API doesn't allow relative urls + const wsBase = getWsBase(); + const tokenId = useSelector((state: AppState) => state.user?.id_token); + // Add params to Url + const urlParams = useCallback((mapper: Record) => { + const usp = new URLSearchParams(); + Object.entries(mapper).forEach(([key, value]) => { + usp.append(key, value); + }); + return usp; + }, []); + + const urlMapper = useMemo(() => { + // return a mapper with NOTIFICATIONS_URL_KEYS and undefined value if URL is not yet buildable (tokenId) + // it will be used to register listeners as soon as possible. + let mapper: Object = { + [NOTIFICATIONS_URL_KEYS.CONFIG]: tokenId + ? getUrlWithToken( + `${wsBase}${PREFIX_CONFIG_NOTIFICATION_WS}/notify?${urlParams({ appName: APP_NAME })}` + ) + : undefined, + }; + return mapper; + }, [wsBase, urlParams, tokenId]); + return urlMapper; +}; + +export default useNotificationsUrlGenerator;