import React, { MouseEventHandler, ReactNode, useCallback, useState } from 'react'; import { Box, Text, Chip, Icon, Icons, RectCords, PopOut, Menu, config, MenuItem, color, } from 'folds'; import FocusTrap from 'focus-trap-react'; import { stopPropagation } from '../utils/keyboard'; import { SettingTile } from './setting-tile'; import { SecretStorageKeyContent } from '../../types/matrix/accountData'; import { SecretStorageRecoveryKey, SecretStorageRecoveryPassphrase } from './SecretStorage'; import { useMatrixClient } from '../hooks/useMatrixClient'; import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback'; import { storePrivateKey } from '../../client/state/secretStorageKeys'; export enum ManualVerificationMethod { RecoveryPassphrase = 'passphrase', RecoveryKey = 'key', } type ManualVerificationMethodSwitcherProps = { value: ManualVerificationMethod; onChange: (value: ManualVerificationMethod) => void; }; export function ManualVerificationMethodSwitcher({ value, onChange, }: ManualVerificationMethodSwitcherProps) { const [menuCords, setMenuCords] = useState(); const handleMenu: MouseEventHandler = (evt) => { setMenuCords(evt.currentTarget.getBoundingClientRect()); }; const handleSelect = (method: ManualVerificationMethod) => { setMenuCords(undefined); onChange(method); }; return ( <> } onClick={handleMenu} > {value === ManualVerificationMethod.RecoveryPassphrase && 'Recovery Passphrase'} {value === ManualVerificationMethod.RecoveryKey && 'Recovery Key'} setMenuCords(undefined), clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown' || evt.key === 'ArrowRight', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp' || evt.key === 'ArrowLeft', escapeDeactivates: stopPropagation, }} > handleSelect(ManualVerificationMethod.RecoveryPassphrase)} > Recovery Passphrase handleSelect(ManualVerificationMethod.RecoveryKey)} > Recovery Key } /> ); } type ManualVerificationTileProps = { secretStorageKeyId: string; secretStorageKeyContent: SecretStorageKeyContent; options?: ReactNode; }; export function ManualVerificationTile({ secretStorageKeyId, secretStorageKeyContent, options, }: ManualVerificationTileProps) { const mx = useMatrixClient(); const hasPassphrase = !!secretStorageKeyContent.passphrase; const [method, setMethod] = useState( hasPassphrase ? ManualVerificationMethod.RecoveryPassphrase : ManualVerificationMethod.RecoveryKey ); const verifyAndRestoreBackup = useCallback( async (recoveryKey: Uint8Array) => { const crypto = mx.getCrypto(); if (!crypto) { throw new Error('Unexpected Error! Crypto object not found.'); } storePrivateKey(secretStorageKeyId, recoveryKey); await crypto.bootstrapCrossSigning({}); await crypto.bootstrapSecretStorage({}); await crypto.loadSessionBackupPrivateKeyFromSecretStorage(); }, [mx, secretStorageKeyId] ); const [verifyState, handleDecodedRecoveryKey] = useAsyncCallback( verifyAndRestoreBackup ); const verifying = verifyState.status === AsyncStatus.Loading; return ( {hasPassphrase && ( )} {options} } /> {verifyState.status === AsyncStatus.Success ? ( Device verified! ) : ( {method === ManualVerificationMethod.RecoveryKey && ( )} {method === ManualVerificationMethod.RecoveryPassphrase && secretStorageKeyContent.passphrase && ( )} {verifyState.status === AsyncStatus.Error && ( {verifyState.error.message} )} )} ); }