import React, { ReactNode, useContext, useEffect, useMemo } from 'react'; import { useInterpret } from '@xstate/react'; import { Auth } from 'aws-amplify'; import { AuthStatus, AuthMachineHubHandler, createAuthenticatorMachine, defaultAuthHubHandler, listenToAuthHub, } from '@aws-amplify/ui'; import { AuthenticatorContext } from './AuthenticatorContext'; type Options = Parameters[2]; const createHubHandler = (options: Options): AuthMachineHubHandler => async (data, service) => { await defaultAuthHubHandler(data, service, options); }; export default function AuthenticatorProvider({ children, }: { children: ReactNode; }): JSX.Element { // `authStatus` is exposed by `useAuthenticator` but should not be derived directly from the // state machine as the machine only updates on `Authenticator` initiated events, which // leads to scenarios where the state machine `authStatus` gets "stuck". For exmample, // if a user was to sign in using `Auth.signIn` directly rather than using `Authenticator` const [authStatus, setAuthStatus] = React.useState('configuring'); // only run on first render React.useEffect(() => { Auth.currentAuthenticatedUser() .then(() => { setAuthStatus('authenticated'); }) .catch(() => { setAuthStatus('unauthenticated'); }); }, []); /** * Based on use cases, developer might already have added another Provider * outside Authenticator. In that case, we sync the two providers by just * passing the parent value. * * TODO(BREAKING): enforce only one provider in App tree */ const parentProviderVal = useContext(AuthenticatorContext); const service = useInterpret(createAuthenticatorMachine); const value = useMemo( () => (!parentProviderVal ? { authStatus, service } : parentProviderVal), [authStatus, parentProviderVal, service] ); const { service: activeService } = value; useEffect(() => { const onSignIn = () => { setAuthStatus('authenticated'); }; const onSignOut = () => { setAuthStatus('unauthenticated'); }; const unsubscribe = listenToAuthHub( activeService, createHubHandler({ onSignIn, onSignOut }) ); return unsubscribe; }, [activeService]); return ( {children} ); }