diff --git a/native/account/registration/avatar-selection.react.js b/native/account/registration/avatar-selection.react.js --- a/native/account/registration/avatar-selection.react.js +++ b/native/account/registration/avatar-selection.react.js @@ -21,11 +21,13 @@ type AvatarData, ensAvatarSelection, } from './registration-types.js'; +import { enableSIWEBackupCreation } from './registration-types.js'; import EditUserAvatar from '../../avatars/edit-user-avatar.react.js'; import { useCurrentLeafRouteName } from '../../navigation/nav-selectors.js'; import { type NavigationRoute, RegistrationTermsRouteName, + CreateSIWEBackupMessageRouteName, AvatarSelectionRouteName, EmojiAvatarSelectionRouteName, RegistrationUserAvatarCameraModalRouteName, @@ -144,6 +146,16 @@ ...userSelections, avatarData, }; + if ( + userSelections.accountSelection.accountType === 'ethereum' && + enableSIWEBackupCreation + ) { + navigate<'CreateSIWEBackupMessage'>({ + name: CreateSIWEBackupMessageRouteName, + params: { userSelections: newUserSelections }, + }); + return; + } navigate<'RegistrationTerms'>({ name: RegistrationTermsRouteName, params: { userSelections: newUserSelections }, diff --git a/native/account/registration/registration-navigator.react.js b/native/account/registration/registration-navigator.react.js --- a/native/account/registration/registration-navigator.react.js +++ b/native/account/registration/registration-navigator.react.js @@ -17,6 +17,7 @@ import KeyserverSelection from './keyserver-selection.react.js'; import PasswordSelection from './password-selection.react.js'; import RegistrationTerms from './registration-terms.react.js'; +import CreateSIWEBackupMessage from './siwe-backup-message-creation.react.js'; import UsernameSelection from './username-selection.react.js'; import RegistrationUserAvatarCameraModal from '../../media/registration-user-avatar-camera-modal.react.js'; import type { RootNavigationProp } from '../../navigation/root-navigator.react.js'; @@ -24,6 +25,7 @@ KeyserverSelectionRouteName, CoolOrNerdModeSelectionRouteName, ConnectEthereumRouteName, + CreateSIWEBackupMessageRouteName, ExistingEthereumAccountRouteName, UsernameSelectionRouteName, ConnectFarcasterRouteName, @@ -89,6 +91,10 @@ name={ConnectFarcasterRouteName} component={ConnectFarcaster} /> + <Registration.Screen + name={CreateSIWEBackupMessageRouteName} + component={CreateSIWEBackupMessage} + /> <Registration.Screen name={UsernameSelectionRouteName} component={UsernameSelection} diff --git a/native/account/registration/registration-types.js b/native/account/registration/registration-types.js --- a/native/account/registration/registration-types.js +++ b/native/account/registration/registration-types.js @@ -5,7 +5,7 @@ ClientAvatar, } from 'lib/types/avatar-types.js'; import type { NativeMediaSelection } from 'lib/types/media-types.js'; -import type { SIWEResult } from 'lib/types/siwe-types.js'; +import type { SIWEResult, SIWEBackupSecrets } from 'lib/types/siwe-types.js'; export type CoolOrNerdMode = 'cool' | 'nerd'; @@ -53,6 +53,7 @@ +avatarData?: ?AvatarData, +ethereumAccount?: EthereumAccountSelection, +farcasterID?: string, + +siweBackupSecrets?: ?SIWEBackupSecrets, }; export const ensAvatarSelection: AvatarData = { @@ -62,3 +63,5 @@ }; export const enableNewRegistrationMode = __DEV__; + +export const enableSIWEBackupCreation = __DEV__; diff --git a/native/account/registration/siwe-backup-message-creation.react.js b/native/account/registration/siwe-backup-message-creation.react.js new file mode 100644 --- /dev/null +++ b/native/account/registration/siwe-backup-message-creation.react.js @@ -0,0 +1,187 @@ +// @flow + +import Icon from '@expo/vector-icons/MaterialIcons.js'; +import invariant from 'invariant'; +import * as React from 'react'; +import { View, Text } from 'react-native'; + +import { type SIWEResult, SIWEMessageTypes } from 'lib/types/siwe-types.js'; + +import RegistrationButtonContainer from './registration-button-container.react.js'; +import RegistrationButton from './registration-button.react.js'; +import RegistrationContainer from './registration-container.react.js'; +import RegistrationContentContainer from './registration-content-container.react.js'; +import { RegistrationContext } from './registration-context.js'; +import { type RegistrationNavigationProp } from './registration-navigator.react.js'; +import type { + CoolOrNerdMode, + AccountSelection, + AvatarData, +} from './registration-types.js'; +import { + type NavigationRoute, + RegistrationTermsRouteName, +} from '../../navigation/route-names.js'; +import { useStyles } from '../../themes/colors.js'; +import SIWEPanel from '../siwe-panel.react.js'; + +export type CreateSIWEBackupMessageParams = { + +userSelections: { + +coolOrNerdMode: CoolOrNerdMode, + +keyserverURL: string, + +farcasterID: ?string, + +accountSelection: AccountSelection, + +avatarData: ?AvatarData, + }, +}; + +type PanelState = 'closed' | 'opening' | 'open' | 'closing'; + +type Props = { + +navigation: RegistrationNavigationProp<'CreateSIWEBackupMessage'>, + +route: NavigationRoute<'CreateSIWEBackupMessage'>, +}; +function CreateSIWEBackupMessage(props: Props): React.Node { + const { navigate } = props.navigation; + const { params } = props.route; + + const styles = useStyles(unboundStyles); + + const [panelState, setPanelState] = React.useState<PanelState>('closed'); + + const openPanel = React.useCallback(() => { + setPanelState('opening'); + }, []); + const onPanelClosed = React.useCallback(() => { + setPanelState('closed'); + }, []); + const onPanelClosing = React.useCallback(() => { + setPanelState('closing'); + }, []); + + const siwePanelSetLoading = React.useCallback( + (loading: boolean) => { + if (panelState === 'closing' || panelState === 'closed') { + return; + } + setPanelState(loading ? 'opening' : 'open'); + }, + [panelState], + ); + + const registrationContext = React.useContext(RegistrationContext); + invariant(registrationContext, 'registrationContext should be set'); + const { cachedSelections, setCachedSelections } = registrationContext; + + const onSuccessfulWalletSignature = React.useCallback( + (result: SIWEResult) => { + const { message, signature } = result; + setCachedSelections(oldUserSelections => ({ + ...oldUserSelections, + siweBackupSecrets: { message, signature }, + })); + navigate<'RegistrationTerms'>({ + name: RegistrationTermsRouteName, + params, + }); + }, + [navigate, params, setCachedSelections], + ); + + const onExistingWalletSignature = React.useCallback(() => { + navigate<'RegistrationTerms'>({ + name: RegistrationTermsRouteName, + params, + }); + }, [params, navigate]); + + let siwePanel; + if (panelState !== 'closed') { + siwePanel = ( + <SIWEPanel + onClosing={onPanelClosing} + onClosed={onPanelClosed} + closing={panelState === 'closing'} + onSuccessfulWalletSignature={onSuccessfulWalletSignature} + siweMessageType={SIWEMessageTypes.MSG_BACKUP} + setLoading={siwePanelSetLoading} + /> + ); + } + + const { siweBackupSecrets } = cachedSelections; + + const newSignatureButtonText = siweBackupSecrets + ? 'Encrypt with new signature' + : 'Encrypt with Ethereum signature'; + const newSignatureButtonVariant = siweBackupSecrets ? 'outline' : 'enabled'; + + let useExistingSignatureButton; + if (siweBackupSecrets) { + useExistingSignatureButton = ( + <RegistrationButton + onPress={onExistingWalletSignature} + label="Encrypt with existing signature" + variant="enabled" + /> + ); + } + + const body = ( + <Text style={styles.body}> + Comm encrypts user backups so that our backend is not able to see user + data. + </Text> + ); + + return ( + <> + <RegistrationContainer> + <RegistrationContentContainer style={styles.scrollViewContentContainer}> + <Text style={styles.header}>Encrypting your Comm Backup</Text> + {body} + <View style={styles.siweBackupIconContainer}> + <Icon name="backup" size={200} style={styles.siweBackupIcon} /> + </View> + </RegistrationContentContainer> + <RegistrationButtonContainer> + {useExistingSignatureButton} + <RegistrationButton + onPress={openPanel} + label={newSignatureButtonText} + variant={newSignatureButtonVariant} + /> + </RegistrationButtonContainer> + </RegistrationContainer> + {siwePanel} + </> + ); +} + +const unboundStyles = { + scrollViewContentContainer: { + flexGrow: 1, + }, + header: { + fontSize: 24, + color: 'panelForegroundLabel', + paddingBottom: 16, + }, + body: { + fontFamily: 'Arial', + fontSize: 15, + lineHeight: 20, + color: 'panelForegroundSecondaryLabel', + paddingBottom: 16, + }, + siweBackupIcon: { + color: 'panelForegroundIcon', + }, + siweBackupIconContainer: { + flexGrow: 1, + alignItems: 'center', + justifyContent: 'center', + }, +}; + +export default CreateSIWEBackupMessage; 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 @@ -12,6 +12,7 @@ import type { KeyserverSelectionParams } from '../account/registration/keyserver-selection.react.js'; import type { PasswordSelectionParams } from '../account/registration/password-selection.react.js'; import type { RegistrationTermsParams } from '../account/registration/registration-terms.react.js'; +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 { ThreadPickerModalParams } from '../calendar/thread-picker-modal.react.js'; @@ -121,6 +122,7 @@ export const KeyserverSelectionRouteName = 'KeyserverSelection'; export const CoolOrNerdModeSelectionRouteName = 'CoolOrNerdModeSelection'; export const ConnectEthereumRouteName = 'ConnectEthereum'; +export const CreateSIWEBackupMessageRouteName = 'CreateSIWEBackupMessage'; export const ExistingEthereumAccountRouteName = 'ExistingEthereumAccount'; export const ConnectFarcasterRouteName = 'ConnectFarcaster'; export const UsernameSelectionRouteName = 'UsernameSelection'; @@ -265,6 +267,7 @@ +ConnectEthereum: ConnectEthereumParams, +ExistingEthereumAccount: ExistingEthereumAccountParams, +ConnectFarcaster: ConnectFarcasterParams, + +CreateSIWEBackupMessage: CreateSIWEBackupMessageParams, +UsernameSelection: UsernameSelectionParams, +PasswordSelection: PasswordSelectionParams, +AvatarSelection: AvatarSelectionParams, diff --git a/native/themes/colors.js b/native/themes/colors.js --- a/native/themes/colors.js +++ b/native/themes/colors.js @@ -109,6 +109,7 @@ panelForeground: designSystemColors.shadesWhite100, panelForegroundBorder: designSystemColors.shadesWhite60, panelForegroundLabel: designSystemColors.shadesBlack95, + panelForegroundIcon: designSystemColors.shadesBlack95, panelForegroundSecondaryLabel: '#333333', panelForegroundTertiaryLabel: '#888888', panelInputBackground: designSystemColors.shadesWhite60, @@ -211,6 +212,7 @@ panelForeground: designSystemColors.shadesBlack85, panelForegroundBorder: '#2C2C2E', panelForegroundLabel: designSystemColors.shadesWhite100, + panelForegroundIcon: designSystemColors.shadesWhite100, panelForegroundSecondaryLabel: designSystemColors.shadesWhite60, panelForegroundTertiaryLabel: '#AAAAAA', panelInputBackground: designSystemColors.shadesBlack75,