diff --git a/lib/actions/user-actions.js b/lib/actions/user-actions.js --- a/lib/actions/user-actions.js +++ b/lib/actions/user-actions.js @@ -1,5 +1,8 @@ // @flow +import * as React from 'react'; + +import { preRequestUserStateSelector } from '../selectors/account-selectors.js'; import threadWatcher from '../shared/thread-watcher.js'; import type { LogOutResult, @@ -18,6 +21,7 @@ UpdateUserAvatarResponse, } from '../types/avatar-types.js'; import type { RawEntryInfo, CalendarQuery } from '../types/entry-types.js'; +import type { IdentityServiceClient } from '../types/identity-service-types'; import type { RawMessageInfo, MessageTruncationStatuses, @@ -56,6 +60,7 @@ import { getConfig } from '../utils/config.js'; import type { CallKeyserverEndpoint } from '../utils/keyserver-call'; import { useKeyserverCall } from '../utils/keyserver-call.js'; +import { useSelector } from '../utils/redux-utils.js'; import sleep from '../utils/sleep.js'; import { ashoatKeyserverID } from '../utils/validation-utils.js'; @@ -149,6 +154,31 @@ return useKeyserverCall(deleteKeyserverAccount); } +const deleteIdentityAccountActionTypes = Object.freeze({ + started: 'DELETE_IDENTITY_ACCOUNT_STARTED', + success: 'DELETE_IDENTITY_ACCOUNT_SUCCESS', + failed: 'DELETE_IDENTITY_ACCOUNT_FAILED', +}); + +function useDeleteIdentityAccount( + client: IdentityServiceClient, + deviceID: ?string, +): () => Promise { + const preRequestUserState = useSelector(preRequestUserStateSelector); + const userID = useSelector(state => state.currentUserInfo?.id); + const accessToken = useSelector(state => state.commServicesAccessToken); + + const deleteIdentityAccount = React.useCallback(async () => { + if (!userID || !accessToken || !deviceID) { + return { currentUserInfo: loggedOutUserInfo, preRequestUserState }; + } + await client.deleteUser(userID, deviceID, accessToken); + return { currentUserInfo: loggedOutUserInfo, preRequestUserState }; + }, [client, userID, deviceID, accessToken, preRequestUserState]); + + return deleteIdentityAccount; +} + const registerActionTypes = Object.freeze({ started: 'REGISTER_STARTED', success: 'REGISTER_SUCCESS', @@ -526,4 +556,6 @@ updateUserAvatar, resetUserStateActionType, setAccessTokenActionType, + deleteIdentityAccountActionTypes, + useDeleteIdentityAccount, }; diff --git a/lib/types/identity-service-types.js b/lib/types/identity-service-types.js --- a/lib/types/identity-service-types.js +++ b/lib/types/identity-service-types.js @@ -19,6 +19,14 @@ +oneTimeNotifPrekey: ?string, }; +export interface IdentityServiceClient { + +deleteUser: ( + userID: string, + deviceID: string, + accessToken: string, + ) => Promise; +} + export type IdentityServiceAuthLayer = { +userID: string, +deviceID: string, diff --git a/lib/types/redux-types.js b/lib/types/redux-types.js --- a/lib/types/redux-types.js +++ b/lib/types/redux-types.js @@ -229,6 +229,22 @@ +payload: LogOutResult, +loadingInfo: LoadingInfo, } + | { + +type: 'DELETE_IDENTITY_ACCOUNT_STARTED', + +payload?: void, + +loadingInfo: LoadingInfo, + } + | { + +type: 'DELETE_IDENTITY_ACCOUNT_FAILED', + +error: true, + +payload: Error, + +loadingInfo: LoadingInfo, + } + | { + +type: 'DELETE_IDENTITY_ACCOUNT_SUCCESS', + +payload: LogOutResult, + +loadingInfo: LoadingInfo, + } | { +type: 'CREATE_LOCAL_ENTRY', +payload: RawEntryInfo, diff --git a/native/profile/delete-account.react.js b/native/profile/delete-account.react.js --- a/native/profile/delete-account.react.js +++ b/native/profile/delete-account.react.js @@ -5,7 +5,9 @@ import { ScrollView } from 'react-native-gesture-handler'; import { + deleteIdentityAccountActionTypes, deleteKeyserverAccountActionTypes, + useDeleteIdentityAccount, useDeleteKeyserverAccount, } from 'lib/actions/user-actions.js'; import { preRequestUserStateSelector } from 'lib/selectors/account-selectors.js'; @@ -15,10 +17,12 @@ import type { ProfileNavigationProp } from './profile.react.js'; import { deleteNativeCredentialsFor } from '../account/native-credentials.js'; import Button from '../components/button.react.js'; +import { commRustModule } from '../native-modules.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import { useSelector } from '../redux/redux-utils.js'; import { useStyles } from '../themes/colors.js'; import Alert from '../utils/alert.js'; +import { getContentSigningKey } from '../utils/crypto-utils.js'; const loadingStatusSelector = createLoadingStatusSelector( deleteKeyserverAccountActionTypes, @@ -30,12 +34,24 @@ }; const DeleteAccount: React.ComponentType = React.memo( function DeleteAccount() { + const [deviceID, setDeviceID] = React.useState(); const loadingStatus = useSelector(loadingStatusSelector); const preRequestUserState = useSelector(preRequestUserStateSelector); + + React.useEffect(() => { + void (async () => { + const contentSigningKey = await getContentSigningKey(); + setDeviceID(contentSigningKey); + })(); + }, []); const styles = useStyles(unboundStyles); const dispatchActionPromise = useDispatchActionPromise(); - const callDeleteAccount = useDeleteKeyserverAccount(); + const callDeleteKeyserverAccount = useDeleteKeyserverAccount(); + const callDeleteIdentityAccount = useDeleteIdentityAccount( + commRustModule, + deviceID, + ); const buttonContent = loadingStatus === 'loading' ? ( @@ -49,24 +65,39 @@ [styles.warningText, styles.lastWarningText], ); - const deleteAction = React.useCallback(async () => { + const deleteKeyserverAction = React.useCallback(async () => { try { await deleteNativeCredentialsFor(); - return await callDeleteAccount(preRequestUserState); + return await callDeleteKeyserverAccount(preRequestUserState); + } catch (e) { + Alert.alert('Unknown error', 'Uhh... try again?', [{ text: 'OK' }], { + cancelable: false, + }); + throw e; + } + }, [callDeleteKeyserverAccount, preRequestUserState]); + + const deleteIdentityAction = React.useCallback(async () => { + try { + return await callDeleteIdentityAccount(); } catch (e) { Alert.alert('Unknown error', 'Uhh... try again?', [{ text: 'OK' }], { cancelable: false, }); throw e; } - }, [callDeleteAccount, preRequestUserState]); + }, [callDeleteIdentityAccount]); const onDelete = React.useCallback(() => { void dispatchActionPromise( deleteKeyserverAccountActionTypes, - deleteAction(), + deleteKeyserverAction(), + ); + void dispatchActionPromise( + deleteIdentityAccountActionTypes, + deleteIdentityAction(), ); - }, [dispatchActionPromise, deleteAction]); + }, [dispatchActionPromise, deleteKeyserverAction, deleteIdentityAction]); return (