import { useState, useEffect } from 'react'; import { Shield, QrCode, RefreshCw, CheckCircle2, Lock, AlertCircle, Unlock, Eye, EyeOff, FileKey, Info, WifiOff } from 'lucide-react'; import { PgpKeyInput } from './components/PgpKeyInput'; import { QrDisplay } from './components/QrDisplay'; import QRScanner from './components/QRScanner'; import { validateBip39Mnemonic } from './lib/bip39'; import { buildPlaintext, encryptToSeedPgp, decryptSeedPgp } from './lib/seedpgp'; import type { SeedPgpPlaintext } from './lib/types'; import * as openpgp from 'openpgp'; import { StorageIndicator } from './components/StorageIndicator'; import { SecurityWarnings } from './components/SecurityWarnings'; import { ClipboardTracker } from './components/ClipboardTracker'; import { ReadOnly } from './components/ReadOnly'; import { getSessionKey, encryptJsonToBlob, destroySessionKey, EncryptedBlob } from './lib/sessionCrypto'; console.log("OpenPGP.js version:", openpgp.config.versionString); function App() { const [activeTab, setActiveTab] = useState<'backup' | 'restore'>('backup'); const [mnemonic, setMnemonic] = useState(''); const [backupMessagePassword, setBackupMessagePassword] = useState(''); const [restoreMessagePassword, setRestoreMessagePassword] = useState(''); const [publicKeyInput, setPublicKeyInput] = useState(''); const [privateKeyInput, setPrivateKeyInput] = useState(''); const [privateKeyPassphrase, setPrivateKeyPassphrase] = useState(''); const [hasBip39Passphrase, setHasBip39Passphrase] = useState(false); const [qrPayload, setQrPayload] = useState(''); const [recipientFpr, setRecipientFpr] = useState(''); const [restoreInput, setRestoreInput] = useState(''); const [restoredData, setRestoredData] = useState(null); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); const [showMnemonic, setShowMnemonic] = useState(false); const [copied, setCopied] = useState(false); const [showQRScanner, setShowQRScanner] = useState(false); const [isReadOnly, setIsReadOnly] = useState(false); const [encryptedMnemonicCache, setEncryptedMnemonicCache] = useState(null); useEffect(() => { // When entering read-only mode, clear sensitive data for security. if (isReadOnly) { setMnemonic(''); setBackupMessagePassword(''); setRestoreMessagePassword(''); setPublicKeyInput(''); setPrivateKeyInput(''); setPrivateKeyPassphrase(''); setQrPayload(''); setRestoreInput(''); setRestoredData(null); setError(''); } }, [isReadOnly]); // Cleanup session key on component unmount useEffect(() => { return () => { destroySessionKey(); }; }, []); const copyToClipboard = async (text: string) => { if (isReadOnly) { setError("Copy to clipboard is disabled in Read-only mode."); return; } try { await navigator.clipboard.writeText(text); setCopied(true); window.setTimeout(() => setCopied(false), 1500); } catch { const ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; ta.style.left = "-9999px"; document.body.appendChild(ta); ta.focus(); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); setCopied(true); window.setTimeout(() => setCopied(false), 1500); } }; const handleBackup = async () => { setLoading(true); setError(''); setQrPayload(''); setRecipientFpr(''); try { const validation = validateBip39Mnemonic(mnemonic); if (!validation.valid) { throw new Error(validation.error); } const plaintext = buildPlaintext(mnemonic, hasBip39Passphrase); const result = await encryptToSeedPgp({ plaintext, publicKeyArmored: publicKeyInput || undefined, messagePassword: backupMessagePassword || undefined, }); setQrPayload(result.framed); if (result.recipientFingerprint) { setRecipientFpr(result.recipientFingerprint); } // Initialize session key before encrypting await getSessionKey(); // Encrypt mnemonic with session key and clear plaintext state const blob = await encryptJsonToBlob({ mnemonic, timestamp: Date.now() }); setEncryptedMnemonicCache(blob); setMnemonic(''); // Clear plaintext mnemonic } catch (e) { setError(e instanceof Error ? e.message : 'Encryption failed'); } finally { setLoading(false); } }; const handleRestore = async () => { setLoading(true); setError(''); setRestoredData(null); try { const result = await decryptSeedPgp({ frameText: restoreInput, privateKeyArmored: privateKeyInput || undefined, privateKeyPassphrase: privateKeyPassphrase || undefined, messagePassword: restoreMessagePassword || undefined, }); setRestoredData(result); } catch (e) { setError(e instanceof Error ? e.message : 'Decryption failed'); } finally { setLoading(false); } }; const handleLockAndClear = () => { destroySessionKey(); setEncryptedMnemonicCache(null); setMnemonic(''); setBackupMessagePassword(''); setRestoreMessagePassword(''); setPublicKeyInput(''); setPrivateKeyInput(''); setPrivateKeyPassphrase(''); setQrPayload(''); setRecipientFpr(''); setRestoreInput(''); setRestoredData(null); setError(''); setShowMnemonic(false); setCopied(false); setShowQRScanner(false); }; return ( <>
{/* Header */}

SeedPGP v{__APP_VERSION__}

OpenPGP-secured BIP39 backup

{encryptedMnemonicCache && ( // Show only if encrypted data exists )}
{isReadOnly && (
Read-only
)}
{/* Error Display */} {error && (

Error

{error}

)} {/* Info Banner */} {recipientFpr && activeTab === 'backup' && (
Recipient Key: {recipientFpr}
)} {/* Main Content Grid */}
{activeTab === 'backup' ? ( <>