From 6969b676987fae35da0ff59c7078022dd82056d5 Mon Sep 17 00:00:00 2001 From: Lauren Zugai Date: Tue, 3 Feb 2026 13:26:14 -0600 Subject: [PATCH] fix(webchannel): Send 'can_link_account' on email-first for isSync/isFirefoxNonSync when fxa_status resolves Because: * On email-first, we are sending 'can_link_account' before the fxa_status result has resolved, which tells us whether that Firefox supports sending the UID in can_link_account. This is problematic because we should NOT be sending the merge warning at this point if Firefox does support the UID capability This commit: * Sets the default of 'supportsCanLinkAccountUid' to 'undefined' until fxa_status is received, and does not auto-submit the email on email-first if 'supportsCanLinkAccountUid' is undefined fixes FXA-13002 --- .../src/lib/hooks/useFxAStatus/index.tsx | 2 +- .../src/lib/hooks/useFxAStatus/mocks.tsx | 4 +- .../src/pages/Index/container.test.tsx | 70 ++++++++++++++++++- .../src/pages/Index/container.tsx | 15 +++- 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/packages/fxa-settings/src/lib/hooks/useFxAStatus/index.tsx b/packages/fxa-settings/src/lib/hooks/useFxAStatus/index.tsx index 0b680732150..5974e9f7e81 100644 --- a/packages/fxa-settings/src/lib/hooks/useFxAStatus/index.tsx +++ b/packages/fxa-settings/src/lib/hooks/useFxAStatus/index.tsx @@ -41,7 +41,7 @@ export function useFxAStatus(integration: FxAStatusIntegration) { const [supportsKeysOptionalLogin, setSupportsKeysOptionalLogin] = useState(false); const [supportsCanLinkAccountUid, setSupportsCanLinkAccountUid] = - useState(false); + useState(undefined); useEffect(() => { // This sends a web channel message to the browser to prompt a response diff --git a/packages/fxa-settings/src/lib/hooks/useFxAStatus/mocks.tsx b/packages/fxa-settings/src/lib/hooks/useFxAStatus/mocks.tsx index 13ce918c5d6..415f14954a5 100644 --- a/packages/fxa-settings/src/lib/hooks/useFxAStatus/mocks.tsx +++ b/packages/fxa-settings/src/lib/hooks/useFxAStatus/mocks.tsx @@ -7,11 +7,11 @@ import { getSyncEngineIds, syncEngineConfigs } from '../../sync-engines'; export function mockUseFxAStatus({ offeredSyncEnginesOverride, supportsKeysOptionalLogin = false, - supportsCanLinkAccountUid = false, + supportsCanLinkAccountUid, }: { offeredSyncEnginesOverride?: ReturnType; supportsKeysOptionalLogin?: boolean; - supportsCanLinkAccountUid?: boolean; + supportsCanLinkAccountUid?: boolean | undefined; } = {}) { const offeredSyncEngineConfigs = syncEngineConfigs; const offeredSyncEngines = diff --git a/packages/fxa-settings/src/pages/Index/container.test.tsx b/packages/fxa-settings/src/pages/Index/container.test.tsx index 927f8d7df59..a93b38a4069 100644 --- a/packages/fxa-settings/src/pages/Index/container.test.tsx +++ b/packages/fxa-settings/src/pages/Index/container.test.tsx @@ -7,7 +7,6 @@ import * as IndexModule from './index'; import * as ReactUtils from 'fxa-react/lib/utils'; import * as cache from '../../lib/cache'; -import React from 'react'; import { waitFor } from '@testing-library/react'; import { LocationProvider } from '@reach/router'; import { useValidatedQueryParams } from '../../lib/hooks/useValidate'; @@ -971,6 +970,75 @@ describe('IndexContainer', () => { expect(currentIndexProps?.errorBannerMessage).toBeDefined(); }); }); + + describe('useFxAStatusResult.supportsCanLinkAccountUid and processEmailSubmission', () => { + beforeEach(() => { + jest.spyOn(cache, 'currentAccount').mockReturnValue({ + uid: 'abc123', + email: MOCK_EMAIL, + lastLogin: Date.now(), + }); + }); + + it('shows loading spinner and does not call fxaCanLinkAccount when supportsCanLinkAccountUid is undefined', async () => { + mockUseFxAStatusResult = mockUseFxAStatus({ + supportsCanLinkAccountUid: undefined, + }); + + const { getByText } = renderWithLocalizationProvider( + + + + ); + + expect(getByText('LoadingSpinner')).toBeInTheDocument(); + expect(firefox.fxaCanLinkAccount).not.toHaveBeenCalled(); + }); + + it('does not call fxaCanLinkAccount when supportsCanLinkAccountUid is true', async () => { + mockUseFxAStatusResult = mockUseFxAStatus({ + supportsCanLinkAccountUid: true, + }); + + renderWithLocalizationProvider( + + + + ); + + await waitFor(() => { + expect(firefox.fxaCanLinkAccount).not.toHaveBeenCalled(); + }); + }); + + it('calls fxaCanLinkAccount when supportsCanLinkAccountUid is false', async () => { + mockUseFxAStatusResult = mockUseFxAStatus({ + supportsCanLinkAccountUid: false, + }); + + renderWithLocalizationProvider( + + + + ); + + await waitFor(() => { + expect(firefox.fxaCanLinkAccount).toHaveBeenCalled(); + }); + }); + }); }); }); }); diff --git a/packages/fxa-settings/src/pages/Index/container.tsx b/packages/fxa-settings/src/pages/Index/container.tsx index 5a003126788..d2218059456 100644 --- a/packages/fxa-settings/src/pages/Index/container.tsx +++ b/packages/fxa-settings/src/pages/Index/container.tsx @@ -271,12 +271,20 @@ const IndexContainer = ({ if (isUnsupportedContext(integration.data.context)) { hardNavigate('/update_firefox', {}, true); } else if (shouldTrySuggestedEmail && !attemptedEmailAutoSubmit.current) { - // Without this, can_link_account can fire multiple times due to calling it in a `useEffect`, - // causing multiple sync warnings to be displayed. This ensures it is called once. - attemptedEmailAutoSubmit.current = true; // Must be in an async function or else `setIsLoading(false)` can be called prematurely with // the next render, before async actions in `processEmailSubmission` have finished (async () => { + if (integration.isSync() || integration.isFirefoxNonSync()) { + // Wait for this to resolve before calling 'processEmailSubmission'. + // Otherwise, a merge warning may show on email-first for newer Fx versions + // that support "can link account" by UID. + if (useFxAStatusResult.supportsCanLinkAccountUid === undefined) { + return; + } + } + // Without this, can_link_account can fire multiple times due to calling it in a `useEffect`, + // causing multiple sync warnings to be displayed. This ensures it is called once. + attemptedEmailAutoSubmit.current = true; await processEmailSubmission(suggestedEmail, false); })(); } else { @@ -290,6 +298,7 @@ const IndexContainer = ({ shouldTrySuggestedEmail, integration.data.context, integration, + useFxAStatusResult.supportsCanLinkAccountUid, ]); useEffect(() => {