Skip to content

Commit

Permalink
refactor: replace remember with persist
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranjamie committed Jul 29, 2024
1 parent 0ec895c commit eb39896
Show file tree
Hide file tree
Showing 18 changed files with 155 additions and 119 deletions.
2 changes: 1 addition & 1 deletion apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
"react-native-webview": "13.8.6",
"react-redux": "9.1.2",
"readable-stream": "4.5.2",
"redux-remember": "5.1.0"
"redux-persist": "6.0.0"
},
"devDependencies": {
"@babel/core": "7.24.6",
Expand Down
39 changes: 20 additions & 19 deletions apps/mobile/src/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import { Provider as ReduxProvider } from 'react-redux';
import { SplashScreenGuard } from '@/components/splash-screen-guard/splash-screen-guard';
import { initiateI18n } from '@/i18n';
import { queryClient } from '@/queries/query';
import { store } from '@/state';
import { persistor, store } from '@/state';
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
import { i18n } from '@lingui/core';
import { I18nProvider } from '@lingui/react';
import { QueryClientProvider } from '@tanstack/react-query';
import { Slot } from 'expo-router';
import * as SplashScreen from 'expo-splash-screen';
import { PersistGate } from 'redux-persist/integration/react';

import { Box, ThemeProvider, useLoadFonts } from '@leather.io/ui/native';

Expand All @@ -33,27 +34,27 @@ export default function RootLayout() {
},
});

if (!fontsLoaded) {
return null;
}
if (!fontsLoaded) return null;

return (
<ReduxProvider store={store}>
<I18nProvider i18n={i18n}>
<SafeAreaProvider>
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<SplashScreenGuard>
<GestureHandlerRootView style={{ flex: 1 }}>
<BottomSheetModalProvider>
<AppRouter />
</BottomSheetModalProvider>
</GestureHandlerRootView>
</SplashScreenGuard>
</ThemeProvider>
</QueryClientProvider>
</SafeAreaProvider>
</I18nProvider>
<PersistGate loading={null} persistor={persistor}>
<I18nProvider i18n={i18n}>
<SafeAreaProvider>
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<SplashScreenGuard>
<GestureHandlerRootView style={{ flex: 1 }}>
<BottomSheetModalProvider>
<AppRouter />
</BottomSheetModalProvider>
</GestureHandlerRootView>
</SplashScreenGuard>
</ThemeProvider>
</QueryClientProvider>
</SafeAreaProvider>
</I18nProvider>
</PersistGate>
</ReduxProvider>
);
}
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/app/waiting-list/animation-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,6 @@ export function animate<
K extends Record<string, number>,
>(component: T, animFn: (x: number) => number, position: K) {
Object.entries(component).forEach(([key, sharedValue]) => {
sharedValue.value = animFn(position[key]);
sharedValue.value = animFn(position[key] ?? 0);
});
}
8 changes: 4 additions & 4 deletions apps/mobile/src/app/wallet/developer-console/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default function DeveloperConsoleScreen() {
const [getAddressesMessage, setGetAddressesMessage] = useState<BrowserMessage | null>(null);
const addWalletModalRef = useRef<BottomSheetModal>(null);
return (
<Box flex={1} backgroundColor="base.ink.background-primary" style={{}}>
<Box flex={1} backgroundColor="base.ink.background-primary">
<ScrollView
contentContainerStyle={{
paddingHorizontal: theme.spacing['3'],
Expand All @@ -45,13 +45,13 @@ export default function DeveloperConsoleScreen() {
<TitleListItem title="Trigger API presets" />
<PressableListItem
title="getAddresses"
onPress={() => {
onPress={() =>
setGetAddressesMessage({
jsonrpc: '2.0',
id: 'string',
method: 'getAddresses',
});
}}
})
}
/>
<PressableListItem title="signMessage" />
<PressableListItem title="transferBtc" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ export default function WalletManager() {
<Box flex={1} backgroundColor="base.ink.background-primary">
<ScrollView
contentContainerStyle={{
marginTop: theme.spacing['7'],
paddingHorizontal: theme.spacing['3'],
paddingTop: insets.top,
paddingTop: insets.top + theme.spacing['5'],
paddingBottom: theme.spacing['5'] + bottom,
gap: theme.spacing[5],
}}
Expand Down
36 changes: 22 additions & 14 deletions apps/mobile/src/state/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { combineReducers, configureStore } from '@reduxjs/toolkit';
import devToolsEnhancer from 'redux-devtools-expo-dev-plugin';
import { rememberReducer } from 'redux-remember';
import {
FLUSH,
PAUSE,
PERSIST,
PURGE,
REGISTER,
REHYDRATE,
persistReducer,
persistStore,
} from 'redux-persist';

import { createAsyncStorageEnhancer } from './/storage-persistors';
import { persistConfig } from './/storage-persistors';
import { accountsSlice } from './accounts/accounts.slice';
import { bitcoinKeychainSlice } from './keychains/bitcoin/bitcoin-keychains.slice';
import { settingsSlice } from './settings/settings.slice';
Expand All @@ -19,22 +28,21 @@ const reducer = combineReducers({

export type RootState = ReturnType<typeof reducer>;

const storageEnhancer = createAsyncStorageEnhancer([
'wallets',
'accounts',
'keychains',
'settings',
]);

export const store = configureStore({
reducer: rememberReducer(reducer),
reducer: persistReducer(persistConfig, reducer),
devTools: false,
enhancers: getDefaultEnhancers => {
const enhancers = getDefaultEnhancers().concat(storageEnhancer);

if (process.env.NODE_ENV === 'development')
return enhancers.concat(devToolsEnhancer({ trace: true }));
return getDefaultEnhancers().concat(devToolsEnhancer({ trace: true }));

return enhancers;
return getDefaultEnhancers();
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
});

export const persistor = persistStore(store);
4 changes: 2 additions & 2 deletions apps/mobile/src/state/key-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
} from '@leather.io/bitcoin';
import {
deriveBip39SeedFromMnemonic,
deriveAccountDescriptor as deriveKeychainDescriptor,
deriveKeychainDescriptor,
deriveRootBip32Keychain,
extractKeyOriginPathFromDescriptor,
generateMnemonic,
Expand All @@ -13,7 +13,7 @@ import {

import { removesAccount, userAddsAccount } from './accounts/accounts.slice';
import { useBitcoinKeychains } from './keychains/bitcoin/bitcoin-keychains.slice';
import { findHighestAccountIndexOfFingerprint } from './keychains/keychain-state-helpers';
import { findHighestAccountIndexOfFingerprint } from './keychains/keychains';
import { mnemonicStore } from './storage-persistors';
import { makeAccountIdentifer, useAppDispatch } from './utils';
import { useWallets } from './wallets/wallets.slice';
Expand Down
37 changes: 24 additions & 13 deletions apps/mobile/src/state/keychains/bitcoin/bitcoin-keychains.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { useSelector } from 'react-redux';

import { removesAccount, userAddsAccount } from '@/state/accounts/accounts.slice';
import { handleAppResetWithState, userAddsWallet, userRemovesWallet } from '@/state/global-action';
import { selectNetwork } from '@/state/settings/settings.slice';
import { createEntityAdapter, createSelector, createSlice } from '@reduxjs/toolkit';
import memoize from 'just-memoize';

import {
bitcoinNetworkModeToCoreNetworkMode,
deriveNativeSegWitReceiveAddressIndex,
deriveTaprootReceiveAddressIndex,
extractExtendedPublicKeyFromPolicy,
Expand All @@ -16,15 +18,15 @@ import {
extractDerivationPathFromDescriptor,
extractKeyOriginPathFromDescriptor,
} from '@leather.io/crypto';
import { ensureArray, mapObject } from '@leather.io/utils';
import { ensureArray } from '@leather.io/utils';

import type { RootState } from '../..';
import { handleEntityActionWith, useAppDispatch } from '../../utils';
import {
filterKeychainsByAccountIndex,
filterKeychainsByFingerprint,
} from '../keychain-state-helpers';
import { filterKeychainsToRemove } from '../keychains';
filterKeychainsToRemove,
} from '../keychains';

export interface BitcoinKeychainStore {
descriptor: string;
Expand All @@ -43,7 +45,6 @@ export const bitcoinKeychainSlice = createSlice({
},
extraReducers: builder =>
builder

.addCase(
userAddsWallet,
handleEntityActionWith(adapter.addMany, payload => payload.withKeychains?.bitcoin ?? [])
Expand Down Expand Up @@ -73,34 +74,44 @@ function initializeBitcoinKeychain(descriptor: string) {
? deriveNativeSegWitReceiveAddressIndex
: deriveTaprootReceiveAddressIndex;

return {
const result = {
descriptor,
...derivationFn({
xpub: extractExtendedPublicKeyFromPolicy(descriptor),
network: inferNetworkFromPath(extractKeyOriginPathFromDescriptor(descriptor)),
}),
};
return result;
}

const memoizedInitalizeBitcoinKeychain = memoize(initializeBitcoinKeychain);

const bitcoinKeychainList = createSelector(bitcoinKeychainSelectors.selectAll, accounts =>
accounts.map(account => memoizedInitalizeBitcoinKeychain(account.descriptor))
);

const bitcoinKeychainEntities = createSelector(bitcoinKeychainSelectors.selectEntities, entities =>
mapObject(entities, account => memoizedInitalizeBitcoinKeychain(account.descriptor))
const bitcoinKeychainList = createSelector(
bitcoinKeychainSelectors.selectAll,
selectNetwork,
(accounts, network) =>
accounts
.filter(
account =>
inferNetworkFromPath(extractKeyOriginPathFromDescriptor(account.descriptor)) ===
bitcoinNetworkModeToCoreNetworkMode(network.chain.bitcoin.bitcoinNetwork)
)
.map(account => memoizedInitalizeBitcoinKeychain(account.descriptor))
);

export function useBitcoinKeychains() {
const dispatch = useAppDispatch();

const list = useSelector(bitcoinKeychainList);
const entities = useSelector(bitcoinKeychainEntities);
const entities = useSelector(bitcoinKeychainSelectors.selectEntities);

return {
list,
entities,
getKeychainById(keyOrigin: string) {
const key = entities[keyOrigin];
if (!key) return null;
return memoizedInitalizeBitcoinKeychain(key.descriptor);
},
fromFingerprint(fingerprint: string) {
return list.filter(filterKeychainsByFingerprint(fingerprint));
},
Expand Down
33 changes: 0 additions & 33 deletions apps/mobile/src/state/keychains/keychain-state-helpers.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
filterKeychainsByAccountIndex,
filterKeychainsByFingerprint,
findHighestAccountIndexOfFingerprint,
} from './keychain-state-helpers';
} from './keychains';

const fingerprintAlpha = 'deadbeef';
const fingerprintBeta = 'cafebabe';
Expand Down
38 changes: 32 additions & 6 deletions apps/mobile/src/state/keychains/keychains.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { EntityState, EntityStateAdapter } from '@reduxjs/toolkit';

import { extractKeyOriginPathFromDescriptor } from '@leather.io/crypto';
import { isDefined } from '@leather.io/utils';

import {
filterKeychainsByAccountIndex,
filterKeychainsByFingerprint,
} from './keychain-state-helpers';
extractAccountIndexFromDescriptor,
extractAccountIndexFromPath,
extractFingerprintFromDescriptor,
extractKeyOriginPathFromDescriptor,
} from '@leather.io/crypto';
import { isDefined } from '@leather.io/utils';

interface RemoveAccount {
fingerprint: string;
Expand All @@ -29,3 +29,29 @@ export function filterKeychainsToRemove<T extends { descriptor: string }>(
return removeManyFn(state, keyOriginPathsToRemove);
};
}

interface WithDescriptor {
descriptor: string;
}

export function filterKeychainsByFingerprint(fingerprint: string) {
return (account: WithDescriptor) =>
extractFingerprintFromDescriptor(account.descriptor) === fingerprint;
}

export function filterKeychainsByAccountIndex(accountIndex: number) {
return (account: WithDescriptor) =>
extractAccountIndexFromDescriptor(account.descriptor) === accountIndex;
}

export function findHighestAccountIndexOfFingerprint<T extends WithDescriptor>(
accounts: T[],
fingerprint: string
) {
return Math.max(
0,
...accounts
.filter(filterKeychainsByFingerprint(fingerprint))
.map(account => extractAccountIndexFromPath(account.descriptor))
);
}
13 changes: 7 additions & 6 deletions apps/mobile/src/state/storage-persistors.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as SecureStore from 'expo-secure-store';
import { rememberEnhancer } from 'redux-remember';
import { PersistConfig } from 'redux-persist';

import { RootState } from '.';

export function createAsyncStorageEnhancer(reducersToPersist: (keyof RootState)[]) {
return rememberEnhancer(AsyncStorage, reducersToPersist, {
prefix: 'leatherAsync',
});
}
export const persistConfig: PersistConfig<RootState> = {
key: 'root',
version: 0,
storage: AsyncStorage,
whitelist: ['wallets', 'accounts', 'keychains', 'settings'],
};

// Mnemonics are accessed directly from SecureStore to avoid leaving them in the
// app state. Read the key only at the time it is needed for signing.
Expand Down
Loading

0 comments on commit eb39896

Please sign in to comment.