import { useEffect, useRef } from 'react'; import { useAppDispatch, useAppSelector } from '../app/hooks'; import { bootstrapAuthSession, loadSignInContext, logoutSession, } from '../modules/auth/store/authThunk'; import { selectIsAuthenticated } from '../modules/auth/store/authSelectors'; import { seedDashboardState } from '../modules/home/store/accountThunk'; import { seedBeneficiaries } from '../modules/transfer/store/transferThunk'; import { seedRecentPayees } from '../modules/payments/store/paymentsThunk'; import { seedDepositProducts } from '../modules/deposit/store/depositThunk'; import { seedServiceRequests, seedSupportMessages, } from '../modules/services/store/serviceThunk'; import { seedLoanProducts } from '../modules/loan/store/loanThunk'; import { seedInsurancePlans } from '../modules/insurance/store/insuranceThunk'; import { seedCards } from '../modules/cards/store/cardsThunk'; import { seedUserProfile } from '../modules/profile/store/userThunk'; import { markAppReady } from '../modules/ui/store/uiThunk'; import { env } from '../config/env'; import { featureRemoteConfig } from '../features'; import { registerUnauthorizedHandler } from '../services/api/interceptors'; import { registerForPushNotifications } from '../services/notifications/pushNotifications'; import { useSessionTimeout } from './useSessionTimeout'; import { useToast } from './useToast'; /** * Populates the modules that have no CBS backend behind them yet — the * dashboard, cards, loans, insurance, deposits, service requests and the agent * app. Registration, sign-in, accounts/MMID and payees are live and are not * seeded from here. */ const seedAllData = (dispatch: ReturnType) => { dispatch(seedUserProfile()); dispatch(seedDashboardState()); dispatch(seedBeneficiaries()); dispatch(seedRecentPayees()); dispatch(seedDepositProducts()); dispatch(seedServiceRequests()); dispatch(seedSupportMessages()); dispatch(seedLoanProducts()); dispatch(seedInsurancePlans()); dispatch(seedCards()); }; export const useAppBootstrap = () => { const dispatch = useAppDispatch(); const toast = useToast(); const isAuthenticated = useAppSelector(selectIsAuthenticated); const wasAuthenticated = useRef(false); useSessionTimeout(); // Initial bootstrap on mount. `bootstrapAuthSession` also mints the device id // and validates any stored session against the server before trusting it. useEffect(() => { void dispatch(bootstrapAuthSession()); seedAllData(dispatch); dispatch(markAppReady()); void registerForPushNotifications(); }, [dispatch]); useEffect(() => { if (isAuthenticated && !wasAuthenticated.current) { // Logout wipes all Redux state, so profile/dashboard/etc. need // re-populating when the user signs back in. seedAllData(dispatch); // The startup fetch ran unauthenticated, so it could only carry the // org-wide scope. Now that there is a session the server can also tell us // what this customer's role and tenant are allowed to see. void featureRemoteConfig.refresh({ useMock: env.useMockServer }); } if (!isAuthenticated && wasAuthenticated.current) { // Role-scoped switches belong to the session that is ending. Leaving them // in force would apply one customer's entitlements to whoever signs in // next on this handset. The org-wide layer stays — it gates the sign-in // screen itself. featureRemoteConfig.clearRoleScope(); // That same wipe takes the remembered mobile number and the biometric // flag with it, which the sign-in screen needs. Only those two are // re-read — a full bootstrap would raise the hydrating flag and blank the // app behind a loader on the way out. void dispatch(loadSignInContext()); } wasAuthenticated.current = isAuthenticated; }, [isAuthenticated, dispatch]); useEffect(() => { const unregister = registerUnauthorizedHandler(() => { // A token the server rejected, not a customer leaving — so the session // goes and the registration stays. See `LogoutReason`. void dispatch(logoutSession('unauthorized')); toast('Signed out', 'info', 'Your session ended. Please sign in again.'); }); return unregister; }, [dispatch, toast]); };