diff --git a/.changeset/wise-jars-add.md b/.changeset/wise-jars-add.md new file mode 100644 index 000000000..5e5432568 --- /dev/null +++ b/.changeset/wise-jars-add.md @@ -0,0 +1,5 @@ +--- +"@alephium/mobile-wallet": patch +--- + +Fix authentication issues diff --git a/apps/mobile-wallet/src/features/auto-lock/useAutoLock.tsx b/apps/mobile-wallet/src/features/auto-lock/useAutoLock.tsx index e27fb99be..d673c06da 100644 --- a/apps/mobile-wallet/src/features/auto-lock/useAutoLock.tsx +++ b/apps/mobile-wallet/src/features/auto-lock/useAutoLock.tsx @@ -17,20 +17,15 @@ along with the library. If not, see . */ import { appBecameInactive } from '@alephium/shared' -import { useCallback, useEffect, useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { AppState, AppStateStatus } from 'react-native' import BackgroundTimer from 'react-native-background-timer' import { useAppDispatch, useAppSelector } from '~/hooks/redux' -interface UseAutoLockProps { - unlockApp: () => Promise - onAuthRequired: () => void -} - let lockTimer: number | undefined -const useAutoLock = ({ unlockApp, onAuthRequired }: UseAutoLockProps) => { +const useAutoLock = (unlockApp: () => Promise) => { const appState = useRef('active') const settingsLoadedFromStorage = useAppSelector((s) => s.settings.loadedFromStorage) const isCameraOpen = useAppSelector((s) => s.app.isCameraOpen) @@ -41,12 +36,6 @@ const useAutoLock = ({ unlockApp, onAuthRequired }: UseAutoLockProps) => { const [isAppStateChangeCallbackRegistered, setIsAppStateChangeCallbackRegistered] = useState(false) - const lockApp = useCallback(() => { - if (biometricsRequiredForAppAccess) onAuthRequired() - - dispatch(appBecameInactive()) - }, [biometricsRequiredForAppAccess, dispatch, onAuthRequired]) - useEffect(() => { if (!settingsLoadedFromStorage) return @@ -54,12 +43,12 @@ const useAutoLock = ({ unlockApp, onAuthRequired }: UseAutoLockProps) => { if (autoLockSeconds !== -1) { if (nextAppState === 'background' && isWalletUnlocked && !isCameraOpen) { if (autoLockSeconds === 0) { - lockApp() + dispatch(appBecameInactive()) } else { clearBackgroundTimer() lockTimer = BackgroundTimer.setTimeout(() => { if (lockTimer) { - lockApp() + dispatch(appBecameInactive()) } }, autoLockSeconds * 1000) } @@ -70,6 +59,8 @@ const useAutoLock = ({ unlockApp, onAuthRequired }: UseAutoLockProps) => { unlockApp() } } + } else if (nextAppState === 'active' && !isWalletUnlocked) { + unlockApp() } appState.current = nextAppState @@ -91,7 +82,6 @@ const useAutoLock = ({ unlockApp, onAuthRequired }: UseAutoLockProps) => { isAppStateChangeCallbackRegistered, isCameraOpen, isWalletUnlocked, - lockApp, settingsLoadedFromStorage, unlockApp ]) diff --git a/apps/mobile-wallet/src/navigation/RootStackNavigation.tsx b/apps/mobile-wallet/src/navigation/RootStackNavigation.tsx index 525d9d52a..a6ac0636e 100644 --- a/apps/mobile-wallet/src/navigation/RootStackNavigation.tsx +++ b/apps/mobile-wallet/src/navigation/RootStackNavigation.tsx @@ -140,12 +140,11 @@ export default RootStackNavigation const AppUnlockModal = () => { const dispatch = useAppDispatch() const isWalletUnlocked = useAppSelector((s) => s.wallet.isUnlocked) + const biometricsRequiredForAppAccess = useAppSelector((s) => s.settings.usesBiometrics) const navigation = useNavigation>() const { triggerBiometricsAuthGuard } = useBiometricsAuthGuard() const { t } = useTranslation() - const [isAuthModalVisible, setIsAuthModalVisible] = useState(false) - const { width, height } = Dimensions.get('window') const [dimensions, setDimensions] = useState({ width, height }) @@ -155,10 +154,6 @@ const AppUnlockModal = () => { setDimensions({ width, height }) } - const openAuthModal = useCallback(() => { - setIsAuthModalVisible(true) - }, []) - const initializeAppWithStoredWallet = useCallback(async () => { try { dispatch(walletUnlocked(await getStoredWallet())) @@ -168,8 +163,6 @@ const AppUnlockModal = () => { if (!lastRoute || ['LandingScreen', 'LoginWithPinScreen'].includes(lastRoute)) { resetNavigation(navigation) } - - setIsAuthModalVisible(false) } catch (error) { const message = 'Could not initialize app with stored wallet' showExceptionToast(error, message) @@ -188,7 +181,6 @@ const AppUnlockModal = () => { try { await triggerBiometricsAuthGuard({ settingsToCheck: 'appAccess', - onPromptDisplayed: openAuthModal, successCallback: initializeAppWithStoredWallet }) @@ -252,23 +244,16 @@ const AppUnlockModal = () => { showExceptionToast(e, t('Could not unlock app')) } } - }, [ - dispatch, - initializeAppWithStoredWallet, - isWalletUnlocked, - navigation, - openAuthModal, - t, - triggerBiometricsAuthGuard - ]) + }, [dispatch, initializeAppWithStoredWallet, isWalletUnlocked, navigation, t, triggerBiometricsAuthGuard]) - useAutoLock({ - unlockApp, - onAuthRequired: openAuthModal - }) + useAutoLock(unlockApp) return ( - + diff --git a/apps/mobile-wallet/src/screens/LandingScreen.tsx b/apps/mobile-wallet/src/screens/LandingScreen.tsx index feb2dde0f..01ed24de7 100644 --- a/apps/mobile-wallet/src/screens/LandingScreen.tsx +++ b/apps/mobile-wallet/src/screens/LandingScreen.tsx @@ -16,9 +16,10 @@ You should have received a copy of the GNU Lesser General Public License along with the library. If not, see . */ +import { useFocusEffect } from '@react-navigation/native' import { StackScreenProps } from '@react-navigation/stack' import { Canvas, RadialGradient, Rect, vec } from '@shopify/react-native-skia' -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { Trans, useTranslation } from 'react-i18next' import { AppState, Dimensions, Image, LayoutChangeEvent, Platform, StatusBar } from 'react-native' import Animated, { @@ -37,7 +38,7 @@ import styled, { ThemeProvider, useTheme } from 'styled-components/native' import AppText from '~/components/AppText' import Button from '~/components/buttons/Button' import Screen, { ScreenProps } from '~/components/layout/Screen' -import { useAppDispatch } from '~/hooks/redux' +import { useAppDispatch, useAppSelector } from '~/hooks/redux' import altLogoSrc from '~/images/logos/alephiumHackLogo.png' import AlephiumLogo from '~/images/logos/AlephiumLogo' import RootStackParamList from '~/navigation/rootStackRoutes' @@ -46,6 +47,7 @@ import { methodSelected, WalletGenerationMethod } from '~/store/walletGeneration import { BORDER_RADIUS_BIG, BORDER_RADIUS_HUGE } from '~/style/globalStyle' import { themes } from '~/style/themes' import { showExceptionToast } from '~/utils/layout' +import { resetNavigation } from '~/utils/navigation' interface LandingScreenProps extends StackScreenProps, ScreenProps {} @@ -57,11 +59,25 @@ const LandingScreen = ({ navigation, ...props }: LandingScreenProps) => { const insets = useSafeAreaInsets() const theme = useTheme() const { t } = useTranslation() + const isWalletUnlocked = useAppSelector((s) => s.wallet.isUnlocked) const { width, height } = Dimensions.get('window') const [dimensions, setDimensions] = useState({ width, height }) const [showNewWalletButtons, setShowNewWalletButtons] = useState(false) + // Normally, when the app is unlocked, this screen is not in focus. However, under certain conditions we end up with + // an unlocked wallet and no screen in focus at all. This happens when: + // 1. the auto-lock is set to anything but "Fast" + // 2. the user manually kills the app before the auto-lock timer completes + // 3. the WalletConnect feature is activated + // Since there is no screen in focus and since the default screen set in the RootStackNavigation is this screen, we + // need to navigate back to the dashboard. + useFocusEffect( + useCallback(() => { + if (isWalletUnlocked) resetNavigation(navigation) + }, [isWalletUnlocked, navigation]) + ) + useEffect(() => { const unsubscribeBlurListener = navigation.addListener('blur', () => { StatusBar.setBarStyle(theme.name === 'light' ? 'dark-content' : 'light-content') diff --git a/patches/react-native-background-actions@3.0.1.patch b/patches/react-native-background-actions@3.0.1.patch index 5d18a95fe..bda7e3472 100644 --- a/patches/react-native-background-actions@3.0.1.patch +++ b/patches/react-native-background-actions@3.0.1.patch @@ -10,7 +10,7 @@ index b67ef4d1837363489a1749acb852997a6c93c1b5..6aa30a826c2f17a35ccd30b64291d3d2 - -+ ++ diff --git a/android/src/main/java/com/asterinet/react/bgactions/RNBackgroundActionsTask.java b/android/src/main/java/com/asterinet/react/bgactions/RNBackgroundActionsTask.java diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cfcc3c524..883f85fa0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,7 +30,7 @@ patchedDependencies: hash: sm7rpv6bikakdvmsl6ekayqewu path: patches/react-native-aes-crypto@2.1.1.patch react-native-background-actions@3.0.1: - hash: 3u44z2aocqips4ucm32wcdde3y + hash: bczt4hbkdy2a6n4hvzyilx3qg4 path: patches/react-native-background-actions@3.0.1.patch importers: @@ -746,7 +746,7 @@ importers: version: 2.1.1(patch_hash=sm7rpv6bikakdvmsl6ekayqewu) react-native-background-actions: specifier: ^3.0.1 - version: 3.0.1(patch_hash=3u44z2aocqips4ucm32wcdde3y)(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.23.8(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0)) + version: 3.0.1(patch_hash=bczt4hbkdy2a6n4hvzyilx3qg4)(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.23.8(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0)) react-native-background-timer: specifier: ^2.4.1 version: 2.4.1(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.23.8(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0)) @@ -21740,7 +21740,7 @@ snapshots: react-native-aes-crypto@2.1.1(patch_hash=sm7rpv6bikakdvmsl6ekayqewu): {} - react-native-background-actions@3.0.1(patch_hash=3u44z2aocqips4ucm32wcdde3y)(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.23.8(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0)): + react-native-background-actions@3.0.1(patch_hash=bczt4hbkdy2a6n4hvzyilx3qg4)(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.23.8(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0)): dependencies: eventemitter3: 4.0.7 react-native: 0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.23.8(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.2.0)