import React, { MouseEventHandler, useCallback, useState } from 'react'; import { useAtom } from 'jotai'; import { CryptoApi, KeyBackupInfo } from 'matrix-js-sdk/lib/crypto-api'; import { Badge, Box, Button, color, config, Icon, IconButton, Icons, Menu, percent, PopOut, ProgressBar, RectCords, Spinner, Text, } from 'folds'; import FocusTrap from 'focus-trap-react'; import { BackupProgressStatus, backupRestoreProgressAtom } from '../state/backupRestore'; import { InfoCard } from './info-card'; import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback'; import { useKeyBackupInfo, useKeyBackupStatus, useKeyBackupSync, useKeyBackupTrust, } from '../hooks/useKeyBackup'; import { stopPropagation } from '../utils/keyboard'; import { useRestoreBackupOnVerification } from '../hooks/useRestoreBackupOnVerification'; type BackupStatusProps = { enabled: boolean; }; function BackupStatus({ enabled }: BackupStatusProps) { return ( {enabled ? 'Connected' : 'Disconnected'} ); } type BackupSyncingProps = { count: number; }; function BackupSyncing({ count }: BackupSyncingProps) { return ( Syncing ({count}) ); } function BackupProgressFetching() { return ( Restoring: 0% ); } type BackupProgressProps = { total: number; downloaded: number; }; function BackupProgress({ total, downloaded }: BackupProgressProps) { return ( Restoring: {`${Math.round(percent(0, total, downloaded))}%`} {downloaded} / {total} ); } type BackupTrustInfoProps = { crypto: CryptoApi; backupInfo: KeyBackupInfo; }; function BackupTrustInfo({ crypto, backupInfo }: BackupTrustInfoProps) { const trust = useKeyBackupTrust(crypto, backupInfo); if (!trust) return null; return ( {trust.matchesDecryptionKey ? ( Backup has trusted decryption key. ) : ( Backup does not have trusted decryption key! )} {trust.trusted ? ( Backup has trusted by signature. ) : ( Backup does not have trusted signature! )} ); } type BackupRestoreTileProps = { crypto: CryptoApi; }; export function BackupRestoreTile({ crypto }: BackupRestoreTileProps) { const [restoreProgress, setRestoreProgress] = useAtom(backupRestoreProgressAtom); const restoring = restoreProgress.status === BackupProgressStatus.Fetching || restoreProgress.status === BackupProgressStatus.Loading; const backupEnabled = useKeyBackupStatus(crypto); const backupInfo = useKeyBackupInfo(crypto); const [remainingSession, syncFailure] = useKeyBackupSync(); const [menuCords, setMenuCords] = useState(); const handleMenu: MouseEventHandler = (evt) => { setMenuCords(evt.currentTarget.getBoundingClientRect()); }; const [restoreState, restoreBackup] = useAsyncCallback( useCallback(async () => { await crypto.restoreKeyBackup({ progressCallback(progress) { setRestoreProgress(progress); }, }); }, [crypto, setRestoreProgress]) ); const handleRestore = () => { setMenuCords(undefined); restoreBackup(); }; return ( {remainingSession === 0 ? ( ) : ( )} setMenuCords(undefined), clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown' || evt.key === 'ArrowRight', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp' || evt.key === 'ArrowLeft', escapeDeactivates: stopPropagation, }} > Version: {backupInfo?.version ?? 'NIL'}
Keys: {backupInfo?.count ?? 'NIL'} } />
} /> } > {syncFailure && ( {syncFailure} )} {!backupEnabled && backupInfo === null && ( No backup present on server! )} {!syncFailure && !backupEnabled && backupInfo && ( )} {restoreState.status === AsyncStatus.Loading && !restoring && } {restoreProgress.status === BackupProgressStatus.Fetching && } {restoreProgress.status === BackupProgressStatus.Loading && ( )} {restoreState.status === AsyncStatus.Error && ( {restoreState.error.message} )}
); } export function AutoRestoreBackupOnVerification() { useRestoreBackupOnVerification(); return null; }