Skip to content

Commit 39c509d

Browse files
authored
fix: remove dead error branch in handlePostSocialLogin (#27578)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Removes dead code in handlePostSocialLogin where the else branch for result.type !== 'success' was empty with only a placeholder comment. This branch is unreachable because OAuthService.handleOAuthLogin() throws on failure, and the error is caught by the try/catch in onPressContinueWithSocialLogin which routes to handleLoginError → handleOAuthLoginError → captureException (Sentry). Replaced the empty else block with an early return guard and a comment documenting the error handling flow, improving code clarity and preventing silent failures if the flow is ever refactored <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/TO-594 ## **Manual testing steps** ```gherkin Feature: Social login error handling Scenario: user completes social login successfully Given the user is on the Onboarding screen When user taps Google or Apple login and completes authentication Then the user is navigated to the appropriate next screen (ChoosePassword, AccountAlreadyExists, etc.) Scenario: user encounters an error during social login Given the user is on the Onboarding screen When user taps Google or Apple login and an error occurs Then the error is caught by handleLoginError and reported to Sentry And the user sees an error toast and remains on the Onboarding screen ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** https://github.com/user-attachments/assets/0073d035-a502-4fdd-abdb-afb9bbe5cc47 <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk refactor in onboarding social login flow: it removes an unreachable branch and adds an explicit guard, without changing the success-path navigation or state updates. > > **Overview** > `handlePostSocialLogin` now *explicitly guards* against `result.type !== 'success'` with an early return and documents that OAuth failures are handled upstream via `onPressContinueWithSocialLogin`’s try/catch and `handleOAuthLoginError`/Sentry. > > The success-path logic (account type dispatch, metrics event, and navigation for create/import + existing/new users across iOS/Android) is preserved, but is no longer nested under an `if (result.type === 'success')` block. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 6c98ea9. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 76a819d commit 39c509d

2 files changed

Lines changed: 116 additions & 64 deletions

File tree

app/components/Views/Onboarding/index.test.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,56 @@ describe('Onboarding', () => {
13101310
);
13111311
});
13121312

1313+
it('does not navigate when OAuth login result type is not success', async () => {
1314+
mockCreateLoginHandler.mockReturnValue('mockGoogleHandler');
1315+
mockOAuthService.handleOAuthLogin.mockResolvedValue({
1316+
type: 'error',
1317+
existingUser: false,
1318+
});
1319+
1320+
const { getByTestId } = renderScreen(
1321+
Onboarding,
1322+
{ name: 'Onboarding' },
1323+
{
1324+
state: mockInitialState,
1325+
},
1326+
);
1327+
1328+
const createWalletButton = getByTestId(
1329+
OnboardingSelectorIDs.NEW_WALLET_BUTTON,
1330+
);
1331+
await act(async () => {
1332+
fireEvent.press(createWalletButton);
1333+
});
1334+
1335+
const navCall = mockNavigate.mock.calls.find(
1336+
(call) =>
1337+
call[0] === Routes.MODAL.ROOT_MODAL_FLOW &&
1338+
call[1]?.screen === Routes.SHEET.ONBOARDING_SHEET,
1339+
);
1340+
1341+
const googleOAuthFunction = navCall[1].params.onPressContinueWithGoogle;
1342+
1343+
mockNavigate.mockClear();
1344+
1345+
await act(async () => {
1346+
await googleOAuthFunction(true);
1347+
});
1348+
1349+
expect(mockNavigate).not.toHaveBeenCalledWith(
1350+
'ChoosePassword',
1351+
expect.anything(),
1352+
);
1353+
expect(mockNavigate).not.toHaveBeenCalledWith(
1354+
Routes.ONBOARDING.SOCIAL_LOGIN_SUCCESS_NEW_USER,
1355+
expect.anything(),
1356+
);
1357+
expect(mockNavigate).not.toHaveBeenCalledWith(
1358+
'AccountAlreadyExists',
1359+
expect.anything(),
1360+
);
1361+
});
1362+
13131363
it('attempts browser fallback when no credential is available in Android', async () => {
13141364
Platform.OS = 'android';
13151365
const noCredentialError = new OAuthError(

app/components/Views/Onboarding/index.tsx

Lines changed: 66 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -412,83 +412,85 @@ const Onboarding = () => {
412412
socialLoginTraceCtx.current = undefined;
413413
}
414414

415-
if (result.type === 'success') {
416-
const accountType = getSocialAccountType(provider, result.existingUser);
417-
dispatch(setAccountType(accountType));
415+
// Error case (result.type !== 'success') is not handled here because
416+
// OAuthService.handleOAuthLogin() throws on failure, and the error is
417+
// caught by the try/catch in onPressContinueWithSocialLogin, which calls
418+
// handleLoginError → handleOAuthLoginError → captureException (Sentry).
419+
if (result.type !== 'success') {
420+
return;
421+
}
418422

419-
track(MetaMetricsEvents.SOCIAL_LOGIN_COMPLETED, {
420-
account_type: accountType,
421-
});
422-
if (createWallet) {
423-
if (result.existingUser) {
424-
navigation.navigate('AccountAlreadyExists', {
425-
accountName: result.accountName,
426-
oauthLoginSuccess: true,
427-
onboardingTraceCtx: onboardingTraceCtx.current,
428-
provider,
429-
});
430-
} else {
431-
trace({
432-
name: TraceName.OnboardingNewSocialCreateWallet,
433-
op: TraceOperation.OnboardingUserJourney,
434-
tags: getTraceTags(store.getState()),
435-
parentContext: onboardingTraceCtx.current,
436-
});
423+
const accountType = getSocialAccountType(provider, result.existingUser);
424+
dispatch(setAccountType(accountType));
437425

438-
if (isIOS) {
439-
// Navigate to SocialLoginSuccess screen first, then ChoosePassword
440-
navigation.navigate(
441-
Routes.ONBOARDING.SOCIAL_LOGIN_SUCCESS_NEW_USER,
442-
{
443-
accountName: result.accountName,
444-
oauthLoginSuccess: true,
445-
onboardingTraceCtx: onboardingTraceCtx.current,
446-
provider,
447-
},
448-
);
449-
} else {
450-
// Direct navigation to ChoosePassword for Android
451-
navigation.navigate('ChoosePassword', {
452-
[PREVIOUS_SCREEN]: ONBOARDING,
426+
track(MetaMetricsEvents.SOCIAL_LOGIN_COMPLETED, {
427+
account_type: accountType,
428+
});
429+
if (createWallet) {
430+
if (result.existingUser) {
431+
navigation.navigate('AccountAlreadyExists', {
432+
accountName: result.accountName,
433+
oauthLoginSuccess: true,
434+
onboardingTraceCtx: onboardingTraceCtx.current,
435+
provider,
436+
});
437+
} else {
438+
trace({
439+
name: TraceName.OnboardingNewSocialCreateWallet,
440+
op: TraceOperation.OnboardingUserJourney,
441+
tags: getTraceTags(store.getState()),
442+
parentContext: onboardingTraceCtx.current,
443+
});
444+
445+
if (isIOS) {
446+
// Navigate to SocialLoginSuccess screen first, then ChoosePassword
447+
navigation.navigate(
448+
Routes.ONBOARDING.SOCIAL_LOGIN_SUCCESS_NEW_USER,
449+
{
450+
accountName: result.accountName,
453451
oauthLoginSuccess: true,
454452
onboardingTraceCtx: onboardingTraceCtx.current,
455453
provider,
456-
});
457-
}
458-
}
459-
} else if (!createWallet) {
460-
if (result.existingUser) {
461-
trace({
462-
name: TraceName.OnboardingExistingSocialLogin,
463-
op: TraceOperation.OnboardingUserJourney,
464-
tags: getTraceTags(store.getState()),
465-
parentContext: onboardingTraceCtx.current,
466-
});
467-
isIOS
468-
? navigation.navigate(
469-
Routes.ONBOARDING.SOCIAL_LOGIN_SUCCESS_EXISTING_USER,
470-
{
471-
[PREVIOUS_SCREEN]: ONBOARDING,
472-
oauthLoginSuccess: true,
473-
onboardingTraceCtx: onboardingTraceCtx.current,
474-
},
475-
)
476-
: navigation.navigate('Rehydrate', {
477-
[PREVIOUS_SCREEN]: ONBOARDING,
478-
oauthLoginSuccess: true,
479-
onboardingTraceCtx: onboardingTraceCtx.current,
480-
});
454+
},
455+
);
481456
} else {
482-
navigation.navigate('AccountNotFound', {
483-
accountName: result.accountName,
457+
// Direct navigation to ChoosePassword for Android
458+
navigation.navigate('ChoosePassword', {
459+
[PREVIOUS_SCREEN]: ONBOARDING,
484460
oauthLoginSuccess: true,
485461
onboardingTraceCtx: onboardingTraceCtx.current,
486462
provider,
487463
});
488464
}
489465
}
466+
} else if (result.existingUser) {
467+
trace({
468+
name: TraceName.OnboardingExistingSocialLogin,
469+
op: TraceOperation.OnboardingUserJourney,
470+
tags: getTraceTags(store.getState()),
471+
parentContext: onboardingTraceCtx.current,
472+
});
473+
isIOS
474+
? navigation.navigate(
475+
Routes.ONBOARDING.SOCIAL_LOGIN_SUCCESS_EXISTING_USER,
476+
{
477+
[PREVIOUS_SCREEN]: ONBOARDING,
478+
oauthLoginSuccess: true,
479+
onboardingTraceCtx: onboardingTraceCtx.current,
480+
},
481+
)
482+
: navigation.navigate('Rehydrate', {
483+
[PREVIOUS_SCREEN]: ONBOARDING,
484+
oauthLoginSuccess: true,
485+
onboardingTraceCtx: onboardingTraceCtx.current,
486+
});
490487
} else {
491-
// handle error: show error message in the UI
488+
navigation.navigate('AccountNotFound', {
489+
accountName: result.accountName,
490+
oauthLoginSuccess: true,
491+
onboardingTraceCtx: onboardingTraceCtx.current,
492+
provider,
493+
});
492494
}
493495
},
494496
[navigation, track, dispatch],

0 commit comments

Comments
 (0)