diff --git a/packages/javascript/src/api/v2/executeEmbeddedUserOnboardingFlowV2.ts b/packages/javascript/src/api/v2/executeEmbeddedUserOnboardingFlowV2.ts index 9c91c5582..cee444f6d 100644 --- a/packages/javascript/src/api/v2/executeEmbeddedUserOnboardingFlowV2.ts +++ b/packages/javascript/src/api/v2/executeEmbeddedUserOnboardingFlowV2.ts @@ -18,7 +18,10 @@ import AsgardeoAPIError from '../../errors/AsgardeoAPIError'; import {EmbeddedFlowType} from '../../models/embedded-flow'; -import {EmbeddedFlowExecuteRequestConfig as EmbeddedFlowExecuteRequestConfigV2} from '../../models/v2/embedded-flow-v2'; +import { + EmbeddedFlowExecuteRequestConfig as EmbeddedFlowExecuteRequestConfigV2, + FlowExecuteError, +} from '../../models/v2/embedded-flow-v2'; /** * Response from the user onboarding flow execution. @@ -45,9 +48,9 @@ export interface EmbeddedUserOnboardingFlowResponse { executionId: string; /** - * Reason for failure if flowStatus is ERROR. + * Structured error detail present when flowStatus is ERROR. */ - failureReason?: string; + error?: FlowExecuteError; /** * Current status of the flow. diff --git a/packages/javascript/src/index.ts b/packages/javascript/src/index.ts index 8777f0546..739ddbfc0 100644 --- a/packages/javascript/src/index.ts +++ b/packages/javascript/src/index.ts @@ -93,6 +93,7 @@ export { ConsentDecisions as ConsentDecisionsV2, ConsentPurposeData as ConsentPurposeDataV2, ConsentPromptData as ConsentPromptDataV2, + FlowExecuteError, } from './models/v2/embedded-flow-v2'; export { EmbeddedSignInFlowStatus as EmbeddedSignInFlowStatusV2, @@ -116,6 +117,7 @@ export { export { EmbeddedRecoveryFlowStatus as EmbeddedRecoveryFlowStatusV2, EmbeddedRecoveryFlowType as EmbeddedRecoveryFlowTypeV2, + ExtendedEmbeddedRecoveryFlowResponse as ExtendedEmbeddedRecoveryFlowResponseV2, EmbeddedRecoveryFlowResponse as EmbeddedRecoveryFlowResponseV2, EmbeddedRecoveryFlowInitiateRequest as EmbeddedRecoveryFlowInitiateRequestV2, EmbeddedRecoveryFlowRequest as EmbeddedRecoveryFlowRequestV2, diff --git a/packages/javascript/src/models/v2/embedded-flow-v2.ts b/packages/javascript/src/models/v2/embedded-flow-v2.ts index a479ae36d..651c916e7 100644 --- a/packages/javascript/src/models/v2/embedded-flow-v2.ts +++ b/packages/javascript/src/models/v2/embedded-flow-v2.ts @@ -532,6 +532,20 @@ export interface ConsentPromptData { purposes: ConsentPurposeData[]; } +/** + * Structured error detail returned by the flow/execute endpoint when flowStatus is ERROR. + * + * @experimental Part of the new Asgardeo API + */ +export interface FlowExecuteError { + /** Machine-readable error code (e.g. "FEE-60005"). */ + code: string; + /** Localizable description with translation key and English default. */ + description: {default: string; key: string}; + /** Localizable short message with translation key and English default. */ + message: {default: string; key: string}; +} + /** * Extended request configuration for Asgardeo V2 embedded flow operations. * diff --git a/packages/javascript/src/models/v2/embedded-recovery-flow-v2.ts b/packages/javascript/src/models/v2/embedded-recovery-flow-v2.ts index 05ad554fd..68a98d20f 100644 --- a/packages/javascript/src/models/v2/embedded-recovery-flow-v2.ts +++ b/packages/javascript/src/models/v2/embedded-recovery-flow-v2.ts @@ -17,6 +17,7 @@ */ import {EmbeddedFlowType} from '../embedded-flow'; +import {EmbeddedFlowResponseData as EmbeddedFlowResponseDataV2, FlowExecuteError} from './embedded-flow-v2'; /** * Status enumeration for the embedded recovery flow operations. @@ -59,12 +60,24 @@ export enum EmbeddedRecoveryFlowType { View = 'VIEW', } +/** + * Extended response structure for the embedded recovery flow. + * @remarks This response is only done from the SDK level. + * @experimental + */ +export interface ExtendedEmbeddedRecoveryFlowResponse { + /** + * The URL to redirect the user after completing the recovery flow. + */ + redirectUrl?: string; +} + /** * Response structure for the embedded recovery flow. * * @experimental Part of the new Asgardeo API */ -export interface EmbeddedRecoveryFlowResponse { +export interface EmbeddedRecoveryFlowResponse extends ExtendedEmbeddedRecoveryFlowResponse { /** * Per-step challenge token for replay protection. * Must be included in the next request to continue this flow. @@ -72,24 +85,9 @@ export interface EmbeddedRecoveryFlowResponse { challengeToken?: string; /** - * Flow data containing UI components for the current step. + * Core response data containing UI components and flow metadata. */ - data: { - /** - * Additional data from the flow step. - */ - additionalData?: Record; - - /** - * UI components to render for the current step. - */ - components?: any[]; - - /** - * Redirect URL if type is REDIRECTION. - */ - redirectURL?: string; - }; + data: EmbeddedFlowResponseDataV2; /** * Unique identifier for this recovery flow execution. @@ -97,9 +95,9 @@ export interface EmbeddedRecoveryFlowResponse { executionId: string; /** - * Optional reason for failure when flowStatus is ERROR. + * Structured error detail present when flowStatus is ERROR. */ - failureReason?: string; + error?: FlowExecuteError; /** * Current status of the recovery flow. @@ -151,9 +149,9 @@ export interface EmbeddedRecoveryFlowErrorResponse { executionId: string; /** - * Human-readable explanation of why the recovery operation failed. + * Structured error detail describing why the recovery operation failed. */ - failureReason: string; + error: FlowExecuteError; /** * Status of the recovery flow — always ERROR for this interface. diff --git a/packages/javascript/src/models/v2/embedded-signin-flow-v2.ts b/packages/javascript/src/models/v2/embedded-signin-flow-v2.ts index 9aa0bda8a..fbafa39c4 100644 --- a/packages/javascript/src/models/v2/embedded-signin-flow-v2.ts +++ b/packages/javascript/src/models/v2/embedded-signin-flow-v2.ts @@ -16,7 +16,7 @@ * under the License. */ -import {EmbeddedFlowResponseData as EmbeddedFlowResponseDataV2} from './embedded-flow-v2'; +import {EmbeddedFlowResponseData as EmbeddedFlowResponseDataV2, FlowExecuteError} from './embedded-flow-v2'; import { EmbeddedFlowResponseType as EmbeddedFlowResponseTypeV1, EmbeddedFlowType as EmbeddedFlowTypeV1, @@ -209,10 +209,9 @@ export interface EmbeddedSignInFlowResponse extends ExtendedEmbeddedSignInFlowRe executionId: string; /** - * Optional reason for flow failure in case of an error. - * Provides additional context when flowStatus is set to ERROR. + * Structured error detail present when flowStatus is ERROR. */ - failureReason?: string; + error?: FlowExecuteError; /** * Current status of the sign-in flow. diff --git a/packages/javascript/src/models/v2/embedded-signup-flow-v2.ts b/packages/javascript/src/models/v2/embedded-signup-flow-v2.ts index 3b951ac1e..fd3b12f22 100644 --- a/packages/javascript/src/models/v2/embedded-signup-flow-v2.ts +++ b/packages/javascript/src/models/v2/embedded-signup-flow-v2.ts @@ -20,6 +20,7 @@ import { EmbeddedFlowResponseType as EmbeddedFlowResponseTypeV1, EmbeddedFlowType as EmbeddedFlowTypeV1, } from '../embedded-flow'; +import {EmbeddedFlowResponseData as EmbeddedFlowResponseDataV2, FlowExecuteError} from './embedded-flow-v2'; /** * Status enumeration for Asgardeo embedded sign-up flow operations. @@ -148,12 +149,13 @@ export interface EmbeddedSignUpFlowResponse extends ExtendedEmbeddedSignUpFlowRe challengeToken?: string; /** - * Flow data containing form inputs and available actions. + * Core response data containing UI components and flow metadata. * This is transformed to component-driven format by the React transformer. */ - data: { + data: EmbeddedFlowResponseDataV2 & { /** * Available actions the user can take (e.g., form submission, social sign-up). + * @deprecated Use data.meta.components for new implementations */ actions?: { id: string; @@ -162,6 +164,7 @@ export interface EmbeddedSignUpFlowResponse extends ExtendedEmbeddedSignUpFlowRe /** * Input fields required for the current step of the sign-up flow. + * @deprecated Use data.meta.components for new implementations */ inputs?: { name: string; @@ -176,10 +179,9 @@ export interface EmbeddedSignUpFlowResponse extends ExtendedEmbeddedSignUpFlowRe executionId: string; /** - * Optional reason for flow failure in case of an error. - * Provides additional context when flowStatus is set to ERROR. + * Structured error detail present when flowStatus is ERROR. */ - failureReason?: string; + error?: FlowExecuteError; /** * Current status of the sign-up flow. @@ -284,7 +286,10 @@ export interface EmbeddedSignUpFlowErrorResponse { */ executionId: string; - failureReason: string; + /** + * Structured error detail describing why the sign-up operation failed. + */ + error: FlowExecuteError; /** * Status of the sign-up flow, which will be `EmbeddedSignUpFlowStatus.Error` diff --git a/packages/react/src/components/presentation/auth/AcceptInvite/v2/BaseAcceptInvite.tsx b/packages/react/src/components/presentation/auth/AcceptInvite/v2/BaseAcceptInvite.tsx index f32d45a7b..75720cf08 100644 --- a/packages/react/src/components/presentation/auth/AcceptInvite/v2/BaseAcceptInvite.tsx +++ b/packages/react/src/components/presentation/auth/AcceptInvite/v2/BaseAcceptInvite.tsx @@ -50,8 +50,8 @@ export interface AcceptInviteFlowResponse { }; redirectURL?: string; }; + error?: {code: string; description?: {default: string; key: string}; message?: {default: string; key: string}}; executionId: string; - failureReason?: string; flowStatus: 'INCOMPLETE' | 'COMPLETE' | 'ERROR'; type?: 'VIEW' | 'REDIRECTION'; } @@ -329,8 +329,7 @@ const BaseAcceptInvite: FC = ({ const handleError: any = useCallback( (error: any) => { // Extract error message from response failureReason or use extractErrorMessage - const errorMessage: string = - error?.failureReason || extractErrorMessage(error, t, 'components.acceptInvite.errors.generic'); + const errorMessage: string = extractErrorMessage(error, t, 'components.acceptInvite.errors.generic'); // Set the API error state setApiError(error instanceof Error ? error : new Error(errorMessage)); diff --git a/packages/react/src/components/presentation/auth/InviteUser/v2/BaseInviteUser.tsx b/packages/react/src/components/presentation/auth/InviteUser/v2/BaseInviteUser.tsx index db3b3af0e..6e73af8f9 100644 --- a/packages/react/src/components/presentation/auth/InviteUser/v2/BaseInviteUser.tsx +++ b/packages/react/src/components/presentation/auth/InviteUser/v2/BaseInviteUser.tsx @@ -52,8 +52,8 @@ export interface InviteUserFlowResponse { components?: any[]; }; }; + error?: {code: string; description?: {default: string; key: string}; message?: {default: string; key: string}}; executionId: string; - failureReason?: string; flowStatus: 'INCOMPLETE' | 'COMPLETE' | 'ERROR'; type?: 'VIEW' | 'REDIRECTION'; } @@ -321,8 +321,7 @@ const BaseInviteUser: FC = ({ const handleError: any = useCallback( (error: any) => { // Extract error message from response failureReason or use extractErrorMessage - const errorMessage: string = - error?.failureReason || extractErrorMessage(error, t, 'components.inviteUser.errors.generic'); + const errorMessage: string = extractErrorMessage(error, t, 'components.inviteUser.errors.generic'); // Set the API error state setApiError(error instanceof Error ? error : new Error(errorMessage)); diff --git a/packages/react/src/components/presentation/auth/Recovery/v2/BaseRecovery.tsx b/packages/react/src/components/presentation/auth/Recovery/v2/BaseRecovery.tsx index 56416e518..c5d6e6d31 100644 --- a/packages/react/src/components/presentation/auth/Recovery/v2/BaseRecovery.tsx +++ b/packages/react/src/components/presentation/auth/Recovery/v2/BaseRecovery.tsx @@ -17,9 +17,9 @@ */ import { - EmbeddedFlowExecuteRequestPayload, - EmbeddedFlowExecuteResponse, - EmbeddedFlowStatus, + EmbeddedRecoveryFlowRequestV2, + EmbeddedRecoveryFlowResponseV2, + EmbeddedRecoveryFlowStatusV2, EmbeddedFlowComponentTypeV2 as EmbeddedFlowComponentType, withVendorCSSClassPrefix, Preferences, @@ -83,11 +83,11 @@ export interface BaseRecoveryProps { inputClassName?: string; isInitialized?: boolean; messageClassName?: string; - onComplete?: (response: EmbeddedFlowExecuteResponse) => void; + onComplete?: (response: EmbeddedRecoveryFlowResponseV2) => void; onError?: (error: Error) => void; - onFlowChange?: (response: EmbeddedFlowExecuteResponse) => void; - onInitialize?: (payload?: EmbeddedFlowExecuteRequestPayload) => Promise; - onSubmit?: (payload: EmbeddedFlowExecuteRequestPayload) => Promise; + onFlowChange?: (response: EmbeddedRecoveryFlowResponseV2) => void; + onInitialize?: (payload?: EmbeddedRecoveryFlowRequestV2) => Promise; + onSubmit?: (payload: EmbeddedRecoveryFlowRequestV2) => Promise; /** * Component-level preferences to override global i18n and theme settings. */ @@ -132,7 +132,7 @@ const BaseRecoveryContent: FC = ({ const [isLoading, setIsLoading] = useState(false); const [isFlowInitialized, setIsFlowInitialized] = useState(false); - const [currentFlow, setCurrentFlow] = useState(null); + const [currentFlow, setCurrentFlow] = useState(null); const [apiError, setApiError] = useState(null); const initializationAttemptedRef: any = useRef(false); @@ -140,7 +140,7 @@ const BaseRecoveryContent: FC = ({ const handleError: any = useCallback( (error: any) => { - const errorMessage: string = error?.failureReason || extractErrorMessage(error, t); + const errorMessage: string = extractErrorMessage(error, t); setApiError(error instanceof Error ? error : new Error(errorMessage)); clearMessages(); addMessage({message: errorMessage, type: 'error'}); @@ -149,8 +149,8 @@ const BaseRecoveryContent: FC = ({ ); const normalizeFlowResponseLocal: any = useCallback( - (response: EmbeddedFlowExecuteResponse): EmbeddedFlowExecuteResponse => { - if (response?.data?.components && Array.isArray(response.data.components)) { + (response: EmbeddedRecoveryFlowResponseV2): EmbeddedRecoveryFlowResponseV2 => { + if ((response?.data as any)?.components && Array.isArray((response.data as any).components)) { return response; } @@ -215,7 +215,7 @@ const BaseRecoveryContent: FC = ({ [t], ); - const formFields: any = currentFlow?.data?.components ? extractFormFields(currentFlow.data.components) : []; + const formFields: any = (currentFlow?.data as any)?.components ? extractFormFields((currentFlow.data as any).components) : []; const form: any = useForm>({ fields: formFields, @@ -238,8 +238,8 @@ const BaseRecoveryContent: FC = ({ } = form; const setupFormFields: any = useCallback( - (flowResponse: EmbeddedFlowExecuteResponse) => { - const fields: any = extractFormFields(flowResponse.data?.components || []); + (flowResponse: EmbeddedRecoveryFlowResponseV2) => { + const fields: any = extractFormFields((flowResponse.data as any)?.components || []); const initialValues: Record = {}; fields.forEach((field: any) => { initialValues[field.name] = field.initialValue || ''; @@ -281,7 +281,7 @@ const BaseRecoveryContent: FC = ({ }); } - const payload: EmbeddedFlowExecuteRequestPayload = { + const payload: EmbeddedRecoveryFlowRequestV2 = { ...((currentFlow as any).executionId && {executionId: (currentFlow as any).executionId}), flowType: (currentFlow as any).flowType || 'RECOVERY', ...(component.id && {action: component.id}), @@ -298,12 +298,12 @@ const BaseRecoveryContent: FC = ({ challengeTokenRef.current = response.challengeToken ?? null; } - if (response.flowStatus === EmbeddedFlowStatus.Complete) { + if (response.flowStatus === EmbeddedRecoveryFlowStatusV2.Complete) { onComplete?.(response); return; } - if (response.flowStatus === EmbeddedFlowStatus.Incomplete) { + if (response.flowStatus === EmbeddedRecoveryFlowStatusV2.Incomplete) { setCurrentFlow(response); setupFormFields(response); } @@ -407,12 +407,12 @@ const BaseRecoveryContent: FC = ({ setIsFlowInitialized(true); onFlowChange?.(response); - if (response.flowStatus === EmbeddedFlowStatus.Complete) { + if (response.flowStatus === EmbeddedRecoveryFlowStatusV2.Complete) { onComplete?.(response); return; } - if (response.flowStatus === EmbeddedFlowStatus.Incomplete) { + if (response.flowStatus === EmbeddedRecoveryFlowStatusV2.Incomplete) { setupFormFields(response); } } catch (err) { @@ -488,7 +488,7 @@ const BaseRecoveryContent: FC = ({ ); } - const componentsToRender: any = currentFlow.data?.components || []; + const componentsToRender: any = (currentFlow.data as any)?.components || []; const {title, subtitle, componentsWithoutHeadings} = getAuthComponentHeadings( componentsToRender, flowTitle, diff --git a/packages/react/src/components/presentation/auth/Recovery/v2/Recovery.tsx b/packages/react/src/components/presentation/auth/Recovery/v2/Recovery.tsx index d2cceed59..dd2cc332c 100644 --- a/packages/react/src/components/presentation/auth/Recovery/v2/Recovery.tsx +++ b/packages/react/src/components/presentation/auth/Recovery/v2/Recovery.tsx @@ -16,7 +16,7 @@ * under the License. */ -import {EmbeddedFlowExecuteRequestPayload, EmbeddedFlowExecuteResponse, EmbeddedFlowType} from '@asgardeo/browser'; +import {EmbeddedFlowType, EmbeddedRecoveryFlowRequestV2, EmbeddedRecoveryFlowResponseV2} from '@asgardeo/browser'; import {FC, PropsWithChildren, ReactElement, useCallback} from 'react'; import BaseRecovery, {BaseRecoveryProps} from './BaseRecovery'; import useAsgardeo from '../../../../../contexts/Asgardeo/useAsgardeo'; @@ -70,9 +70,9 @@ const Recovery: FC = ({ }: RecoveryProps): ReactElement => { const {recover, isInitialized, applicationId} = useAsgardeo(); - const handleInitialize: (payload?: EmbeddedFlowExecuteRequestPayload) => Promise = + const handleInitialize: (payload?: EmbeddedRecoveryFlowRequestV2) => Promise = useCallback( - async (payload?: EmbeddedFlowExecuteRequestPayload): Promise => { + async (payload?: EmbeddedRecoveryFlowRequestV2): Promise => { const urlParams: URLSearchParams = new URL(window.location.href).searchParams; const applicationIdFromUrl: string | null = urlParams.get('applicationId'); const effectiveApplicationId: string | null = applicationId ?? applicationIdFromUrl; @@ -87,7 +87,7 @@ const Recovery: FC = ({ inputs: {[tokenUrlParam]: tokenValue}, verbose: true, }; - return (await recover(resumePayload)) as EmbeddedFlowExecuteResponse; + return (await recover(resumePayload)) as EmbeddedRecoveryFlowResponseV2; } } @@ -96,20 +96,20 @@ const Recovery: FC = ({ ...(effectiveApplicationId && {applicationId: effectiveApplicationId}), }; - return (await recover(initialPayload)) as EmbeddedFlowExecuteResponse; + return (await recover(initialPayload)) as EmbeddedRecoveryFlowResponseV2; }, [applicationId, tokenUrlParam, recover], ); - const handleOnSubmit: (payload: EmbeddedFlowExecuteRequestPayload) => Promise = + const handleOnSubmit: (payload: EmbeddedRecoveryFlowRequestV2) => Promise = useCallback( - async (payload: EmbeddedFlowExecuteRequestPayload): Promise => - (await recover(payload)) as EmbeddedFlowExecuteResponse, + async (payload: EmbeddedRecoveryFlowRequestV2): Promise => + (await recover(payload)) as EmbeddedRecoveryFlowResponseV2, [recover], ); - const handleComplete: (response: EmbeddedFlowExecuteResponse) => void = useCallback( - (response: EmbeddedFlowExecuteResponse): void => { + const handleComplete: (response: EmbeddedRecoveryFlowResponseV2) => void = useCallback( + (response: EmbeddedRecoveryFlowResponseV2): void => { onComplete?.(response); if (afterRecoveryUrl) { diff --git a/packages/react/src/components/presentation/auth/SignIn/v2/BaseSignIn.tsx b/packages/react/src/components/presentation/auth/SignIn/v2/BaseSignIn.tsx index eac7693b6..fb0ae1cb0 100644 --- a/packages/react/src/components/presentation/auth/SignIn/v2/BaseSignIn.tsx +++ b/packages/react/src/components/presentation/auth/SignIn/v2/BaseSignIn.tsx @@ -258,7 +258,7 @@ const BaseSignInContent: FC = ({ const handleError: any = useCallback( (error: any) => { // Extract error message from response failureReason or use extractErrorMessage - const errorMessage: string = error?.failureReason || extractErrorMessage(error, t); + const errorMessage: string = extractErrorMessage(error, t); // Set the API error state setApiError(error instanceof Error ? error : new Error(errorMessage)); diff --git a/packages/react/src/components/presentation/auth/SignIn/v2/SignIn.tsx b/packages/react/src/components/presentation/auth/SignIn/v2/SignIn.tsx index aa0203b63..b2e4f22ce 100644 --- a/packages/react/src/components/presentation/auth/SignIn/v2/SignIn.tsx +++ b/packages/react/src/components/presentation/auth/SignIn/v2/SignIn.tsx @@ -35,7 +35,7 @@ import useAsgardeo from '../../../../../contexts/Asgardeo/useAsgardeo'; import useTranslation from '../../../../../hooks/useTranslation'; import {useOAuthCallback} from '../../../../../hooks/v2/useOAuthCallback'; import {initiateOAuthRedirect} from '../../../../../utils/oauth'; -import {normalizeFlowResponse} from '../../../../../utils/v2/flowTransformer'; +import {normalizeFlowResponse, extractErrorMessage} from '../../../../../utils/v2/flowTransformer'; import {handlePasskeyAuthentication, handlePasskeyRegistration} from '../../../../../utils/v2/passkey'; /** @@ -480,11 +480,7 @@ const SignIn: FC = ({ const err: any = error as any; await clearFlowState(); - // Extract error message from response or error object - const errorMessage: any = err?.failureReason || (err instanceof Error ? err.message : String(err)); - - // Set error with the extracted message - setError(new Error(errorMessage)); + setError(new Error(extractErrorMessage(err, t))); initializationAttemptedRef.current = false; } }; @@ -690,10 +686,7 @@ const SignIn: FC = ({ // Handle Error flow status - flow has failed and is invalidated if (response.flowStatus === EmbeddedSignInFlowStatusV2.Error) { await clearFlowState(); - // Extract failureReason from response if available - const failureReason: any = (response as any)?.failureReason; - const errorMessage: any = failureReason || 'Authentication flow failed. Please try again.'; - const err: any = new Error(errorMessage); + const err: any = new Error(extractErrorMessage(response, t, 'errors.signin.flow.failure')); setError(err); cleanupFlowUrlParams(); // Throw the error so it's caught by the catch block and propagated to BaseSignIn @@ -746,19 +739,15 @@ const SignIn: FC = ({ // Clean up executionId from URL after setting it in state cleanupFlowUrlParams(); - // Display failure reason from INCOMPLETE response - if ((response as any)?.failureReason) { - setFlowError(new Error((response as any).failureReason)); + // Display error detail from INCOMPLETE response + if ((response as any)?.error) { + setFlowError(new Error(extractErrorMessage(response, t))); } } } catch (error) { const err: any = error as any; await clearFlowState(); - - // Extract error message from response or error object - const errorMessage: any = err?.failureReason || (err instanceof Error ? err.message : String(err)); - - setError(new Error(errorMessage)); + setError(new Error(extractErrorMessage(err, t))); return; } finally { setIsSubmitting(false); diff --git a/packages/react/src/components/presentation/auth/SignUp/v2/BaseSignUp.tsx b/packages/react/src/components/presentation/auth/SignUp/v2/BaseSignUp.tsx index 1ad9de5b3..06a0a7049 100644 --- a/packages/react/src/components/presentation/auth/SignUp/v2/BaseSignUp.tsx +++ b/packages/react/src/components/presentation/auth/SignUp/v2/BaseSignUp.tsx @@ -17,10 +17,10 @@ */ import { - EmbeddedFlowExecuteRequestPayload, - EmbeddedFlowExecuteResponse, - EmbeddedFlowStatus, - EmbeddedFlowResponseType, + EmbeddedSignUpFlowRequestV2, + EmbeddedSignUpFlowResponseV2, + EmbeddedSignUpFlowStatusV2, + EmbeddedSignUpFlowTypeV2, withVendorCSSClassPrefix, EmbeddedFlowComponentTypeV2 as EmbeddedFlowComponentType, createPackageComponentLogger, @@ -187,7 +187,7 @@ export interface BaseSignUpProps { * This allows platform-specific handling of redirects (e.g., Next.js router.push). * @param response - The response from the sign-up flow containing the redirect URL, etc. */ - onComplete?: (response: EmbeddedFlowExecuteResponse) => void; + onComplete?: (response: EmbeddedSignUpFlowResponseV2) => void; /** * Callback function called when sign-up fails. @@ -199,20 +199,20 @@ export interface BaseSignUpProps { * Callback function called when sign-up flow status changes. * @param response - The current sign-up response. */ - onFlowChange?: (response: EmbeddedFlowExecuteResponse) => void; + onFlowChange?: (response: EmbeddedSignUpFlowResponseV2) => void; /** * Function to initialize sign-up flow. * @returns Promise resolving to the initial sign-up response. */ - onInitialize?: (payload?: EmbeddedFlowExecuteRequestPayload) => Promise; + onInitialize?: (payload?: EmbeddedSignUpFlowRequestV2) => Promise; /** * Function to handle sign-up steps. * @param payload - The sign-up payload. * @returns Promise resolving to the sign-up response. */ - onSubmit?: (payload: EmbeddedFlowExecuteRequestPayload) => Promise; + onSubmit?: (payload: EmbeddedSignUpFlowRequestV2) => Promise; /** * Component-level preferences to override global i18n and theme settings. * Preferences are deep-merged with global ones, with component preferences @@ -283,7 +283,7 @@ const BaseSignUpContent: FC = ({ const [isLoading, setIsLoading] = useState(false); const [isFlowInitialized, setIsFlowInitialized] = useState(false); - const [currentFlow, setCurrentFlow] = useState(null); + const [currentFlow, setCurrentFlow] = useState(null); const [apiError, setApiError] = useState(null); const [passkeyState, setPasskeyState] = useState({ actionId: null, @@ -343,7 +343,7 @@ const BaseSignUpContent: FC = ({ const handleError: any = useCallback( (error: any) => { // Extract error message from response failureReason or use extractErrorMessage - const errorMessage: string = error?.failureReason || extractErrorMessage(error, t); + const errorMessage: string = extractErrorMessage(error, t); // Set the API error state setApiError(error instanceof Error ? error : new Error(errorMessage)); @@ -363,16 +363,16 @@ const BaseSignUpContent: FC = ({ * Uses normalizeFlowResponse for modern API format responses */ const normalizeFlowResponseLocal: any = useCallback( - (response: EmbeddedFlowExecuteResponse): EmbeddedFlowExecuteResponse => { + (response: EmbeddedSignUpFlowResponseV2): EmbeddedSignUpFlowResponseV2 => { // If response already has components, return as-is - if (response?.data?.components && Array.isArray(response.data.components)) { + if ((response?.data as any)?.components && Array.isArray((response.data as any).components)) { return response; } // Use the transformer to handle meta.components structure if (response?.data) { const {components} = normalizeFlowResponse( - response, + response as any, t, { defaultErrorKey: 'components.signUp.errors.generic', @@ -385,8 +385,8 @@ const BaseSignUpContent: FC = ({ ...response, data: { ...response.data, - components: components as any, - }, + ...(components && {components: components as any}), + } as any, }; } @@ -449,7 +449,7 @@ const BaseSignUpContent: FC = ({ [t], ); - const formFields: any = currentFlow?.data?.components ? extractFormFields(currentFlow.data.components) : []; + const formFields: any = (currentFlow?.data as any)?.components ? extractFormFields((currentFlow.data as any).components) : []; const form: any = useForm>({ fields: formFields, @@ -475,8 +475,8 @@ const BaseSignUpContent: FC = ({ * Setup form fields based on the current flow. */ const setupFormFields: any = useCallback( - (flowResponse: EmbeddedFlowExecuteResponse) => { - const fields: any = extractFormFields(flowResponse.data?.components || []); + (flowResponse: EmbeddedSignUpFlowResponseV2) => { + const fields: any = extractFormFields((flowResponse.data as any)?.components || []); const initialValues: Record = {}; fields.forEach((field: any) => { @@ -514,12 +514,12 @@ const BaseSignUpContent: FC = ({ * @param response - The sign-up response * @returns true if a redirect was performed, false otherwise */ - const handleRedirectionIfNeeded = (response: EmbeddedFlowExecuteResponse): boolean => { - if (response?.type === EmbeddedFlowResponseType.Redirection && response?.data?.redirectURL) { + const handleRedirectionIfNeeded = (response: EmbeddedSignUpFlowResponseV2): boolean => { + if (response?.type === EmbeddedSignUpFlowTypeV2.Redirection && (response?.data as any)?.redirectURL) { /** * Open a popup window to handle redirection prompts for social sign-up */ - const redirectUrl: any = response.data.redirectURL; + const redirectUrl: any = (response.data as any).redirectURL; const popup: any = window.open(redirectUrl, 'oauth_popup', 'width=500,height=600,scrollbars=yes,resizable=yes'); if (!popup) { @@ -567,7 +567,7 @@ const BaseSignUpContent: FC = ({ if (code && state) { hasProcessedCallback = true; - const payload: EmbeddedFlowExecuteRequestPayload = { + const payload: EmbeddedSignUpFlowRequestV2 = { ...((currentFlow as any).executionId && {executionId: (currentFlow as any).executionId}), action: '', flowType: (currentFlow as any).flowType || 'REGISTRATION', @@ -582,9 +582,9 @@ const BaseSignUpContent: FC = ({ const continueResponse: any = await onSubmit(payload); onFlowChange?.(continueResponse); - if (continueResponse.flowStatus === EmbeddedFlowStatus.Complete) { + if (continueResponse.flowStatus === EmbeddedSignUpFlowStatusV2.Complete) { onComplete?.(continueResponse); - } else if (continueResponse.flowStatus === EmbeddedFlowStatus.Incomplete) { + } else if (continueResponse.flowStatus === EmbeddedSignUpFlowStatusV2.Incomplete) { setCurrentFlow(continueResponse); setupFormFields(continueResponse); } @@ -639,7 +639,7 @@ const BaseSignUpContent: FC = ({ } if (code && state) { - const payload: EmbeddedFlowExecuteRequestPayload = { + const payload: EmbeddedSignUpFlowRequestV2 = { ...((currentFlow as any).executionId && {executionId: (currentFlow as any).executionId}), action: '', flowType: (currentFlow as any).flowType || 'REGISTRATION', @@ -654,9 +654,9 @@ const BaseSignUpContent: FC = ({ const continueResponse: any = await onSubmit(payload); onFlowChange?.(continueResponse); - if (continueResponse.flowStatus === EmbeddedFlowStatus.Complete) { + if (continueResponse.flowStatus === EmbeddedSignUpFlowStatusV2.Complete) { onComplete?.(continueResponse); - } else if (continueResponse.flowStatus === EmbeddedFlowStatus.Incomplete) { + } else if (continueResponse.flowStatus === EmbeddedSignUpFlowStatusV2.Incomplete) { setCurrentFlow(continueResponse); setupFormFields(continueResponse); } @@ -719,7 +719,7 @@ const BaseSignUpContent: FC = ({ }); } - const payload: EmbeddedFlowExecuteRequestPayload = { + const payload: EmbeddedSignUpFlowRequestV2 = { ...((currentFlow as any).executionId && {executionId: (currentFlow as any).executionId}), flowType: (currentFlow as any).flowType || 'REGISTRATION', ...(component.id && {action: component.id}), @@ -733,12 +733,12 @@ const BaseSignUpContent: FC = ({ await setChallengeToken(response.challengeToken ?? null); - if (response.flowStatus === EmbeddedFlowStatus.Complete) { + if (response.flowStatus === EmbeddedSignUpFlowStatusV2.Complete) { onComplete?.(response); return; } - if (response.flowStatus === EmbeddedFlowStatus.Incomplete) { + if (response.flowStatus === EmbeddedSignUpFlowStatusV2.Incomplete) { if (handleRedirectionIfNeeded(response)) { return; } @@ -798,8 +798,8 @@ const BaseSignUpContent: FC = ({ }; // After successful registration, submit the result to the server - const payload: EmbeddedFlowExecuteRequestPayload = { - actionId: passkeyState.actionId || 'submit', + const payload: EmbeddedSignUpFlowRequestV2 = { + action: passkeyState.actionId || 'submit', executionId: passkeyState.executionId as string, flowType: (currentFlow as any)?.flowType || 'REGISTRATION', inputs, @@ -810,7 +810,7 @@ const BaseSignUpContent: FC = ({ const processedResponse: any = normalizeFlowResponseLocal(nextResponse); onFlowChange?.(processedResponse); - if (processedResponse.flowStatus === EmbeddedFlowStatus.Complete) { + if (processedResponse.flowStatus === EmbeddedSignUpFlowStatusV2.Complete) { onComplete?.(processedResponse); } else { setCurrentFlow(processedResponse); @@ -938,12 +938,12 @@ const BaseSignUpContent: FC = ({ setIsFlowInitialized(true); onFlowChange?.(response); - if (response.flowStatus === EmbeddedFlowStatus.Complete) { + if (response.flowStatus === EmbeddedSignUpFlowStatusV2.Complete) { onComplete?.(response); return; } - if (response.flowStatus === EmbeddedFlowStatus.Incomplete) { + if (response.flowStatus === EmbeddedSignUpFlowStatusV2.Incomplete) { setupFormFields(response); } } catch (err) { @@ -970,7 +970,7 @@ const BaseSignUpContent: FC = ({ // If render props are provided, use them if (children) { const renderProps: BaseSignUpRenderProps = { - components: currentFlow?.data?.components || [], + components: (currentFlow?.data as any)?.components || [], error: apiError, fieldErrors: formErrors, handleInputChange, @@ -1017,7 +1017,7 @@ const BaseSignUpContent: FC = ({ } // Extract heading and subheading components and filter them from the main components - const componentsToRender: any = currentFlow.data?.components || []; + const componentsToRender: any = (currentFlow.data as any)?.components || []; const {title, subtitle, componentsWithoutHeadings} = getAuthComponentHeadings( componentsToRender, flowTitle, diff --git a/packages/react/src/components/presentation/auth/SignUp/v2/SignUp.tsx b/packages/react/src/components/presentation/auth/SignUp/v2/SignUp.tsx index 90b74b540..aac930f0f 100644 --- a/packages/react/src/components/presentation/auth/SignUp/v2/SignUp.tsx +++ b/packages/react/src/components/presentation/auth/SignUp/v2/SignUp.tsx @@ -17,10 +17,10 @@ */ import { - EmbeddedFlowExecuteRequestPayload, - EmbeddedFlowExecuteResponse, - EmbeddedFlowResponseType, EmbeddedFlowType, + EmbeddedSignUpFlowRequestV2, + EmbeddedSignUpFlowResponseV2, + EmbeddedSignUpFlowTypeV2, } from '@asgardeo/browser'; import {FC, ReactElement, ReactNode} from 'react'; // eslint-disable-next-line import/no-named-as-default @@ -62,10 +62,10 @@ const SignUp: FC = ({ * Initialize the sign-up flow. */ const handleInitialize = async ( - payload?: EmbeddedFlowExecuteRequestPayload, - ): Promise => { + payload?: EmbeddedSignUpFlowRequestV2, + ): Promise => { const urlParams: URLSearchParams = new URL(window.location.href).searchParams; - const applicationIdFromUrl: string = urlParams.get('applicationId'); + const applicationIdFromUrl: string | null = urlParams.get('applicationId'); // Priority order: applicationId from context > applicationId from URL const effectiveApplicationId: any = applicationId || applicationIdFromUrl; @@ -75,44 +75,44 @@ const SignUp: FC = ({ ...(effectiveApplicationId && {applicationId: effectiveApplicationId}), }; - return (await signUp(initialPayload)) as EmbeddedFlowExecuteResponse; + return (await signUp(initialPayload)) as EmbeddedSignUpFlowResponseV2; }; /** * Handle sign-up steps. */ - const handleOnSubmit = async (payload: EmbeddedFlowExecuteRequestPayload): Promise => - (await signUp(payload)) as EmbeddedFlowExecuteResponse; + const handleOnSubmit = async (payload: EmbeddedSignUpFlowRequestV2): Promise => + (await signUp(payload)) as EmbeddedSignUpFlowResponseV2; /** * Handle successful sign-up and redirect. */ - const handleComplete = (response: EmbeddedFlowExecuteResponse): any => { + const handleComplete = (response: EmbeddedSignUpFlowResponseV2): any => { onComplete?.(response); // Check if OAuth flow completed and we have a redirect URL with authorization code // This happens when registration completes with assertion and OAuth authorize succeeds - const oauthRedirectUrl: any = (response as any)?.redirectUrl; - if (shouldRedirectAfterSignUp && oauthRedirectUrl) { - window.location.href = oauthRedirectUrl; + if (shouldRedirectAfterSignUp && response?.redirectUrl) { + window.location.href = response.redirectUrl; return; } // For non-redirection responses (regular sign-up completion), handle redirect if configured - if (shouldRedirectAfterSignUp && response?.type !== EmbeddedFlowResponseType.Redirection && afterSignUpUrl) { + if (shouldRedirectAfterSignUp && response?.type !== EmbeddedSignUpFlowTypeV2.Redirection && afterSignUpUrl) { window.location.href = afterSignUpUrl; } // For redirection responses (social sign-up), they are handled by BaseSignUp's popup mechanism // and we only redirect after the OAuth flow is complete if shouldRedirectAfterSignUp is true + const redirectURL: string | undefined = (response?.data as any)?.redirectURL; if ( shouldRedirectAfterSignUp && - response?.type === EmbeddedFlowResponseType.Redirection && - response?.data?.redirectURL && - !response.data.redirectURL.includes('oauth') && // Not a social provider redirect - !response.data.redirectURL.includes('auth') // Not an auth provider redirect + response?.type === EmbeddedSignUpFlowTypeV2.Redirection && + redirectURL && + !redirectURL.includes('oauth') && // Not a social provider redirect + !redirectURL.includes('auth') // Not an auth provider redirect ) { - window.location.href = response.data.redirectURL; + window.location.href = redirectURL; } }; diff --git a/packages/react/src/utils/v2/flowTransformer.ts b/packages/react/src/utils/v2/flowTransformer.ts index 50d61be75..a4fcc784b 100644 --- a/packages/react/src/utils/v2/flowTransformer.ts +++ b/packages/react/src/utils/v2/flowTransformer.ts @@ -50,8 +50,8 @@ import {UseTranslation} from '../../hooks/useTranslation'; * Generic flow error response interface that covers common error structure */ export interface FlowErrorResponse { + error?: {code: string; description?: {defaultValue: string; key: string}; message?: {defaultValue: string; key: string}}; executionId: string; - failureReason?: string; flowStatus: 'ERROR'; } @@ -244,9 +244,15 @@ export const extractErrorMessage = ( t: UseTranslation['t'], defaultErrorKey: string = 'errors.flow.generic', ): string => { - // Check for failureReason in the error object - if (error && typeof error === 'object' && error.failureReason) { - return error.failureReason; + // Check for structured error object + if (error && typeof error === 'object' && error.error) { + return ( + (error.error.message?.key && t(error.error.message.key)) || + error.error.message?.defaultValue || + (error.error.description?.key && t(error.error.description.key)) || + error.error.description?.defaultValue || + t(defaultErrorKey) + ); } // Check if error is a standard Error object with a message