diff --git a/index.html b/index.html index 80a65c821f..7dc1e8fbd5 100644 --- a/index.html +++ b/index.html @@ -17,6 +17,6 @@
- + diff --git a/package.json b/package.json index 851c2b5cad..445b268281 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", + "@grgit idsuite/commons-ui": "0.237.0", "@gridsuite/commons-ui": "0.240.0", "@hello-pangea/dnd": "^18.0.1", "@hookform/resolvers": "^4.1.3", diff --git a/src/components/silent-renew.tsx b/src/components/silent-renew.tsx new file mode 100644 index 0000000000..64abbd1f8e --- /dev/null +++ b/src/components/silent-renew.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 'services/utils'; + +export default function SilentRenew() { + 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.jsx b/src/index.jsx deleted file mode 100644 index 70fb6048ce..0000000000 --- a/src/index.jsx +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2020, 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 'core-js/es/array/flat-map'; - -import 'typeface-roboto'; - -import { createRoot } from 'react-dom/client'; - -import '@xyflow/react/dist/base.css'; -import './index.css'; -import './configure-yup-init'; - -import AppWrapper from './components/app-wrapper'; - -const container = document.getElementById('root'); -const root = createRoot(container); -root.render(); diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000000..1fbe5ffdb1 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2020, 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 { createRoot } from 'react-dom/client'; +import SilentRenew from './components/silent-renew'; +import { SILENT_RENEW_CALLBACK_PATH } from './services/utils'; + +const container = document.getElementById('root'); +const root = createRoot(container!); + +if (globalThis.location.pathname.endsWith(SILENT_RENEW_CALLBACK_PATH)) { + root.render(); +} else { + Promise.all([ + import('core-js/es/array/flat-map'), + import('typeface-roboto'), + import('@xyflow/react/dist/base.css'), + import('./index.css'), + import('./configure-yup-init'), + import('./components/app-wrapper'), + ]).then(([, , , , , { default: AppWrapper }]) => { + root.render(); + }); +} 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 0000000000..55f96692d8 --- /dev/null +++ b/src/module-core-js-array-flat-map.d.ts @@ -0,0 +1,8 @@ +/** + * 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 0000000000..01671d4749 --- /dev/null +++ b/src/module-typeface-roboto.d.ts @@ -0,0 +1,8 @@ +/** + * 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/services/utils.ts b/src/services/utils.ts index 06ce711401..ccecd6200c 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -4,7 +4,7 @@ * 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 { catchErrorHandler, fetchStudyMetadata, StudyMetadata } from '@gridsuite/commons-ui'; +import { catchErrorHandler, fetchStudyMetadata, IdpSettings, StudyMetadata } from '@gridsuite/commons-ui'; export const FetchStatus = { SUCCEED: 'SUCCEED', FAILED: 'FAILED', @@ -13,7 +13,8 @@ export const FetchStatus = { }; export const MAX_INT32: number = 2147483647; - +const IDP_SETTINGS_CACHE_KEY = 'gridsuite-idp-settings'; +export const SILENT_RENEW_CALLBACK_PATH = '/silent-renew-callback'; type DefaultParameters = StudyMetadata['defaultParametersValues']; export const getWsBase = () => document.baseURI.replace(/^http:\/\//, 'ws://').replace(/^https:\/\//, 'wss://'); @@ -49,8 +50,34 @@ function fetchEnv() { return fetch('env.json').then((res) => res.json()); } -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) => { + try { + localStorage.setItem(IDP_SETTINGS_CACHE_KEY, JSON.stringify(settings)); + } catch (e) { + console.warn('Failed to cache IdP settings:', e); + } + 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 { + try { + const cached = localStorage.getItem(IDP_SETTINGS_CACHE_KEY); + if (cached) { + return Promise.resolve(JSON.parse(cached) as IdpSettings); + } + } catch (e) { + // localStorage unavailable, or cache corrupted -> fall back to fresh fetch + console.warn('Failed to read cached IdP settings:', e); + } + return fetchIdpSettings(); } export function fetchVersion() {