diff --git a/native/account/registration/siwe-backup-message-creation.react.js b/native/account/registration/siwe-backup-message-creation.react.js
--- a/native/account/registration/siwe-backup-message-creation.react.js
+++ b/native/account/registration/siwe-backup-message-creation.react.js
@@ -37,9 +37,9 @@
function CreateSIWEBackupMessageBase(
props: CreateSIWEBackupMessageBaseProps,
): React.Node {
+ const styles = useStyles(unboundStyles);
const { onSuccessfulWalletSignature, onExistingWalletSignature, onSkip } =
props;
- const styles = useStyles(unboundStyles);
const {
panelState,
@@ -198,6 +198,74 @@
);
}
+type SignSIWEBackupMessageForRestoreBaseProps = {
+ +messageToSign: string,
+ +onSuccessfulWalletSignature: (result: SIWEResult) => void,
+ +onSkip: () => void,
+};
+
+function SignSIWEBackupMessageForRestore(
+ props: SignSIWEBackupMessageForRestoreBaseProps,
+): React.Node {
+ const styles = useStyles(unboundStyles);
+ const {
+ panelState,
+ openPanel,
+ onPanelClosed,
+ onPanelClosing,
+ siwePanelSetLoading,
+ } = useSIWEPanelState();
+
+ const { messageToSign, onSuccessfulWalletSignature, onSkip } = props;
+
+ let siwePanel;
+ if (panelState !== 'closed') {
+ siwePanel = (
+
+ );
+ }
+
+ return (
+ <>
+
+
+ Decrypting your Comm backup
+
+ Comm user backups are encrypted so that our backend is not able to
+ see user data.
+
+
+
+
+
+
+
+
+
+
+ {siwePanel}
+ >
+ );
+}
+
const unboundStyles = {
scrollViewContentContainer: {
flexGrow: 1,
@@ -224,4 +292,8 @@
},
};
-export { CreateSIWEBackupMessageBase, CreateSIWEBackupMessage };
+export {
+ CreateSIWEBackupMessageBase,
+ CreateSIWEBackupMessage,
+ SignSIWEBackupMessageForRestore,
+};
diff --git a/native/backup/restore-siwe-backup.react.js b/native/backup/restore-siwe-backup.react.js
new file mode 100644
--- /dev/null
+++ b/native/backup/restore-siwe-backup.react.js
@@ -0,0 +1,76 @@
+// @flow
+
+import * as React from 'react';
+import { Alert } from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+
+import { type SIWEResult } from 'lib/types/siwe-types.js';
+import { getMessageForException } from 'lib/utils/errors.js';
+
+import { SignSIWEBackupMessageForRestore } from '../account/registration/siwe-backup-message-creation.react.js';
+import { commCoreModule } from '../native-modules.js';
+import { type RootNavigationProp } from '../navigation/root-navigator.react.js';
+import { type NavigationRoute } from '../navigation/route-names.js';
+import { useStyles } from '../themes/colors.js';
+
+export type RestoreSIWEBackupParams = {
+ +backupID: string,
+ +siweBackupMsg: string,
+};
+
+type Props = {
+ +navigation: RootNavigationProp<'RestoreSIWEBackup'>,
+ +route: NavigationRoute<'RestoreSIWEBackup'>,
+};
+
+function RestoreSIWEBackup(props: Props): React.Node {
+ const styles = useStyles(unboundStyles);
+ const { goBack } = props.navigation;
+ const { route } = props;
+ const {
+ params: { backupID, siweBackupMsg },
+ } = route;
+
+ const onSuccessfulWalletSignature = React.useCallback(
+ (result: SIWEResult) => {
+ void (async () => {
+ const { signature } = result;
+ let message = 'success';
+ try {
+ await commCoreModule.restoreSIWEBackup(signature, backupID);
+ } catch (e) {
+ message = `Backup restore error: ${String(
+ getMessageForException(e),
+ )}`;
+ console.error(message);
+ }
+ Alert.alert('Restore protocol result', message);
+ goBack();
+ })();
+ },
+ [goBack, backupID],
+ );
+
+ return (
+
+ {
+ goBack();
+ }}
+ onSuccessfulWalletSignature={onSuccessfulWalletSignature}
+ />
+
+ );
+}
+
+const safeAreaEdges = ['top'];
+const unboundStyles = {
+ container: {
+ flex: 1,
+ backgroundColor: 'panelBackground',
+ justifyContent: 'space-between',
+ },
+};
+
+export default RestoreSIWEBackup;
diff --git a/native/backup/use-client-backup.js b/native/backup/use-client-backup.js
--- a/native/backup/use-client-backup.js
+++ b/native/backup/use-client-backup.js
@@ -11,9 +11,15 @@
import { commCoreModule } from '../native-modules.js';
import { useSelector } from '../redux/redux-utils.js';
+type SIWEBackupData = {
+ +backupID: string,
+ +siweBackupMsg: string,
+};
+
type ClientBackup = {
+uploadBackupProtocol: () => Promise,
- +restoreBackupProtocol: () => Promise,
+ +restorePasswordUserBackupProtocol: () => Promise,
+ +retrieveLatestSIWEBackupData: () => Promise,
};
async function getBackupSecret(): Promise {
@@ -78,21 +84,60 @@
currentUserInfo,
]);
- const restoreBackupProtocol = React.useCallback(async () => {
+ const restorePasswordUserBackupProtocol = React.useCallback(async () => {
if (!loggedIn || !currentUserID) {
throw new Error('Attempt to restore backup for not logged in user.');
}
- console.info('Start restoring backup...');
+ if (!accountHasPassword(currentUserInfo)) {
+ throw new Error(
+ 'Attempt to restore from password for non-password user.',
+ );
+ }
+ console.info('Start restoring backup...');
await setMockCommServicesAuthMetadata();
+
const backupSecret = await getBackupSecret();
await commCoreModule.restoreBackup(backupSecret);
console.info('Backup restored.');
- }, [currentUserID, loggedIn, setMockCommServicesAuthMetadata]);
+ return;
+ }, [
+ currentUserID,
+ loggedIn,
+ setMockCommServicesAuthMetadata,
+ currentUserInfo,
+ ]);
+
+ const retrieveLatestSIWEBackupData = React.useCallback(async () => {
+ if (!loggedIn || !currentUserID) {
+ throw new Error('Attempt to restore backup for not logged in user.');
+ }
+
+ if (accountHasPassword(currentUserInfo)) {
+ throw new Error(
+ 'Attempt to retrieve siwe backup data for password user.',
+ );
+ }
+
+ await setMockCommServicesAuthMetadata();
+ const serializedBackupData =
+ await commCoreModule.retrieveLatestSIWEBackupData();
+ const siweBackupData: SIWEBackupData = JSON.parse(serializedBackupData);
+ return siweBackupData;
+ }, [
+ currentUserID,
+ currentUserInfo,
+ loggedIn,
+ setMockCommServicesAuthMetadata,
+ ]);
- return { uploadBackupProtocol, restoreBackupProtocol };
+ return {
+ uploadBackupProtocol,
+ restorePasswordUserBackupProtocol,
+ retrieveLatestSIWEBackupData,
+ };
}
export { getBackupSecret, useClientBackup };
diff --git a/native/navigation/root-navigator.react.js b/native/navigation/root-navigator.react.js
--- a/native/navigation/root-navigator.react.js
+++ b/native/navigation/root-navigator.react.js
@@ -55,11 +55,13 @@
ConnectFarcasterBottomSheetRouteName,
TagFarcasterChannelNavigatorRouteName,
CreateMissingSIWEBackupMessageRouteName,
+ RestoreSIWEBackupRouteName,
} from './route-names.js';
import LoggedOutModal from '../account/logged-out-modal.react.js';
import CreateMissingSIWEBackupMessage from '../account/registration/missing-registration-data/missing-siwe-backup-message.react.js';
import RegistrationNavigator from '../account/registration/registration-navigator.react.js';
import TermsAndPrivacyModal from '../account/terms-and-privacy-modal.react.js';
+import RestoreSIWEBackup from '../backup/restore-siwe-backup.react.js';
import ThreadPickerModal from '../calendar/thread-picker-modal.react.js';
import ImagePasteModal from '../chat/image-paste-modal.react.js';
import MessageReactionsModal from '../chat/message-reactions-modal.react.js';
@@ -303,6 +305,10 @@
name={CreateMissingSIWEBackupMessageRouteName}
component={CreateMissingSIWEBackupMessage}
/>
+
);
}
diff --git a/native/navigation/route-names.js b/native/navigation/route-names.js
--- a/native/navigation/route-names.js
+++ b/native/navigation/route-names.js
@@ -15,6 +15,7 @@
import type { CreateSIWEBackupMessageParams } from '../account/registration/siwe-backup-message-creation.react.js';
import type { UsernameSelectionParams } from '../account/registration/username-selection.react.js';
import type { TermsAndPrivacyModalParams } from '../account/terms-and-privacy-modal.react.js';
+import type { RestoreSIWEBackupParams } from '../backup/restore-siwe-backup.react.js';
import type { ThreadPickerModalParams } from '../calendar/thread-picker-modal.react.js';
import type { ComposeSubchannelParams } from '../chat/compose-subchannel.react.js';
import type { FullScreenThreadMediaGalleryParams } from '../chat/fullscreen-thread-media-gallery.react.js';
@@ -127,6 +128,7 @@
export const CreateSIWEBackupMessageRouteName = 'CreateSIWEBackupMessage';
export const CreateMissingSIWEBackupMessageRouteName =
'CreateMissingSIWEBackupMessage';
+export const RestoreSIWEBackupRouteName = 'RestoreSIWEBackup';
export const ExistingEthereumAccountRouteName = 'ExistingEthereumAccount';
export const ConnectFarcasterRouteName = 'ConnectFarcaster';
export const UsernameSelectionRouteName = 'UsernameSelection';
@@ -187,6 +189,7 @@
+ConnectFarcasterBottomSheet: void,
+TagFarcasterChannelNavigator: void,
+CreateMissingSIWEBackupMessage: void,
+ +RestoreSIWEBackup: RestoreSIWEBackupParams,
};
export type MessageTooltipRouteNames =
diff --git a/native/profile/backup-menu.react.js b/native/profile/backup-menu.react.js
--- a/native/profile/backup-menu.react.js
+++ b/native/profile/backup-menu.react.js
@@ -1,9 +1,11 @@
// @flow
+import { useNavigation } from '@react-navigation/native';
import * as React from 'react';
import { Alert, Switch, Text, View } from 'react-native';
import { ScrollView } from 'react-native-gesture-handler';
+import { accountHasPassword } from 'lib/shared/account-utils.js';
import { getMessageForException } from 'lib/utils/errors.js';
import { useDispatch } from 'lib/utils/redux-utils.js';
@@ -11,6 +13,7 @@
import { useClientBackup } from '../backup/use-client-backup.js';
import Button from '../components/button.react.js';
import type { NavigationRoute } from '../navigation/route-names.js';
+import { RestoreSIWEBackupRouteName } from '../navigation/route-names.js';
import { setLocalSettingsActionType } from '../redux/action-types.js';
import { useSelector } from '../redux/redux-utils.js';
import { useColors, useStyles } from '../themes/colors.js';
@@ -24,12 +27,18 @@
const styles = useStyles(unboundStyles);
const dispatch = useDispatch();
const colors = useColors();
+ const currentUserInfo = useSelector(state => state.currentUserInfo);
+ const navigation = useNavigation();
const isBackupEnabled = useSelector(
state => state.localSettings.isBackupEnabled,
);
- const { uploadBackupProtocol, restoreBackupProtocol } = useClientBackup();
+ const {
+ uploadBackupProtocol,
+ restorePasswordUserBackupProtocol,
+ retrieveLatestSIWEBackupData,
+ } = useClientBackup();
const uploadBackup = React.useCallback(async () => {
let message = 'Success';
@@ -42,16 +51,34 @@
Alert.alert('Upload protocol result', message);
}, [uploadBackupProtocol]);
- const testRestore = React.useCallback(async () => {
+ const testRestoreForPasswordUser = React.useCallback(async () => {
let message = 'success';
try {
- await restoreBackupProtocol();
+ await restorePasswordUserBackupProtocol();
} catch (e) {
message = `Backup restore error: ${String(getMessageForException(e))}`;
console.error(message);
}
Alert.alert('Restore protocol result', message);
- }, [restoreBackupProtocol]);
+ }, [restorePasswordUserBackupProtocol]);
+
+ const testRestoreForSIWEUser = React.useCallback(async () => {
+ let message = 'success';
+ try {
+ const { backupID, siweBackupMsg } = await retrieveLatestSIWEBackupData();
+
+ navigation.navigate<'RestoreSIWEBackup'>({
+ name: RestoreSIWEBackupRouteName,
+ params: {
+ backupID,
+ siweBackupMsg,
+ },
+ });
+ } catch (e) {
+ message = `Backup restore error: ${String(getMessageForException(e))}`;
+ console.error(message);
+ }
+ }, [navigation, retrieveLatestSIWEBackupData]);
const onBackupToggled = React.useCallback(
(value: boolean) => {
@@ -63,6 +90,10 @@
[dispatch],
);
+ const onPressRestoreButton = accountHasPassword(currentUserInfo)
+ ? testRestoreForPasswordUser
+ : testRestoreForSIWEUser;
+
return (