/* global __APP_VERSION__, __GIT_COMMIT__, __GIT_COMMIT_URL__, __RELEASE_URL__ */ import React, { useEffect, useMemo, useState } from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faClock } from '@fortawesome/free-solid-svg-icons' import { Hooks } from 'applesauce-react' import { Accounts } from 'applesauce-accounts' import { NostrConnectSigner } from 'applesauce-signers' import { getDefaultBunkerPermissions } from '../services/nostrConnect' import { DebugBus, type DebugLogEntry } from '../utils/debugBus' import VersionFooter from './VersionFooter' const defaultPayload = 'The quick brown fox jumps over the lazy dog.' const Debug: React.FC = () => { const activeAccount = Hooks.useActiveAccount() const accountManager = Hooks.useAccountManager() const [payload, setPayload] = useState(defaultPayload) const [cipher44, setCipher44] = useState('') const [cipher04, setCipher04] = useState('') const [plain44, setPlain44] = useState('') const [plain04, setPlain04] = useState('') const [tEncrypt44, setTEncrypt44] = useState(null) const [tEncrypt04, setTEncrypt04] = useState(null) const [tDecrypt44, setTDecrypt44] = useState(null) const [tDecrypt04, setTDecrypt04] = useState(null) const [logs, setLogs] = useState(DebugBus.snapshot()) const [debugEnabled, setDebugEnabled] = useState(() => localStorage.getItem('debug') === '*') // Bunker login state const [bunkerUri, setBunkerUri] = useState('') const [isBunkerLoading, setIsBunkerLoading] = useState(false) const [bunkerError, setBunkerError] = useState(null) // Live timing state const [liveTiming, setLiveTiming] = useState<{ nip44?: { type: 'encrypt' | 'decrypt'; startTime: number } nip04?: { type: 'encrypt' | 'decrypt'; startTime: number } }>({}) useEffect(() => { return DebugBus.subscribe((e) => setLogs(prev => [...prev, e].slice(-300))) }, []) // Live timer effect - triggers re-renders for live timing updates useEffect(() => { const interval = setInterval(() => { // Force re-render to update live timing display setLiveTiming(prev => prev) }, 16) // ~60fps for smooth updates return () => clearInterval(interval) }, []) const signer = useMemo(() => (activeAccount as unknown as { signer?: unknown })?.signer, [activeAccount]) const pubkey = (activeAccount as unknown as { pubkey?: string })?.pubkey const hasNip04 = typeof (signer as { nip04?: { encrypt?: unknown; decrypt?: unknown } } | undefined)?.nip04?.encrypt === 'function' const hasNip44 = typeof (signer as { nip44?: { encrypt?: unknown; decrypt?: unknown } } | undefined)?.nip44?.encrypt === 'function' const doEncrypt = async (mode: 'nip44' | 'nip04') => { if (!signer || !pubkey) return try { const api = (signer as { [key: string]: { encrypt: (pubkey: string, message: string) => Promise } })[mode] DebugBus.info('debug', `encrypt start ${mode}`, { pubkey, len: payload.length }) // Start live timing const start = performance.now() setLiveTiming(prev => ({ ...prev, [mode]: { type: 'encrypt', startTime: start } })) const cipher = await api.encrypt(pubkey, payload) const ms = Math.round(performance.now() - start) // Stop live timing setLiveTiming(prev => ({ ...prev, [mode]: undefined })) DebugBus.info('debug', `encrypt done ${mode}`, { len: typeof cipher === 'string' ? cipher.length : -1, ms }) if (mode === 'nip44') setCipher44(cipher) else setCipher04(cipher) if (mode === 'nip44') setTEncrypt44(ms) else setTEncrypt04(ms) } catch (e) { // Stop live timing on error setLiveTiming(prev => ({ ...prev, [mode]: undefined })) DebugBus.error('debug', `encrypt error ${mode}`, e instanceof Error ? e.message : String(e)) } } const doDecrypt = async (mode: 'nip44' | 'nip04') => { if (!signer || !pubkey) return try { const api = (signer as { [key: string]: { decrypt: (pubkey: string, ciphertext: string) => Promise } })[mode] const cipher = mode === 'nip44' ? cipher44 : cipher04 if (!cipher) { DebugBus.warn('debug', `no cipher to decrypt for ${mode}`) return } DebugBus.info('debug', `decrypt start ${mode}`, { len: cipher.length }) // Start live timing const start = performance.now() setLiveTiming(prev => ({ ...prev, [mode]: { type: 'decrypt', startTime: start } })) const plain = await api.decrypt(pubkey, cipher) const ms = Math.round(performance.now() - start) // Stop live timing setLiveTiming(prev => ({ ...prev, [mode]: undefined })) DebugBus.info('debug', `decrypt done ${mode}`, { len: typeof plain === 'string' ? plain.length : -1, ms }) if (mode === 'nip44') setPlain44(String(plain)) else setPlain04(String(plain)) if (mode === 'nip44') setTDecrypt44(ms) else setTDecrypt04(ms) } catch (e) { // Stop live timing on error setLiveTiming(prev => ({ ...prev, [mode]: undefined })) DebugBus.error('debug', `decrypt error ${mode}`, e instanceof Error ? e.message : String(e)) } } const toggleDebug = () => { const next = !debugEnabled setDebugEnabled(next) if (next) localStorage.setItem('debug', '*') else localStorage.removeItem('debug') } const handleBunkerLogin = async () => { if (!bunkerUri.trim()) { setBunkerError('Please enter a bunker URI') return } if (!bunkerUri.startsWith('bunker://')) { setBunkerError('Invalid bunker URI. Must start with bunker://') return } try { setIsBunkerLoading(true) setBunkerError(null) // Create signer from bunker URI with default permissions const permissions = getDefaultBunkerPermissions() const signer = await NostrConnectSigner.fromBunkerURI(bunkerUri, { permissions }) // Get pubkey from signer const pubkey = await signer.getPublicKey() // Create account from signer const account = new Accounts.NostrConnectAccount(pubkey, signer) // Add to account manager and set active accountManager.addAccount(account) accountManager.setActive(account) // Clear input on success setBunkerUri('') } catch (err) { console.error('[bunker] Login failed:', err) const errorMessage = err instanceof Error ? err.message : 'Failed to connect to bunker' // Check for permission-related errors if (errorMessage.toLowerCase().includes('permission') || errorMessage.toLowerCase().includes('unauthorized')) { setBunkerError('Your bunker connection is missing signing permissions. Reconnect and approve signing.') } else { setBunkerError(errorMessage) } } finally { setIsBunkerLoading(false) } } const CodeBox = ({ value }: { value: string }) => (
{value || '—'}
) const getLiveTiming = (mode: 'nip44' | 'nip04', type: 'encrypt' | 'decrypt') => { const timing = liveTiming[mode] if (timing && timing.type === type) { const elapsed = Math.round(performance.now() - timing.startTime) return `${elapsed}ms` } return null } const Stat = ({ label, value, mode, type }: { label: string; value?: string | number | null; mode?: 'nip44' | 'nip04'; type?: 'encrypt' | 'decrypt'; }) => { const liveValue = mode && type ? getLiveTiming(mode, type) : null const displayValue = liveValue || (value ?? '—') const isLive = !!liveValue return ( {label}: {displayValue} {isLive && ⏱️} ) } return (

Bunker Debug

Active pubkey: {pubkey || 'none'}
{/* Bunker Login Section */}

Bunker Connection

{!activeAccount ? (
Connect to your bunker (Nostr Connect signer) to enable encryption/decryption testing
setBunkerUri(e.target.value)} disabled={isBunkerLoading} />
{bunkerError && (
{bunkerError}
)}
) : (
Connected to bunker
{pubkey}
)}
{/* Encryption Tools Section */}

Encryption Tools