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 @@ -3,7 +3,10 @@ import invariant from 'invariant'; import * as React from 'react'; -import { useBroadcastDeviceListUpdates } from '../hooks/peer-list-hooks.js'; +import { + useBroadcastDeviceListUpdates, + useBroadcastAccountDeletion, +} from '../hooks/peer-list-hooks.js'; import type { CallSingleKeyserverEndpoint, CallSingleKeyserverEndpointOptions, @@ -506,10 +509,17 @@ failed: 'DELETE_ACCOUNT_FAILED', }); +const accountDeletionBroadcastOptions = Object.freeze({ + broadcastToOwnDevices: true, +}); function useDeleteAccount(): (password: ?string) => Promise { const client = React.useContext(IdentityClientContext); const identityClient = client?.identityClient; + const broadcastAccountDeletion = useBroadcastAccountDeletion( + accountDeletionBroadcastOptions, + ); + const preRequestUserState = usePreRequestUserState(); const callKeyserverDeleteAccount = useKeyserverCall(deleteKeyserverAccount); @@ -523,16 +533,16 @@ if (!identityClient) { throw new Error('Identity service client is not initialized'); } - if ( - !identityClient.deleteWalletUser || - !identityClient.deletePasswordUser - ) { + const { deleteWalletUser, deletePasswordUser } = identityClient; + if (!deleteWalletUser || !deletePasswordUser) { throw new Error('Delete user method unimplemented'); } + + await broadcastAccountDeletion(); if (password) { - await identityClient.deletePasswordUser(password); + await deletePasswordUser(password); } else { - await identityClient.deleteWalletUser(); + await deleteWalletUser(); } } try { @@ -565,6 +575,7 @@ }; }, [ + broadcastAccountDeletion, callKeyserverDeleteAccount, commServicesAccessToken, identityClient, diff --git a/lib/hooks/peer-list-hooks.js b/lib/hooks/peer-list-hooks.js --- a/lib/hooks/peer-list-hooks.js +++ b/lib/hooks/peer-list-hooks.js @@ -4,7 +4,10 @@ import * as React from 'react'; import { setPeerDeviceListsActionType } from '../actions/aux-user-actions.js'; -import { getAllPeerDevices } from '../selectors/user-selectors.js'; +import { + getAllPeerDevices, + getForeignPeerDevices, +} from '../selectors/user-selectors.js'; import { IdentityClientContext } from '../shared/identity-client-context.js'; import { useTunnelbroker } from '../tunnelbroker/tunnelbroker-context.js'; import type { @@ -17,6 +20,10 @@ type DeviceListUpdated, peerToPeerMessageTypes, } from '../types/tunnelbroker/peer-to-peer-message-types.js'; +import { + userActionsP2PMessageTypes, + type AccountDeletionP2PMessage, +} from '../types/tunnelbroker/user-actions-peer-to-peer-message-types.js'; import { getContentSigningKey } from '../utils/crypto-utils.js'; import { convertSignedDeviceListsToRawDeviceLists } from '../utils/device-list-utils.js'; import { values } from '../utils/objects.js'; @@ -133,8 +140,39 @@ ); } +function useBroadcastAccountDeletion( + options: { broadcastToOwnDevices?: boolean } = {}, +): () => Promise { + const { broadcastToOwnDevices } = options; + const { sendMessageToDevice } = useTunnelbroker(); + const devicesSelector = broadcastToOwnDevices + ? getAllPeerDevices + : getForeignPeerDevices; + const peerDevices = useSelector(devicesSelector); + + return React.useCallback(async () => { + const thisDeviceID = await getContentSigningKey(); + const messageToPeer: AccountDeletionP2PMessage = { + type: userActionsP2PMessageTypes.ACCOUNT_DELETION, + }; + const payload = JSON.stringify(messageToPeer); + + const recipientDeviceIDs = peerDevices.filter( + peerDeviceID => peerDeviceID !== thisDeviceID, + ); + const promises = recipientDeviceIDs.map((deviceID: string) => + sendMessageToDevice({ + deviceID, + payload, + }), + ); + await Promise.all(promises); + }, [peerDevices, sendMessageToDevice]); +} + export { useGetDeviceListsForUsers, useBroadcastDeviceListUpdates, useGetAndUpdateDeviceListsForUsers, + useBroadcastAccountDeletion, };