import browser from 'webextension-polyfill' import React, { useState, useEffect } from 'react' import { createRoot } from 'react-dom/client' import * as nip19 from 'nostr-tools/nip19' import Identicon from 'identicon.js' import { PERMISSION_NAMES, getSessionVault } from './common' import {getPublicKey} from 'nostr-tools/pure' import {hexToBytes} from 'nostr-tools/utils' function shortenPubkey(pubkey) { if (!pubkey) return '' const npub = nip19.npubEncode(pubkey) return npub.slice(0, 12) + '...' + npub.slice(-8) } function getIdenticonUri(pubkey) { if (!pubkey || pubkey.length < 15) return '' const svg = new Identicon(pubkey, { format: 'svg' }).toString() return `data:image/svg+xml;base64,${svg}` } function Prompt() { let qs = new URLSearchParams(location.search) let id = qs.get('id') let host = qs.get('host') let type = qs.get('type') let pubkey = qs.get('pubkey') let unlockOnly = qs.get('unlockOnly') === 'true' let favicon = qs.get('favicon') || '' let accountName = qs.get('accountName') || '' let accountPicture = qs.get('accountPicture') || '' let params, event try { params = JSON.parse(qs.get('params')) if (Object.keys(params).length === 0) params = null else if (params.event) event = params.event } catch (err) { params = null } const [isLocked, setIsLocked] = useState(true) const [isLoading, setIsLoading] = useState(true) const [password, setPassword] = useState('') const [error, setError] = useState('') const [currentPubkey, setCurrentPubkey] = useState(pubkey) const [currentName, setCurrentName] = useState(accountName) const [faviconError, setFaviconError] = useState(false) const [avatarError, setAvatarError] = useState(false) useEffect(() => { checkLockState() }, []) async function checkLockState() { // Check if vault exists first const { encryptedVault } = await browser.storage.local.get(['encryptedVault']) if (!encryptedVault) { // No vault exists, reject the request browser.runtime.sendMessage({ prompt: true, id, host, type, accept: false, conditions: null }) return } // Check if vault is unlocked (key is in background memory) const { unlocked } = await browser.runtime.sendMessage({ type: 'GET_LOCK_STATUS' }) setIsLocked(!unlocked) setIsLoading(false) // If already unlocked, fetch the current pubkey if (unlocked) { await fetchCurrentPubkey() } } async function fetchCurrentPubkey() { try { const vault = await getSessionVault() if (vault?.accountDefault) { const pk = getPublicKey(hexToBytes(vault.accountDefault)) setCurrentPubkey(pk) } } catch (err) { // Ignore errors - pubkey display is optional } } async function unlockVault(e) { e.preventDefault() setError('') // Send unlock request to background (key stays in background memory) const response = await browser.runtime.sendMessage({ type: 'UNLOCK_VAULT', password: password }) // Clear password from UI memory immediately setPassword('') if (!response.success) { setError(response.error || 'Invalid password') return } // Get the pubkey from the unlocked vault (now in session storage) await fetchCurrentPubkey() // If permission was already granted, auto-approve after unlock if (unlockOnly) { browser.runtime.sendMessage({ prompt: true, id, host, type, accept: true, conditions: null // Don't update permission, it's already set }) return } setIsLocked(false) } if (isLoading) { return (

Loading...

) } if (isLocked) { return (

Vault Locked

{favicon && !faviconError && ( setFaviconError(true)} /> )}
{host}

is requesting permission to {PERMISSION_NAMES[type]}

Enter your password to unlock and continue:

setPassword(e.target.value)} className="prompt__unlock-input" /> {error &&
{error}
}
) } return (
{/* Header with icon */}

Permission Request

{/* Active account indicator */} {currentPubkey && (
setAvatarError(true)} />
Signing as {currentName && {currentName}} {shortenPubkey(currentPubkey)}
)} {/* Request details */}
{favicon && !faviconError && ( setFaviconError(true)} /> )}
{host}

is requesting permission to {PERMISSION_NAMES[type]}

{/* Event/params preview */} {params && (
Event data:
            {JSON.stringify(event || params, null, 2)}
          
)} {/* Action buttons */}
{event?.kind !== undefined && ( )}
{event?.kind !== undefined ? ( ) : ( )}
) function authorizeHandler(accept, conditions) { return function (ev) { ev.preventDefault() browser.runtime.sendMessage({ prompt: true, id, host, type, accept, conditions }) } } } const container = document.getElementById('main') const root = createRoot(container) root.render()