nostr: link replacing.
this commit also includes (unrelated) naïvely hiding the private key and displaying on blur/focus.
This commit is contained in:
@@ -1,10 +1,17 @@
|
||||
import browser from 'webextension-polyfill'
|
||||
import {validateEvent, signEvent, getEventHash, getPublicKey} from 'nostr-tools'
|
||||
import {
|
||||
validateEvent,
|
||||
signEvent,
|
||||
getEventHash,
|
||||
getPublicKey,
|
||||
nip19
|
||||
} from 'nostr-tools'
|
||||
import {encrypt, decrypt} from 'nostr-tools/nip04'
|
||||
import {Mutex} from 'async-mutex'
|
||||
|
||||
import {
|
||||
PERMISSIONS_REQUIRED,
|
||||
NO_PERMISSIONS_REQUIRED,
|
||||
readPermissionLevel,
|
||||
updatePermission
|
||||
} from './common'
|
||||
@@ -41,6 +48,46 @@ browser.windows.onRemoved.addListener(windowId => {
|
||||
})
|
||||
|
||||
async function handleContentScriptMessage({type, params, host}) {
|
||||
if (NO_PERMISSIONS_REQUIRED[type]) {
|
||||
// authorized, and we won't do anything with private key here, so do a separate handler
|
||||
switch (type) {
|
||||
case 'replaceURL': {
|
||||
let {protocol_handler: ph} = await browser.storage.local.get([
|
||||
'protocol_handler'
|
||||
])
|
||||
if (!ph) return false
|
||||
|
||||
let {url} = params
|
||||
let raw = url.split('nostr:')[1]
|
||||
let {type, data} = nip19.decode(raw)
|
||||
let replacements = {
|
||||
raw,
|
||||
hrp: type,
|
||||
hex:
|
||||
type === 'npub' || type === 'note'
|
||||
? data
|
||||
: type === 'nprofile'
|
||||
? data.pubkey
|
||||
: type === 'nevent'
|
||||
? data.id
|
||||
: null,
|
||||
p_or_e: {npub: 'p', note: 'e', nprofile: 'p', nevent: 'e'}[type],
|
||||
u_or_n: {npub: 'u', note: 'n', nprofile: 'u', nevent: 'n'}[type],
|
||||
relay0: type === 'nprofile' ? data.relays[0] : null,
|
||||
relay1: type === 'nprofile' ? data.relays[1] : null,
|
||||
relay2: type === 'nprofile' ? data.relays[2] : null
|
||||
}
|
||||
let result = ph
|
||||
Object.entries(replacements).forEach(([pattern, value]) => {
|
||||
result = result.replace(new RegExp(`{ *${pattern} *}`, 'g'), value)
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
} else {
|
||||
let level = await readPermissionLevel(host)
|
||||
|
||||
if (level >= PERMISSIONS_REQUIRED[type]) {
|
||||
@@ -57,6 +104,7 @@ async function handleContentScriptMessage({type, params, host}) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let results = await browser.storage.local.get('private_key')
|
||||
if (!results || !results.private_key) {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import browser from 'webextension-polyfill'
|
||||
|
||||
export const NO_PERMISSIONS_REQUIRED = {
|
||||
replaceURL: true
|
||||
}
|
||||
|
||||
export const PERMISSIONS_REQUIRED = {
|
||||
getPublicKey: 1,
|
||||
getRelays: 5,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "nos2x",
|
||||
"description": "Nostr Signer Extension",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.0",
|
||||
"homepage_url": "https://github.com/fiatjaf/nos2x",
|
||||
"manifest_version": 3,
|
||||
"icons": {
|
||||
@@ -18,17 +18,17 @@
|
||||
"default_title": "nos2x",
|
||||
"default_popup": "popup.html"
|
||||
},
|
||||
"content_scripts": [{
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": [
|
||||
"content-script.build.js"
|
||||
]
|
||||
}],
|
||||
"permissions": [
|
||||
"storage"
|
||||
"js": ["content-script.build.js"]
|
||||
}
|
||||
],
|
||||
"web_accessible_resources": [{
|
||||
"permissions": ["storage"],
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": ["nostr-provider.js"],
|
||||
"matches": ["https://*/*", "http://localhost:*/*"]
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ window.nostr = {
|
||||
window.addEventListener('message', message => {
|
||||
if (
|
||||
!message.data ||
|
||||
!message.data.response ||
|
||||
message.data.response === null ||
|
||||
message.data.response === undefined ||
|
||||
message.data.ext !== 'nos2x' ||
|
||||
!window.nostr._requests[message.data.id]
|
||||
)
|
||||
@@ -60,5 +61,23 @@ window.addEventListener('message', message => {
|
||||
window.nostr._requests[message.data.id].resolve(message.data.response)
|
||||
}
|
||||
|
||||
delete window.nostr._requests[message.data.id];
|
||||
delete window.nostr._requests[message.data.id]
|
||||
})
|
||||
|
||||
// hack to replace nostr:nprofile.../etc links with something else
|
||||
let replacing = null
|
||||
let links = document.querySelectorAll('a[href^="nostr:"]')
|
||||
for (let i = 0; i < links.length; i++) {
|
||||
links[i].addEventListener('mouseenter', replaceNostrSchemeLink)
|
||||
}
|
||||
async function replaceNostrSchemeLink(e) {
|
||||
if (replacing === false) return
|
||||
|
||||
let response = await window.nostr._call('replaceURL', {url: e.target.href})
|
||||
if (response === false) {
|
||||
replacing = false
|
||||
return
|
||||
}
|
||||
|
||||
e.target.href = response
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ function Options() {
|
||||
let [relays, setRelays] = useState([])
|
||||
let [newRelayURL, setNewRelayURL] = useState('')
|
||||
let [permissions, setPermissions] = useState()
|
||||
let [protocolHandler, setProtocolHandler] = useState(null)
|
||||
let [hidingPrivateKey, hidePrivateKey] = useState(true)
|
||||
let [message, setMessage] = useState('')
|
||||
|
||||
const showMessage = useCallback(msg => {
|
||||
@@ -22,7 +24,9 @@ function Options() {
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
browser.storage.local.get(['private_key', 'relays']).then(results => {
|
||||
browser.storage.local
|
||||
.get(['private_key', 'relays', 'protocol_handler'])
|
||||
.then(results => {
|
||||
if (results.private_key) setKey(nip19.nsecEncode(results.private_key))
|
||||
if (results.relays) {
|
||||
let relaysList = []
|
||||
@@ -34,6 +38,9 @@ function Options() {
|
||||
}
|
||||
setRelays(relaysList)
|
||||
}
|
||||
if (results.protocol_handler) {
|
||||
setProtocolHandler(results.protocol_handler)
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
@@ -41,6 +48,21 @@ function Options() {
|
||||
loadPermissions()
|
||||
}, [])
|
||||
|
||||
function loadPermissions() {
|
||||
readPermissions().then(permissions => {
|
||||
setPermissions(
|
||||
Object.entries(permissions).map(
|
||||
([host, {level, condition, created_at}]) => ({
|
||||
host,
|
||||
level,
|
||||
condition,
|
||||
created_at
|
||||
})
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>nos2x</h1>
|
||||
@@ -89,15 +111,18 @@ function Options() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{marginBottom: '10px'}}>
|
||||
<label>
|
||||
<div>private key: </div>
|
||||
<div style={{marginLeft: '10px'}}>
|
||||
<div style={{display: 'flex'}}>
|
||||
<input
|
||||
style={{width: '500px'}}
|
||||
type={hidingPrivateKey ? 'password' : 'text'}
|
||||
style={{width: '600px'}}
|
||||
value={key}
|
||||
onChange={handleKeyChange}
|
||||
onFocus={() => hidePrivateKey(false)}
|
||||
onBlur={() => hidePrivateKey(true)}
|
||||
/>
|
||||
{key === '' && <button onClick={generate}>generate</button>}
|
||||
</div>
|
||||
@@ -144,6 +169,70 @@ function Options() {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h2>
|
||||
handle{' '}
|
||||
<span style={{padding: '2px', background: 'silver'}}>nostr:</span>{' '}
|
||||
links:
|
||||
</h2>
|
||||
<div style={{marginLeft: '10px'}}>
|
||||
<div>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="ph"
|
||||
value="no"
|
||||
checked={protocolHandler === null}
|
||||
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
|
||||
{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
|
||||
{raw} = anything after the colon, i.e. the full nip19 bech32 string
|
||||
{hrp} = human-readable prefix of the nip19 string
|
||||
|
||||
examples:
|
||||
- https://nostr.guru/{p_or_e}/{hex}
|
||||
- https://brb.io/{u_or_n}/{hex}
|
||||
- https://notes.blockcore.net/{p_or_e}/{hex}
|
||||
`}</pre>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
style={{marginTop: '10px'}}
|
||||
onClick={saveNostrProtocolHandlerSettings}
|
||||
>
|
||||
save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{marginTop: '12px', fontSize: '120%'}}>{message}</div>
|
||||
</>
|
||||
)
|
||||
@@ -216,7 +305,6 @@ function Options() {
|
||||
}
|
||||
|
||||
async function handleRevoke(e) {
|
||||
e.preventDefault()
|
||||
let host = e.target.dataset.domain
|
||||
if (window.confirm(`revoke all permissions from ${host}?`)) {
|
||||
await removePermissions(host)
|
||||
@@ -225,21 +313,6 @@ function Options() {
|
||||
}
|
||||
}
|
||||
|
||||
function loadPermissions() {
|
||||
readPermissions().then(permissions => {
|
||||
setPermissions(
|
||||
Object.entries(permissions).map(
|
||||
([host, {level, condition, created_at}]) => ({
|
||||
host,
|
||||
level,
|
||||
condition,
|
||||
created_at
|
||||
})
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
async function saveRelays() {
|
||||
await browser.storage.local.set({
|
||||
relays: Object.fromEntries(
|
||||
@@ -250,6 +323,24 @@ function Options() {
|
||||
})
|
||||
showMessage('saved relays!')
|
||||
}
|
||||
|
||||
function handleChangeProtocolHandler(e) {
|
||||
if (e.target.type === 'text') setProtocolHandler(e.target.value)
|
||||
else
|
||||
switch (e.target.value) {
|
||||
case 'no':
|
||||
setProtocolHandler(null)
|
||||
break
|
||||
case 'yes':
|
||||
setProtocolHandler('')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
async function saveNostrProtocolHandlerSettings() {
|
||||
await browser.storage.local.set({protocol_handler: protocolHandler})
|
||||
showMessage('saved protocol handler!')
|
||||
}
|
||||
}
|
||||
|
||||
render(<Options />, document.getElementById('main'))
|
||||
|
||||
Reference in New Issue
Block a user