import { useState, useEffect } from 'react' import { nostrAuthService } from '@/lib/nostrAuth' import { keyManagementService } from '@/lib/keyManagement' import { nip19 } from 'nostr-tools' import { t } from '@/lib/i18n' interface PublicKeys { publicKey: string npub: string } export function KeyManagementManager(): React.ReactElement { const [publicKeys, setPublicKeys] = useState(null) const [accountExists, setAccountExists] = useState(false) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [importKey, setImportKey] = useState('') const [importing, setImporting] = useState(false) const [showImportForm, setShowImportForm] = useState(false) const [showReplaceWarning, setShowReplaceWarning] = useState(false) const [recoveryPhrase, setRecoveryPhrase] = useState(null) const [newNpub, setNewNpub] = useState(null) const [copiedNpub, setCopiedNpub] = useState(false) const [copiedPublicKey, setCopiedPublicKey] = useState(false) const [copiedRecoveryPhrase, setCopiedRecoveryPhrase] = useState(false) useEffect(() => { void loadKeys() }, []) async function loadKeys(): Promise { try { setLoading(true) setError(null) const exists = await nostrAuthService.accountExists() setAccountExists(exists) if (exists) { const keys = await keyManagementService.getPublicKeys() if (keys) { setPublicKeys(keys) } } } catch (e) { const errorMessage = e instanceof Error ? e.message : t('settings.keyManagement.loading') setError(errorMessage) console.error('Error loading keys:', e) } finally { setLoading(false) } } function extractKeyFromUrl(url: string): string | null { try { // Try to parse as URL const urlObj = new URL(url) // Check if it's a nostr:// URL with nsec if (urlObj.protocol === 'nostr:' || urlObj.protocol === 'nostr://') { const path = urlObj.pathname || urlObj.href.replace(/^nostr:?\/\//, '') if (path.startsWith('nsec')) { return path } } // Check if URL contains nsec const nsecMatch = url.match(/nsec1[a-z0-9]+/i) if (nsecMatch) { return nsecMatch[0] } return null } catch { // Not a valid URL, try to extract nsec from text const nsecMatch = url.match(/nsec1[a-z0-9]+/i) if (nsecMatch) { return nsecMatch[0] } // Assume it's already a key (hex or nsec) return url.trim() } } async function handleImport(): Promise { if (!importKey.trim()) { setError(t('settings.keyManagement.import.error.required')) return } // Extract key from URL or text const extractedKey = extractKeyFromUrl(importKey.trim()) if (!extractedKey) { setError(t('settings.keyManagement.import.error.invalid')) return } // Validate key format try { // Try to decode as nsec const decoded = nip19.decode(extractedKey) if (decoded.type !== 'nsec') { throw new Error('Invalid nsec format') } // decoded.data can be string (hex) or Uint8Array, both are valid if (typeof decoded.data !== 'string' && !(decoded.data instanceof Uint8Array)) { throw new Error('Invalid nsec format') } } catch { // If decoding failed, assume it's hex, validate length (64 hex chars = 32 bytes) if (!/^[0-9a-f]{64}$/i.test(extractedKey)) { setError(t('settings.keyManagement.import.error.invalid')) return } } // If account exists, show warning if (accountExists) { setShowReplaceWarning(true) return } await performImport(extractedKey) } async function performImport(key: string): Promise { try { setImporting(true) setError(null) setShowReplaceWarning(false) // If account exists, delete it first if (accountExists) { await nostrAuthService.deleteAccount() } // Create new account with imported key const result = await nostrAuthService.createAccount(key) setRecoveryPhrase(result.recoveryPhrase) setNewNpub(result.npub) setImportKey('') setShowImportForm(false) await loadKeys() // Sync user content to IndexedDB cache if (result.publicKey) { const { syncUserContentToCache } = await import('@/lib/userContentSync') void syncUserContentToCache(result.publicKey) } } catch (e) { const errorMessage = e instanceof Error ? e.message : t('settings.keyManagement.import.error.failed') setError(errorMessage) console.error('Error importing key:', e) } finally { setImporting(false) } } async function handleCopyRecoveryPhrase(): Promise { if (!recoveryPhrase) { return } try { await navigator.clipboard.writeText(recoveryPhrase.join(' ')) setCopiedRecoveryPhrase(true) setTimeout(() => { setCopiedRecoveryPhrase(false) }, 2000) } catch (e) { console.error('Error copying recovery phrase:', e) } } async function handleCopyNpub(): Promise { if (!publicKeys?.npub) { return } try { await navigator.clipboard.writeText(publicKeys.npub) setCopiedNpub(true) setTimeout(() => { setCopiedNpub(false) }, 2000) } catch (e) { console.error('Error copying npub:', e) } } async function handleCopyPublicKey(): Promise { if (!publicKeys?.publicKey) { return } try { await navigator.clipboard.writeText(publicKeys.publicKey) setCopiedPublicKey(true) setTimeout(() => { setCopiedPublicKey(false) }, 2000) } catch (e) { console.error('Error copying public key:', e) } } if (loading) { return (

{t('settings.keyManagement.loading')}

) } return (

{t('settings.keyManagement.title')}

{error && (

{error}

)} {/* Public Keys Display */} {publicKeys && (

{t('settings.keyManagement.publicKey.npub')}

{publicKeys.npub}

{t('settings.keyManagement.publicKey.hex')}

{publicKeys.publicKey}

)} {!publicKeys && !accountExists && (

{t('settings.keyManagement.noAccount.title')}

{t('settings.keyManagement.noAccount.description')}

)} {/* Import Form */} {!showImportForm && ( )} {showImportForm && (

{t('settings.keyManagement.import.warning.title')}

{accountExists && (

)}