You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			265 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			265 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			TypeScript
		
	
import React, { useCallback, useState } from 'react';
 | 
						|
import { useDispatch } from 'react-redux';
 | 
						|
import { ed25519Str } from '../../session/onions/onionPath';
 | 
						|
import { forceNetworkDeletion } from '../../session/apis/snode_api/SNodeAPI';
 | 
						|
import { forceSyncConfigurationNowIfNeeded } from '../../session/utils/syncUtils';
 | 
						|
import { updateConfirmModal, updateDeleteAccountModal } from '../../state/ducks/modalDialog';
 | 
						|
import { SpacerLG } from '../basic/Text';
 | 
						|
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
 | 
						|
import { SessionSpinner } from '../basic/SessionSpinner';
 | 
						|
import { SessionWrapperModal } from '../SessionWrapperModal';
 | 
						|
 | 
						|
import { Data } from '../../data/data';
 | 
						|
import { deleteAllLogs } from '../../node/logs';
 | 
						|
import { SessionRadioGroup } from '../basic/SessionRadioGroup';
 | 
						|
 | 
						|
const deleteDbLocally = async () => {
 | 
						|
  window?.log?.info('last message sent successfully. Deleting everything');
 | 
						|
  window.persistStore?.purge();
 | 
						|
  await deleteAllLogs();
 | 
						|
  await Data.removeAll();
 | 
						|
  await Data.close();
 | 
						|
  await Data.removeDB();
 | 
						|
  await Data.removeOtherData();
 | 
						|
  window.localStorage.setItem('restart-reason', 'delete-account');
 | 
						|
};
 | 
						|
 | 
						|
async function sendConfigMessageAndDeleteEverything() {
 | 
						|
  try {
 | 
						|
    // DELETE LOCAL DATA ONLY, NOTHING ON NETWORK
 | 
						|
    window?.log?.info('DeleteAccount => Sending a last SyncConfiguration');
 | 
						|
 | 
						|
    // be sure to wait for the message being effectively sent. Otherwise we won't be able to encrypt it for our devices !
 | 
						|
    await forceSyncConfigurationNowIfNeeded(true);
 | 
						|
    window?.log?.info('Last configuration message sent!');
 | 
						|
    await deleteDbLocally();
 | 
						|
    window.restart();
 | 
						|
  } catch (error) {
 | 
						|
    // if an error happened, it's not related to the delete everything on network logic as this is handled above.
 | 
						|
    // this could be a last sync configuration message not being sent.
 | 
						|
    // in all case, we delete everything, and restart
 | 
						|
    window?.log?.error(
 | 
						|
      'Something went wrong deleting all data:',
 | 
						|
      error && error.stack ? error.stack : error
 | 
						|
    );
 | 
						|
    try {
 | 
						|
      await deleteDbLocally();
 | 
						|
    } catch (e) {
 | 
						|
      window?.log?.error(e);
 | 
						|
    } finally {
 | 
						|
      window.restart();
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
async function deleteEverythingAndNetworkData() {
 | 
						|
  try {
 | 
						|
    // DELETE EVERYTHING ON NETWORK, AND THEN STUFF LOCALLY STORED
 | 
						|
    // a bit of duplicate code below, but it's easier to follow every case like that (helped with returns)
 | 
						|
 | 
						|
    // send deletion message to the network
 | 
						|
    const potentiallyMaliciousSnodes = await forceNetworkDeletion();
 | 
						|
    if (potentiallyMaliciousSnodes === null) {
 | 
						|
      window?.log?.warn('DeleteAccount => forceNetworkDeletion failed');
 | 
						|
 | 
						|
      // close this dialog
 | 
						|
      window.inboxStore?.dispatch(updateDeleteAccountModal(null));
 | 
						|
      window.inboxStore?.dispatch(
 | 
						|
        updateConfirmModal({
 | 
						|
          title: window.i18n('dialogClearAllDataDeletionFailedTitle'),
 | 
						|
          message: window.i18n('dialogClearAllDataDeletionFailedDesc'),
 | 
						|
          okTheme: SessionButtonColor.Danger,
 | 
						|
          okText: window.i18n('deviceOnly'),
 | 
						|
          onClickOk: async () => {
 | 
						|
            await deleteDbLocally();
 | 
						|
            window.restart();
 | 
						|
          },
 | 
						|
        })
 | 
						|
      );
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (potentiallyMaliciousSnodes.length > 0) {
 | 
						|
      const snodeStr = potentiallyMaliciousSnodes.map(ed25519Str);
 | 
						|
      window?.log?.warn(
 | 
						|
        'DeleteAccount => forceNetworkDeletion Got some potentially malicious snodes',
 | 
						|
        snodeStr
 | 
						|
      );
 | 
						|
      // close this dialog
 | 
						|
      window.inboxStore?.dispatch(updateDeleteAccountModal(null));
 | 
						|
      // open a new confirm dialog to ask user what to do
 | 
						|
      window.inboxStore?.dispatch(
 | 
						|
        updateConfirmModal({
 | 
						|
          title: window.i18n('dialogClearAllDataDeletionFailedTitle'),
 | 
						|
          message: window.i18n('dialogClearAllDataDeletionFailedMultiple', [
 | 
						|
            potentiallyMaliciousSnodes.join(', '),
 | 
						|
          ]),
 | 
						|
          messageSub: window.i18n('dialogClearAllDataDeletionFailedTitleQuestion'),
 | 
						|
          okTheme: SessionButtonColor.Danger,
 | 
						|
          okText: window.i18n('deviceOnly'),
 | 
						|
          onClickOk: async () => {
 | 
						|
            await deleteDbLocally();
 | 
						|
            window.restart();
 | 
						|
          },
 | 
						|
        })
 | 
						|
      );
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // We removed everything on the network successfully (no malicious node!). Now delete the stuff we got locally
 | 
						|
    // without sending a last configuration message (otherwise this one will still be on the network)
 | 
						|
    await deleteDbLocally();
 | 
						|
    window.restart();
 | 
						|
  } catch (error) {
 | 
						|
    // if an error happened, it's not related to the delete everything on network logic as this is handled above.
 | 
						|
    // this could be a last sync configuration message not being sent.
 | 
						|
    // in all case, we delete everything, and restart
 | 
						|
    window?.log?.error(
 | 
						|
      'Something went wrong deleting all data:',
 | 
						|
      error && error.stack ? error.stack : error
 | 
						|
    );
 | 
						|
    try {
 | 
						|
      await deleteDbLocally();
 | 
						|
    } catch (e) {
 | 
						|
      window?.log?.error(e);
 | 
						|
    }
 | 
						|
    window.restart();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
const DEVICE_ONLY = 'device_only';
 | 
						|
const DEVICE_AND_NETWORK = 'device_and_network';
 | 
						|
type DeleteModes = typeof DEVICE_ONLY | typeof DEVICE_AND_NETWORK;
 | 
						|
 | 
						|
const DescriptionBeforeAskingConfirmation = (props: {
 | 
						|
  deleteMode: DeleteModes;
 | 
						|
  setDeleteMode: (deleteMode: DeleteModes) => void;
 | 
						|
}) => {
 | 
						|
  const { deleteMode, setDeleteMode } = props;
 | 
						|
  return (
 | 
						|
    <>
 | 
						|
      <span className="session-confirm-main-message">{window.i18n('deleteAccountWarning')}</span>
 | 
						|
      <span className="session-confirm-main-message">
 | 
						|
        {window.i18n('dialogClearAllDataDeletionQuestion')}
 | 
						|
      </span>
 | 
						|
 | 
						|
      <SpacerLG />
 | 
						|
      <SessionRadioGroup
 | 
						|
        group="delete_account"
 | 
						|
        initialItem={deleteMode}
 | 
						|
        onClick={value => {
 | 
						|
          if (value === DEVICE_ONLY || value === DEVICE_AND_NETWORK) {
 | 
						|
            setDeleteMode(value);
 | 
						|
          }
 | 
						|
        }}
 | 
						|
        items={[
 | 
						|
          { label: window.i18n('deviceOnly'), value: DEVICE_ONLY },
 | 
						|
          { label: window.i18n('entireAccount'), value: 'device_and_network' },
 | 
						|
        ]}
 | 
						|
      />
 | 
						|
    </>
 | 
						|
  );
 | 
						|
};
 | 
						|
 | 
						|
const DescriptionWhenAskingConfirmation = (props: { deleteMode: DeleteModes }) => {
 | 
						|
  return (
 | 
						|
    <span className="session-confirm-main-message">
 | 
						|
      {props.deleteMode === 'device_and_network'
 | 
						|
        ? window.i18n('areYouSureDeleteEntireAccount')
 | 
						|
        : window.i18n('areYouSureDeleteDeviceOnly')}
 | 
						|
    </span>
 | 
						|
  );
 | 
						|
};
 | 
						|
 | 
						|
export const DeleteAccountModal = () => {
 | 
						|
  const [isLoading, setIsLoading] = useState(false);
 | 
						|
  const [askingConfirmation, setAskingConfirmation] = useState(false);
 | 
						|
  const [deleteMode, setDeleteMode] = useState<DeleteModes>(DEVICE_ONLY);
 | 
						|
 | 
						|
  const dispatch = useDispatch();
 | 
						|
 | 
						|
  const onDeleteEverythingLocallyOnly = async () => {
 | 
						|
    if (!isLoading) {
 | 
						|
      setIsLoading(true);
 | 
						|
      try {
 | 
						|
        window.log.warn('Deleting everything on device but keeping network data');
 | 
						|
 | 
						|
        await sendConfigMessageAndDeleteEverything();
 | 
						|
      } catch (e) {
 | 
						|
        window.log.warn(e);
 | 
						|
      } finally {
 | 
						|
        setIsLoading(false);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  };
 | 
						|
  const onDeleteEverythingAndNetworkData = async () => {
 | 
						|
    if (!isLoading) {
 | 
						|
      setIsLoading(true);
 | 
						|
      try {
 | 
						|
        window.log.warn('Deleting everything including network data');
 | 
						|
        await deleteEverythingAndNetworkData();
 | 
						|
      } catch (e) {
 | 
						|
        window.log.warn(e);
 | 
						|
      } finally {
 | 
						|
        setIsLoading(false);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  };
 | 
						|
 | 
						|
  /**
 | 
						|
   * Performs specified on close action then removes the modal.
 | 
						|
   */
 | 
						|
  const onClickCancelHandler = useCallback(() => {
 | 
						|
    dispatch(updateDeleteAccountModal(null));
 | 
						|
  }, []);
 | 
						|
 | 
						|
  return (
 | 
						|
    <SessionWrapperModal
 | 
						|
      title={window.i18n('clearAllData')}
 | 
						|
      onClose={onClickCancelHandler}
 | 
						|
      showExitIcon={true}
 | 
						|
    >
 | 
						|
      {askingConfirmation ? (
 | 
						|
        <DescriptionWhenAskingConfirmation deleteMode={deleteMode} />
 | 
						|
      ) : (
 | 
						|
        <DescriptionBeforeAskingConfirmation
 | 
						|
          deleteMode={deleteMode}
 | 
						|
          setDeleteMode={setDeleteMode}
 | 
						|
        />
 | 
						|
      )}
 | 
						|
      <div className="session-modal__centered">
 | 
						|
        <div className="session-modal__button-group">
 | 
						|
          <SessionButton
 | 
						|
            text={window.i18n('clear')}
 | 
						|
            buttonColor={SessionButtonColor.Danger}
 | 
						|
            buttonType={SessionButtonType.Simple}
 | 
						|
            onClick={() => {
 | 
						|
              if (!askingConfirmation) {
 | 
						|
                setAskingConfirmation(true);
 | 
						|
                return;
 | 
						|
              }
 | 
						|
              if (deleteMode === 'device_only') {
 | 
						|
                void onDeleteEverythingLocallyOnly();
 | 
						|
              } else if (deleteMode === 'device_and_network') {
 | 
						|
                void onDeleteEverythingAndNetworkData();
 | 
						|
              }
 | 
						|
            }}
 | 
						|
            disabled={isLoading}
 | 
						|
          />
 | 
						|
 | 
						|
          <SessionButton
 | 
						|
            text={window.i18n('cancel')}
 | 
						|
            buttonType={SessionButtonType.Simple}
 | 
						|
            onClick={() => {
 | 
						|
              dispatch(updateDeleteAccountModal(null));
 | 
						|
            }}
 | 
						|
            disabled={isLoading}
 | 
						|
          />
 | 
						|
        </div>
 | 
						|
        <SpacerLG />
 | 
						|
        <SessionSpinner loading={isLoading} />
 | 
						|
      </div>
 | 
						|
    </SessionWrapperModal>
 | 
						|
  );
 | 
						|
};
 |