Skip to content

Commit e702a26

Browse files
authored
Merge pull request #307 from brionmario/refactor-thunder-flows
Align error handling & Sign Up claim issues in AsgardeoV2
2 parents c8f5df4 + ca71b3b commit e702a26

File tree

7 files changed

+155
-52
lines changed

7 files changed

+155
-52
lines changed

.changeset/fifty-dragons-drive.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@asgardeo/javascript': patch
3+
'@asgardeo/react': patch
4+
---
5+
6+
Align error handling

packages/javascript/src/models/v2/embedded-signin-flow-v2.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,12 @@ export interface EmbeddedSignInFlowResponse extends ExtendedEmbeddedSignInFlowRe
172172
*/
173173
flowId: string;
174174

175+
/**
176+
* Optional reason for flow failure in case of an error.
177+
* Provides additional context when flowStatus is set to ERROR.
178+
*/
179+
failureReason?: string;
180+
175181
/**
176182
* Current status of the sign-in flow.
177183
* Determines the next action required by the client application.
@@ -294,7 +300,7 @@ export type EmbeddedSignInFlowInitiateRequest = {
294300
* // Continue existing flow with user input
295301
* const stepRequest: EmbeddedSignInFlowRequest = {
296302
* flowId: "flow_12345",
297-
* actionId: "action_001",
303+
* action: "action_001",
298304
* inputs: {
299305
* username: "user@example.com",
300306
* password: "securePassword123"

packages/javascript/src/models/v2/embedded-signup-flow-v2.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,12 @@ export interface EmbeddedSignUpFlowResponse extends ExtendedEmbeddedSignUpFlowRe
146146
*/
147147
flowId: string;
148148

149+
/**
150+
* Optional reason for flow failure in case of an error.
151+
* Provides additional context when flowStatus is set to ERROR.
152+
*/
153+
failureReason?: string;
154+
149155
/**
150156
* Current status of the sign-up flow.
151157
* Determines whether more input is needed or the flow is complete.
@@ -204,7 +210,7 @@ export type EmbeddedSignUpFlowInitiateRequest = {
204210
*/
205211
export interface EmbeddedSignUpFlowRequest extends Partial<EmbeddedSignUpFlowInitiateRequest> {
206212
flowId?: string;
207-
actionId?: string;
213+
action?: string;
208214
inputs?: Record<string, any>;
209215
}
210216

packages/react/src/components/presentation/auth/SignIn/v2/BaseSignIn.tsx

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,14 @@ export interface BaseSignInRenderProps {
4848
values: Record<string, string>;
4949

5050
/**
51-
* Form errors
51+
* Field validation errors
5252
*/
53-
errors: Record<string, string>;
53+
fieldErrors: Record<string, string>;
54+
55+
/**
56+
* API error (if any)
57+
*/
58+
error?: Error | null;
5459

5560
/**
5661
* Touched fields
@@ -85,7 +90,7 @@ export interface BaseSignInRenderProps {
8590
/**
8691
* Function to validate the form
8792
*/
88-
validateForm: () => {isValid: boolean; errors: Record<string, string>};
93+
validateForm: () => {isValid: boolean; fieldErrors: Record<string, string>};
8994

9095
/**
9196
* Flow title
@@ -127,6 +132,11 @@ export interface BaseSignInProps {
127132
*/
128133
errorClassName?: string;
129134

135+
/**
136+
* Error object to display
137+
*/
138+
error?: Error | null;
139+
130140
/**
131141
* Flag to determine if the component is ready to be rendered.
132142
*/
@@ -266,6 +276,7 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
266276
components = [],
267277
onSubmit,
268278
onError,
279+
error: externalError,
269280
className = '',
270281
inputClassName = '',
271282
buttonClassName = '',
@@ -285,6 +296,7 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
285296
const styles = useStyles(theme, theme.vars.colors.text.primary);
286297

287298
const [isSubmitting, setIsSubmitting] = useState(false);
299+
const [apiError, setApiError] = useState<Error | null>(null);
288300

289301
const isLoading: boolean = externalIsLoading || isSubmitting;
290302

@@ -294,7 +306,11 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
294306
*/
295307
const handleError = useCallback(
296308
(error: any) => {
297-
const errorMessage: string = extractErrorMessage(error, t);
309+
// Extract error message from response failureReason or use extractErrorMessage
310+
const errorMessage: string = error?.failureReason || extractErrorMessage(error, t);
311+
312+
// Set the API error state
313+
setApiError(error instanceof Error ? error : new Error(errorMessage));
298314

299315
// Clear existing messages and add the error message
300316
clearMessages();
@@ -315,7 +331,11 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
315331

316332
const processComponents = (comps: EmbeddedFlowComponent[]) => {
317333
comps.forEach(component => {
318-
if (component.type === 'TEXT_INPUT' || component.type === 'PASSWORD_INPUT') {
334+
if (
335+
component.type === 'TEXT_INPUT' ||
336+
component.type === 'PASSWORD_INPUT' ||
337+
component.type === 'EMAIL_INPUT'
338+
) {
319339
const identifier: string = component.ref;
320340
fields.push({
321341
name: identifier,
@@ -325,6 +345,15 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
325345
if (component.required && (!value || value.trim() === '')) {
326346
return t('validations.required.field.error');
327347
}
348+
// Add email validation if it's an email field
349+
if (
350+
(component.type === 'EMAIL_INPUT' || component.variant === 'EMAIL') &&
351+
value &&
352+
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
353+
) {
354+
return t('field.email.invalid');
355+
}
356+
328357
return null;
329358
},
330359
});
@@ -364,13 +393,16 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
364393

365394
/**
366395
* Handle input value changes.
396+
* Only updates the value without marking as touched.
397+
* Touched state is set on blur to avoid premature validation.
367398
*/
368399
const handleInputChange = (name: string, value: string): void => {
369400
setFormValue(name, value);
370401
};
371402

372403
/**
373-
* Handle input blur events (when field loses focus).
404+
* Handle input blur event.
405+
* Marks the field as touched, which triggers validation.
374406
*/
375407
const handleInputBlur = (name: string): void => {
376408
setFormTouched(name, true);
@@ -397,6 +429,7 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
397429
}
398430

399431
setIsSubmitting(true);
432+
setApiError(null);
400433
clearMessages();
401434
console.log('Submitting component:', component, 'with data:', data);
402435

@@ -502,14 +535,18 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
502535
if (children) {
503536
const renderProps: BaseSignInRenderProps = {
504537
values: formValues,
505-
errors: formErrors,
538+
fieldErrors: formErrors,
539+
error: apiError,
506540
touched: touchedFields,
507541
isValid: isFormValid,
508542
isLoading,
509543
components,
510544
handleInputChange,
511545
handleSubmit,
512-
validateForm,
546+
validateForm: () => {
547+
const result = validateForm();
548+
return {isValid: result.isValid, fieldErrors: result.errors};
549+
},
513550
title: flowTitle || t('signin.heading'),
514551
subtitle: flowSubtitle || t('signin.subheading'),
515552
messages: flowMessages || [],
@@ -569,6 +606,13 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
569606
</Card.Header>
570607
)}
571608
<Card.Content>
609+
{externalError && (
610+
<div className={styles.flowMessagesContainer}>
611+
<Alert variant="error" className={cx(styles.flowMessageItem, messageClasses)}>
612+
<Alert.Description>{externalError.message}</Alert.Description>
613+
</Alert>
614+
</div>
615+
)}
572616
{flowMessages && flowMessages.length > 0 && (
573617
<div className={styles.flowMessagesContainer}>
574618
{flowMessages.map((message, index) => (

packages/react/src/components/presentation/auth/SignIn/v2/SignIn.tsx

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,6 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
274274
* Clears flow state, creates error, and cleans up URL.
275275
*/
276276
const handleOAuthError = (error: string, errorDescription: string | null): void => {
277-
console.warn('[SignIn] OAuth error detected:', error);
278277
clearFlowState();
279278
const errorMessage = errorDescription || `OAuth error: ${error}`;
280279
const err = new AsgardeoRuntimeError(errorMessage, 'SIGN_IN_ERROR', 'react');
@@ -408,15 +407,14 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
408407
cleanupFlowUrlParams();
409408
}
410409
} catch (error) {
411-
const err = error as Error;
410+
const err = error as any;
412411
clearFlowState();
413412

414-
// Extract error message
415-
const errorMessage = err instanceof Error ? err.message : String(err);
413+
// Extract error message from response or error object
414+
const errorMessage = err?.failureReason || (err instanceof Error ? err.message : String(err));
416415

417-
// Create error with backend message
418-
const displayError = new AsgardeoRuntimeError(errorMessage, 'SIGN_IN_ERROR', 'react');
419-
setError(displayError);
416+
// Set error with the extracted message
417+
setError(new Error(errorMessage));
420418
initializationAttemptedRef.current = false;
421419
return;
422420
}
@@ -430,10 +428,6 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
430428
const effectiveFlowId = payload.flowId || currentFlowId;
431429

432430
if (!effectiveFlowId) {
433-
console.error('[SignIn] handleSubmit - ERROR: No flowId available', {
434-
payloadFlowId: payload.flowId,
435-
currentFlowId,
436-
});
437431
throw new Error('No active flow ID');
438432
}
439433

@@ -450,21 +444,21 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
450444
return;
451445
}
452446

453-
const {flowId, components} = normalizeFlowResponse(response, t, {
447+
const {flowId, components, ...rest} = normalizeFlowResponse(response, t, {
454448
resolveTranslations: !children,
455449
});
456450

457451
// Handle Error flow status - flow has failed and is invalidated
458452
if (response.flowStatus === EmbeddedSignInFlowStatusV2.Error) {
459-
console.error('[SignIn] Flow returned Error status, clearing flow state');
460453
clearFlowState();
461454
// Extract failureReason from response if available
462455
const failureReason = (response as any)?.failureReason;
463456
const errorMessage = failureReason || 'Authentication flow failed. Please try again.';
464-
const err = new AsgardeoRuntimeError(errorMessage, 'SIGN_IN_ERROR', 'react');
457+
const err = new Error(errorMessage);
465458
setError(err);
466459
cleanupFlowUrlParams();
467-
return;
460+
// Throw the error so it's caught by the catch block and propagated to BaseSignIn
461+
throw err;
468462
}
469463

470464
if (response.flowStatus === EmbeddedSignInFlowStatusV2.Complete) {
@@ -492,8 +486,6 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
492486

493487
if (finalRedirectUrl && window?.location) {
494488
window.location.href = finalRedirectUrl;
495-
} else {
496-
console.warn('[SignIn] Flow completed but no redirect URL available');
497489
}
498490

499491
return;
@@ -509,14 +501,13 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
509501
cleanupFlowUrlParams();
510502
}
511503
} catch (error) {
512-
const err = error as Error;
504+
const err = error as any;
513505
clearFlowState();
514506

515-
// Extract error message
516-
const errorMessage = err instanceof Error ? err.message : String(err);
507+
// Extract error message from response or error object
508+
const errorMessage = err?.failureReason || (err instanceof Error ? err.message : String(err));
517509

518-
const displayError = new AsgardeoRuntimeError(errorMessage, 'SIGN_IN_ERROR', 'react');
519-
setError(displayError);
510+
setError(new Error(errorMessage));
520511
return;
521512
} finally {
522513
setIsSubmitting(false);
@@ -527,7 +518,6 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
527518
* Handle authentication errors.
528519
*/
529520
const handleError = (error: Error): void => {
530-
console.error('Authentication error:', error);
531521
setError(error);
532522
};
533523

@@ -569,7 +559,6 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
569559
};
570560

571561
handleSubmit(submitPayload).catch(error => {
572-
console.error('[SignIn] OAuth callback submission failed:', error);
573562
cleanupOAuthUrlParams(true);
574563
});
575564
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -594,6 +583,7 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
594583
isLoading={isLoading || !isInitialized || !isFlowInitialized}
595584
onSubmit={handleSubmit}
596585
onError={handleError}
586+
error={flowError}
597587
className={className}
598588
size={size}
599589
variant={variant}

0 commit comments

Comments
 (0)