Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3508904
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
24 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/native/account/registration/registration-server-call.js b/native/account/registration/registration-server-call.js
index f4ada4c5c..2d23cc2cb 100644
--- a/native/account/registration/registration-server-call.js
+++ b/native/account/registration/registration-server-call.js
@@ -1,338 +1,351 @@
// @flow
import * as React from 'react';
import { setDataLoadedActionType } from 'lib/actions/client-db-store-actions.js';
import { setSyncedMetadataEntryActionType } from 'lib/actions/synced-metadata-actions.js';
import {
keyserverRegisterActionTypes,
keyserverRegister,
useIdentityPasswordRegister,
identityRegisterActionTypes,
} from 'lib/actions/user-actions.js';
import { useLegacyAshoatKeyserverCall } from 'lib/keyserver-conn/legacy-keyserver-call.js';
import { isLoggedInToKeyserver } from 'lib/selectors/user-selectors.js';
import type { LogInStartingPayload } from 'lib/types/account-types.js';
import { syncedMetadataNames } from 'lib/types/synced-metadata-types.js';
import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js';
import { useDispatch } from 'lib/utils/redux-utils.js';
import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js';
import { setURLPrefix } from 'lib/utils/url-utils.js';
import type {
RegistrationServerCallInput,
UsernameAccountSelection,
AvatarData,
} from './registration-types.js';
import { authoritativeKeyserverID } from '../../authoritative-keyserver.js';
import {
useNativeSetUserAvatar,
useUploadSelectedMedia,
} from '../../avatars/avatar-hooks.js';
+import { commCoreModule } from '../../native-modules.js';
import { useSelector } from '../../redux/redux-utils.js';
import { nativeLogInExtraInfoSelector } from '../../selectors/account-selectors.js';
import {
AppOutOfDateAlertDetails,
UsernameReservedAlertDetails,
UsernameTakenAlertDetails,
UnknownErrorAlertDetails,
} from '../../utils/alert-messages.js';
import Alert from '../../utils/alert.js';
import { setNativeCredentials } from '../native-credentials.js';
import {
useLegacySIWEServerCall,
useIdentityWalletRegisterCall,
} from '../siwe-hooks.js';
// We can't just do everything in one async callback, since the server calls
// would get bound to Redux state from before the registration. The registration
// flow has multiple steps where critical Redux state is changed, where
// subsequent steps depend on accessing the updated Redux state.
// To address this, we break the registration process up into multiple steps.
// When each step completes we update the currentStep state, and we have Redux
// selectors that trigger useEffects for subsequent steps when relevant data
// starts to appear in Redux.
type CurrentStep =
| { +step: 'inactive' }
| {
+step: 'waiting_for_registration_call',
+ +clearCachedSelections: () => void,
+avatarData: ?AvatarData,
+resolve: () => void,
+reject: Error => void,
};
const inactiveStep = { step: 'inactive' };
function useRegistrationServerCall(): RegistrationServerCallInput => Promise<void> {
const [currentStep, setCurrentStep] =
React.useState<CurrentStep>(inactiveStep);
// STEP 1: ACCOUNT REGISTRATION
const logInExtraInfo = useSelector(nativeLogInExtraInfoSelector);
const dispatchActionPromise = useDispatchActionPromise();
const callKeyserverRegister = useLegacyAshoatKeyserverCall(keyserverRegister);
const callIdentityPasswordRegister = useIdentityPasswordRegister();
const identityRegisterUsernameAccount = React.useCallback(
async (
accountSelection: UsernameAccountSelection,
farcasterID: ?string,
) => {
const identityRegisterPromise = (async () => {
try {
const result = await callIdentityPasswordRegister(
accountSelection.username,
accountSelection.password,
farcasterID,
);
await setNativeCredentials({
username: accountSelection.username,
password: accountSelection.password,
});
return result;
} catch (e) {
if (e.message === 'username reserved') {
Alert.alert(
UsernameReservedAlertDetails.title,
UsernameReservedAlertDetails.message,
);
} else if (e.message === 'username already exists') {
Alert.alert(
UsernameTakenAlertDetails.title,
UsernameTakenAlertDetails.message,
);
} else if (e.message === 'Unsupported version') {
Alert.alert(
AppOutOfDateAlertDetails.title,
AppOutOfDateAlertDetails.message,
);
} else {
Alert.alert(
UnknownErrorAlertDetails.title,
UnknownErrorAlertDetails.message,
);
}
throw e;
}
})();
void dispatchActionPromise(
identityRegisterActionTypes,
identityRegisterPromise,
);
await identityRegisterPromise;
},
[callIdentityPasswordRegister, dispatchActionPromise],
);
const keyserverRegisterUsernameAccount = React.useCallback(
async (
accountSelection: UsernameAccountSelection,
keyserverURL: string,
) => {
const extraInfo = await logInExtraInfo();
const keyserverRegisterPromise = (async () => {
try {
const result = await callKeyserverRegister(
{
...extraInfo,
username: accountSelection.username,
password: accountSelection.password,
},
{
urlPrefixOverride: keyserverURL,
},
);
await setNativeCredentials({
username: result.currentUserInfo.username,
password: accountSelection.password,
});
return result;
} catch (e) {
if (e.message === 'username_reserved') {
Alert.alert(
UsernameReservedAlertDetails.title,
UsernameReservedAlertDetails.message,
);
} else if (e.message === 'username_taken') {
Alert.alert(
UsernameTakenAlertDetails.title,
UsernameTakenAlertDetails.message,
);
} else if (e.message === 'client_version_unsupported') {
Alert.alert(
AppOutOfDateAlertDetails.title,
AppOutOfDateAlertDetails.message,
);
} else {
Alert.alert(
UnknownErrorAlertDetails.title,
UnknownErrorAlertDetails.message,
);
}
throw e;
}
})();
void dispatchActionPromise(
keyserverRegisterActionTypes,
keyserverRegisterPromise,
undefined,
({ calendarQuery: extraInfo.calendarQuery }: LogInStartingPayload),
);
await keyserverRegisterPromise;
},
[logInExtraInfo, callKeyserverRegister, dispatchActionPromise],
);
const legacySiweServerCall = useLegacySIWEServerCall();
const identityWalletRegisterCall = useIdentityWalletRegisterCall();
const dispatch = useDispatch();
const returnedFunc = React.useCallback(
(input: RegistrationServerCallInput) =>
new Promise<void>(
// eslint-disable-next-line no-async-promise-executor
async (resolve, reject) => {
try {
if (currentStep.step !== 'inactive') {
return;
}
- const { accountSelection, avatarData, keyserverURL, farcasterID } =
- input;
+ const {
+ accountSelection,
+ avatarData,
+ keyserverURL,
+ farcasterID,
+ siweBackupSecrets,
+ clearCachedSelections,
+ } = input;
if (
accountSelection.accountType === 'username' &&
!usingCommServicesAccessToken
) {
await keyserverRegisterUsernameAccount(
accountSelection,
keyserverURL,
);
} else if (accountSelection.accountType === 'username') {
await identityRegisterUsernameAccount(
accountSelection,
farcasterID,
);
} else if (!usingCommServicesAccessToken) {
try {
await legacySiweServerCall(accountSelection, {
urlPrefixOverride: keyserverURL,
});
} catch (e) {
Alert.alert(
UnknownErrorAlertDetails.title,
UnknownErrorAlertDetails.message,
);
throw e;
}
} else {
try {
await identityWalletRegisterCall({
address: accountSelection.address,
message: accountSelection.message,
signature: accountSelection.signature,
fid: farcasterID,
});
} catch (e) {
Alert.alert(
UnknownErrorAlertDetails.title,
UnknownErrorAlertDetails.message,
);
throw e;
}
}
dispatch({
type: setURLPrefix,
payload: keyserverURL,
});
if (farcasterID) {
dispatch({
type: setSyncedMetadataEntryActionType,
payload: {
name: syncedMetadataNames.CURRENT_USER_FID,
data: farcasterID,
},
});
}
+ if (siweBackupSecrets) {
+ await commCoreModule.setSIWEBackupSecrets(siweBackupSecrets);
+ }
setCurrentStep({
step: 'waiting_for_registration_call',
avatarData,
+ clearCachedSelections,
resolve,
reject,
});
} catch (e) {
reject(e);
}
},
),
[
currentStep,
keyserverRegisterUsernameAccount,
identityRegisterUsernameAccount,
legacySiweServerCall,
dispatch,
identityWalletRegisterCall,
],
);
// STEP 2: SETTING AVATAR
const uploadSelectedMedia = useUploadSelectedMedia();
const nativeSetUserAvatar = useNativeSetUserAvatar();
const isLoggedInToAuthoritativeKeyserver = useSelector(
isLoggedInToKeyserver(authoritativeKeyserverID),
);
const avatarBeingSetRef = React.useRef(false);
React.useEffect(() => {
if (
!isLoggedInToAuthoritativeKeyserver ||
currentStep.step !== 'waiting_for_registration_call' ||
avatarBeingSetRef.current
) {
return;
}
avatarBeingSetRef.current = true;
- const { avatarData, resolve } = currentStep;
+ const { avatarData, resolve, clearCachedSelections } = currentStep;
void (async () => {
try {
if (!avatarData) {
return;
}
let updateUserAvatarRequest;
if (!avatarData.needsUpload) {
({ updateUserAvatarRequest } = avatarData);
} else {
const { mediaSelection } = avatarData;
updateUserAvatarRequest = await uploadSelectedMedia(mediaSelection);
if (!updateUserAvatarRequest) {
return;
}
}
await nativeSetUserAvatar(updateUserAvatarRequest);
} finally {
dispatch({
type: setDataLoadedActionType,
payload: {
dataLoaded: true,
},
});
+ clearCachedSelections();
setCurrentStep(inactiveStep);
avatarBeingSetRef.current = false;
resolve();
}
})();
}, [
currentStep,
isLoggedInToAuthoritativeKeyserver,
uploadSelectedMedia,
nativeSetUserAvatar,
dispatch,
]);
return returnedFunc;
}
export { useRegistrationServerCall };
diff --git a/native/account/registration/registration-terms.react.js b/native/account/registration/registration-terms.react.js
index 2823d3ebc..bfbc24bb5 100644
--- a/native/account/registration/registration-terms.react.js
+++ b/native/account/registration/registration-terms.react.js
@@ -1,130 +1,138 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { Text, View, Image, Linking } from 'react-native';
+import type { SIWEBackupSecrets } 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 commSwooshSource from '../../img/comm-swoosh.png';
import type { NavigationRoute } from '../../navigation/route-names.js';
import { useStyles } from '../../themes/colors.js';
export type RegistrationTermsParams = {
+userSelections: {
+coolOrNerdMode: CoolOrNerdMode,
+keyserverURL: string,
+farcasterID: ?string,
+accountSelection: AccountSelection,
+avatarData: ?AvatarData,
+ +siweBackupSecrets?: ?SIWEBackupSecrets,
},
};
const onTermsOfUsePressed = () => {
void Linking.openURL('https://comm.app/terms');
};
const onPrivacyPolicyPressed = () => {
void Linking.openURL('https://comm.app/privacy');
};
type Props = {
+navigation: RegistrationNavigationProp<'RegistrationTerms'>,
+route: NavigationRoute<'RegistrationTerms'>,
};
function RegistrationTerms(props: Props): React.Node {
const registrationContext = React.useContext(RegistrationContext);
invariant(registrationContext, 'registrationContext should be set');
- const { register } = registrationContext;
+ const { register, setCachedSelections } = registrationContext;
const [registrationInProgress, setRegistrationInProgress] =
React.useState(false);
const { userSelections } = props.route.params;
+
+ const clearCachedSelections = React.useCallback(() => {
+ setCachedSelections({});
+ }, [setCachedSelections]);
+
const onProceed = React.useCallback(async () => {
setRegistrationInProgress(true);
try {
- await register(userSelections);
+ await register({ ...userSelections, clearCachedSelections });
} finally {
setRegistrationInProgress(false);
}
- }, [register, userSelections]);
+ }, [register, userSelections, clearCachedSelections]);
const styles = useStyles(unboundStyles);
const termsNotice = (
<Text style={styles.body}>
By registering, you are agreeing to our{' '}
<Text style={styles.hyperlinkText} onPress={onTermsOfUsePressed}>
Terms of Use
</Text>
{' and '}
<Text style={styles.hyperlinkText} onPress={onPrivacyPolicyPressed}>
Privacy Policy
</Text>
.
</Text>
);
return (
<RegistrationContainer>
<RegistrationContentContainer style={styles.scrollViewContentContainer}>
<Text style={styles.header}>Finish registration</Text>
{termsNotice}
<View style={styles.commSwooshContainer}>
<Image source={commSwooshSource} style={styles.commSwoosh} />
</View>
</RegistrationContentContainer>
<RegistrationButtonContainer>
<RegistrationButton
onPress={onProceed}
label="Register"
variant={registrationInProgress ? 'loading' : 'enabled'}
/>
</RegistrationButtonContainer>
</RegistrationContainer>
);
}
const unboundStyles = {
scrollViewContentContainer: {
flexGrow: 1,
},
header: {
fontSize: 24,
color: 'panelForegroundLabel',
paddingBottom: 16,
},
body: {
fontFamily: 'Arial',
fontSize: 15,
lineHeight: 20,
color: 'panelForegroundSecondaryLabel',
paddingBottom: 16,
},
commSwooshContainer: {
flexGrow: 1,
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
},
commSwoosh: {
resizeMode: 'center',
width: '100%',
height: '100%',
},
hyperlinkText: {
color: 'purpleLink',
},
};
export default RegistrationTerms;
diff --git a/native/account/registration/registration-types.js b/native/account/registration/registration-types.js
index 22681afa6..8db7816ef 100644
--- a/native/account/registration/registration-types.js
+++ b/native/account/registration/registration-types.js
@@ -1,67 +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,
};
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__;
diff --git a/native/account/registration/siwe-backup-message-creation.react.js b/native/account/registration/siwe-backup-message-creation.react.js
index 0d1abbc70..4ecb4b806 100644
--- a/native/account/registration/siwe-backup-message-creation.react.js
+++ b/native/account/registration/siwe-backup-message-creation.react.js
@@ -1,187 +1,198 @@
// @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 { userSelections } = params;
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;
+ const newUserSelections = {
+ ...userSelections,
+ siweBackupSecrets: { message, signature },
+ };
setCachedSelections(oldUserSelections => ({
...oldUserSelections,
siweBackupSecrets: { message, signature },
}));
navigate<'RegistrationTerms'>({
name: RegistrationTermsRouteName,
- params,
+ params: { userSelections: newUserSelections },
});
},
- [navigate, params, setCachedSelections],
+ [navigate, setCachedSelections, userSelections],
);
+ const { siweBackupSecrets } = cachedSelections;
const onExistingWalletSignature = React.useCallback(() => {
+ const registrationTermsParams = {
+ userSelections: {
+ ...userSelections,
+ siweBackupSecrets,
+ },
+ };
+
navigate<'RegistrationTerms'>({
name: RegistrationTermsRouteName,
- params,
+ params: registrationTermsParams,
});
- }, [params, navigate]);
+ }, [navigate, siweBackupSecrets, userSelections]);
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;
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Dec 23, 12:21 AM (2 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2689989
Default Alt Text
(24 KB)
Attached To
Mode
rCOMM Comm
Attached
Detach File
Event Timeline
Log In to Comment