diff --git a/native/account/fullscreen-siwe-panel.react.js b/native/account/fullscreen-siwe-panel.react.js index 6121c3bd0..0f5b1c6c3 100644 --- a/native/account/fullscreen-siwe-panel.react.js +++ b/native/account/fullscreen-siwe-panel.react.js @@ -1,228 +1,207 @@ // @flow import { useNavigation } from '@react-navigation/native'; import invariant from 'invariant'; import * as React from 'react'; import { ActivityIndicator, View } from 'react-native'; import { setDataLoadedActionType } from 'lib/actions/client-db-store-actions.js'; import { useWalletLogIn } from 'lib/hooks/login-hooks.js'; import { type SIWEResult, SIWEMessageTypes } from 'lib/types/siwe-types.js'; import { ServerError, getMessageForException } from 'lib/utils/errors.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js'; import { useGetEthereumAccountFromSIWEResult } from './registration/ethereum-utils.js'; import { RegistrationContext } from './registration/registration-context.js'; -import { enableNewRegistrationMode } from './registration/registration-types.js'; import { useLegacySIWEServerCall } from './siwe-hooks.js'; import SIWEPanel from './siwe-panel.react.js'; import { commRustModule } from '../native-modules.js'; import { AccountDoesNotExistRouteName, RegistrationRouteName, } from '../navigation/route-names.js'; import { unknownErrorAlertDetails, appOutOfDateAlertDetails, } from '../utils/alert-messages.js'; import Alert from '../utils/alert.js'; const siweSignatureRequestData = { messageType: SIWEMessageTypes.MSG_AUTH }; type Props = { +goBackToPrompt: () => mixed, +closing: boolean, }; function FullscreenSIWEPanel(props: Props): React.Node { const [loading, setLoading] = React.useState(true); const activity = loading ? : null; const activityContainer = React.useMemo( () => ({ flex: 1, }), [], ); const registrationContext = React.useContext(RegistrationContext); invariant(registrationContext, 'registrationContext should be set'); - const { setSkipEthereumLoginOnce, register: registrationServerCall } = - registrationContext; + const { setSkipEthereumLoginOnce } = registrationContext; const getEthereumAccountFromSIWEResult = useGetEthereumAccountFromSIWEResult(); const { navigate } = useNavigation(); const { goBackToPrompt } = props; const onAccountDoesNotExist = React.useCallback( async (result: SIWEResult) => { await getEthereumAccountFromSIWEResult(result); setSkipEthereumLoginOnce(true); goBackToPrompt(); navigate<'Registration'>(RegistrationRouteName, { screen: AccountDoesNotExistRouteName, }); }, [ getEthereumAccountFromSIWEResult, navigate, goBackToPrompt, setSkipEthereumLoginOnce, ], ); const onNonceExpired = React.useCallback( (registrationOrLogin: 'registration' | 'login') => { Alert.alert( registrationOrLogin === 'registration' ? 'Registration attempt timed out' : 'Login attempt timed out', 'Please try again', [{ text: 'OK', onPress: goBackToPrompt }], { cancelable: false }, ); }, [goBackToPrompt], ); const legacySiweServerCall = useLegacySIWEServerCall(); const walletLogIn = useWalletLogIn(); const successRef = React.useRef(false); const dispatch = useDispatch(); const onSuccess = React.useCallback( async (result: SIWEResult) => { successRef.current = true; if (usingCommServicesAccessToken) { try { const findUserIDResponseString = await commRustModule.findUserIDForWalletAddress(result.address); const findUserIDResponse = JSON.parse(findUserIDResponseString); if (findUserIDResponse.userID || findUserIDResponse.isReserved) { try { await walletLogIn( result.address, result.message, result.signature, ); } catch (e) { const messageForException = getMessageForException(e); if (messageForException === 'nonce_expired') { onNonceExpired('login'); } else if ( messageForException === 'unsupported_version' || messageForException === 'client_version_unsupported' ) { Alert.alert( appOutOfDateAlertDetails.title, appOutOfDateAlertDetails.message, [{ text: 'OK', onPress: goBackToPrompt }], { cancelable: false }, ); } else { throw e; } } - } else if (enableNewRegistrationMode) { - await onAccountDoesNotExist(result); } else { - try { - await registrationServerCall({ - farcasterID: null, - accountSelection: { - accountType: 'ethereum', - ...result, - avatarURI: null, - }, - avatarData: null, - clearCachedSelections: () => {}, - onNonceExpired: () => onNonceExpired('registration'), - onAlertAcknowledged: goBackToPrompt, - }); - } catch { - // We swallow exceptions here because registrationServerCall - // already handles showing Alerts, and we don't want to show two - } + await onAccountDoesNotExist(result); } } catch (e) { Alert.alert( unknownErrorAlertDetails.title, unknownErrorAlertDetails.message, [{ text: 'OK', onPress: goBackToPrompt }], { cancelable: false }, ); } } else { try { await legacySiweServerCall({ ...result, - doNotRegister: enableNewRegistrationMode, + doNotRegister: true, }); } catch (e) { if ( e instanceof ServerError && e.message === 'account_does_not_exist' ) { await onAccountDoesNotExist(result); } else if ( e instanceof ServerError && e.message === 'client_version_unsupported' ) { Alert.alert( appOutOfDateAlertDetails.title, appOutOfDateAlertDetails.message, [{ text: 'OK', onPress: goBackToPrompt }], { cancelable: false }, ); } else { Alert.alert( unknownErrorAlertDetails.title, unknownErrorAlertDetails.message, [{ text: 'OK', onPress: goBackToPrompt }], { cancelable: false }, ); } return; } dispatch({ type: setDataLoadedActionType, payload: { dataLoaded: true, }, }); } }, [ walletLogIn, - registrationServerCall, goBackToPrompt, dispatch, legacySiweServerCall, onAccountDoesNotExist, onNonceExpired, ], ); const ifBeforeSuccessGoBackToPrompt = React.useCallback(() => { if (!successRef.current) { goBackToPrompt(); } }, [goBackToPrompt]); const { closing } = props; return ( <> {activity} ); } export default FullscreenSIWEPanel; diff --git a/native/account/legacy-register-panel.react.js b/native/account/legacy-register-panel.react.js deleted file mode 100644 index 1016ac585..000000000 --- a/native/account/legacy-register-panel.react.js +++ /dev/null @@ -1,508 +0,0 @@ -// @flow - -import invariant from 'invariant'; -import * as React from 'react'; -import { - Text, - View, - StyleSheet, - Platform, - Keyboard, - Linking, -} from 'react-native'; - -import { setDataLoadedActionType } from 'lib/actions/client-db-store-actions.js'; -import { - legacyKeyserverRegisterActionTypes, - legacyKeyserverRegister, - getOlmSessionInitializationDataActionTypes, -} from 'lib/actions/user-actions.js'; -import { useLegacyAshoatKeyserverCall } from 'lib/keyserver-conn/legacy-keyserver-call.js'; -import { - createLoadingStatusSelector, - combineLoadingStatuses, -} from 'lib/selectors/loading-selectors.js'; -import { validUsernameRegex } from 'lib/shared/account-utils.js'; -import { useInitialNotificationsEncryptedMessage } from 'lib/shared/crypto-utils.js'; -import type { - LegacyRegisterInfo, - LegacyLogInExtraInfo, - LegacyRegisterResult, - LegacyLogInStartingPayload, -} from 'lib/types/account-types.js'; -import type { LoadingStatus } from 'lib/types/loading-types.js'; -import type { Dispatch } from 'lib/types/redux-types.js'; -import { - useDispatchActionPromise, - type DispatchActionPromise, -} from 'lib/utils/redux-promise-utils.js'; -import { useDispatch } from 'lib/utils/redux-utils.js'; - -import { TextInput } from './modal-components.react.js'; -import { setNativeCredentials } from './native-credentials.js'; -import { PanelButton, Panel } from './panel-components.react.js'; -import { authoritativeKeyserverID } from '../authoritative-keyserver.js'; -import SWMansionIcon from '../components/swmansion-icon.react.js'; -import { useSelector } from '../redux/redux-utils.js'; -import { nativeLegacyLogInExtraInfoSelector } from '../selectors/account-selectors.js'; -import type { KeyPressEvent } from '../types/react-native.js'; -import type { ViewStyle } from '../types/styles.js'; -import { - appOutOfDateAlertDetails, - usernameReservedAlertDetails, - usernameTakenAlertDetails, - unknownErrorAlertDetails, -} from '../utils/alert-messages.js'; -import Alert from '../utils/alert.js'; -import { type StateContainer } from '../utils/state-container.js'; - -type WritableLegacyRegisterState = { - usernameInputText: string, - passwordInputText: string, - confirmPasswordInputText: string, -}; -export type LegacyRegisterState = $ReadOnly; -type BaseProps = { - +setActiveAlert: (activeAlert: boolean) => void, - +opacityStyle: ViewStyle, - +legacyRegisterState: StateContainer, -}; -type Props = { - ...BaseProps, - +loadingStatus: LoadingStatus, - +legacyLogInExtraInfo: () => Promise, - +dispatch: Dispatch, - +dispatchActionPromise: DispatchActionPromise, - +legacyRegister: ( - registerInfo: LegacyRegisterInfo, - ) => Promise, - +getInitialNotificationsEncryptedMessage: () => Promise, -}; -type State = { - +confirmPasswordFocused: boolean, -}; -class LegacyRegisterPanel extends React.PureComponent { - state: State = { - confirmPasswordFocused: false, - }; - usernameInput: ?TextInput; - passwordInput: ?TextInput; - confirmPasswordInput: ?TextInput; - passwordBeingAutoFilled = false; - - render(): React.Node { - let confirmPasswordTextInputExtraProps; - if ( - Platform.OS !== 'ios' || - this.state.confirmPasswordFocused || - this.props.legacyRegisterState.state.confirmPasswordInputText.length > 0 - ) { - confirmPasswordTextInputExtraProps = { - secureTextEntry: true, - textContentType: 'password', - }; - } - - let onPasswordKeyPress; - if (Platform.OS === 'ios') { - onPasswordKeyPress = this.onPasswordKeyPress; - } - - const privatePolicyNotice = ( - - - By signing up, you agree to our{' '} - - Terms - - {' & '} - - Privacy Policy - - . - - - ); - - return ( - - - - - - - - - - - - - - {privatePolicyNotice} - - - - ); - } - - usernameInputRef = (usernameInput: ?TextInput) => { - this.usernameInput = usernameInput; - }; - - passwordInputRef = (passwordInput: ?TextInput) => { - this.passwordInput = passwordInput; - }; - - confirmPasswordInputRef = (confirmPasswordInput: ?TextInput) => { - this.confirmPasswordInput = confirmPasswordInput; - }; - - focusUsernameInput = () => { - invariant(this.usernameInput, 'ref should be set'); - this.usernameInput.focus(); - }; - - focusPasswordInput = () => { - invariant(this.passwordInput, 'ref should be set'); - this.passwordInput.focus(); - }; - - focusConfirmPasswordInput = () => { - invariant(this.confirmPasswordInput, 'ref should be set'); - this.confirmPasswordInput.focus(); - }; - - onTermsOfUsePressed = () => { - void Linking.openURL('https://comm.app/terms'); - }; - - onPrivacyPolicyPressed = () => { - void Linking.openURL('https://comm.app/privacy'); - }; - - onChangeUsernameInputText = (text: string) => { - this.props.legacyRegisterState.setState({ usernameInputText: text }); - }; - - onChangePasswordInputText = (text: string) => { - const stateUpdate: Partial = {}; - stateUpdate.passwordInputText = text; - if (this.passwordBeingAutoFilled) { - this.passwordBeingAutoFilled = false; - stateUpdate.confirmPasswordInputText = text; - } - this.props.legacyRegisterState.setState(stateUpdate); - }; - - onPasswordKeyPress = (event: KeyPressEvent) => { - const { key } = event.nativeEvent; - if ( - key.length > 1 && - key !== 'Backspace' && - key !== 'Enter' && - this.props.legacyRegisterState.state.confirmPasswordInputText.length === 0 - ) { - this.passwordBeingAutoFilled = true; - } - }; - - onChangeConfirmPasswordInputText = (text: string) => { - this.props.legacyRegisterState.setState({ confirmPasswordInputText: text }); - }; - - onConfirmPasswordFocus = () => { - this.setState({ confirmPasswordFocused: true }); - }; - - onSubmit = async () => { - this.props.setActiveAlert(true); - if (this.props.legacyRegisterState.state.passwordInputText === '') { - Alert.alert( - 'Empty password', - 'Password cannot be empty', - [{ text: 'OK', onPress: this.onPasswordAlertAcknowledged }], - { cancelable: false }, - ); - } else if ( - this.props.legacyRegisterState.state.passwordInputText !== - this.props.legacyRegisterState.state.confirmPasswordInputText - ) { - Alert.alert( - 'Passwords don’t match', - 'Password fields must contain the same password', - [{ text: 'OK', onPress: this.onPasswordAlertAcknowledged }], - { cancelable: false }, - ); - } else if ( - this.props.legacyRegisterState.state.usernameInputText.search( - validUsernameRegex, - ) === -1 - ) { - Alert.alert( - 'Invalid username', - 'Usernames must be at least six characters long, start with either a ' + - 'letter or a number, and may contain only letters, numbers, or the ' + - 'characters “-” and “_”', - [{ text: 'OK', onPress: this.onUsernameAlertAcknowledged }], - { cancelable: false }, - ); - } else { - Keyboard.dismiss(); - const extraInfo = await this.props.legacyLogInExtraInfo(); - const initialNotificationsEncryptedMessage = - await this.props.getInitialNotificationsEncryptedMessage(); - void this.props.dispatchActionPromise( - legacyKeyserverRegisterActionTypes, - this.legacyRegisterAction({ - ...extraInfo, - initialNotificationsEncryptedMessage, - }), - undefined, - ({ - calendarQuery: extraInfo.calendarQuery, - }: LegacyLogInStartingPayload), - ); - } - }; - - onPasswordAlertAcknowledged = () => { - this.props.setActiveAlert(false); - this.props.legacyRegisterState.setState( - { - passwordInputText: '', - confirmPasswordInputText: '', - }, - () => { - invariant(this.passwordInput, 'ref should exist'); - this.passwordInput.focus(); - }, - ); - }; - - onUsernameAlertAcknowledged = () => { - this.props.setActiveAlert(false); - this.props.legacyRegisterState.setState( - { - usernameInputText: '', - }, - () => { - invariant(this.usernameInput, 'ref should exist'); - this.usernameInput.focus(); - }, - ); - }; - - async legacyRegisterAction( - extraInfo: LegacyLogInExtraInfo, - ): Promise { - try { - const result = await this.props.legacyRegister({ - ...extraInfo, - username: this.props.legacyRegisterState.state.usernameInputText, - password: this.props.legacyRegisterState.state.passwordInputText, - }); - this.props.setActiveAlert(false); - this.props.dispatch({ - type: setDataLoadedActionType, - payload: { - dataLoaded: true, - }, - }); - await setNativeCredentials({ - username: result.currentUserInfo.username, - password: this.props.legacyRegisterState.state.passwordInputText, - }); - return result; - } catch (e) { - if (e.message === 'username_reserved') { - Alert.alert( - usernameReservedAlertDetails.title, - usernameReservedAlertDetails.message, - [{ text: 'OK', onPress: this.onUsernameAlertAcknowledged }], - { cancelable: false }, - ); - } else if (e.message === 'username_taken') { - Alert.alert( - usernameTakenAlertDetails.title, - usernameTakenAlertDetails.message, - [{ text: 'OK', onPress: this.onUsernameAlertAcknowledged }], - { cancelable: false }, - ); - } else if (e.message === 'client_version_unsupported') { - Alert.alert( - appOutOfDateAlertDetails.title, - appOutOfDateAlertDetails.message, - [{ text: 'OK', onPress: this.onOtherErrorAlertAcknowledged }], - { cancelable: false }, - ); - } else { - Alert.alert( - unknownErrorAlertDetails.title, - unknownErrorAlertDetails.message, - [{ text: 'OK', onPress: this.onOtherErrorAlertAcknowledged }], - { cancelable: false }, - ); - } - throw e; - } - } - - onOtherErrorAlertAcknowledged = () => { - this.props.setActiveAlert(false); - }; -} - -const styles = StyleSheet.create({ - container: { - zIndex: 2, - }, - footer: { - alignItems: 'stretch', - flexDirection: 'row', - flexShrink: 1, - justifyContent: 'space-between', - paddingLeft: 24, - }, - hyperlinkText: { - color: '#036AFF', - fontWeight: 'bold', - }, - icon: { - bottom: 10, - left: 4, - position: 'absolute', - }, - input: { - paddingLeft: 35, - }, - notice: { - alignSelf: 'center', - display: 'flex', - flexShrink: 1, - maxWidth: 190, - paddingBottom: 18, - paddingRight: 8, - paddingTop: 12, - }, - noticeText: { - color: '#444', - fontSize: 13, - lineHeight: 20, - textAlign: 'center', - }, - row: { - marginHorizontal: 24, - }, -}); - -const registerLoadingStatusSelector = createLoadingStatusSelector( - legacyKeyserverRegisterActionTypes, -); -const olmSessionInitializationDataLoadingStatusSelector = - createLoadingStatusSelector(getOlmSessionInitializationDataActionTypes); - -const ConnectedLegacyRegisterPanel: React.ComponentType = - React.memo(function ConnectedLegacyRegisterPanel( - props: BaseProps, - ) { - const registerLoadingStatus = useSelector(registerLoadingStatusSelector); - const olmSessionInitializationDataLoadingStatus = useSelector( - olmSessionInitializationDataLoadingStatusSelector, - ); - const loadingStatus = combineLoadingStatuses( - registerLoadingStatus, - olmSessionInitializationDataLoadingStatus, - ); - - const legacyLogInExtraInfo = useSelector( - nativeLegacyLogInExtraInfoSelector, - ); - - const dispatch = useDispatch(); - const dispatchActionPromise = useDispatchActionPromise(); - const callLegacyRegister = useLegacyAshoatKeyserverCall( - legacyKeyserverRegister, - ); - const getInitialNotificationsEncryptedMessage = - useInitialNotificationsEncryptedMessage(authoritativeKeyserverID); - - return ( - - ); - }); - -export default ConnectedLegacyRegisterPanel; diff --git a/native/account/logged-out-modal.react.js b/native/account/logged-out-modal.react.js index 67920f62f..adf96643b 100644 --- a/native/account/logged-out-modal.react.js +++ b/native/account/logged-out-modal.react.js @@ -1,723 +1,657 @@ // @flow import Icon from '@expo/vector-icons/FontAwesome.js'; import * as React from 'react'; import { View, Text, TouchableOpacity, Image, Keyboard, Platform, BackHandler, ActivityIndicator, } from 'react-native'; import { Easing, useSharedValue, withTiming, useAnimatedStyle, runOnJS, } from 'react-native-reanimated'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useIsLoggedInToAuthoritativeKeyserver } from 'lib/hooks/account-hooks.js'; import { setActiveSessionRecoveryActionType } from 'lib/keyserver-conn/keyserver-conn-types.js'; import { isLoggedIn } from 'lib/selectors/user-selectors.js'; import { recoveryFromReduxActionSources } from 'lib/types/account-types.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js'; import { splashBackgroundURI } from './background-info.js'; import FullscreenSIWEPanel from './fullscreen-siwe-panel.react.js'; -import LegacyRegisterPanel from './legacy-register-panel.react.js'; -import type { LegacyRegisterState } from './legacy-register-panel.react.js'; import LogInPanel from './log-in-panel.react.js'; import type { LogInState } from './log-in-panel.react.js'; import LoggedOutStaffInfo from './logged-out-staff-info.react.js'; -import { enableNewRegistrationMode } from './registration/registration-types.js'; import { authoritativeKeyserverID } from '../authoritative-keyserver.js'; import KeyboardAvoidingView from '../components/keyboard-avoiding-view.react.js'; import ConnectedStatusBar from '../connected-status-bar.react.js'; import { useRatchetingKeyboardHeight } from '../keyboard/animated-keyboard.js'; import { createIsForegroundSelector } from '../navigation/nav-selectors.js'; import { NavContext } from '../navigation/navigation-context.js'; import type { RootNavigationProp } from '../navigation/root-navigator.react.js'; import { type NavigationRoute, LoggedOutModalRouteName, RegistrationRouteName, QRCodeSignInNavigatorRouteName, } from '../navigation/route-names.js'; import { useSelector } from '../redux/redux-utils.js'; import { usePersistedStateLoaded } from '../selectors/app-state-selectors.js'; import { derivedDimensionsInfoSelector } from '../selectors/dimensions-selectors.js'; import { splashStyleSelector } from '../splash.js'; import { useStyles } from '../themes/colors.js'; import { AnimatedView } from '../types/styles.js'; import EthereumLogo from '../vectors/ethereum-logo.react.js'; let initialAppLoad = true; const safeAreaEdges = ['top', 'bottom']; -export type LoggedOutMode = - | 'loading' - | 'prompt' - | 'log-in' - | 'register' - | 'siwe'; +export type LoggedOutMode = 'loading' | 'prompt' | 'log-in' | 'siwe'; const timingConfig = { duration: 250, easing: Easing.out(Easing.ease), }; // prettier-ignore function getPanelPaddingTop( modeValue /*: string */, keyboardHeightValue /*: number */, contentHeightValue /*: number */, ) /*: number */ { 'worklet'; const headerHeight = Platform.OS === 'ios' ? 62.33 : 58.54; let containerSize = headerHeight; if (modeValue === 'loading' || modeValue === 'prompt') { containerSize += Platform.OS === 'ios' ? 40 : 61; } else if (modeValue === 'log-in') { containerSize += 140; - } else if (modeValue === 'register') { - containerSize += Platform.OS === 'ios' ? 181 : 180; } else if (modeValue === 'siwe') { containerSize += 250; } const freeSpace = contentHeightValue - keyboardHeightValue - containerSize; const targetPanelPaddingTop = Math.max(freeSpace, 0) / 2; return withTiming(targetPanelPaddingTop, timingConfig); } // prettier-ignore function getPanelOpacity( modeValue /*: string */, finishResettingToPrompt/*: () => void */, ) /*: number */ { 'worklet'; const targetPanelOpacity = modeValue === 'loading' || modeValue === 'prompt' ? 0 : 1; return withTiming( targetPanelOpacity, timingConfig, (succeeded /*?: boolean */) => { if (succeeded && targetPanelOpacity === 0) { runOnJS(finishResettingToPrompt)(); } }, ); } const unboundStyles = { animationContainer: { flex: 1, }, backButton: { position: 'absolute', top: 13, }, button: { borderRadius: 4, marginBottom: 4, marginTop: 4, marginLeft: 4, marginRight: 4, paddingBottom: 14, paddingLeft: 18, paddingRight: 18, paddingTop: 14, flex: 1, }, buttonContainer: { bottom: 0, left: 0, marginLeft: 26, marginRight: 26, paddingBottom: 20, position: 'absolute', right: 0, }, buttonText: { fontFamily: 'OpenSans-Semibold', fontSize: 17, textAlign: 'center', }, classicAuthButton: { backgroundColor: 'purpleButton', }, classicAuthButtonText: { color: 'whiteText', }, registerButtons: { flexDirection: 'row', }, signInButtons: { flexDirection: 'row', }, container: { backgroundColor: 'transparent', flex: 1, }, header: { color: 'white', fontFamily: Platform.OS === 'ios' ? 'IBMPlexSans' : 'IBMPlexSans-Medium', fontSize: 56, fontWeight: '500', lineHeight: 66, textAlign: 'center', }, loadingIndicator: { paddingTop: 15, }, modalBackground: { bottom: 0, left: 0, position: 'absolute', right: 0, top: 0, }, siweButton: { backgroundColor: 'siweButton', flex: 1, flexDirection: 'row', justifyContent: 'center', }, siweButtonText: { color: 'siweButtonText', }, siweOr: { flex: 1, flexDirection: 'row', marginBottom: 18, marginTop: 14, }, siweOrLeftHR: { borderColor: 'logInSpacer', borderTopWidth: 1, flex: 1, marginRight: 18, marginTop: 10, }, siweOrRightHR: { borderColor: 'logInSpacer', borderTopWidth: 1, flex: 1, marginLeft: 18, marginTop: 10, }, siweOrText: { color: 'whiteText', fontSize: 17, textAlign: 'center', }, siweIcon: { paddingRight: 10, }, }; const isForegroundSelector = createIsForegroundSelector( LoggedOutModalRouteName, ); const backgroundSource = { uri: splashBackgroundURI }; const initialLogInState = { usernameInputText: null, passwordInputText: null, }; -const initialLegacyRegisterState = { - usernameInputText: '', - passwordInputText: '', - confirmPasswordInputText: '', -}; type Mode = { +curMode: LoggedOutMode, +nextMode: LoggedOutMode, }; type Props = { +navigation: RootNavigationProp<'LoggedOutModal'>, +route: NavigationRoute<'LoggedOutModal'>, }; function LoggedOutModal(props: Props) { const mountedRef = React.useRef(false); React.useEffect(() => { mountedRef.current = true; return () => { mountedRef.current = false; }; }, []); const [logInState, baseSetLogInState] = React.useState(initialLogInState); const setLogInState = React.useCallback( (newLogInState: Partial) => { if (!mountedRef.current) { return; } baseSetLogInState(prevLogInState => ({ ...prevLogInState, ...newLogInState, })); }, [], ); const logInStateContainer = React.useMemo( () => ({ state: logInState, setState: setLogInState, }), [logInState, setLogInState], ); - const [legacyRegisterState, baseSetLegacyRegisterState] = - React.useState(initialLegacyRegisterState); - const setLegacyRegisterState = React.useCallback( - (newLegacyRegisterState: Partial) => { - if (!mountedRef.current) { - return; - } - baseSetLegacyRegisterState(prevLegacyRegisterState => ({ - ...prevLegacyRegisterState, - ...newLegacyRegisterState, - })); - }, - [], - ); - const legacyRegisterStateContainer = React.useMemo( - () => ({ - state: legacyRegisterState, - setState: setLegacyRegisterState, - }), - [legacyRegisterState, setLegacyRegisterState], - ); - const persistedStateLoaded = usePersistedStateLoaded(); const initialMode = persistedStateLoaded ? 'prompt' : 'loading'; const [mode, baseSetMode] = React.useState(() => ({ curMode: initialMode, nextMode: initialMode, })); const setMode = React.useCallback((newMode: Partial) => { if (!mountedRef.current) { return; } baseSetMode(prevMode => ({ ...prevMode, ...newMode, })); }, []); const nextModeRef = React.useRef(initialMode); const dimensions = useSelector(derivedDimensionsInfoSelector); const contentHeight = useSharedValue(dimensions.safeAreaHeight); const modeValue = useSharedValue(initialMode); const buttonOpacity = useSharedValue(persistedStateLoaded ? 1 : 0); const onPrompt = mode.curMode === 'prompt'; const prevOnPromptRef = React.useRef(onPrompt); React.useEffect(() => { if (onPrompt && !prevOnPromptRef.current) { buttonOpacity.value = withTiming(1, { easing: Easing.out(Easing.ease), }); } prevOnPromptRef.current = onPrompt; }, [onPrompt, buttonOpacity]); const curContentHeight = dimensions.safeAreaHeight; const prevContentHeightRef = React.useRef(curContentHeight); React.useEffect(() => { if (curContentHeight === prevContentHeightRef.current) { return; } prevContentHeightRef.current = curContentHeight; contentHeight.value = curContentHeight; }, [curContentHeight, contentHeight]); const combinedSetMode = React.useCallback( (newMode: LoggedOutMode) => { nextModeRef.current = newMode; setMode({ curMode: newMode, nextMode: newMode }); modeValue.value = newMode; }, [setMode, modeValue], ); const goBackToPrompt = React.useCallback(() => { nextModeRef.current = 'prompt'; setMode({ nextMode: 'prompt' }); modeValue.value = 'prompt'; Keyboard.dismiss(); }, [setMode, modeValue]); const loadingCompleteRef = React.useRef(persistedStateLoaded); React.useEffect(() => { if (!loadingCompleteRef.current && persistedStateLoaded) { combinedSetMode('prompt'); loadingCompleteRef.current = true; } }, [persistedStateLoaded, combinedSetMode]); const [activeAlert, setActiveAlert] = React.useState(false); const navContext = React.useContext(NavContext); const isForeground = isForegroundSelector(navContext); const ratchetingKeyboardHeightInput = React.useMemo( () => ({ ignoreKeyboardDismissal: activeAlert, disabled: !isForeground, }), [activeAlert, isForeground], ); const keyboardHeightValue = useRatchetingKeyboardHeight( ratchetingKeyboardHeightInput, ); // We remove the password from the TextInput on iOS before dismissing it, // because otherwise iOS will prompt the user to save the password if the // iCloud password manager is enabled. We'll put the password back after the // dismissal concludes. const temporarilyHiddenPassword = React.useRef(); const curLogInPassword = logInState.passwordInputText; const resetToPrompt = React.useCallback(() => { if (nextModeRef.current === 'prompt') { return false; } if (Platform.OS === 'ios' && curLogInPassword) { temporarilyHiddenPassword.current = curLogInPassword; setLogInState({ passwordInputText: null }); } goBackToPrompt(); return true; }, [goBackToPrompt, curLogInPassword, setLogInState]); const finishResettingToPrompt = React.useCallback(() => { setMode({ curMode: nextModeRef.current }); if (temporarilyHiddenPassword.current) { setLogInState({ passwordInputText: temporarilyHiddenPassword.current }); temporarilyHiddenPassword.current = null; } }, [setMode, setLogInState]); React.useEffect(() => { if (!isForeground) { return undefined; } BackHandler.addEventListener('hardwareBackPress', resetToPrompt); return () => { BackHandler.removeEventListener('hardwareBackPress', resetToPrompt); }; }, [isForeground, resetToPrompt]); const rehydrateConcluded = useSelector( state => !!(state._persist && state._persist.rehydrated && navContext), ); const isLoggedInToAuthKeyserver = useIsLoggedInToAuthoritativeKeyserver(); const loggedIn = useSelector(isLoggedIn); const dispatch = useDispatch(); React.useEffect(() => { // This gets triggered when an app is killed and restarted // Not when it is returned from being backgrounded if (!initialAppLoad || !rehydrateConcluded) { return; } initialAppLoad = false; if (usingCommServicesAccessToken || __DEV__) { return; } if (loggedIn === isLoggedInToAuthKeyserver) { return; } const actionSource = loggedIn ? recoveryFromReduxActionSources.appStartReduxLoggedInButInvalidCookie : recoveryFromReduxActionSources.appStartCookieLoggedInButInvalidRedux; dispatch({ type: setActiveSessionRecoveryActionType, payload: { activeSessionRecovery: actionSource, keyserverID: authoritativeKeyserverID, }, }); }, [rehydrateConcluded, loggedIn, isLoggedInToAuthKeyserver, dispatch]); const onPressSIWE = React.useCallback(() => { combinedSetMode('siwe'); }, [combinedSetMode]); const onPressLogIn = React.useCallback(() => { combinedSetMode('log-in'); }, [combinedSetMode]); const { navigate } = props.navigation; const onPressQRCodeSignIn = React.useCallback(() => { navigate(QRCodeSignInNavigatorRouteName); }, [navigate]); - const onPressRegister = React.useCallback(() => { - combinedSetMode('register'); - }, [combinedSetMode]); - const onPressNewRegister = React.useCallback(() => { navigate(RegistrationRouteName); }, [navigate]); const opacityStyle = useAnimatedStyle(() => ({ opacity: getPanelOpacity(modeValue.value, finishResettingToPrompt), })); const styles = useStyles(unboundStyles); const panel = React.useMemo(() => { if (mode.curMode === 'log-in') { return ( ); - } else if (mode.curMode === 'register') { - return ( - - ); } else if (mode.curMode === 'loading') { return ( ); } return null; }, [ mode.curMode, setActiveAlert, opacityStyle, logInStateContainer, - legacyRegisterStateContainer, styles.loadingIndicator, ]); const classicAuthButtonStyle = React.useMemo( () => [styles.button, styles.classicAuthButton], [styles.button, styles.classicAuthButton], ); const classicAuthButtonTextStyle = React.useMemo( () => [styles.buttonText, styles.classicAuthButtonText], [styles.buttonText, styles.classicAuthButtonText], ); const siweAuthButtonStyle = React.useMemo( () => [styles.button, styles.siweButton], [styles.button, styles.siweButton], ); const siweAuthButtonTextStyle = React.useMemo( () => [styles.buttonText, styles.siweButtonText], [styles.buttonText, styles.siweButtonText], ); const buttonsViewOpacity = useAnimatedStyle(() => ({ opacity: buttonOpacity.value, })); const buttonsViewStyle = React.useMemo( () => [styles.buttonContainer, buttonsViewOpacity], [styles.buttonContainer, buttonsViewOpacity], ); const buttons = React.useMemo(() => { if (mode.curMode !== 'prompt') { return null; } - const registerButtons = []; - registerButtons.push( - - Register - , - ); - if (enableNewRegistrationMode) { - registerButtons.push( - - Register (new) - , - ); - } - const signInButtons = []; signInButtons.push( Sign in , ); if (__DEV__) { signInButtons.push( Sign in (QR) , ); } return ( Sign in with Ethereum or {signInButtons} - {registerButtons} + + + Register + + ); }, [ mode.curMode, - onPressRegister, onPressNewRegister, onPressLogIn, onPressQRCodeSignIn, onPressSIWE, classicAuthButtonStyle, classicAuthButtonTextStyle, siweAuthButtonStyle, siweAuthButtonTextStyle, buttonsViewStyle, styles.siweIcon, styles.siweOr, styles.siweOrLeftHR, styles.siweOrText, styles.siweOrRightHR, styles.signInButtons, styles.registerButtons, ]); const windowWidth = dimensions.width; const backButtonStyle = React.useMemo( () => [ styles.backButton, opacityStyle, { left: windowWidth < 360 ? 28 : 40 }, ], [styles.backButton, opacityStyle, windowWidth], ); const paddingTopStyle = useAnimatedStyle(() => ({ paddingTop: getPanelPaddingTop( modeValue.value, keyboardHeightValue.value, contentHeight.value, ), })); const animatedContentStyle = React.useMemo( () => [styles.animationContainer, paddingTopStyle], [styles.animationContainer, paddingTopStyle], ); const animatedContent = React.useMemo( () => ( Comm {panel} ), [ animatedContentStyle, styles.header, backButtonStyle, resetToPrompt, panel, ], ); const curModeIsSIWE = mode.curMode === 'siwe'; const nextModeIsPrompt = mode.nextMode === 'prompt'; const siwePanel = React.useMemo(() => { if (!curModeIsSIWE) { return null; } return ( ); }, [curModeIsSIWE, goBackToPrompt, nextModeIsPrompt]); const splashStyle = useSelector(splashStyleSelector); const backgroundStyle = React.useMemo( () => [styles.modalBackground, splashStyle], [styles.modalBackground, splashStyle], ); return React.useMemo( () => ( <> {animatedContent} {buttons} {siwePanel} ), [backgroundStyle, styles.container, animatedContent, buttons, siwePanel], ); } const MemoizedLoggedOutModal: React.ComponentType = React.memo(LoggedOutModal); export default MemoizedLoggedOutModal; diff --git a/native/account/registration/registration-types.js b/native/account/registration/registration-types.js index 0c2c6e791..e35f1c490 100644 --- a/native/account/registration/registration-types.js +++ b/native/account/registration/registration-types.js @@ -1,71 +1,69 @@ // @flow import type { UpdateUserAvatarRequest, ClientAvatar, } from 'lib/types/avatar-types.js'; import type { NativeMediaSelection } from 'lib/types/media-types.js'; import type { SIWEResult, SIWEBackupSecrets } from 'lib/types/siwe-types.js'; export type CoolOrNerdMode = 'cool' | 'nerd'; export type EthereumAccountSelection = { +accountType: 'ethereum', ...SIWEResult, +avatarURI: ?string, }; export type UsernameAccountSelection = { +accountType: 'username', +username: string, +password: string, }; export type AccountSelection = | EthereumAccountSelection | UsernameAccountSelection; export type AvatarData = | { +needsUpload: true, +mediaSelection: NativeMediaSelection, +clientAvatar: ClientAvatar, } | { +needsUpload: false, +updateUserAvatarRequest: UpdateUserAvatarRequest, +clientAvatar: ClientAvatar, }; export type RegistrationServerCallInput = { +coolOrNerdMode?: ?CoolOrNerdMode, +keyserverURL?: ?string, +farcasterID: ?string, +accountSelection: AccountSelection, +avatarData: ?AvatarData, +siweBackupSecrets?: ?SIWEBackupSecrets, +clearCachedSelections: () => void, +onNonceExpired: () => mixed, +onAlertAcknowledged?: () => mixed, }; export type CachedUserSelections = { +coolOrNerdMode?: CoolOrNerdMode, +keyserverURL?: string, +username?: string, +password?: string, +avatarData?: ?AvatarData, +ethereumAccount?: ?EthereumAccountSelection, +farcasterID?: string, +siweBackupSecrets?: ?SIWEBackupSecrets, }; export const ensAvatarSelection: AvatarData = { needsUpload: false, updateUserAvatarRequest: { type: 'ens' }, clientAvatar: { type: 'ens' }, }; -export const enableNewRegistrationMode = __DEV__; - export const enableSIWEBackupCreation = __DEV__;