import browser from 'webextension-polyfill' import React, {useState, useCallback, useEffect} from 'react' import {render} from 'react-dom' import {generatePrivateKey, nip19} from 'nostr-tools' import QRCode from 'react-qr-code' import {removePermissions} from './common' function Options() { let [privKey, setPrivKey] = useState('') let [relays, setRelays] = useState([]) let [newRelayURL, setNewRelayURL] = useState('') let [policies, setPermissions] = useState([]) let [protocolHandler, setProtocolHandler] = useState('https://njump.me/{raw}') let [hidingPrivateKey, hidePrivateKey] = useState(true) let [showNotifications, setNotifications] = useState(false) let [messages, setMessages] = useState([]) let [handleNostrLinks, setHandleNostrLinks] = useState(false) let [showProtocolHandlerHelp, setShowProtocolHandlerHelp] = useState(false) let [unsavedChanges, setUnsavedChanges] = useState([]) const showMessage = useCallback(msg => { messages.push(msg) setMessages(messages) setTimeout(() => setMessages([]), 3000) }) useEffect(() => { browser.storage.local .get(['private_key', 'relays', 'protocol_handler', 'notifications']) .then(results => { if (results.private_key) { setPrivKey(nip19.nsecEncode(results.private_key)) } if (results.relays) { let relaysList = [] for (let url in results.relays) { relaysList.push({ url, policy: results.relays[url] }) } setRelays(relaysList) } if (results.protocol_handler) { setProtocolHandler(results.protocol_handler) setHandleNostrLinks(true) setShowProtocolHandlerHelp(false) } if (results.notifications) { setNotifications(true) } }) }, []) useEffect(() => { loadPermissions() }, []) async function loadPermissions() { let {policies = {}} = await browser.storage.local.get('policies') let list = [] Object.entries(policies).forEach(([host, accepts]) => { Object.entries(accepts).forEach(([accept, types]) => { Object.entries(types).forEach(([type, {conditions, created_at}]) => { list.push({ host, type, accept, conditions, created_at }) }) }) }) setPermissions(list) } return (

Nostr Connect

Nostr signer

Private key:
{!privKey && ( )} {privKey && hidingPrivateKey && ( )} {privKey && !hidingPrivateKey && ( )}

Your key is stored locally. The developer has no way of seeing your keys.

{privKey && !isKeyValid() && (
private key is invalid!
)} {!hidingPrivateKey && isKeyValid() && (
)}
Relays 10
Permissions 0
Preferred Relays:
{relays.map(({url, policy}, i) => (
))}
setNewRelayURL(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') addNewRelay() }} className="flex-1 h-9 bg-transparent border px-3 py-1 border-gray-200 dark:border-gray-800 rounded-lg" />
Advanced
{/*
{handleNostrLinks && (
{!showProtocolHandlerHelp && ( )}
{showProtocolHandlerHelp && (
{`
    {raw} = anything after the colon, i.e. the full nip19 bech32 string
    {hex} = hex pubkey for npub or nprofile, hex event id for note or nevent
    {p_or_e} = "p" for npub or nprofile, "e" for note or nevent
    {u_or_n} = "u" for npub or nprofile, "n" for note or nevent
    {relay0} = first relay in a nprofile or nevent
    {relay1} = second relay in a nprofile or nevent
    {relay2} = third relay in a nprofile or nevent
    {hrp} = human-readable prefix of the nip19 string

    examples:
      - https://njump.me/{raw}
      - https://snort.social/{raw}
      - https://nostr.band/{raw}
                `}
)}
)}
*/} {/*
{messages.map((message, i) => (
{message}
))}
*/}
{/*

permissions

{policies.map(({host, type, accept, conditions, created_at}) => ( ))} {!policies.length && ( {Array(5) .fill('N/A') .map((v, i) => ( ))} )}
domain permission answer conditions since
{host} {type} {accept === 'true' ? 'allow' : 'deny'} {conditions.kinds ? `kinds: ${Object.keys(conditions.kinds).join(', ')}` : 'always'} {new Date(created_at * 1000) .toISOString() .split('.')[0] .split('T') .join(' ')}
{v}
{!policies.length && (
no permissions have been granted yet
)}
*/}
) async function handleKeyChange(e) { let key = e.target.value.toLowerCase().trim() setPrivKey(key) addUnsavedChanges('private_key') } async function generate() { setPrivKey(nip19.nsecEncode(generatePrivateKey())) addUnsavedChanges('private_key') } async function saveKey() { if (!isKeyValid()) { showMessage('PRIVATE KEY IS INVALID! did not save private key.') return } let hexOrEmptyKey = privKey try { let {type, data} = nip19.decode(privKey) if (type === 'nsec') hexOrEmptyKey = data } catch (_) {} await browser.storage.local.set({ private_key: hexOrEmptyKey }) if (hexOrEmptyKey !== '') { setPrivKey(nip19.nsecEncode(hexOrEmptyKey)) } showMessage('saved private key!') } function isKeyValid() { if (privKey === '') return true if (privKey.match(/^[a-f0-9]{64}$/)) return true try { if (nip19.decode(privKey).type === 'nsec') return true } catch (_) {} return false } function changeRelayURL(i, ev) { setRelays([ ...relays.slice(0, i), {url: ev.target.value, policy: relays[i].policy}, ...relays.slice(i + 1) ]) addUnsavedChanges('relays') } function toggleRelayPolicy(i, cat) { setRelays([ ...relays.slice(0, i), { url: relays[i].url, policy: {...relays[i].policy, [cat]: !relays[i].policy[cat]} }, ...relays.slice(i + 1) ]) addUnsavedChanges('relays') } function removeRelay(i) { setRelays([...relays.slice(0, i), ...relays.slice(i + 1)]) addUnsavedChanges('relays') } function addNewRelay() { if (newRelayURL.trim() === '') return relays.push({ url: newRelayURL, policy: {read: true, write: true} }) setRelays(relays) addUnsavedChanges('relays') setNewRelayURL('') } async function handleRevoke(e) { let {host, accept, type} = e.target.dataset if ( window.confirm( `revoke all ${ accept === 'true' ? 'accept' : 'deny' } ${type} policies from ${host}?` ) ) { await removePermissions(host, accept, type) showMessage('removed policies') loadPermissions() } } function handleNotifications() { setNotifications(!showNotifications) addUnsavedChanges('notifications') if (!showNotifications) requestBrowserNotificationPermissions() } async function requestBrowserNotificationPermissions() { let granted = await browser.permissions.request({ permissions: ['notifications'] }) if (!granted) setNotifications(false) } async function saveNotifications() { await browser.storage.local.set({notifications: showNotifications}) showMessage('saved notifications!') } async function saveRelays() { await browser.storage.local.set({ relays: Object.fromEntries( relays .filter(({url}) => url.trim() !== '') .map(({url, policy}) => [url.trim(), policy]) ) }) showMessage('saved relays!') } function changeShowProtocolHandlerHelp() { setShowProtocolHandlerHelp(true) } function changeHandleNostrLinks() { if (handleNostrLinks) { setProtocolHandler('') addUnsavedChanges('protocol_handler') } else setShowProtocolHandlerHelp(true) setHandleNostrLinks(!handleNostrLinks) } function handleChangeProtocolHandler(e) { setProtocolHandler(e.target.value) addUnsavedChanges('protocol_handler') } async function saveNostrProtocolHandlerSettings() { await browser.storage.local.set({protocol_handler: protocolHandler}) showMessage('saved protocol handler!') } function addUnsavedChanges(section) { if (!unsavedChanges.find(s => s === section)) { unsavedChanges.push(section) setUnsavedChanges(unsavedChanges) } } async function saveChanges() { for (let section of unsavedChanges) { switch (section) { case 'private_key': await saveKey() break case 'relays': await saveRelays() break case 'protocol_handler': await saveNostrProtocolHandlerSettings() break case 'notifications': await saveNotifications() break } } setUnsavedChanges([]) } } render(, document.getElementById('main'))