/* eslint-disable react/prop-types */ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import './KeyBackup.scss'; import { twemojify } from '../../../util/twemojify'; import initMatrix from '../../../client/initMatrix'; import { openReusableDialog } from '../../../client/action/navigation'; import { deletePrivateKey } from '../../../client/state/secretStorageKeys'; import Text from '../../atoms/text/Text'; import Button from '../../atoms/button/Button'; import IconButton from '../../atoms/button/IconButton'; import Spinner from '../../atoms/spinner/Spinner'; import InfoCard from '../../atoms/card/InfoCard'; import SettingTile from '../../molecules/setting-tile/SettingTile'; import { accessSecretStorage } from './SecretStorageAccess'; import InfoIC from '../../../../public/res/ic/outlined/info.svg'; import BinIC from '../../../../public/res/ic/outlined/bin.svg'; import DownloadIC from '../../../../public/res/ic/outlined/download.svg'; import { useStore } from '../../hooks/useStore'; import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus'; function CreateKeyBackupDialog({ keyData }) { const [done, setDone] = useState(false); const mx = initMatrix.matrixClient; const mountStore = useStore(); const doBackup = async () => { setDone(false); let info; try { info = await mx.prepareKeyBackupVersion( null, { secureSecretStorage: true }, ); info = await mx.createKeyBackupVersion(info); await mx.scheduleAllGroupSessionsForBackup(); if (!mountStore.getItem()) return; setDone(true); } catch (e) { deletePrivateKey(keyData.keyId); await mx.deleteKeyBackupVersion(info.version); if (!mountStore.getItem()) return; setDone(null); } }; useEffect(() => { mountStore.setItem(true); doBackup(); }, []); return (
{done === false && (
Creating backup...
)} {done === true && ( <> {twemojify('✅')} Successfully created backup )} {done === null && ( <> Failed to create backup )}
); } CreateKeyBackupDialog.propTypes = { keyData: PropTypes.shape({}).isRequired, }; function RestoreKeyBackupDialog({ keyData }) { const [status, setStatus] = useState(false); const mx = initMatrix.matrixClient; const mountStore = useStore(); const restoreBackup = async () => { setStatus(false); let meBreath = true; const progressCallback = (progress) => { if (!progress.successes) return; if (meBreath === false) return; meBreath = false; setTimeout(() => { meBreath = true; }, 200); setStatus({ message: `Restoring backup keys... (${progress.successes}/${progress.total})` }); }; try { const backupInfo = await mx.getKeyBackupVersion(); const info = await mx.restoreKeyBackupWithSecretStorage( backupInfo, undefined, undefined, { progressCallback }, ); if (!mountStore.getItem()) return; setStatus({ done: `Successfully restored backup keys (${info.imported}/${info.total}).` }); } catch (e) { if (!mountStore.getItem()) return; if (e.errcode === 'RESTORE_BACKUP_ERROR_BAD_KEY') { deletePrivateKey(keyData.keyId); setStatus({ error: 'Failed to restore backup. Key is invalid!', errorCode: 'BAD_KEY' }); } else { setStatus({ error: 'Failed to restore backup.', errCode: 'UNKNOWN' }); } } }; useEffect(() => { mountStore.setItem(true); restoreBackup(); }, []); return (
{(status === false || status.message) && (
{status.message ?? 'Restoring backup keys...'}
)} {status.done && ( <> {twemojify('✅')} {status.done} )} {status.error && ( <> {status.error} )}
); } RestoreKeyBackupDialog.propTypes = { keyData: PropTypes.shape({}).isRequired, }; function DeleteKeyBackupDialog({ requestClose }) { const [isDeleting, setIsDeleting] = useState(false); const mx = initMatrix.matrixClient; const mountStore = useStore(); mountStore.setItem(true); const deleteBackup = async () => { setIsDeleting(true); try { const backupInfo = await mx.getKeyBackupVersion(); if (backupInfo) await mx.deleteKeyBackupVersion(backupInfo.version); if (!mountStore.getItem()) return; requestClose(true); } catch { if (!mountStore.getItem()) return; setIsDeleting(false); } }; return (
{twemojify('🗑')} Deleting key backup is permanent. All encrypted messages keys stored on server will be deleted. { isDeleting ? : }
); } DeleteKeyBackupDialog.propTypes = { requestClose: PropTypes.func.isRequired, }; function KeyBackup() { const mx = initMatrix.matrixClient; const isCSEnabled = useCrossSigningStatus(); const [keyBackup, setKeyBackup] = useState(undefined); const mountStore = useStore(); const fetchKeyBackupVersion = async () => { const info = await mx.getKeyBackupVersion(); if (!mountStore.getItem()) return; setKeyBackup(info); }; useEffect(() => { mountStore.setItem(true); fetchKeyBackupVersion(); const handleAccountData = (event) => { if (event.getType() === 'm.megolm_backup.v1') { fetchKeyBackupVersion(); } }; mx.on('accountData', handleAccountData); return () => { mx.removeListener('accountData', handleAccountData); }; }, [isCSEnabled]); const openCreateKeyBackup = async () => { const keyData = await accessSecretStorage('Create Key Backup'); if (keyData === null) return; openReusableDialog( Create Key Backup, () => , () => fetchKeyBackupVersion(), ); }; const openRestoreKeyBackup = async () => { const keyData = await accessSecretStorage('Restore Key Backup'); if (keyData === null) return; openReusableDialog( Restore Key Backup, () => , ); }; const openDeleteKeyBackup = () => openReusableDialog( Delete Key Backup, (requestClose) => ( { if (isDone) setKeyBackup(null); requestClose(); }} /> ), ); const renderOptions = () => { if (keyBackup === undefined) return ; if (keyBackup === null) return ; return ( <> ); }; return ( Online backup your encrypted messages keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key. {!isCSEnabled && ( )} )} options={isCSEnabled ? renderOptions() : null} /> ); } export default KeyBackup;