Adding QR codes for better air-gaped experience

This commit is contained in:
Luis Miguel García Mancebo
2023-02-26 21:33:54 +01:00
committed by fiatjaf_
parent 9302797132
commit 72c9e516e3
5 changed files with 389 additions and 216 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "nos2x", "name": "nos2x",
"description": "Nostr Signer Extension", "description": "Nostr Signer Extension",
"version": "1.8.1", "version": "1.8.2",
"homepage_url": "https://github.com/fiatjaf/nos2x", "homepage_url": "https://github.com/fiatjaf/nos2x",
"manifest_version": 3, "manifest_version": 3,
"icons": { "icons": {

View File

@@ -1,7 +1,8 @@
import browser from 'webextension-polyfill' import browser from 'webextension-polyfill'
import React, {useState, useCallback, useEffect} from 'react' import React, {useState, useCallback, useEffect} from 'react'
import {render} from 'react-dom' import {render} from 'react-dom'
import {generatePrivateKey, nip19} from 'nostr-tools' import {generatePrivateKey, getPublicKey, nip19} from 'nostr-tools'
import QRCode from 'react-qr-code'
import { import {
getPermissionsString, getPermissionsString,
@@ -10,13 +11,15 @@ import {
} from './common' } from './common'
function Options() { function Options() {
let [key, setKey] = useState('') let [pubKey, setPubKey] = useState('')
let [privKey, setPrivKey] = useState('')
let [relays, setRelays] = useState([]) let [relays, setRelays] = useState([])
let [newRelayURL, setNewRelayURL] = useState('') let [newRelayURL, setNewRelayURL] = useState('')
let [permissions, setPermissions] = useState() let [permissions, setPermissions] = useState()
let [protocolHandler, setProtocolHandler] = useState(null) let [protocolHandler, setProtocolHandler] = useState(null)
let [hidingPrivateKey, hidePrivateKey] = useState(true) let [hidingPrivateKey, hidePrivateKey] = useState(true)
let [message, setMessage] = useState('') let [message, setMessage] = useState('')
let [showQR, setShowQR] = useState('')
const showMessage = useCallback(msg => { const showMessage = useCallback(msg => {
setMessage(msg) setMessage(msg)
@@ -25,23 +28,30 @@ function Options() {
useEffect(() => { useEffect(() => {
browser.storage.local browser.storage.local
.get(['private_key', 'relays', 'protocol_handler']) .get(['private_key', 'relays', 'protocol_handler'])
.then(results => { .then(results => {
if (results.private_key) setKey(nip19.nsecEncode(results.private_key)) if (results.private_key) {
if (results.relays) { setPrivKey(nip19.nsecEncode(results.private_key))
let relaysList = []
for (let url in results.relays) { let hexKey = getPublicKey(results.private_key)
relaysList.push({ let npubKey = nip19.npubEncode(hexKey)
url,
policy: results.relays[url] setPubKey(npubKey)
}) }
} if (results.relays) {
setRelays(relaysList) let relaysList = []
for (let url in results.relays) {
relaysList.push({
url,
policy: results.relays[url]
})
} }
if (results.protocol_handler) { setRelays(relaysList)
setProtocolHandler(results.protocol_handler) }
} if (results.protocol_handler) {
}) setProtocolHandler(results.protocol_handler)
}
})
}, []) }, [])
useEffect(() => { useEffect(() => {
@@ -51,164 +61,184 @@ function Options() {
function loadPermissions() { function loadPermissions() {
readPermissions().then(permissions => { readPermissions().then(permissions => {
setPermissions( setPermissions(
Object.entries(permissions).map( Object.entries(permissions).map(
([host, {level, condition, created_at}]) => ({ ([host, {level, condition, created_at}]) => ({
host, host,
level, level,
condition, condition,
created_at created_at
}) })
) )
) )
}) })
} }
return ( return (
<> <>
<h1>nos2x</h1> <h1>nos2x</h1>
<p>nostr signer extension</p> <p>nostr signer extension</p>
<h2>options</h2> <h2>options</h2>
<div style={{marginBottom: '10px'}}> <div style={{marginBottom: '10px'}}>
<div style={{display: 'flex', alignItems: 'center'}}> <div style={{display: 'flex', alignItems: 'center'}}>
<span>preferred relays:</span> <span>preferred relays:</span>
<button style={{marginLeft: '20px'}} onClick={saveRelays}> <button style={{marginLeft: '20px'}} onClick={saveRelays}>
save
</button>
</div>
<div style={{marginLeft: '10px'}}>
{relays.map(({url, policy}, i) => (
<div key={i} style={{display: 'flex'}}>
<input
style={{marginRight: '10px', width: '400px'}}
value={url}
onChange={changeRelayURL.bind(null, i)}
/>
<label>
read
<input
type="checkbox"
checked={policy.read}
onChange={toggleRelayPolicy.bind(null, i, 'read')}
/>
</label>
<label>
write
<input
type="checkbox"
checked={policy.write}
onChange={toggleRelayPolicy.bind(null, i, 'write')}
/>
</label>
</div>
))}
<div style={{display: 'flex'}}>
<input
style={{width: '400px'}}
value={newRelayURL}
onChange={e => setNewRelayURL(e.target.value)}
onBlur={addNewRelay}
/>
</div>
</div>
</div>
<div style={{marginBottom: '10px'}}>
<label>
<div>private key:&nbsp;</div>
<div style={{marginLeft: '10px'}}>
<div style={{display: 'flex'}}>
<input
type={hidingPrivateKey ? 'password' : 'text'}
style={{width: '600px'}}
value={key}
onChange={handleKeyChange}
onFocus={() => hidePrivateKey(false)}
onBlur={() => hidePrivateKey(true)}
/>
{key === '' && <button onClick={generate}>generate</button>}
</div>
<button disabled={!isKeyValid()} onClick={saveKey}>
save save
</button> </button>
</div> </div>
</label> <div style={{marginLeft: '10px'}}>
{permissions?.length > 0 && ( {relays.map(({url, policy}, i) => (
<> <div key={i} style={{display: 'flex'}}>
<h2>permissions</h2> <input
<table> style={{marginRight: '10px', width: '400px'}}
<thead> value={url}
<tr> onChange={changeRelayURL.bind(null, i)}
<th>domain</th> />
<th>permissions</th> <label>
<th>condition</th> read
<th>since</th> <input
<th></th> type="checkbox"
</tr> checked={policy.read}
</thead> onChange={toggleRelayPolicy.bind(null, i, 'read')}
<tbody> />
{permissions.map(({host, level, condition, created_at}) => ( </label>
<tr key={host}> <label>
<td>{host}</td> write
<td>{getPermissionsString(level)}</td> <input
<td>{condition}</td> type="checkbox"
<td> checked={policy.write}
{new Date(created_at * 1000) onChange={toggleRelayPolicy.bind(null, i, 'write')}
.toISOString() />
.split('.')[0] </label>
.split('T') </div>
.join(' ')} ))}
</td> <div style={{display: 'flex'}}>
<td> <input
<button onClick={handleRevoke} data-domain={host}> style={{width: '400px'}}
revoke value={newRelayURL}
</button> onChange={e => setNewRelayURL(e.target.value)}
</td> onBlur={addNewRelay}
/>
</div>
</div>
</div>
<div style={{marginBottom: '10px'}}>
<label>
<div>private key:&nbsp;</div>
<div style={{marginLeft: '10px'}}>
<div style={{display: 'flex'}}>
<input
type={hidingPrivateKey ? 'password' : 'text'}
style={{width: '600px'}}
value={privKey}
onChange={handleKeyChange}
onFocus={() => hidePrivateKey(false)}
onBlur={() => hidePrivateKey(true)}
/>
{privKey === '' && <button onClick={generate}>generate</button>}
</div>
<button disabled={!isKeyValid()} onClick={saveKey}>
save
</button>
<button disabled={!isKeyValid()} onClick={() => setShowQR('priv')}>
Show QR for private key
</button>
<button disabled={!isKeyValid()} onClick={() => setShowQR('pub')}>
Show QR for public key
</button>
{ showQR && (
<div id={'qrCodeDiv'} style={{ height: 'auto', maxWidth: 256, width: '100%', marginTop: '20px', marginBottom: '30px' }}>
<QRCode
size={256}
style={{ height: 'auto', maxWidth: '100%', width: '100%' }}
value={showQR === 'priv' ? privKey : pubKey}
viewBox={`0 0 256 256`}
/>
</div>
)}
</div>
</label>
{permissions?.length > 0 && (
<>
<h2>permissions</h2>
<table>
<thead>
<tr>
<th>domain</th>
<th>permissions</th>
<th>condition</th>
<th>since</th>
<th></th>
</tr> </tr>
))} </thead>
</tbody> <tbody>
</table> {permissions.map(({host, level, condition, created_at}) => (
</> <tr key={host}>
)} <td>{host}</td>
</div> <td>{getPermissionsString(level)}</td>
<div> <td>{condition}</td>
<h2> <td>
handle{' '} {new Date(created_at * 1000)
<span style={{padding: '2px', background: 'silver'}}>nostr:</span>{' '} .toISOString()
links: .split('.')[0]
</h2> .split('T')
<div style={{marginLeft: '10px'}}> .join(' ')}
<div> </td>
<label> <td>
<input <button onClick={handleRevoke} data-domain={host}>
type="radio" revoke
name="ph" </button>
value="no" </td>
checked={protocolHandler === null} </tr>
onChange={handleChangeProtocolHandler} ))}
/>{' '} </tbody>
no </table>
</label> </>
</div> )}
<div> </div>
<label> <div>
<input <h2>
type="radio" handle{' '}
name="ph" <span style={{padding: '2px', background: 'silver'}}>nostr:</span>{' '}
value="yes" links:
checked={protocolHandler !== null} </h2>
onChange={handleChangeProtocolHandler} <div style={{marginLeft: '10px'}}>
/>
yes
</label>
</div>
{protocolHandler !== null && (
<div> <div>
<input <label>
placeholder="url template" <input
value={protocolHandler} type="radio"
onChange={handleChangeProtocolHandler} name="ph"
style={{width: '680px', maxWidth: '90%'}} value="no"
/> checked={protocolHandler === null}
<pre>{` onChange={handleChangeProtocolHandler}
/>{' '}
no
</label>
</div>
<div>
<label>
<input
type="radio"
name="ph"
value="yes"
checked={protocolHandler !== null}
onChange={handleChangeProtocolHandler}
/>
yes
</label>
</div>
{protocolHandler !== null && (
<div>
<input
placeholder="url template"
value={protocolHandler}
onChange={handleChangeProtocolHandler}
style={{width: '680px', maxWidth: '90%'}}
/>
<pre>{`
{hex} = hex pubkey for npub or nprofile, hex event id for note or nevent {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 {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 {u_or_n} = "u" for npub or nprofile, "n" for note or nevent
@@ -223,36 +253,36 @@ function Options() {
- https://brb.io/{u_or_n}/{hex} - https://brb.io/{u_or_n}/{hex}
- https://notes.blockcore.net/{p_or_e}/{hex} - https://notes.blockcore.net/{p_or_e}/{hex}
`}</pre> `}</pre>
</div> </div>
)} )}
<button <button
style={{marginTop: '10px'}} style={{marginTop: '10px'}}
onClick={saveNostrProtocolHandlerSettings} onClick={saveNostrProtocolHandlerSettings}
> >
save save
</button> </button>
</div>
</div> </div>
</div> <div style={{marginTop: '12px', fontSize: '120%'}}>{message}</div>
<div style={{marginTop: '12px', fontSize: '120%'}}>{message}</div> </>
</>
) )
async function handleKeyChange(e) { async function handleKeyChange(e) {
let key = e.target.value.toLowerCase().trim() let key = e.target.value.toLowerCase().trim()
setKey(key) setPrivKey(key)
} }
async function generate(e) { async function generate() {
setKey(nip19.nsecEncode(generatePrivateKey())) setPrivKey(nip19.nsecEncode(generatePrivateKey()))
} }
async function saveKey() { async function saveKey() {
if (!isKeyValid()) return if (!isKeyValid()) return
let hexOrEmptyKey = key let hexOrEmptyKey = privKey
try { try {
let {type, data} = nip19.decode(key) let {type, data} = nip19.decode(privKey)
if (type === 'nsec') hexOrEmptyKey = data if (type === 'nsec') hexOrEmptyKey = data
} catch (_) {} } catch (_) {}
@@ -261,17 +291,17 @@ function Options() {
}) })
if (hexOrEmptyKey !== '') { if (hexOrEmptyKey !== '') {
setKey(nip19.nsecEncode(hexOrEmptyKey)) setPrivKey(nip19.nsecEncode(hexOrEmptyKey))
} }
showMessage('saved private key!') showMessage('saved private key!')
} }
function isKeyValid() { function isKeyValid() {
if (key === '') return true if (privKey === '') return true
if (key.match(/^[a-f0-9]{64}$/)) return true if (privKey.match(/^[a-f0-9]{64}$/)) return true
try { try {
if (nip19.decode(key).type === 'nsec') return true if (nip19.decode(privKey).type === 'nsec') return true
} catch (_) {} } catch (_) {}
return false return false
} }
@@ -316,7 +346,7 @@ function Options() {
async function saveRelays() { async function saveRelays() {
await browser.storage.local.set({ await browser.storage.local.set({
relays: Object.fromEntries( relays: Object.fromEntries(
relays relays
.filter(({url}) => url.trim() !== '') .filter(({url}) => url.trim() !== '')
.map(({url, policy}) => [url.trim(), policy]) .map(({url, policy}) => [url.trim(), policy])
) )

View File

@@ -2,18 +2,33 @@ import browser from 'webextension-polyfill'
import {render} from 'react-dom' import {render} from 'react-dom'
import {getPublicKey, nip19} from 'nostr-tools' import {getPublicKey, nip19} from 'nostr-tools'
import React, {useState, useRef, useEffect} from 'react' import React, {useState, useRef, useEffect} from 'react'
import QRCode from 'react-qr-code'
function Popup() { function Popup() {
let [key, setKey] = useState('') let [pubKey, setPubKey] = useState('')
let [privKey, setPrivKey] = useState('')
let keys = useRef([]) let keys = useRef([])
let [showQR, setShowQR] = useState('')
const QrIcon = () => (
<svg width="30px" height="30px" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" className="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round"
d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 013.75 9.375v-4.5zM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 01-1.125-1.125v-4.5zM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0113.5 9.375v-4.5z"/>
<path stroke-linecap="round" stroke-linejoin="round"
d="M6.75 6.75h.75v.75h-.75v-.75zM6.75 16.5h.75v.75h-.75v-.75zM16.5 6.75h.75v.75h-.75v-.75zM13.5 13.5h.75v.75h-.75v-.75zM13.5 19.5h.75v.75h-.75v-.75zM19.5 13.5h.75v.75h-.75v-.75zM19.5 19.5h.75v.75h-.75v-.75zM16.5 16.5h.75v.75h-.75v-.75z"/>
</svg>
)
useEffect(() => { useEffect(() => {
browser.storage.local.get(['private_key', 'relays']).then(results => { browser.storage.local.get(['private_key', 'relays']).then(results => {
setPrivKey(results.private_key)
if (results.private_key) { if (results.private_key) {
let hexKey = getPublicKey(results.private_key) let hexKey = getPublicKey(results.private_key)
let npubKey = nip19.npubEncode(hexKey) let npubKey = nip19.npubEncode(hexKey)
setKey(npubKey) setPubKey(npubKey)
keys.current.push(npubKey) keys.current.push(npubKey)
keys.current.push(hexKey) keys.current.push(hexKey)
@@ -35,42 +50,66 @@ function Popup() {
} }
} }
} else { } else {
setKey(null) setPubKey(null)
} }
}) })
}, []) }, [])
return ( return (
<> <>
<h2>nos2x</h2> <h2>nos2x</h2>
{key === null ? ( {pubKey === null ? (
<p style={{width: '150px'}}> <p style={{width: '150px'}}>
you don't have a private key set. use the options page to set one. you don't have a private key set. use the options page to set one.
</p> </p>
) : ( ) : (
<> <>
<p> <p>
<a onClick={toggleKeyType}>↩️</a> your public key: <a onClick={toggleKeyType}>↩️</a> your public key:
</p> </p>
<pre <pre
style={{ style={{
whiteSpace: 'pre-wrap', whiteSpace: 'pre-wrap',
wordBreak: 'break-all', wordBreak: 'break-all',
width: '100px' width: '200px'
}} }}
> >
<code>{key}</code> <code>{pubKey}</code>
</pre> </pre>
</>
)} <div style={{float: 'left', marginRight: '30px', marginBottom: '20px'}}>
</> <a onClick={() => setShowQR('pub')}>
<QrIcon></QrIcon> PUB
</a>
</div>
<div style={{float: 'left', marginRight: '30px', marginBottom: '20px'}}>
<a onClick={() => setShowQR('priv')}>
<QrIcon></QrIcon> PRIV
</a>
</div>
{ showQR && (
<div id={'qrCodeDiv'} style={{ height: 'auto', margin: '0 auto', maxWidth: 256, width: '100%', marginTop: '50px' }}>
{showQR === 'priv' ? (<p>PRIVATE KEY</p>) : (<p>PUBLIC KEY</p>)}
<QRCode
size={256}
style={{ height: 'auto', maxWidth: '100%', width: '100%' }}
value={showQR === 'priv' ? privKey : pubKey}
viewBox={`0 0 256 256`}
/>
</div>
)}
</>
)}
</>
) )
function toggleKeyType(e) { function toggleKeyType(e) {
e.preventDefault() e.preventDefault()
let nextKeyType = let nextKeyType =
keys.current[(keys.current.indexOf(key) + 1) % keys.current.length] keys.current[(keys.current.indexOf(pubKey) + 1) % keys.current.length]
setKey(nextKeyType) setPubKey(nextKeyType)
} }
} }

View File

@@ -10,6 +10,8 @@
"prettier": "^2.5.1", "prettier": "^2.5.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-native-svg": "^13.8.0",
"react-qr-code": "^2.0.11",
"use-boolean-state": "^1.0.2", "use-boolean-state": "^1.0.2",
"use-debounce": "^7.0.1", "use-debounce": "^7.0.1",
"webextension-polyfill": "^0.8.0" "webextension-polyfill": "^0.8.0"

104
yarn.lock
View File

@@ -142,6 +142,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
boolbase@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
brace-expansion@^1.1.7: brace-expansion@^1.1.7:
version "1.1.11" version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -197,6 +202,30 @@ cross-spawn@^7.0.2:
shebang-command "^2.0.0" shebang-command "^2.0.0"
which "^2.0.1" which "^2.0.1"
css-select@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==
dependencies:
boolbase "^1.0.0"
css-what "^6.1.0"
domhandler "^5.0.2"
domutils "^3.0.1"
nth-check "^2.0.1"
css-tree@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d"
integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==
dependencies:
mdn-data "2.0.14"
source-map "^0.6.1"
css-what@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
debug@^4.1.1, debug@^4.3.2: debug@^4.1.1, debug@^4.3.2:
version "4.3.3" version "4.3.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
@@ -230,6 +259,36 @@ doctrine@^3.0.0:
dependencies: dependencies:
esutils "^2.0.2" esutils "^2.0.2"
dom-serializer@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
dependencies:
domelementtype "^2.3.0"
domhandler "^5.0.2"
entities "^4.2.0"
domelementtype@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
domhandler@^5.0.1, domhandler@^5.0.2:
version "5.0.3"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
dependencies:
domelementtype "^2.3.0"
domutils@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c"
integrity sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==
dependencies:
dom-serializer "^2.0.0"
domelementtype "^2.3.0"
domhandler "^5.0.1"
enquirer@^2.3.5: enquirer@^2.3.5:
version "2.3.6" version "2.3.6"
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
@@ -237,6 +296,11 @@ enquirer@^2.3.5:
dependencies: dependencies:
ansi-colors "^4.1.1" ansi-colors "^4.1.1"
entities@^4.2.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174"
integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==
es-abstract@^1.19.0, es-abstract@^1.19.1: es-abstract@^1.19.0, es-abstract@^1.19.1:
version "1.19.1" version "1.19.1"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3"
@@ -846,6 +910,11 @@ lru-cache@^6.0.0:
dependencies: dependencies:
yallist "^4.0.0" yallist "^4.0.0"
mdn-data@2.0.14:
version "2.0.14"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
minimatch@^3.0.4: minimatch@^3.0.4:
version "3.0.4" version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@@ -874,6 +943,13 @@ nostr-tools@^1.1.0:
"@scure/bip32" "^1.1.1" "@scure/bip32" "^1.1.1"
"@scure/bip39" "^1.1.0" "@scure/bip39" "^1.1.0"
nth-check@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==
dependencies:
boolbase "^1.0.0"
object-assign@^4.1.1: object-assign@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -990,7 +1066,7 @@ progress@^2.0.0:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
prop-types@^15.7.2: prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1" version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -1004,6 +1080,11 @@ punycode@^2.1.0:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
qr.js@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f"
integrity sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==
react-dom@^17.0.2: react-dom@^17.0.2:
version "17.0.2" version "17.0.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
@@ -1018,6 +1099,22 @@ react-is@^16.13.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-native-svg@^13.8.0:
version "13.8.0"
resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-13.8.0.tgz#b6a22cf77f8098f910490a13aeb160a37e182f97"
integrity sha512-G8Mx6W86da+vFimZBJvA93POw8yz0fgDS5biy6oIjMWVJVQSDzCyzwO/zY0yuZmCDhKSZzogl5m0wXXvW2OcTA==
dependencies:
css-select "^5.1.0"
css-tree "^1.1.3"
react-qr-code@^2.0.11:
version "2.0.11"
resolved "https://registry.yarnpkg.com/react-qr-code/-/react-qr-code-2.0.11.tgz#444c759a2100424972e17135fbe0e32eaffa19e8"
integrity sha512-P7mvVM5vk9NjGdHMt4Z0KWeeJYwRAtonHTghZT2r+AASinLUUKQ9wfsGH2lPKsT++gps7hXmaiMGRvwTDEL9OA==
dependencies:
prop-types "^15.8.1"
qr.js "0.0.0"
react@^17.0.2: react@^17.0.2:
version "17.0.2" version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
@@ -1100,6 +1197,11 @@ side-channel@^1.0.4:
get-intrinsic "^1.0.2" get-intrinsic "^1.0.2"
object-inspect "^1.9.0" object-inspect "^1.9.0"
source-map@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
string.prototype.matchall@^4.0.6: string.prototype.matchall@^4.0.6:
version "4.0.6" version "4.0.6"
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz#5abb5dabc94c7b0ea2380f65ba610b3a544b15fa" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz#5abb5dabc94c7b0ea2380f65ba610b3a544b15fa"