From 04caa4ff5d80b666b3c1bb4d97b3af125fcc6552 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Mon, 8 Jun 2026 14:50:57 +0200 Subject: [PATCH 1/9] use useProfile instead of user --- src/components/directory-content-dialog.tsx | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/components/directory-content-dialog.tsx b/src/components/directory-content-dialog.tsx index 9407583f7..5352374c2 100644 --- a/src/components/directory-content-dialog.tsx +++ b/src/components/directory-content-dialog.tsx @@ -79,8 +79,11 @@ function DirectoryContentDialog( const itemSelectionForCopy = useSelector((state: AppState) => state.itemSelectionForCopy); const activeDirectory = useSelector((state: AppState) => state.activeDirectory); const isDeveloperMode = useSelector((state: AppState) => state.isDeveloperMode); - const user = useSelector((state: AppState) => state.user); - + const userProfile = useSelector( + (state: AppState) => state.user?.profile ?? null, + (a, b) => + a === b || (a?.sub === b?.sub && a?.name === b?.name && a?.email === b?.email && a?.profile === b?.profile) + ); const [languageLocal] = useParameterState(PARAM_LANGUAGE); const [elementName, setElementName] = useState(''); const [elementDescription, setElementDescription] = useState(''); @@ -377,7 +380,7 @@ function DirectoryContentDialog( titleId="editParameters" name={elementName} description={activeElement.description} - user={user} + userProfile={userProfile} activeDirectory={activeDirectory} language={languageLocal} isDeveloperMode={isDeveloperMode} @@ -393,7 +396,7 @@ function DirectoryContentDialog( titleId="editParameters" name={elementName} description={activeElement.description} - user={user} + userProfile={userProfile} activeDirectory={activeDirectory} language={languageLocal} /> @@ -408,7 +411,7 @@ function DirectoryContentDialog( titleId="editParameters" name={elementName} description={activeElement.description} - user={user} + userProfile={userProfile} activeDirectory={activeDirectory} language={languageLocal} /> @@ -423,7 +426,7 @@ function DirectoryContentDialog( titleId="editParameters" name={elementName} description={activeElement.description} - user={user} + userProfile={userProfile} activeDirectory={activeDirectory} language={languageLocal} /> @@ -438,7 +441,7 @@ function DirectoryContentDialog( titleId="editParameters" name={elementName} description={activeElement.description} - user={user} + userProfile={userProfile} activeDirectory={activeDirectory} language={languageLocal} /> @@ -453,7 +456,7 @@ function DirectoryContentDialog( titleId="editParameters" name={elementName} description={activeElement.description} - user={user} + userProfile={userProfile} activeDirectory={activeDirectory} language={languageLocal} isDeveloperMode={isDeveloperMode} @@ -469,7 +472,7 @@ function DirectoryContentDialog( titleId="editParameters" name={elementName} description={activeElement.description} - user={user} + userProfile={userProfile} activeDirectory={activeDirectory} language={languageLocal} /> From 189bc0a867db3aa6a9d67d32d2b25c59fb7891e5 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Mon, 8 Jun 2026 15:22:21 +0200 Subject: [PATCH 2/9] use user profile --- src/components/tree-views-container.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/tree-views-container.tsx b/src/components/tree-views-container.tsx index 865f384b6..3c1e57722 100644 --- a/src/components/tree-views-container.tsx +++ b/src/components/tree-views-container.tsx @@ -92,7 +92,11 @@ export default function TreeViewsContainer({ sourceItemUuid }: { readonly source const [openDialog, setOpenDialog] = useState(constants.DialogsId.NONE); - const user = useSelector((state: AppState) => state.user); + const userProfile = useSelector( + (state: AppState) => state.user?.profile ?? null, + (a, b) => + a === b || (a?.sub === b?.sub && a?.name === b?.name && a?.email === b?.email && a?.profile === b?.profile) + ); const selectedDirectory = useSelector((state: AppState) => state.selectedDirectory); const activeDirectory = useSelector((state: AppState) => state.activeDirectory); const currentPath = useSelector((state: AppState) => state.currentPath); @@ -210,10 +214,10 @@ export default function TreeViewsContainer({ sourceItemUuid }: { readonly source /* rootDirectories initialization */ useEffect(() => { - if (user != null) { + if (userProfile != null) { updateRootDirectories(); } - }, [user, updateRootDirectories]); + }, [userProfile, updateRootDirectories]); /* Manage current path data */ useEffect(() => { From 86e824b4bdce6d272c8312a8910fa754f788b3bd Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 16 Jun 2026 15:15:24 +0200 Subject: [PATCH 3/9] Avoid booting the full app inside the OIDC silent-renew iframe --- src/components/app.tsx | 23 +++++++++++++++- src/components/silent-renew-app.tsx | 42 +++++++++++++++++++++++++++++ src/index.tsx | 9 ++++++- src/utils/rest-api.ts | 27 +++++++++++++++++-- 4 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 src/components/silent-renew-app.tsx diff --git a/src/components/app.tsx b/src/components/app.tsx index 3b52d894f..e5da3d0fe 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -28,7 +28,7 @@ import { UserManagerState, useSnackMessage, } from '@gridsuite/commons-ui'; -import { Box } from '@mui/material'; +import { Box, Button } from '@mui/material'; import { selectComputedLanguage, selectEnableDeveloperMode, selectLanguage, selectTheme } from '../redux/actions'; import { ConfigParameters, fetchIdpSettings } from '../utils/rest-api'; import { APP_NAME } from '../utils/config-params'; @@ -158,12 +158,33 @@ export default function App() { } return undefined; }, [userProfile, dispatch, updateParams, snackError]); + const CTX = window === window.parent ? 'PARENT' : 'IFRAME'; + const triggerSilentRenew = useCallback(async () => { + if (userManager.instance) { + console.log(`====[${CTX}][signinSilent] start/done`); + console.log('====[debug] signinSilent start'); + try { + await userManager.instance.signinSilent(); + console.log('====[debug] signinSilent done'); + } catch (e) { + console.error('====[debug] signinSilent error', e); + } + } + }, [CTX, userManager.instance]); // We use instead of because flex rules were too complexes or conflicts with MUI grid rules return ( + {import.meta.env.DEV && ( + + )} diff --git a/src/components/silent-renew-app.tsx b/src/components/silent-renew-app.tsx new file mode 100644 index 000000000..74374106a --- /dev/null +++ b/src/components/silent-renew-app.tsx @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2026, 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 { useCallback, useEffect, useState } from 'react'; +import type { UserManager } from 'oidc-client-ts'; +import { + handleSilentRenewCallback, + initializeAuthenticationProd, + SilentRenewCallbackHandler, +} from '@gridsuite/commons-ui'; +import { getCachedIdpSettings } from '../utils/rest-api'; + +export default function SilentRenewApp() { + const [userManager, setUserManager] = useState(null); + + useEffect(() => { + initializeAuthenticationProd( + () => undefined /* dispatch: unused when isSilentRenew=true */, + true /* isSilentRenew */, + getCachedIdpSettings /* reads the cache instead of fetching idpSettings.json */, + false /* isSigningCallback */ + ) + .then(setUserManager) + .catch((e) => console.error('Silent renew init failed:', e)); + }, []); + + const handleSilentRenewCallbackClosure = useCallback(() => { + if (userManager) { + handleSilentRenewCallback(userManager); + } + }, [userManager]); + + return ( + + ); +} diff --git a/src/index.tsx b/src/index.tsx index 3527dd723..d99c138c0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -12,6 +12,13 @@ import './configure-yup-init'; import { createRoot } from 'react-dom/client'; import AppWrapper from './components/app-wrapper'; +import SilentRenewApp from './components/silent-renew-app'; const container = document.getElementById('root'); -createRoot(container!).render(); +const root = createRoot(container!); + +if (window.location.pathname.endsWith('/silent-renew-callback')) { + root.render(); +} else { + root.render(); +} diff --git a/src/utils/rest-api.ts b/src/utils/rest-api.ts index f9873e39b..8a2ceafdd 100644 --- a/src/utils/rest-api.ts +++ b/src/utils/rest-api.ts @@ -16,6 +16,7 @@ import { type GsLang, type GsTheme, hasElementPermission, + IdpSettings, LAST_SELECTED_DIRECTORY, PARAM_DEVELOPER_MODE, PARAM_LANGUAGE, @@ -44,6 +45,7 @@ const PREFIX_NETWORK_CONVERSION_SERVER_QUERIES = `${import.meta.env.VITE_API_GAT const PREFIX_FILTERS_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/filter/v1/filters`; const PREFIX_STUDY_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/study`; const PREFIX_SPREADSHEET_CONFIG_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/study-config`; +const IDP_SETTINGS_CACHE_KEY = 'gridsuite-idp-settings'; export type KeyOfWithoutIndexSignature = { // copy every declared property from T but remove index signatures @@ -93,8 +95,29 @@ const getContingencyUriParamType = (contingencyListType: string | null | undefin } }; -export function fetchIdpSettings() { - return fetch('idpSettings.json').then((res) => res.json()); +// Always hits the network: picks up config changes on each full app load +// AND refreshes the cache read by the silent-renew iframe. +export function fetchIdpSettings(): Promise { + return fetch('idpSettings.json') + .then((res) => res.json()) + .then((settings: IdpSettings) => { + localStorage.setItem(IDP_SETTINGS_CACHE_KEY, JSON.stringify(settings)); + return settings; + }); +} + +// Used only on the silent-renew path: reads the cache (no network), +// falls back to a real fetch if the cache is missing/corrupted. +export function getCachedIdpSettings(): Promise { + const cached = localStorage.getItem(IDP_SETTINGS_CACHE_KEY); + if (cached) { + try { + return Promise.resolve(JSON.parse(cached) as IdpSettings); + } catch { + // corrupted cache -> fall back to a fresh fetch + } + } + return fetchIdpSettings(); } export function fetchVersion() { From de37c38707b7bac5435d7e6733e170621278be6c Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 16 Jun 2026 15:21:25 +0200 Subject: [PATCH 4/9] revert --- src/components/app.tsx | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/components/app.tsx b/src/components/app.tsx index e5da3d0fe..3b52d894f 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -28,7 +28,7 @@ import { UserManagerState, useSnackMessage, } from '@gridsuite/commons-ui'; -import { Box, Button } from '@mui/material'; +import { Box } from '@mui/material'; import { selectComputedLanguage, selectEnableDeveloperMode, selectLanguage, selectTheme } from '../redux/actions'; import { ConfigParameters, fetchIdpSettings } from '../utils/rest-api'; import { APP_NAME } from '../utils/config-params'; @@ -158,33 +158,12 @@ export default function App() { } return undefined; }, [userProfile, dispatch, updateParams, snackError]); - const CTX = window === window.parent ? 'PARENT' : 'IFRAME'; - const triggerSilentRenew = useCallback(async () => { - if (userManager.instance) { - console.log(`====[${CTX}][signinSilent] start/done`); - console.log('====[debug] signinSilent start'); - try { - await userManager.instance.signinSilent(); - console.log('====[debug] signinSilent done'); - } catch (e) { - console.error('====[debug] signinSilent error', e); - } - } - }, [CTX, userManager.instance]); // We use instead of because flex rules were too complexes or conflicts with MUI grid rules return ( - {import.meta.env.DEV && ( - - )} From de04fbdda047945f46ebe2fdda7b4f30a366e144 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Wed, 24 Jun 2026 14:52:27 +0200 Subject: [PATCH 5/9] code review remarks --- ...{silent-renew-app.tsx => silent-renew.tsx} | 2 +- src/index.tsx | 24 ++++++++++--------- src/module-core-js-array-flat-map.d.ts | 7 ++++++ src/module-typeface-roboto.d.ts | 7 ++++++ src/utils/rest-api.ts | 18 +++++++++----- 5 files changed, 40 insertions(+), 18 deletions(-) rename src/components/{silent-renew-app.tsx => silent-renew.tsx} (97%) create mode 100644 src/module-core-js-array-flat-map.d.ts create mode 100644 src/module-typeface-roboto.d.ts diff --git a/src/components/silent-renew-app.tsx b/src/components/silent-renew.tsx similarity index 97% rename from src/components/silent-renew-app.tsx rename to src/components/silent-renew.tsx index 74374106a..067212c9e 100644 --- a/src/components/silent-renew-app.tsx +++ b/src/components/silent-renew.tsx @@ -13,7 +13,7 @@ import { } from '@gridsuite/commons-ui'; import { getCachedIdpSettings } from '../utils/rest-api'; -export default function SilentRenewApp() { +export default function SilentRenew() { const [userManager, setUserManager] = useState(null); useEffect(() => { diff --git a/src/index.tsx b/src/index.tsx index d99c138c0..60edc190e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,21 +4,23 @@ * 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 'core-js/es/array/flat-map'; - -import 'typeface-roboto'; -import './index.css'; -import './configure-yup-init'; - import { createRoot } from 'react-dom/client'; -import AppWrapper from './components/app-wrapper'; -import SilentRenewApp from './components/silent-renew-app'; +import SilentRenew from './components/silent-renew'; +import { SILENT_RENEW_CALLBACK_PATH } from './utils/rest-api'; const container = document.getElementById('root'); const root = createRoot(container!); -if (window.location.pathname.endsWith('/silent-renew-callback')) { - root.render(); -} else { +async function renderApp() { + if (window.location.pathname.endsWith(SILENT_RENEW_CALLBACK_PATH)) { + root.render(); + return; + } + await import('core-js/es/array/flat-map'); + await import('typeface-roboto'); + await import('./index.css'); + await import('./configure-yup-init'); + const { default: AppWrapper } = await import('./components/app-wrapper'); root.render(); } +renderApp().catch((error) => console.error(error)); diff --git a/src/module-core-js-array-flat-map.d.ts b/src/module-core-js-array-flat-map.d.ts new file mode 100644 index 000000000..ee11d44f0 --- /dev/null +++ b/src/module-core-js-array-flat-map.d.ts @@ -0,0 +1,7 @@ +/** + * Copyright (c) 2026, 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/. + */ +declare module 'core-js/es/array/flat-map'; diff --git a/src/module-typeface-roboto.d.ts b/src/module-typeface-roboto.d.ts new file mode 100644 index 000000000..bfcdec5a3 --- /dev/null +++ b/src/module-typeface-roboto.d.ts @@ -0,0 +1,7 @@ +/** + * Copyright (c) 2026, 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/. + */ +declare module 'typeface-roboto'; diff --git a/src/utils/rest-api.ts b/src/utils/rest-api.ts index bb0f5f580..281064f8f 100644 --- a/src/utils/rest-api.ts +++ b/src/utils/rest-api.ts @@ -49,6 +49,7 @@ const PREFIX_STUDY_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/study`; const PREFIX_SPREADSHEET_CONFIG_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/study-config`; const PREFIX_MONITOR_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/monitor`; const IDP_SETTINGS_CACHE_KEY = 'gridsuite-idp-settings'; +export const SILENT_RENEW_CALLBACK_PATH = '/silent-renew-callback'; export type KeyOfWithoutIndexSignature = { // copy every declared property from T but remove index signatures @@ -104,7 +105,11 @@ export function fetchIdpSettings(): Promise { return fetch('idpSettings.json') .then((res) => res.json()) .then((settings: IdpSettings) => { - localStorage.setItem(IDP_SETTINGS_CACHE_KEY, JSON.stringify(settings)); + try { + localStorage.setItem(IDP_SETTINGS_CACHE_KEY, JSON.stringify(settings)); + } catch (e) { + console.warn('Failed to cache IdP settings:', e); + } return settings; }); } @@ -112,13 +117,14 @@ export function fetchIdpSettings(): Promise { // Used only on the silent-renew path: reads the cache (no network), // falls back to a real fetch if the cache is missing/corrupted. export function getCachedIdpSettings(): Promise { - const cached = localStorage.getItem(IDP_SETTINGS_CACHE_KEY); - if (cached) { - try { + try { + const cached = localStorage.getItem(IDP_SETTINGS_CACHE_KEY); + if (cached) { return Promise.resolve(JSON.parse(cached) as IdpSettings); - } catch { - // corrupted cache -> fall back to a fresh fetch } + } catch (e) { + // localStorage unavailable, or cache corrupted -> fall back to fresh fetch + console.warn('Failed to read cached IdP settings:', e); } return fetchIdpSettings(); } From 17c16b34b7921300d7bae57e55735a083cccb53c Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Wed, 24 Jun 2026 15:24:50 +0200 Subject: [PATCH 6/9] code review remarks --- src/module-core-js-array-flat-map.d.ts | 7 ------- src/vite-env.d.ts | 1 + 2 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 src/module-core-js-array-flat-map.d.ts diff --git a/src/module-core-js-array-flat-map.d.ts b/src/module-core-js-array-flat-map.d.ts deleted file mode 100644 index ee11d44f0..000000000 --- a/src/module-core-js-array-flat-map.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Copyright (c) 2026, 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/. - */ -declare module 'core-js/es/array/flat-map'; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index db95603f7..379ed1ef8 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -7,3 +7,4 @@ /// /// +declare module 'core-js/es/array/flat-map'; From 1e8078d48f220a6d7bde0e1cf2de4679610ef90a Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Wed, 24 Jun 2026 16:44:11 +0200 Subject: [PATCH 7/9] Revert "code review remarks" This reverts commit 17c16b34b7921300d7bae57e55735a083cccb53c. --- src/module-core-js-array-flat-map.d.ts | 7 +++++++ src/vite-env.d.ts | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/module-core-js-array-flat-map.d.ts diff --git a/src/module-core-js-array-flat-map.d.ts b/src/module-core-js-array-flat-map.d.ts new file mode 100644 index 000000000..ee11d44f0 --- /dev/null +++ b/src/module-core-js-array-flat-map.d.ts @@ -0,0 +1,7 @@ +/** + * Copyright (c) 2026, 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/. + */ +declare module 'core-js/es/array/flat-map'; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 379ed1ef8..db95603f7 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -7,4 +7,3 @@ /// /// -declare module 'core-js/es/array/flat-map'; From dfd387188a456769411877ecde1b9f88dc6c492c Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Thu, 25 Jun 2026 11:08:48 +0200 Subject: [PATCH 8/9] upgrade commons-ui version --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f2c6beb71..3dac94699 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", - "@gridsuite/commons-ui": "0.234.0", + "@gridsuite/commons-ui": "0.237.0", "@hello-pangea/dnd": "^18.0.1", "@hookform/resolvers": "^4.1.3", "@mui/icons-material": "^6.5.0", @@ -3309,9 +3309,9 @@ } }, "node_modules/@gridsuite/commons-ui": { - "version": "0.234.0", - "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.234.0.tgz", - "integrity": "sha512-OXkfwLifENo+r9QhP6FVHiw2gmVuRA4BX0Mp24dPeXnbCzU9LJHdTYKq743IMxeclGyKKqjO0llKT7HHJ591oA==", + "version": "0.237.0", + "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.237.0.tgz", + "integrity": "sha512-iB2gle7cOdBjj0Nyr6YJ6pkLREJ36eJHI9VAugZ1MX0kdXTHDN+eUYF7nArs0Oqrcv274PKDtgjhinflKLsC0w==", "license": "MPL-2.0", "dependencies": { "@ag-grid-community/locale": "^35.3.1", diff --git a/package.json b/package.json index 8d650c80e..0442993ac 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", - "@gridsuite/commons-ui": "0.234.0", + "@gridsuite/commons-ui": "0.237.0", "@hello-pangea/dnd": "^18.0.1", "@hookform/resolvers": "^4.1.3", "@mui/icons-material": "^6.5.0", From 4a3387f936b69c38aa27568c49b19977a6ed4551 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Thu, 25 Jun 2026 14:13:37 +0200 Subject: [PATCH 9/9] fix sonar issue --- src/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.tsx b/src/index.tsx index 60edc190e..10ebfeb47 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -12,7 +12,7 @@ const container = document.getElementById('root'); const root = createRoot(container!); async function renderApp() { - if (window.location.pathname.endsWith(SILENT_RENEW_CALLBACK_PATH)) { + if (globalThis.location.pathname.endsWith(SILENT_RENEW_CALLBACK_PATH)) { root.render(); return; }