@@ -20,11 +20,13 @@ import { SuperTokensBranding } from "../../../../../components/SuperTokensBrandi
2020import SuperTokens from "../../../../../superTokens" ;
2121import { AuthPageFooter , AuthPageHeader } from "../../../../../ui" ;
2222import UserContextWrapper from "../../../../../usercontext/userContextWrapper" ;
23+ import { handleCallAPI } from "../../../../../utils" ;
2324import { PasskeyConfirmation } from "../signUp/confirmation" ;
2425import { ThemeBase } from "../themeBase" ;
2526
2627import { PasskeyRecoverAccountSuccess } from "./success" ;
2728
29+ import type { FieldState } from "../../../../emailpassword/components/library/formBase" ;
2830import type { RecoverAccountWithTokenThemeProps } from "../../../types" ;
2931
3032export enum RecoverAccountScreen {
@@ -48,6 +50,7 @@ function PasskeyRecoverAccountWithTokenTheme(props: RecoverAccountWithTokenTheme
4850 const [ errorMessageLabel , setErrorMessageLabel ] = useState < string | null > ( null ) ;
4951 const [ activeScreen , setActiveScreen ] = useState < RecoverAccountScreen > ( RecoverAccountScreen . ContinueWithPasskey ) ;
5052 const [ registerOptions , setRegisterOptions ] = useState < RegisterOptions | null > ( null ) ;
53+ const [ isLoading , setIsLoading ] = useState ( false ) ;
5154
5255 const onResetFactorList = ( ) => {
5356 throw new Error ( "Should never come here as we don't have back functionality" ) ;
@@ -99,10 +102,7 @@ function PasskeyRecoverAccountWithTokenTheme(props: RecoverAccountWithTokenTheme
99102 void fetchAndStoreRegisterOptions ( ) ;
100103 } , [ ] ) ;
101104
102- const onContinueClick = useCallback ( async ( ) => {
103- // TODO: Add support to make the network call and show the next screen based
104- // on that result.
105- //
105+ const callAPI = useCallback ( async ( ) => {
106106 // We will do the following things in the order when the user clicks on the continue
107107 // button.
108108 // 1. Check if the fetched register options have expired
@@ -122,9 +122,85 @@ function PasskeyRecoverAccountWithTokenTheme(props: RecoverAccountWithTokenTheme
122122 await fetchAndStoreRegisterOptions ( ) ;
123123 }
124124
125- // TODO: Do rest of the logic once recover flow is ready in core.
126- setActiveScreen ( RecoverAccountScreen . Success ) ;
127- } , [ setActiveScreen ] ) ;
125+ if ( props . token === null ) {
126+ // The token should not be null because while fetching the register options
127+ // we already checked for null and redirected to the sign in page if it is null.
128+ throw new Error ( "Should never come here" ) ;
129+ }
130+
131+ // Use the register options to register the credential and recover the account.
132+ // We should have received a valid registration options response.
133+ const registerCredentialResponse = await props . recipeImplementation . registerCredential ( {
134+ registrationOptions : registerOptions ,
135+ } ) ;
136+ if ( registerCredentialResponse . status !== "OK" ) {
137+ return registerCredentialResponse ;
138+ }
139+
140+ const recoverAccountResponse = await props . recipeImplementation . recoverAccount ( {
141+ token : props . token ,
142+ webauthnGeneratedOptionsId : registerOptions . webauthnGeneratedOptionsId ,
143+ credential : registerCredentialResponse . registrationResponse ,
144+ userContext : props . userContext ,
145+ } ) ;
146+
147+ return recoverAccountResponse ;
148+ } , [ fetchAndStoreRegisterOptions , props , registerOptions ] ) ;
149+
150+ const onContinueClick = useCallback ( async ( ) => {
151+ const fieldUpdates : FieldState [ ] = [ ] ;
152+ setIsLoading ( true ) ;
153+
154+ try {
155+ const { result, generalError, fetchError } = await handleCallAPI < any > ( {
156+ apiFields : [ ] ,
157+ fieldUpdates,
158+ callAPI : callAPI ,
159+ } ) ;
160+
161+ if ( generalError !== undefined || fetchError !== undefined ) {
162+ setErrorMessageLabel ( "WEBAUTHN_ACCOUNT_RECOVERY_GENERAL_ERROR" ) ;
163+ } else {
164+ // If successful
165+ if ( result . status === "OK" ) {
166+ if ( setIsLoading ) {
167+ setIsLoading ( false ) ;
168+ }
169+ setActiveScreen ( RecoverAccountScreen . Success ) ;
170+ } else {
171+ switch ( result . status ) {
172+ case "RECOVER_ACCOUNT_TOKEN_INVALID_ERROR" :
173+ setErrorMessageLabel ( "WEBAUTHN_ACCOUNT_RECOVERY_TOKEN_INVALID_ERROR" ) ;
174+ break ;
175+ case "GENERAL_ERROR" :
176+ setErrorMessageLabel ( "WEBAUTHN_ACCOUNT_RECOVERY_GENERAL_ERROR" ) ;
177+ break ;
178+ case "INVALID_GENERATED_OPTIONS_ERROR" :
179+ setErrorMessageLabel ( "WEBAUTHN_ACCOUNT_RECOVERY_INVALID_GENERATED_OPTIONS_ERROR" ) ;
180+ break ;
181+ case "INVALID_CREDENTIALS_ERROR" :
182+ setErrorMessageLabel ( "WEBAUTHN_ACCOUNT_RECOVERY_INVALID_CREDENTIALS_ERROR" ) ;
183+ break ;
184+ case "GENERATED_OPTIONS_NOT_FOUND_ERROR" :
185+ setErrorMessageLabel ( "WEBAUTHN_ACCOUNT_RECOVERY_GENERATED_OPTIONS_NOT_FOUND_ERROR" ) ;
186+ break ;
187+ case "INVALID_AUTHENTICATOR_ERROR" :
188+ setErrorMessageLabel ( "WEBAUTHN_ACCOUNT_RECOVERY_INVALID_AUTHENTICATOR_ERROR" ) ;
189+ break ;
190+ default :
191+ throw new Error ( "Should never come here" ) ;
192+ }
193+ return ;
194+ }
195+ }
196+ } catch ( e ) {
197+ setErrorMessageLabel ( "WEBAUTHN_ACCOUNT_RECOVERY_GENERAL_ERROR" ) ;
198+ } finally {
199+ if ( setIsLoading ) {
200+ setIsLoading ( false ) ;
201+ }
202+ }
203+ } , [ callAPI ] ) ;
128204
129205 return (
130206 < UserContextWrapper userContext = { props . userContext } >
@@ -155,6 +231,7 @@ function PasskeyRecoverAccountWithTokenTheme(props: RecoverAccountWithTokenTheme
155231 errorMessageLabel = { errorMessageLabel }
156232 email = { registerOptions ?. user . name || null }
157233 isContinueDisabled = { registerOptions === null }
234+ isLoading = { isLoading }
158235 />
159236 { activeScreen !== RecoverAccountScreen . Success && (
160237 < AuthPageFooter
@@ -180,6 +257,7 @@ const RecoverAccountThemeInner = (
180257 errorMessageLabel : string | null ;
181258 email : string | null ;
182259 isContinueDisabled : boolean ;
260+ isLoading : boolean ;
183261 }
184262) => {
185263 return props . activeScreen === RecoverAccountScreen . ContinueWithPasskey ? (
@@ -188,7 +266,7 @@ const RecoverAccountThemeInner = (
188266 email = { props . email || undefined }
189267 onContinueClick = { props . onContinueClick }
190268 errorMessageLabel = { props . errorMessageLabel || undefined }
191- isLoading = { false }
269+ isLoading = { props . isLoading }
192270 hideContinueWithoutPasskey
193271 isContinueDisabled = { props . isContinueDisabled }
194272 />
0 commit comments