Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions app/components/UI/Ramp/Deposit/Views/Root/Root.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,12 @@ describe('Root Component', () => {
expect(screen.toJSON()).toMatchSnapshot();
});

it('redirects to BUILD_QUOTE immediately when no created orders exist', async () => {
it('redirects to BUILD_QUOTE when no created orders exist after hydrating stored token', async () => {
mockCheckExistingToken.mockResolvedValue(false);
render(Root);
await waitFor(() => {
expect(mockCheckExistingToken).toHaveBeenCalled();
});
await waitFor(() => {
expect(mockReset).toHaveBeenCalledWith({
index: 0,
Expand All @@ -100,7 +104,6 @@ describe('Root Component', () => {
],
});
});
expect(mockCheckExistingToken).not.toHaveBeenCalled();
});

it('calls checkExistingToken when a created order exists', async () => {
Expand Down
111 changes: 66 additions & 45 deletions app/components/UI/Ramp/Deposit/Views/Root/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const Root = () => {
const [initialRoute] = useState<string>(Routes.DEPOSIT.BUILD_QUOTE);
const { checkExistingToken, setIntent } = useDepositSDK();
const hasCheckedToken = useRef(false);
const initializationInFlight = useRef(false);
const orders = useSelector(getAllDepositOrders);
const theme = useTheme();

Expand Down Expand Up @@ -64,59 +65,79 @@ const Root = () => {
};

const initializeFlow = async () => {
if (hasCheckedToken.current) return;
hasCheckedToken.current = true;

const createdOrder = orders.find(
(order) => order.state === FIAT_ORDER_STATES.CREATED,
);
// 1. If token has already been checked, do not run again.
if (hasCheckedToken.current) {
return;
}

if (!createdOrder) {
navigateToDefaultRoute();
// 2. Single runner: effect can re-run while checkExistingToken is in flight; avoid duplicate work.
if (initializationInFlight.current) {
return;
}
initializationInFlight.current = true;

let isAuthenticatedFromToken = false;
try {
isAuthenticatedFromToken = await withTimeout(
checkExistingToken(),
TOKEN_CHECK_TIMEOUT_MS,
);
} catch (error) {
Logger.error(
error as Error,
'Deposit Root: checkExistingToken failed or timed out',
// 3. Default until vault / SDK hydration succeeds.
let isAuthenticatedFromToken = false;

// 4. Attempt to restore auth from stored token; mark checked after the attempt finishes.
try {
isAuthenticatedFromToken = await withTimeout(
checkExistingToken(),
TOKEN_CHECK_TIMEOUT_MS,
);
} catch (error) {
Logger.error(
error as Error,
'Deposit Root: checkExistingToken failed or timed out',
);
} finally {
hasCheckedToken.current = true;
}

// 5. Resume in-progress deposit order if any.
const createdOrder = orders.find(
(order) => order.state === FIAT_ORDER_STATES.CREATED,
);
}

if (!isAuthenticatedFromToken) {
const [routeName, navParams] = createEnterEmailNavDetails({
redirectToRootAfterAuth: true,
});
navigation.reset({
index: 0,
routes: [
{
name: routeName,
params: { ...navParams, animationEnabled: false },
},
],
});
return;
}
// 6. Created order: require auth or continue to bank details.
if (createdOrder) {
if (!isAuthenticatedFromToken) {
const [routeName, navParams] = createEnterEmailNavDetails({
redirectToRootAfterAuth: true,
});
navigation.reset({
index: 0,
routes: [
{
name: routeName,
params: { ...navParams, animationEnabled: false },
},
],
});
return;
}

const [routeName, navParams] = createBankDetailsNavDetails({
orderId: createdOrder.id,
});
navigation.reset({
index: 0,
routes: [
{
name: routeName,
params: { ...navParams, animationEnabled: false },
},
],
});
const [routeName, navParams] = createBankDetailsNavDetails({
orderId: createdOrder.id,
});
navigation.reset({
index: 0,
routes: [
{
name: routeName,
params: { ...navParams, animationEnabled: false },
},
],
});
return;
}

// 7. No created order: default entry (Build Quote); honor deeplink / intent flags when present.
navigateToDefaultRoute();
} finally {
initializationInFlight.current = false;
}
};

initializeFlow().catch((error) => {
Expand Down
Loading