diff --git a/src/components/app.js b/src/components/app.js index 6bd138546..368d85387 100644 --- a/src/components/app.js +++ b/src/components/app.js @@ -34,6 +34,7 @@ import { useMatch } from 'react-router-dom'; import { FormattedMessage } from 'react-intl'; import { + connectGlobalNotificationsWs, connectNotificationsWsUpdateConfig, fetchAuthorizationCodeFlowFeatureFlag, fetchConfigParameter, @@ -53,6 +54,7 @@ import Grid from '@mui/material/Grid'; import TreeViewsContainer from './tree-views-container'; import DirectoryContent from './directory-content'; import DirectoryBreadcrumbs from './directory-breadcrumbs'; +import { closeSnackbar, enqueueSnackbar } from 'notistack'; const noUserManager = { instance: null, error: null }; @@ -73,6 +75,12 @@ const App = () => { const [userManager, setUserManager] = useState(noUserManager); + const [maintenanceSnackBarId, setMaintenanceSnackBarId] = useState(null); + + const HEADER_MAINTENANCE = 'maintenance'; + + const HEADER_CANCEL_MAINTENANCE = 'cancelMaintenance'; + const navigate = useNavigate(); const dispatch = useDispatch(); @@ -148,6 +156,55 @@ const App = () => { }) ); + const connectGlobalNotifications = useCallback(() => { + const ws = connectGlobalNotificationsWs(); + ws.onmessage = function (event) { + let eventData = JSON.parse(event.data); + if (eventData.headers.messageType === HEADER_MAINTENANCE) { + //first we close the previous snackbar (no need to check if there is one because closeSnackbar doesn't fail on null or wrong id) + closeSnackbar(maintenanceSnackBarId); + if (eventData.headers.duration) { + setMaintenanceSnackBarId( + enqueueSnackbar(eventData.payload, { + autoHideDuration: eventData.headers.duration * 1000, + variant: 'info', + style: { + whiteSpace: 'pre-line', + }, + anchorOrigin: { + vertical: 'top', + horizontal: 'center', + }, + }) + ); + } else { + setMaintenanceSnackBarId( + enqueueSnackbar(eventData.payload, { + variant: 'info', + persist: true, + style: { + whiteSpace: 'pre-line', + }, + anchorOrigin: { + vertical: 'top', + horizontal: 'center', + }, + }) + ); + } + } else if ( + eventData.headers.messageType === HEADER_CANCEL_MAINTENANCE + ) { + //nothing happens if the id is null or if the snackbar it references is already closed + closeSnackbar(maintenanceSnackBarId); + } + }; + ws.onerror = function (event) { + console.error('Unexpected Notification WebSocket error', event); + }; + return ws; + }, [maintenanceSnackBarId]); + useEffect(() => { fetchAuthorizationCodeFlowFeatureFlag() .then((authorizationCodeFlowEnabled) => { @@ -194,8 +251,10 @@ const App = () => { ); const ws = connectNotificationsUpdateConfig(); + const ws2 = connectGlobalNotifications(); return function () { ws.close(); + ws2.close(); }; } }, [ @@ -204,6 +263,7 @@ const App = () => { updateParams, snackError, connectNotificationsUpdateConfig, + connectGlobalNotifications, ]); return ( diff --git a/src/utils/rest-api.js b/src/utils/rest-api.js index 8e4bfe149..483d81906 100644 --- a/src/utils/rest-api.js +++ b/src/utils/rest-api.js @@ -35,6 +35,14 @@ function getToken() { return state.user.id_token; } +function getUrlWithToken(baseUrl) { + if (baseUrl.includes('?')) { + return baseUrl + '&access_token=' + getToken(); + } else { + return baseUrl + '?access_token=' + getToken(); + } +} + export function connectNotificationsWsUpdateConfig() { const webSocketBaseUrl = document.baseURI .replace(/^http:\/\//, 'ws://') @@ -45,8 +53,8 @@ export function connectNotificationsWsUpdateConfig() { '/notify?appName=' + APP_NAME; - const reconnectingWebSocket = new ReconnectingWebSocket( - () => webSocketUrl + '&access_token=' + getToken() + const reconnectingWebSocket = new ReconnectingWebSocket(() => + getUrlWithToken(webSocketUrl) ); reconnectingWebSocket.onopen = function () { console.info( @@ -56,6 +64,25 @@ export function connectNotificationsWsUpdateConfig() { return reconnectingWebSocket; } +export function connectGlobalNotificationsWs() { + const webSocketBaseUrl = document.baseURI + .replace(/^http:\/\//, 'ws://') + .replace(/^https:\/\//, 'wss://'); + const webSocketUrl = + webSocketBaseUrl + PREFIX_CONFIG_NOTIFICATION_WS + '/global'; + + const reconnectingWebSocket = new ReconnectingWebSocket(() => + getUrlWithToken(webSocketUrl) + ); + + reconnectingWebSocket.onopen = function () { + console.info( + 'Connected Websocket for global messages ' + webSocketUrl + ' ...' + ); + }; + return reconnectingWebSocket; +} + function parseError(text) { try { return JSON.parse(text);