diff --git a/mobile/src/features/Links/components/ActionHandler.tsx b/mobile/src/features/Links/components/ActionHandler.tsx index 611722ea67..e0e99ba9c9 100644 --- a/mobile/src/features/Links/components/ActionHandler.tsx +++ b/mobile/src/features/Links/components/ActionHandler.tsx @@ -139,18 +139,7 @@ export const ActionHandler = () => { // Update observables when React state changes React.useEffect(() => { if (pendingAction) { - const newActionId = createActionId(pendingAction) - // If this actionId was processed before, check if it's a new instance - if (newActionId && processedActionsRef.current.has(newActionId)) { - const previousAction = processedActionsRef.current.get(newActionId) - // If the action object reference is different, it's a new trigger - if (previousAction !== pendingAction) { - // Same actionId but new instance - clear to allow reprocessing - processedActionsRef.current.delete(newActionId) - isProcessingRef.current = false - } - } - currentActionIdRef.current = newActionId + currentActionIdRef.current = createActionId(pendingAction) } else { currentActionIdRef.current = null } @@ -168,10 +157,8 @@ export const ActionHandler = () => { }, [wallet]) // Track processing state - // Map actionId -> pendingAction object reference to detect new triggers - const processedActionsRef = React.useRef>( - new Map(), - ) + // Set of actionIds that have been processed + const processedActionsRef = React.useRef>(new Set()) const isProcessingRef = React.useRef(false) const hasShownModalRef = React.useRef(false) const prevWalletRef = React.useRef(wallet) @@ -319,19 +306,8 @@ export const ActionHandler = () => { return } - // Check if we've already processed this exact action instance - if (actionId && processedActionsRef.current.has(actionId)) { - const processedAction = processedActionsRef.current.get(actionId) - // If it's the same action object reference, skip (already processing) - if (processedAction === action) { - return - } - // Different action object with same actionId - new trigger, allow it - processedActionsRef.current.delete(actionId) - isProcessingRef.current = false - } - - if (!actionId) { + // Check if we've already processed this action + if (!actionId || processedActionsRef.current.has(actionId)) { return } @@ -340,8 +316,7 @@ export const ActionHandler = () => { } isProcessingRef.current = true - // Store action object reference, not just actionId - processedActionsRef.current.set(actionId, action) + processedActionsRef.current.add(actionId) InteractionManager.runAfterInteractions(() => { try { diff --git a/mobile/src/features/Links/components/ClaimActionHandler.tsx b/mobile/src/features/Links/components/ClaimActionHandler.tsx index 2fe27927fe..ad14bf5537 100644 --- a/mobile/src/features/Links/components/ClaimActionHandler.tsx +++ b/mobile/src/features/Links/components/ClaimActionHandler.tsx @@ -18,7 +18,8 @@ import {logger} from '~/kernel/logger/logger' export const ClaimActionHandler = () => { const {pendingAction} = useLinks() const {reset: resetClaimState, scanActionClaimChanged, address} = useClaim() - const processedActionRef = React.useRef(null) + // Track processed claim URLs to prevent re-processing + const processedClaimUrlsRef = React.useRef>(new Set()) // Handle claim action from pendingAction context // Use useFocusEffect to ensure we process when screen is focused @@ -48,12 +49,9 @@ export const ClaimActionHandler = () => { ) { const cardanoAction = pendingAction.action as Links.CardanoActionClaim - // Check if we've already processed this action - if ( - processedActionRef.current && - processedActionRef.current.url === cardanoAction.url && - processedActionRef.current.code === cardanoAction.code - ) { + // Check if we've already processed this claim URL + const claimKey = `${cardanoAction.url}:${cardanoAction.code}` + if (processedClaimUrlsRef.current.has(claimKey)) { logger.info( 'ClaimActionHandler: action already processed, skipping', { @@ -75,7 +73,7 @@ export const ClaimActionHandler = () => { scanActionClaimChanged(cardanoAction) // Mark as processed to prevent re-processing if screen refocuses - processedActionRef.current = cardanoAction + processedClaimUrlsRef.current.add(claimKey) } else { logger.info('ClaimActionHandler: conditions not met', { hasPendingAction: !!pendingAction, diff --git a/mobile/src/features/Links/hooks/useDeepLinkWatcher.tsx b/mobile/src/features/Links/hooks/useDeepLinkWatcher.tsx index 6abcae4c71..56a8e2428d 100644 --- a/mobile/src/features/Links/hooks/useDeepLinkWatcher.tsx +++ b/mobile/src/features/Links/hooks/useDeepLinkWatcher.tsx @@ -12,6 +12,10 @@ export const useDeepLinkWatcher = () => { const {isLoggedIn} = useAuth() const {pendingAction, setPendingAction} = useLinks() + // Track URLs that have already been processed to prevent re-processing + // when processLink callback reference changes + const processedUrlsRef = React.useRef>(new Set()) + const processLink = React.useCallback( (url: string) => { // Try Yoroi links first (yoroi://) @@ -70,7 +74,8 @@ export const useDeepLinkWatcher = () => { React.useEffect(() => { const getInitialURL = async () => { const url = await Linking.getInitialURL() - if (url !== null) { + if (url !== null && !processedUrlsRef.current.has(url)) { + processedUrlsRef.current.add(url) processLink(url) } } @@ -92,40 +97,42 @@ export const useDeepLinkWatcher = () => { const checkInitialUrlAfterLogin = async () => { const url = await Linking.getInitialURL() - if (url !== null) { - // Try both Yoroi and Cardano links - const parsedYoroiAction = linksYoroiParser(url) - if (parsedYoroiAction != null) { - if ( - parsedYoroiAction.params?.isSandbox === true && - __DEV__ === false - ) { - return - } - const pendingAction: PendingAction = { - source: 'yoroi', - action: {info: parsedYoroiAction, isTrusted: false}, - } - setPendingAction(pendingAction) + // Skip if URL was already processed + if (url === null || processedUrlsRef.current.has(url)) { + return + } + + // Try both Yoroi and Cardano links + const parsedYoroiAction = linksYoroiParser(url) + if (parsedYoroiAction != null) { + if (parsedYoroiAction.params?.isSandbox === true && __DEV__ === false) { return } + processedUrlsRef.current.add(url) + const pendingAction: PendingAction = { + source: 'yoroi', + action: {info: parsedYoroiAction, isTrusted: false}, + } + setPendingAction(pendingAction) + return + } - if (isWebCardanoLink(url)) { - try { - const cardanoAction = parseCardanoLink(url) - const pendingAction: PendingAction = { - source: 'cardano', - action: cardanoAction, - } - setPendingAction(pendingAction) - } catch (error) { - logger.error('useDeepLinkWatcher: error parsing URL after login', { - error, - errorMessage: - error instanceof Error ? error.message : String(error), - url, - }) + if (isWebCardanoLink(url)) { + try { + processedUrlsRef.current.add(url) + const cardanoAction = parseCardanoLink(url) + const pendingAction: PendingAction = { + source: 'cardano', + action: cardanoAction, } + setPendingAction(pendingAction) + } catch (error) { + logger.error('useDeepLinkWatcher: error parsing URL after login', { + error, + errorMessage: + error instanceof Error ? error.message : String(error), + url, + }) } } } diff --git a/mobile/src/ui/Counter/Counter.tsx b/mobile/src/ui/Counter/Counter.tsx index 62bbbb987e..2671bf2c15 100644 --- a/mobile/src/ui/Counter/Counter.tsx +++ b/mobile/src/ui/Counter/Counter.tsx @@ -44,7 +44,7 @@ export const Counter = ({ {unitsText !== undefined && ( - {unitsText ?? ''} + {` ${unitsText}`} )} @@ -56,7 +56,7 @@ export const Counter = ({ : [a.body_2_md_regular, {color: p.primary_600}], ]} > - {closingText ?? ''} + {` ${closingText}`} )}