import type { ReactNode } from 'react'; import React, { useContext, useEffect, useMemo } from 'react'; import { useInterpret } from '@xstate/react'; import { getCurrentUser } from 'aws-amplify/auth'; import type { AuthStatus, AuthMachineHubHandler } from '@aws-amplify/ui'; import { createAuthenticatorMachine, defaultAuthHubHandler, listenToAuthHub, } from '@aws-amplify/ui'; import { AuthenticatorContext } from './AuthenticatorContext'; type Options = Parameters[2]; const createHubHandler = (options: Options): AuthMachineHubHandler => (data, service) => { defaultAuthHubHandler(data, service, options); }; export default function AuthenticatorProvider({ children, }: { children: ReactNode; }): React.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(() => { getCurrentUser() .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 }, [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} ); }