Skip to content

Commit f213e75

Browse files
committed
Use different approach with MsalProvider in index
1 parent 5356dfe commit f213e75

File tree

3 files changed

+71
-71
lines changed

3 files changed

+71
-71
lines changed

.github/chatmodes/fixer.chatmode.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ You MUST check task output readiness before debugging, testing, or declaring wor
2424
- Frontend: Vite provides HMR; changes in the frontend are picked up automatically without restarting the task.
2525
- Backend: Quart was started with --reload; Python changes trigger an automatic restart.
2626
- If watchers seem stuck or output stops updating, stop the tasks and run the "Development" task again.
27-
- To interact with a running application, use the Playwright MCP server
27+
- To interact with a running application, use the Playwright MCP server. If testing login, you will need to navigate to 'localhost' instead of '127.0.0.1' since that's the URL allowed by the Entra application.

app/frontend/src/index.tsx

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import { createHashRouter, RouterProvider } from "react-router-dom";
44
import { I18nextProvider } from "react-i18next";
55
import { HelmetProvider } from "react-helmet-async";
66
import { initializeIcons } from "@fluentui/react";
7+
import { MsalProvider } from "@azure/msal-react";
8+
import { AuthenticationResult, EventType, PublicClientApplication } from "@azure/msal-browser";
79

810
import "./index.css";
911

1012
import Chat from "./pages/chat/Chat";
1113
import LayoutWrapper from "./layoutWrapper";
1214
import i18next from "./i18n/config";
15+
import { msalConfig, useLogin } from "./authConfig";
1316

1417
initializeIcons();
1518

@@ -34,12 +37,54 @@ const router = createHashRouter([
3437
}
3538
]);
3639

37-
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
38-
<React.StrictMode>
39-
<I18nextProvider i18n={i18next}>
40-
<HelmetProvider>
41-
<RouterProvider router={router} />
42-
</HelmetProvider>
43-
</I18nextProvider>
44-
</React.StrictMode>
45-
);
40+
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
41+
42+
// Bootstrap the app once; conditionally wrap with MsalProvider when login is enabled
43+
(async () => {
44+
let msalInstance: PublicClientApplication | undefined;
45+
46+
if (useLogin) {
47+
msalInstance = new PublicClientApplication(msalConfig);
48+
try {
49+
await msalInstance.initialize();
50+
51+
// Default active account to the first one if none is set
52+
if (!msalInstance.getActiveAccount() && msalInstance.getAllAccounts().length > 0) {
53+
msalInstance.setActiveAccount(msalInstance.getAllAccounts()[0]);
54+
}
55+
56+
// Keep active account in sync on login success
57+
msalInstance.addEventCallback(event => {
58+
if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) {
59+
const result = event.payload as AuthenticationResult;
60+
if (result.account) {
61+
msalInstance!.setActiveAccount(result.account);
62+
}
63+
}
64+
});
65+
} catch (e) {
66+
// Non-fatal: render the app even if MSAL initialization fails
67+
// eslint-disable-next-line no-console
68+
console.error("MSAL initialize failed", e);
69+
msalInstance = undefined;
70+
}
71+
}
72+
73+
const appTree = (
74+
<React.StrictMode>
75+
<I18nextProvider i18n={i18next}>
76+
<HelmetProvider>
77+
{useLogin && msalInstance ? (
78+
<MsalProvider instance={msalInstance}>
79+
<RouterProvider router={router} />
80+
</MsalProvider>
81+
) : (
82+
<RouterProvider router={router} />
83+
)}
84+
</HelmetProvider>
85+
</I18nextProvider>
86+
</React.StrictMode>
87+
);
88+
89+
root.render(appTree);
90+
})();

app/frontend/src/layoutWrapper.tsx

Lines changed: 16 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,33 @@
1-
import { AuthenticationResult, EventType, PublicClientApplication } from "@azure/msal-browser";
2-
import { checkLoggedIn, msalConfig, useLogin } from "./authConfig";
3-
import { useEffect, useMemo, useRef, useState } from "react";
4-
import { MsalProvider } from "@azure/msal-react";
1+
import { useEffect, useRef, useState } from "react";
2+
import { useMsal } from "@azure/msal-react";
3+
import { useLogin, checkLoggedIn } from "./authConfig";
54
import { LoginContext } from "./loginContext";
65
import Layout from "./pages/layout/Layout";
76

87
const LayoutWrapper = () => {
98
const [loggedIn, setLoggedIn] = useState(false);
109
if (useLogin) {
11-
// Create a stable MSAL instance (avoid re-init/duplicate listeners; single shared client for MsalProvider).
12-
const msalInstance = useMemo(() => new PublicClientApplication(msalConfig), []);
13-
const [initialized, setInitialized] = useState(false);
14-
// Track mount state so we don't call setState after unmount if async init resolves late
10+
const { instance } = useMsal();
11+
// Keep track of the mounted state to avoid setting state in an unmounted component
1512
const mounted = useRef<boolean>(true);
16-
1713
useEffect(() => {
18-
// React StrictMode in development invokes effects twice (mount -> cleanup -> mount).
19-
// Reset the flag here so this run is considered mounted.
2014
mounted.current = true;
21-
const init = async () => {
22-
try {
23-
await msalInstance.initialize();
24-
25-
// Default to using the first account if no account is active on page load
26-
if (!msalInstance.getActiveAccount() && msalInstance.getAllAccounts().length > 0) {
27-
// Account selection logic is app dependent. Adjust as needed for different use cases.
28-
msalInstance.setActiveAccount(msalInstance.getAllAccounts()[0]);
29-
}
30-
31-
// Listen for sign-in event and set active account
32-
msalInstance.addEventCallback(event => {
33-
if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) {
34-
const result = event.payload as AuthenticationResult;
35-
if (result.account) {
36-
msalInstance.setActiveAccount(result.account);
37-
}
38-
}
39-
});
40-
41-
if (mounted.current) {
42-
const isLoggedIn = await checkLoggedIn(msalInstance).catch(e => {
43-
// Swallow check error but still allow app to render
44-
console.error("checkLoggedIn failed", e);
45-
return false;
46-
});
47-
setLoggedIn(isLoggedIn);
48-
}
49-
} catch (e) {
50-
console.error("MSAL initialize failed", e);
51-
} finally {
52-
if (mounted.current) {
53-
setInitialized(true);
54-
}
55-
}
56-
};
57-
init();
15+
checkLoggedIn(instance)
16+
.then(isLoggedIn => {
17+
if (mounted.current) setLoggedIn(isLoggedIn);
18+
})
19+
.catch(e => {
20+
console.error("checkLoggedIn failed", e);
21+
});
5822
return () => {
59-
// On unmount: flag as unmounted so any pending async in init() doesn't call setState
60-
// This avoids React warnings about setting state on an unmounted component.
6123
mounted.current = false;
6224
};
63-
}, [msalInstance]);
64-
65-
if (!initialized) {
66-
// Lightweight placeholder while MSAL initializes
67-
return <p>Loading authentication…</p>;
68-
}
25+
}, [instance]);
6926

7027
return (
71-
<MsalProvider instance={msalInstance}>
72-
<LoginContext.Provider value={{ loggedIn, setLoggedIn }}>
73-
<Layout />
74-
</LoginContext.Provider>
75-
</MsalProvider>
28+
<LoginContext.Provider value={{ loggedIn, setLoggedIn }}>
29+
<Layout />
30+
</LoginContext.Provider>
7631
);
7732
} else {
7833
return (

0 commit comments

Comments
 (0)