Skip to content

Commit

Permalink
fix: double pin screen (openwallet-foundation#1121)
Browse files Browse the repository at this point in the history
Signed-off-by: wadeking98 <[email protected]>
  • Loading branch information
wadeking98 authored Mar 26, 2024
1 parent 29c94f4 commit 3b63696
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 107 deletions.
8 changes: 8 additions & 0 deletions packages/legacy/core/App/container-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export enum SERVICE_TOKENS {
SERVICE_TERMS = 'screen.terms',
}

export enum LOAD_STATE_TOKENS {
LOAD_STATE = 'state.load',
}

export enum OBJECT_TOKENS {
OBJECT_ONBOARDINGCONFIG = 'object.onboarding-config',
}
Expand All @@ -40,6 +44,7 @@ export const TOKENS = {
...STACK_TOKENS,
...FN_TOKENS,
...COMP_TOKENS,
...LOAD_STATE_TOKENS,
...OBJECT_TOKENS,
}

Expand All @@ -48,13 +53,16 @@ export type FN_ONBOARDING_DONE = (
navigation: StackNavigationProp<AuthenticateStackParams>
) => GenericFn

type FN_LOADSTATE = (dispatch: React.Dispatch<ReducerAction<unknown>>) => Promise<void>

export interface TokenMapping {
[TOKENS.SCREEN_PREFACE]: React.FC
[TOKENS.STACK_ONBOARDING]: React.FC
[TOKENS.SCREEN_TERMS]: { screen: React.FC; version: boolean | string }
[TOKENS.SCREEN_DEVELOPER]: React.FC
[TOKENS.SCREEN_ONBOARDING]: typeof Onboarding
[TOKENS.FN_ONBOARDING_DONE]: FN_ONBOARDING_DONE
[TOKENS.LOAD_STATE]: FN_LOADSTATE
[TOKENS.COMP_BUTTON]: Button
[TOKENS.OBJECT_ONBOARDINGCONFIG]: ScreenOptionsType
}
Expand Down
51 changes: 49 additions & 2 deletions packages/legacy/core/App/container-impl.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import AsyncStorage from '@react-native-async-storage/async-storage'
import { StackNavigationProp } from '@react-navigation/stack'
import { createContext, useContext } from 'react'
import { DependencyContainer } from 'tsyringe'

import Button from './components/buttons/Button'
import { LocalStorageKeys } from './constants'
import { TOKENS, Container, TokenMapping } from './container-api'
import { DispatchAction, ReducerAction } from './contexts/reducers/store'
import { defaultState } from './contexts/store'
import OnboardingStack from './navigators/OnboardingStack'
import { DefaultScreenOptionsDictionary } from './navigators/defaultStackOptions'
import Developer from './screens/Developer'
import Onboarding from './screens/Onboarding'
import Preface from './screens/Preface'
import ScreenTerms, { TermsVersion } from './screens/Terms'
import { loadLoginAttempt } from './services/keychain'
import { AuthenticateStackParams, Screens } from './types/navigators'
import {
Migration as MigrationState,
Preferences as PreferencesState,
State,
Onboarding as StoreOnboardingState,
Tours as ToursState,
} from './types/state'

export class MainContainer implements Container {
public static readonly TOKENS = TOKENS
Expand Down Expand Up @@ -43,12 +54,48 @@ export class MainContainer implements Container {
}
)

this.container.registerInstance(TOKENS.LOAD_STATE, async (dispatch: React.Dispatch<ReducerAction<unknown>>) => {
const loadState = async <Type>(key: LocalStorageKeys, updateVal: (newVal: Type) => void) => {
const data = await AsyncStorage.getItem(key)
if (data) {
const dataAsJSON = JSON.parse(data) as Type
updateVal(dataAsJSON)
}
}

let loginAttempt = defaultState.loginAttempt
let preferences = defaultState.preferences
let migration = defaultState.migration
let tours = defaultState.tours
let onboarding = defaultState.onboarding

await Promise.all([
loadLoginAttempt().then((data) => {
if (data) {
loginAttempt = data
}
}),
loadState<PreferencesState>(LocalStorageKeys.Preferences, (val) => (preferences = val)),
loadState<MigrationState>(LocalStorageKeys.Migration, (val) => (migration = val)),
loadState<ToursState>(LocalStorageKeys.Tours, (val) => (tours = val)),
loadState<StoreOnboardingState>(LocalStorageKeys.Onboarding, (val) => (onboarding = val)),
])

const state: State = {
...defaultState,
loginAttempt: loginAttempt,
preferences: preferences,
migration: migration,
tours: tours,
onboarding: onboarding,
}
dispatch({ type: DispatchAction.STATE_DISPATCH, payload: [state] })
})

return this
}

public resolve<K extends keyof TokenMapping>(token: K): TokenMapping[K] {
// eslint-disable-next-line no-console
console.log(`resolving ${token}`)
return this.container.resolve(token) as TokenMapping[K]
}

Expand Down
14 changes: 14 additions & 0 deletions packages/legacy/core/App/contexts/reducers/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import {
} from '../../types/state'
import { generateRandomWalletName } from '../../utils/helpers'

enum StateDispatchAction {
STATE_DISPATCH = 'state/stateDispatch',
STATE_LOADED = 'state/stateLoaded',
}

enum OnboardingDispatchAction {
ONBOARDING_UPDATED = 'onboarding/onboardingStateLoaded',
DID_SEE_PREFACE = 'onboarding/didSeePreface',
Expand Down Expand Up @@ -71,6 +76,7 @@ enum DeepLinkDispatchAction {
}

export type DispatchAction =
| StateDispatchAction
| OnboardingDispatchAction
| LoginAttemptDispatchAction
| LockoutDispatchAction
Expand All @@ -81,6 +87,7 @@ export type DispatchAction =
| MigrationDispatchAction

export const DispatchAction = {
...StateDispatchAction,
...OnboardingDispatchAction,
...LoginAttemptDispatchAction,
...LockoutDispatchAction,
Expand All @@ -98,6 +105,13 @@ export interface ReducerAction<R> {

export const reducer = <S extends State>(state: S, action: ReducerAction<DispatchAction>): S => {
switch (action.type) {
case StateDispatchAction.STATE_LOADED: {
return { ...state, stateLoaded: true }
}
case StateDispatchAction.STATE_DISPATCH: {
const newState: State = (action?.payload || []).pop()
return { ...state, ...newState }
}
case PreferencesDispatchAction.ENABLE_DEVELOPER_MODE: {
const choice = (action?.payload ?? []).pop() ?? false
const preferences = { ...state.preferences, developerModeEnabled: choice }
Expand Down
1 change: 1 addition & 0 deletions packages/legacy/core/App/contexts/store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const defaultState: State = {
activeDeepLink: '',
},
loading: false,
stateLoaded: false,
}

export const StoreContext = createContext<[State, Dispatch<ReducerAction<any>>]>([
Expand Down
13 changes: 12 additions & 1 deletion packages/legacy/core/App/navigators/RootStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const RootStack: React.FC = () => {
} = useConfiguration()
const container = useContainer()
const OnboardingStack = container.resolve(TOKENS.STACK_ONBOARDING)
const loadState = container.resolve(TOKENS.LOAD_STATE)
const { version: TermsVersion } = container.resolve(TOKENS.SCREEN_TERMS)
useDeepLinks()

Expand Down Expand Up @@ -89,6 +90,17 @@ const RootStack: React.FC = () => {
}
}

useEffect(() => {
loadState(dispatch)
.then(() => {
dispatch({ type: DispatchAction.STATE_LOADED })
})
.catch((err) => {
const error = new BifoldError(t('Error.Title1044'), t('Error.Message1044'), err.message, 1001)
DeviceEventEmitter.emit(EventTypes.ERROR_ADDED, error)
})
}, [])

// handle deeplink events
useEffect(() => {
async function handleDeepLink(deepLink: string) {
Expand Down Expand Up @@ -280,7 +292,6 @@ const RootStack: React.FC = () => {
) {
return state.authentication.didAuthenticate ? mainStack() : authStack()
}

return <OnboardingStack />
}

Expand Down
135 changes: 33 additions & 102 deletions packages/legacy/core/App/screens/Splash.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Agent, ConsoleLogger, HttpOutboundTransport, LogLevel, WsOutboundTransport } from '@aries-framework/core'
import { useAgent } from '@aries-framework/react-hooks'
import { agentDependencies } from '@aries-framework/react-native'
import AsyncStorage from '@react-native-async-storage/async-storage'
import { useNavigation } from '@react-navigation/core'
import { CommonActions } from '@react-navigation/native'
import React, { useEffect } from 'react'
Expand All @@ -10,24 +9,17 @@ import { DeviceEventEmitter, StyleSheet } from 'react-native'
import { Config } from 'react-native-config'
import { SafeAreaView } from 'react-native-safe-area-context'

import { EventTypes, LocalStorageKeys } from '../constants'
import { EventTypes } from '../constants'
import { TOKENS, useContainer } from '../container-api'
import { useAnimatedComponents } from '../contexts/animated-components'
import { useAuth } from '../contexts/auth'
import { useConfiguration } from '../contexts/configuration'
import { DispatchAction } from '../contexts/reducers/store'
import { useStore } from '../contexts/store'
import { useTheme } from '../contexts/theme'
import { loadLoginAttempt } from '../services/keychain'
import { BifoldError } from '../types/error'
import { Screens, Stacks } from '../types/navigators'
import {
LoginAttempt as LoginAttemptState,
Migration as MigrationState,
Preferences as PreferencesState,
Onboarding as StoreOnboardingState,
Tours as ToursState,
} from '../types/state'
import { Onboarding as StoreOnboardingState } from '../types/state'
import { getAgentModules, createLinkSecretIfRequired } from '../utils/agent'
import { migrateToAskar, didMigrateToAskar } from '../utils/migration'

Expand Down Expand Up @@ -110,17 +102,6 @@ const Splash: React.FC = () => {
},
})

const loadAuthAttempts = async (): Promise<LoginAttemptState | undefined> => {
const attempts = await loadLoginAttempt()
if (attempts) {
dispatch({
type: DispatchAction.ATTEMPT_UPDATED,
payload: [attempts],
})
return attempts
}
}

useEffect(() => {
if (store.authentication.didAuthenticate) {
return
Expand All @@ -129,102 +110,52 @@ const Splash: React.FC = () => {
const initOnboarding = async (): Promise<void> => {
try {
// load authentication attempts from storage
const attemptData = await loadAuthAttempts()

const preferencesData = await AsyncStorage.getItem(LocalStorageKeys.Preferences)
if (preferencesData) {
const dataAsJSON = JSON.parse(preferencesData) as PreferencesState

dispatch({
type: DispatchAction.PREFERENCES_UPDATED,
payload: [dataAsJSON],
})
}

const migrationData = await AsyncStorage.getItem(LocalStorageKeys.Migration)
if (migrationData) {
const dataAsJSON = JSON.parse(migrationData) as MigrationState

dispatch({
type: DispatchAction.MIGRATION_UPDATED,
payload: [dataAsJSON],
})
}

const toursData = await AsyncStorage.getItem(LocalStorageKeys.Tours)
if (toursData) {
const dataAsJSON = JSON.parse(toursData) as ToursState

dispatch({
type: DispatchAction.TOUR_DATA_UPDATED,
payload: [dataAsJSON],
})
if (!store.stateLoaded) {
return
}

const data = await AsyncStorage.getItem(LocalStorageKeys.Onboarding)
if (data) {
const onboardingState = JSON.parse(data) as StoreOnboardingState
dispatch({ type: DispatchAction.ONBOARDING_UPDATED, payload: [onboardingState] })
if (onboardingComplete(onboardingState, { termsVersion: TermsVersion })) {
// if they previously completed onboarding before wallet naming was enabled, mark complete
if (!store.onboarding.didNameWallet) {
dispatch({ type: DispatchAction.DID_NAME_WALLET, payload: [true] })
}
if (onboardingComplete(store.onboarding, { termsVersion: TermsVersion })) {
// if they previously completed onboarding before wallet naming was enabled, mark complete
if (!store.onboarding.didNameWallet) {
dispatch({ type: DispatchAction.DID_NAME_WALLET, payload: [true] })
}

// if they previously completed onboarding before preface was enabled, mark seen
if (!store.onboarding.didSeePreface) {
dispatch({ type: DispatchAction.DID_SEE_PREFACE })
}
// if they previously completed onboarding before preface was enabled, mark seen
if (!store.onboarding.didSeePreface) {
dispatch({ type: DispatchAction.DID_SEE_PREFACE })
}

if (!attemptData?.lockoutDate) {
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: Screens.EnterPIN }],
})
)
} else {
// return to lockout screen if lockout date is set
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: Screens.AttemptLockout }],
})
)
}
return
if (!store.loginAttempt.lockoutDate) {
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: Screens.EnterPIN }],
})
)
} else {
// If onboarding was interrupted we need to pickup from where we left off.
// return to lockout screen if lockout date is set
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [
{
name: resumeOnboardingAt(onboardingState, {
enableWalletNaming: store.preferences.enableWalletNaming,
showPreface,
termsVersion: TermsVersion,
}),
},
],
routes: [{ name: Screens.AttemptLockout }],
})
)
}
return
}
// We have no onboarding state, starting from step zero.
if (showPreface) {
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: Screens.Preface }],
})
)
} else {
// If onboarding was interrupted we need to pickup from where we left off.
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: Screens.Onboarding }],
routes: [
{
name: resumeOnboardingAt(store.onboarding, {
enableWalletNaming: store.preferences.enableWalletNaming,
showPreface,
termsVersion: TermsVersion,
}),
},
],
})
)
}
Expand All @@ -240,7 +171,7 @@ const Splash: React.FC = () => {
}

initOnboarding()
}, [store.authentication.didAuthenticate])
}, [store.authentication.didAuthenticate, store.stateLoaded])

useEffect(() => {
if (!store.authentication.didAuthenticate || !store.onboarding.didConsiderBiometry) {
Expand Down
Loading

0 comments on commit 3b63696

Please sign in to comment.