Skip to content

Commit

Permalink
hotfix(mobile): race condition fix (#764)
Browse files Browse the repository at this point in the history
* fix(mobile): fix migration using biometry

* bump(mobile): 4.0.1

* fix(mobile): Move isMigrated flag to separated storage to avoid race condition

* fix(mobile): add setAsync method

* add persist options for migrationStore

* fix(mobile): fix seed phrase on small displays

---------

Co-authored-by: Andrey Sorokin <[email protected]>
  • Loading branch information
voloshinskii and sorokin0andrey authored Mar 18, 2024
1 parent e5a889c commit f18d810
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 53 deletions.
9 changes: 9 additions & 0 deletions packages/@core-js/src/utils/State.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ export class State<TData extends DefaultStateData> {
this.storeIfNeeded();
};

public setAsync = async (
updater: Partial<TData> | ((data: TData) => Partial<TData>),
) => {
const newData = typeof updater === 'function' ? updater(this.data) : updater;
this.data = { ...this.data, ...newData };
this.emit();
await this.storeIfNeeded();
};

private emit() {
this.subscribers.forEach((subscriber) => subscriber(this.data));
}
Expand Down
2 changes: 1 addition & 1 deletion packages/mobile/src/navigation/MainStack/MainStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const MainStack: FC = () => {
const showLockScreen =
tk.lockEnabled && !isUnlocked && hasWallet && !attachedScreen.pathname;

const isMigrated = useExternalState(tk.walletsStore, (state) => state.isMigrated);
const isMigrated = useExternalState(tk.migrationStore, (state) => state.isMigrated);

const root = useMemo(() => {
if (hasWallet) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,43 +41,45 @@ export const BackupPhraseScreen = memo(() => {
return (
<Screen>
<Screen.Header />
<View style={styles.container}>
<Text type="h2" textAlign="center">
{t('recovery_phrase.title')}
</Text>
<Spacer y={4} />
<Text type="body1" color="textSecondary" textAlign="center">
{t('recovery_phrase.caption')}
</Text>
<Spacer y={16} />
<Screen.ScrollView>
<View style={styles.container}>
<Text type="h2" textAlign="center">
{t('recovery_phrase.title')}
</Text>
<Spacer y={4} />
<Text type="body1" color="textSecondary" textAlign="center">
{t('recovery_phrase.caption')}
</Text>
<Spacer y={16} />

<View style={styles.centered}>
<View style={styles.columns}>
<View style={styles.leftColumn}>
{leftColumn.map((word, index) => (
<View style={styles.line} key={`${word}-${index}`}>
<Text type="body2" color="textSecondary" style={styles.num}>
{index + 1}.
</Text>
<Text type="body1">{word}</Text>
</View>
))}
</View>
<View>
{rightColumn.map((word, index) => (
<View style={styles.line} key={`${word}-${index + 1 + 12}`}>
<Text type="body2" color="textSecondary" style={styles.num}>
{index + 1 + 12}.
</Text>
<Text type="body1">{word}</Text>
</View>
))}
<View style={styles.centered}>
<View style={styles.columns}>
<View style={styles.leftColumn}>
{leftColumn.map((word, index) => (
<View style={styles.line} key={`${word}-${index}`}>
<Text type="body2" color="textSecondary" style={styles.num}>
{index + 1}.
</Text>
<Text type="body1">{word}</Text>
</View>
))}
</View>
<View>
{rightColumn.map((word, index) => (
<View style={styles.line} key={`${word}-${index + 1 + 12}`}>
<Text type="body2" color="textSecondary" style={styles.num}>
{index + 1 + 12}.
</Text>
<Text type="body1">{word}</Text>
</View>
))}
</View>
</View>
</View>
</View>
</View>

<View style={[styles.buttonContainer, { paddingBottom: safeArea.bottom + 32 }]}>
<Screen.ButtonSpacer />
</Screen.ScrollView>
<Screen.ButtonContainer>
{lastBackupAt !== null && !params.isBackupAgain ? (
<Button
title={t('recovery_phrase.copy_button')}
Expand All @@ -94,15 +96,14 @@ export const BackupPhraseScreen = memo(() => {
}
/>
)}
</View>
</Screen.ButtonContainer>
</Screen>
);
});

const styles = StyleSheet.create({
container: {
paddingHorizontal: 16,
flex: 1,
},
centered: {
justifyContent: 'center',
Expand All @@ -111,7 +112,7 @@ const styles = StyleSheet.create({
columns: {
flexDirection: 'row',
maxWidth: 310,
paddingTop: 16,
paddingVertical: 16,
},
line: {
width: 151,
Expand All @@ -129,8 +130,4 @@ const styles = StyleSheet.create({
position: 'relative',
top: 3,
},
buttonContainer: {
marginTop: 16,
marginHorizontal: 32,
},
});
45 changes: 33 additions & 12 deletions packages/mobile/src/wallet/Tonkeeper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ export interface WalletsStoreState {
selectedIdentifier: string;
biometryEnabled: boolean;
lockEnabled: boolean;
}

export interface MigrationState {
isMigrated: boolean;
}

Expand Down Expand Up @@ -77,6 +80,9 @@ export class Tonkeeper {
selectedIdentifier: '',
biometryEnabled: false,
lockEnabled: true,
});

public migrationStore = new State<MigrationState>({
isMigrated: false,
});

Expand All @@ -99,6 +105,11 @@ export class Tonkeeper {
storage: this.storage,
key: 'walletsStore',
});

this.migrationStore.persist({
storage: this.storage,
key: 'migrationStore',
});
}

public get wallet() {
Expand All @@ -122,12 +133,18 @@ export class Tonkeeper {
await Promise.all([
this.walletsStore.rehydrate(),
this.tonPrice.rehydrate(),
this.migrationStore.rehydrate(),
this.biometry.detectTypes(),
]);

// @ts-ignore moved to migrationStore, may be set on some clients. So we need to migrate it
if (this.walletsStore.data.isMigrated) {
this.setMigrated();
}

this.tonPrice.load();

if (!this.walletsStore.data.isMigrated) {
if (!this.migrationStore.data.isMigrated) {
this.migrationData = await this.getMigrationData();
}

Expand Down Expand Up @@ -237,7 +254,9 @@ export class Tonkeeper {
return indexA - indexB;
});

this.walletsStore.set(({ wallets }) => ({ wallets: [...wallets, ...sortedWallets] }));
await this.walletsStore.setAsync(({ wallets }) => ({
wallets: [...wallets, ...sortedWallets],
}));
const walletsInstances = await Promise.all(
sortedWallets.map((wallet) => this.createWalletInstance(wallet)),
);
Expand Down Expand Up @@ -334,7 +353,9 @@ export class Tonkeeper {
version,
};

this.walletsStore.set(({ wallets }) => ({ wallets: [...wallets, config] }));
await this.walletsStore.setAsync(({ wallets }) => ({
wallets: [...wallets, config],
}));
const wallet = await this.createWalletInstance(config);
this.setWallet(wallet);

Expand All @@ -348,7 +369,7 @@ export class Tonkeeper {
Array.from(this.wallets.keys()).find((item) => item !== identifier) ?? '',
) ?? null;

this.walletsStore.set(({ wallets }) => ({
await this.walletsStore.setAsync(({ wallets }) => ({
wallets: wallets.filter((w) => w.identifier !== identifier),
selectedIdentifier: nextWallet?.identifier ?? '',
}));
Expand All @@ -358,7 +379,7 @@ export class Tonkeeper {
this.wallets.delete(identifier);

if (this.wallets.size === 0) {
this.walletsStore.set({ biometryEnabled: false });
await this.walletsStore.setAsync({ biometryEnabled: false });
this.vault.destroy();
}

Expand All @@ -369,7 +390,7 @@ export class Tonkeeper {
}

public async removeAllWallets() {
this.walletsStore.set({
await this.walletsStore.setAsync({
wallets: [],
selectedIdentifier: '',
biometryEnabled: false,
Expand Down Expand Up @@ -441,7 +462,7 @@ export class Tonkeeper {
},
);

this.walletsStore.set({ wallets: updatedWallets });
await this.walletsStore.setAsync({ wallets: updatedWallets });

identifiers.forEach((identifier) => {
const currentConfig = updatedWallets.find(
Expand Down Expand Up @@ -496,7 +517,7 @@ export class Tonkeeper {

public setMigrated() {
console.log('migrated');
this.walletsStore.set({ isMigrated: true });
this.migrationStore.set({ isMigrated: true });
}

public saveLastBackupTimestampAll(identifiers: string[], dismissSetup = false) {
Expand All @@ -512,22 +533,22 @@ export class Tonkeeper {
public async enableBiometry(passcode: string) {
await this.vault.setupBiometry(passcode);

this.walletsStore.set({ biometryEnabled: true });
await this.walletsStore.setAsync({ biometryEnabled: true });
}

public async disableBiometry() {
try {
await this.vault.removeBiometry();
} catch {}

this.walletsStore.set({ biometryEnabled: false });
await this.walletsStore.setAsync({ biometryEnabled: false });
}

public async enableLock() {
this.walletsStore.set({ lockEnabled: true });
await this.walletsStore.setAsync({ lockEnabled: true });
}

public async disableLock() {
this.walletsStore.set({ lockEnabled: false });
await this.walletsStore.setAsync({ lockEnabled: false });
}
}

0 comments on commit f18d810

Please sign in to comment.