diff --git a/src/components/AuthModal.vue b/src/components/AuthModal.vue index 911636d..ab2eb25 100644 --- a/src/components/AuthModal.vue +++ b/src/components/AuthModal.vue @@ -567,31 +567,73 @@ function handleAmberOpen() { } /** - * Decode a pubkey from text. Handles hex, npub, and nprofile formats. + * Decode a pubkey from text. Handles hex, npub, nprofile, and JSON (e.g. pasted NIP-98 event). + * Normalizes pasted content: trims, strips newlines, accepts hex with extra characters. */ async function decodePubkeyText(text: string): Promise { - if (/^[0-9a-f]{64}$/i.test(text)) return text - if (text.startsWith('npub') || text.startsWith('nprofile')) { + const raw = text.trim().replace(/\s+/g, '') + if (!raw) throw new Error('Not a valid public key. Expected hex or npub format.') + + // JSON: e.g. pasted NIP-98 request or signed event from Amber (use trim only so JSON stays valid) + if (raw.startsWith('{')) { + try { + const parsed = JSON.parse(text.trim()) + if (parsed.pubkey && /^[0-9a-f]{64}$/i.test(String(parsed.pubkey))) { + return String(parsed.pubkey) + } + } catch { + // Not valid JSON, fall through + } + } + + // Exact 64-char hex + if (/^[0-9a-f]{64}$/i.test(raw)) return raw + + // Hex with extra chars: take first 64 hex characters + const hexOnly = raw.replace(/[^0-9a-fA-F]/g, '') + if (hexOnly.length >= 64) { + return hexOnly.slice(0, 64) + } + + // npub / nprofile + const trimmed = text.trim() + if (trimmed.startsWith('npub') || trimmed.startsWith('nprofile')) { const { decodeProfilePointer } = await import('applesauce-core/helpers') - const decoded = decodeProfilePointer(text) + const decoded = decodeProfilePointer(trimmed) if (!decoded?.pubkey) throw new Error('Could not decode npub') return decoded.pubkey } + throw new Error('Not a valid public key. Expected hex or npub format.') } /** * Process a pubkey (from auto-read or paste), then open Amber to sign NIP-98. + * If the user pasted a full signed NIP-98 event (e.g. from Amber), complete login directly. */ async function processPubkey(text: string) { errorMessage.value = null try { + // If they pasted the signed event JSON from Amber, complete login without opening Amber again + const trimmed = text.trim() + if (trimmed.startsWith('{')) { + try { + const parsed = JSON.parse(trimmed) + if (parsed.sig && parsed.pubkey && /^[0-9a-f]{64}$/i.test(String(parsed.pubkey))) { + await processSignature(trimmed) + return + } + } catch { + // Not a signed event, continue to pubkey extraction + } + } + const pubkey = await decodePubkeyText(text) amberPubkey.value = pubkey showPasteFallback.value = false amberPasteInput.value = '' - // Build the unsigned NIP-98 event + // Build the unsigned NIP-98 event (name tag so Amber shows "IndeeHub" not "null") const sessionUrl = authService.getNostrSessionUrl() const unsignedEvent = { kind: 27235, @@ -599,6 +641,7 @@ async function processPubkey(text: string) { tags: [ ['u', sessionUrl], ['method', 'POST'], + ['name', 'IndeeHub'], ], content: '', pubkey,