Skip to content
Open
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
3 changes: 2 additions & 1 deletion src/constants/asyncStorageKeys.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const ASYNC_STORAGE_KEYS = {
FIRST_LAUNCH_KEY: 'hasAppLaunchedBefore',
FAILED_KEYS_KEY: 'failedSecureStoreKeys',
LAST_ACTIVITY_AT: 'lastActivityAt'
LAST_ACTIVITY_AT: 'lastActivityAt',
LAST_OPENED_VAULT_ID: 'lastOpenedVaultId'
}
2 changes: 2 additions & 0 deletions src/containers/Auth/UnlockVault.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
Text
} from 'react-native'
import { colors } from 'src/utils/colors'
import { setLastOpenedVaultId } from 'src/utils/lastOpenedVaultStorage'

import {
ButtonPrimary,
Expand Down Expand Up @@ -56,6 +57,7 @@ export const UnlockVault = ({ vaultId }) => {
setIsLoading(true)

await refetchVault(vaultId, { password: values.password })
await setLastOpenedVaultId(vaultId)

setIsLoading(false)
navigation.replace('MainTabNavigator')
Expand Down
4 changes: 3 additions & 1 deletion src/containers/Auth/VaultWizard/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Toast from 'react-native-toast-message'

import { StepIdentity } from './StepIdentity'
import { TOAST_CONFIG } from '../../../constants/toast'
import { setLastOpenedVaultId } from '../../../utils/lastOpenedVaultStorage'
import { logger } from '../../../utils/logger'

export const VaultWizard = () => {
Expand All @@ -34,11 +35,12 @@ export const VaultWizard = () => {
try {
setIsLoading(true)
setFormData(data)
await createVault({
const createdVault = await createVault({
name: data.name,
password: data.usePassword ? data.password : undefined
})
await addDevice()
await setLastOpenedVaultId(createdVault?.id)
Toast.show({
type: 'baseToast',
text1: t`Vault created`,
Expand Down
3 changes: 3 additions & 0 deletions src/hooks/useVaultSwitch.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useVault } from '@tetherto/pearpass-lib-vault'
import { VaultPasswordFormModalContent } from '../containers/Modal/VaultPasswordFormModalContent'
import { useGlobalLoading } from '../context/LoadingContext'
import { useModal } from '../context/ModalContext'
import { setLastOpenedVaultId } from '../utils/lastOpenedVaultStorage'

/**
* @param {{
Expand Down Expand Up @@ -53,6 +54,7 @@ export const useVaultSwitch = ({
setIsLoading(true)
try {
await refetchVault(vault.id, { password })
await setLastOpenedVaultId(vault.id)
closeModal()
onSwitchComplete?.()
} finally {
Expand All @@ -63,6 +65,7 @@ export const useVaultSwitch = ({
)
} else {
await refetchVault(vault.id)
await setLastOpenedVaultId(vault.id)
onSwitchComplete?.()
}
} finally {
Expand Down
16 changes: 12 additions & 4 deletions src/screens/Auth/hooks/useAutoSelectVault.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { useNavigation } from '@react-navigation/native'
import { useVault, useVaults } from '@tetherto/pearpass-lib-vault'

import { NAVIGATION_ROUTES } from '../../../constants/navigation'
import {
getLastOpenedVaultId,
setLastOpenedVaultId
} from '../../../utils/lastOpenedVaultStorage'

export const useAutoSelectVault = () => {
const navigation = useNavigation()
Expand All @@ -28,18 +32,22 @@ export const useAutoSelectVault = () => {
return
}

const firstVault = vaults[0]
// Prefer the vault the user last opened; fall back to the first one.
const lastOpenedVaultId = await getLastOpenedVaultId()
const targetVault =
vaults.find((vault) => vault.id === lastOpenedVaultId) ?? vaults[0]

const isProtected = await isVaultProtected(firstVault.id)
const isProtected = await isVaultProtected(targetVault.id)
if (isProtected) {
navigation.replace('Welcome', {
state: NAVIGATION_ROUTES.UNLOCK,
vaultId: firstVault.id
vaultId: targetVault.id
})
return
}

await refetchVault(firstVault.id)
await refetchVault(targetVault.id)
await setLastOpenedVaultId(targetVault.id)
navigation.replace('MainTabNavigator')
}, [refetchVaults, isVaultProtected, refetchVault, navigation])

Expand Down
4 changes: 3 additions & 1 deletion src/screens/Onboarding/hooks/usePasswordCreation.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Toast from 'react-native-toast-message'

import { TOAST_CONFIG } from '../../../constants/toast'
import { clearStaleVaultsDir } from '../../../utils/clearStaleVaultsDir'
import { setLastOpenedVaultId } from '../../../utils/lastOpenedVaultStorage'
import { logger } from '../../../utils/logger'
import {
getPasswordIndicatorVariant,
Expand Down Expand Up @@ -166,8 +167,9 @@ export const usePasswordCreation = () => {
await logIn({ password: loginBuffer })
await refetchUser()
await initVaults({ password: loginBuffer })
await createVault({ name: t`Personal` })
const createdVault = await createVault({ name: t`Personal` })
await addDevice()
await setLastOpenedVaultId(createdVault?.id)
clearBuffer(loginBuffer)

clearBuffer(passwordBuffer)
Expand Down
26 changes: 26 additions & 0 deletions src/utils/lastOpenedVaultStorage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import AsyncStorage from '@react-native-async-storage/async-storage'

import { logger } from './logger'
import { ASYNC_STORAGE_KEYS } from '../constants/asyncStorageKeys'

const { LAST_OPENED_VAULT_ID } = ASYNC_STORAGE_KEYS

export const setLastOpenedVaultId = async (vaultId) => {
if (!vaultId) {
return
}
try {
await AsyncStorage.setItem(LAST_OPENED_VAULT_ID, String(vaultId))
} catch (error) {
logger.error('Error saving last opened vault id:', error)
}
}

export const getLastOpenedVaultId = async () => {
try {
return await AsyncStorage.getItem(LAST_OPENED_VAULT_ID)
} catch (error) {
logger.error('Error loading last opened vault id:', error)
return null
}
}
99 changes: 99 additions & 0 deletions src/utils/lastOpenedVaultStorage.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import AsyncStorage from '@react-native-async-storage/async-storage'

import {
getLastOpenedVaultId,
setLastOpenedVaultId
} from './lastOpenedVaultStorage'
import { logger } from './logger'
import { ASYNC_STORAGE_KEYS } from '../constants/asyncStorageKeys'

jest.mock('@react-native-async-storage/async-storage', () => ({
setItem: jest.fn(),
getItem: jest.fn()
}))

jest.mock('./logger', () => ({
logger: {
error: jest.fn()
}
}))

describe('lastOpenedVaultStorage', () => {
const { LAST_OPENED_VAULT_ID } = ASYNC_STORAGE_KEYS

beforeEach(() => {
jest.clearAllMocks()
})

describe('setLastOpenedVaultId', () => {
it('stores the provided vault id', async () => {
AsyncStorage.setItem.mockResolvedValue()

await setLastOpenedVaultId('vault-123')

expect(AsyncStorage.setItem).toHaveBeenCalledWith(
LAST_OPENED_VAULT_ID,
'vault-123'
)
})

it('coerces a non-string vault id to a string', async () => {
AsyncStorage.setItem.mockResolvedValue()

await setLastOpenedVaultId(42)

expect(AsyncStorage.setItem).toHaveBeenCalledWith(
LAST_OPENED_VAULT_ID,
'42'
)
})

it.each([undefined, null, '', 0])(
'does not store when vault id is falsy (%p)',
async (vaultId) => {
await setLastOpenedVaultId(vaultId)

expect(AsyncStorage.setItem).not.toHaveBeenCalled()
}
)

it('logs when saving fails', async () => {
const error = new Error('write failed')
AsyncStorage.setItem.mockRejectedValue(error)

await setLastOpenedVaultId('vault-123')

expect(logger.error).toHaveBeenCalledWith(
'Error saving last opened vault id:',
error
)
})
})

describe('getLastOpenedVaultId', () => {
it('returns the stored vault id', async () => {
AsyncStorage.getItem.mockResolvedValue('vault-123')

await expect(getLastOpenedVaultId()).resolves.toBe('vault-123')
expect(AsyncStorage.getItem).toHaveBeenCalledWith(LAST_OPENED_VAULT_ID)
})

it('returns null when no value is stored', async () => {
AsyncStorage.getItem.mockResolvedValue(null)

await expect(getLastOpenedVaultId()).resolves.toBeNull()
})

it('logs when loading fails and returns null', async () => {
const error = new Error('read failed')
AsyncStorage.getItem.mockRejectedValue(error)

await expect(getLastOpenedVaultId()).resolves.toBeNull()

expect(logger.error).toHaveBeenCalledWith(
'Error loading last opened vault id:',
error
)
})
})
})
Loading