chore: format and lint
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
import browser from 'webextension-polyfill'
|
||||
import {
|
||||
validateEvent,
|
||||
getSignature,
|
||||
finalizeEvent,
|
||||
getEventHash,
|
||||
getPublicKey,
|
||||
nip19
|
||||
nip19,
|
||||
utils
|
||||
} from 'nostr-tools'
|
||||
import {nip04} from 'nostr-tools'
|
||||
import {Mutex} from 'async-mutex'
|
||||
import { nip04 } from 'nostr-tools'
|
||||
|
||||
const { hexToBytes } = utils
|
||||
import { Mutex } from 'async-mutex'
|
||||
|
||||
import {
|
||||
NO_PERMISSIONS_REQUIRED,
|
||||
@@ -16,10 +19,10 @@ import {
|
||||
showNotification
|
||||
} from './common'
|
||||
|
||||
const {encrypt, decrypt} = nip04
|
||||
const { encrypt, decrypt } = nip04
|
||||
|
||||
let openPrompt = null
|
||||
let promptMutex = new Mutex()
|
||||
const promptMutex = new Mutex()
|
||||
let releasePromptMutex = () => {}
|
||||
|
||||
browser.runtime.onInstalled.addListener((_, __, reason) => {
|
||||
@@ -27,7 +30,7 @@ browser.runtime.onInstalled.addListener((_, __, reason) => {
|
||||
})
|
||||
|
||||
browser.runtime.onMessage.addListener(async (req, sender) => {
|
||||
let {prompt} = req
|
||||
const { prompt } = req
|
||||
|
||||
if (prompt) {
|
||||
handlePromptMessage(req, sender)
|
||||
@@ -37,46 +40,46 @@ browser.runtime.onMessage.addListener(async (req, sender) => {
|
||||
})
|
||||
|
||||
browser.runtime.onMessageExternal.addListener(
|
||||
async ({type, params}, sender) => {
|
||||
let extensionId = new URL(sender.url).host
|
||||
return handleContentScriptMessage({type, params, host: extensionId})
|
||||
async ({ type, params }, sender) => {
|
||||
const extensionId = new URL(sender.url).host
|
||||
return handleContentScriptMessage({ type, params, host: extensionId })
|
||||
}
|
||||
)
|
||||
|
||||
browser.windows.onRemoved.addListener(windowId => {
|
||||
browser.windows.onRemoved.addListener((_windowId) => {
|
||||
if (openPrompt) {
|
||||
// calling this with a simple "no" response will not store anything, so it's fine
|
||||
// it will just return a failure
|
||||
handlePromptMessage({accept: false}, null)
|
||||
handlePromptMessage({ accept: false }, null)
|
||||
}
|
||||
})
|
||||
|
||||
async function handleContentScriptMessage({type, params, host}) {
|
||||
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([
|
||||
const { 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 = {
|
||||
const { url } = params
|
||||
const raw = url.split('nostr:')[1]
|
||||
const { type, data } = nip19.decode(raw)
|
||||
const 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],
|
||||
? 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
|
||||
@@ -95,7 +98,7 @@ async function handleContentScriptMessage({type, params, host}) {
|
||||
// acquire mutex here before reading policies
|
||||
releasePromptMutex = await promptMutex.acquire()
|
||||
|
||||
let allowed = await getPermissionStatus(
|
||||
const allowed = await getPermissionStatus(
|
||||
host,
|
||||
type,
|
||||
type === 'signEvent' ? params.event : undefined
|
||||
@@ -115,8 +118,8 @@ async function handleContentScriptMessage({type, params, host}) {
|
||||
} else {
|
||||
// ask for authorization
|
||||
try {
|
||||
let id = Math.random().toString().slice(4)
|
||||
let qs = new URLSearchParams({
|
||||
const id = Math.random().toString().slice(4)
|
||||
const qs = new URLSearchParams({
|
||||
host,
|
||||
id,
|
||||
params: JSON.stringify(params),
|
||||
@@ -124,8 +127,8 @@ async function handleContentScriptMessage({type, params, host}) {
|
||||
})
|
||||
|
||||
// prompt will be resolved with true or false
|
||||
let accept = await new Promise((resolve, reject) => {
|
||||
openPrompt = {resolve, reject}
|
||||
const accept = await new Promise((resolve, reject) => {
|
||||
openPrompt = { resolve, reject }
|
||||
const url = `${browser.runtime.getURL(
|
||||
'prompt.html'
|
||||
)}?${qs.toString()}`
|
||||
@@ -146,7 +149,7 @@ async function handleContentScriptMessage({type, params, host}) {
|
||||
})
|
||||
|
||||
// denied, stop here
|
||||
if (!accept) return {error: 'denied'}
|
||||
if (!accept) return { error: 'denied' }
|
||||
} catch (err) {
|
||||
// errored, stop here
|
||||
releasePromptMutex()
|
||||
@@ -158,47 +161,48 @@ async function handleContentScriptMessage({type, params, host}) {
|
||||
}
|
||||
|
||||
// if we're here this means it was accepted
|
||||
let results = await browser.storage.local.get('private_key')
|
||||
if (!results || !results.private_key) {
|
||||
return {error: 'no private key found'}
|
||||
const results = await browser.storage.local.get('private_key')
|
||||
if (!results?.private_key) {
|
||||
return { error: 'no private key found' }
|
||||
}
|
||||
|
||||
let sk = results.private_key
|
||||
const sk = results.private_key
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case 'getPublicKey': {
|
||||
return getPublicKey(sk)
|
||||
return getPublicKey(hexToBytes(sk))
|
||||
}
|
||||
case 'getRelays': {
|
||||
let results = await browser.storage.local.get('relays')
|
||||
const results = await browser.storage.local.get('relays')
|
||||
return results.relays || {}
|
||||
}
|
||||
case 'signEvent': {
|
||||
let {event} = params
|
||||
const { event } = params
|
||||
|
||||
if (!event.pubkey) event.pubkey = getPublicKey(sk)
|
||||
if (!event.pubkey) event.pubkey = getPublicKey(hexToBytes(sk))
|
||||
if (!event.id) event.id = getEventHash(event)
|
||||
if (!validateEvent(event)) return {error: {message: 'invalid event'}}
|
||||
if (!validateEvent(event))
|
||||
return { error: { message: 'invalid event' } }
|
||||
|
||||
event.sig = await getSignature(event, sk)
|
||||
return event
|
||||
const signedEvent = finalizeEvent(event, hexToBytes(sk))
|
||||
return signedEvent
|
||||
}
|
||||
case 'nip04.encrypt': {
|
||||
let {peer, plaintext} = params
|
||||
const { peer, plaintext } = params
|
||||
return encrypt(sk, peer, plaintext)
|
||||
}
|
||||
case 'nip04.decrypt': {
|
||||
let {peer, ciphertext} = params
|
||||
const { peer, ciphertext } = params
|
||||
return decrypt(sk, peer, ciphertext)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return {error: {message: error.message, stack: error.stack}}
|
||||
return { error: { message: error.message, stack: error.stack } }
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePromptMessage({host, type, accept, conditions}, sender) {
|
||||
async function handlePromptMessage({ host, type, accept, conditions }, sender) {
|
||||
// return response
|
||||
openPrompt?.resolve?.(accept)
|
||||
|
||||
|
||||
@@ -22,21 +22,18 @@ function matchConditions(conditions, event) {
|
||||
}
|
||||
|
||||
export async function getPermissionStatus(host, type, event) {
|
||||
let {policies} = await browser.storage.local.get('policies')
|
||||
const { policies } = await browser.storage.local.get('policies')
|
||||
|
||||
let answers = [true, false]
|
||||
const answers = [true, false]
|
||||
for (let i = 0; i < answers.length; i++) {
|
||||
let accept = answers[i]
|
||||
let {conditions} = policies?.[host]?.[accept]?.[type] || {}
|
||||
const accept = answers[i]
|
||||
const { conditions } = policies?.[host]?.[accept]?.[type] || {}
|
||||
|
||||
if (conditions) {
|
||||
if (type === 'signEvent') {
|
||||
if (matchConditions(conditions, event)) {
|
||||
return accept // may be true or false
|
||||
} else {
|
||||
// if this doesn't match we just continue so it will either match for the opposite answer (reject)
|
||||
// or it will end up returning undefined at the end
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
return accept // may be true or false
|
||||
@@ -48,17 +45,17 @@ export async function getPermissionStatus(host, type, event) {
|
||||
}
|
||||
|
||||
export async function updatePermission(host, type, accept, conditions) {
|
||||
let {policies = {}} = await browser.storage.local.get('policies')
|
||||
const { policies = {} } = await browser.storage.local.get('policies')
|
||||
|
||||
// if the new conditions is "match everything", override the previous
|
||||
if (Object.keys(conditions).length === 0) {
|
||||
conditions = {}
|
||||
} else {
|
||||
// if we already had a policy for this, merge the conditions
|
||||
let existingConditions = policies[host]?.[accept]?.[type]?.conditions
|
||||
const existingConditions = policies[host]?.[accept]?.[type]?.conditions
|
||||
if (existingConditions) {
|
||||
if (existingConditions.kinds && conditions.kinds) {
|
||||
Object.keys(existingConditions.kinds).forEach(kind => {
|
||||
Object.keys(existingConditions.kinds).forEach((kind) => {
|
||||
conditions.kinds[kind] = true
|
||||
})
|
||||
}
|
||||
@@ -66,8 +63,8 @@ export async function updatePermission(host, type, accept, conditions) {
|
||||
}
|
||||
|
||||
// if we have a reverse policy (accept / reject) that is exactly equal to this, remove it
|
||||
let other = !accept
|
||||
let reverse = policies?.[host]?.[other]?.[type]
|
||||
const other = !accept
|
||||
const reverse = policies?.[host]?.[other]?.[type]
|
||||
if (
|
||||
reverse &&
|
||||
JSON.stringify(reverse.conditions) === JSON.stringify(conditions)
|
||||
@@ -83,19 +80,19 @@ export async function updatePermission(host, type, accept, conditions) {
|
||||
created_at: Math.round(Date.now() / 1000)
|
||||
}
|
||||
|
||||
browser.storage.local.set({policies})
|
||||
browser.storage.local.set({ policies })
|
||||
}
|
||||
|
||||
export async function removePermissions(host, accept, type) {
|
||||
let {policies = {}} = await browser.storage.local.get('policies')
|
||||
const { policies = {} } = await browser.storage.local.get('policies')
|
||||
delete policies[host]?.[accept]?.[type]
|
||||
browser.storage.local.set({policies})
|
||||
browser.storage.local.set({ policies })
|
||||
}
|
||||
|
||||
export async function showNotification(host, answer, type, params) {
|
||||
let ok = await browser.storage.local.get('notifications')
|
||||
const ok = await browser.storage.local.get('notifications')
|
||||
if (ok) {
|
||||
let action = answer ? 'allowed' : 'denied'
|
||||
const action = answer ? 'allowed' : 'denied'
|
||||
browser.notifications.create(undefined, {
|
||||
type: 'basic',
|
||||
title: `${type} ${action} for ${host}`,
|
||||
|
||||
@@ -3,14 +3,14 @@ import browser from 'webextension-polyfill'
|
||||
const EXTENSION = 'nostrconnect'
|
||||
|
||||
// inject the script that will provide window.nostr
|
||||
let script = document.createElement('script')
|
||||
const script = document.createElement('script')
|
||||
script.setAttribute('async', 'false')
|
||||
script.setAttribute('type', 'text/javascript')
|
||||
script.setAttribute('src', browser.runtime.getURL('nostr-provider.js'))
|
||||
document.head.appendChild(script)
|
||||
|
||||
// listen for messages from that script
|
||||
window.addEventListener('message', async message => {
|
||||
window.addEventListener('message', async (message) => {
|
||||
if (message.source !== window) return
|
||||
if (!message.data) return
|
||||
if (!message.data.params) return
|
||||
@@ -25,12 +25,12 @@ window.addEventListener('message', async message => {
|
||||
host: location.host
|
||||
})
|
||||
} catch (error) {
|
||||
response = {error}
|
||||
response = { error }
|
||||
}
|
||||
|
||||
// return response
|
||||
window.postMessage(
|
||||
{id: message.data.id, ext: EXTENSION, response},
|
||||
{ id: message.data.id, ext: EXTENSION, response },
|
||||
message.origin
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from 'react'
|
||||
|
||||
export function LogoIcon() {
|
||||
return (
|
||||
<svg
|
||||
@@ -8,6 +6,7 @@ export function LogoIcon() {
|
||||
height="56"
|
||||
fill="none"
|
||||
viewBox="0 0 56 56"
|
||||
aria-label="Nostr Connect logo"
|
||||
>
|
||||
<rect width="56" height="56" fill="#EEECFD" rx="16"></rect>
|
||||
<rect
|
||||
@@ -70,6 +69,7 @@ export function SettingsIcon(props) {
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
aria-label="Settings"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
|
||||
@@ -11,7 +11,7 @@ window.nostr = {
|
||||
},
|
||||
|
||||
async signEvent(event) {
|
||||
return this._call('signEvent', {event})
|
||||
return this._call('signEvent', { event })
|
||||
},
|
||||
|
||||
async getRelays() {
|
||||
@@ -20,16 +20,16 @@ window.nostr = {
|
||||
|
||||
nip04: {
|
||||
async encrypt(peer, plaintext) {
|
||||
return window.nostr._call('nip04.encrypt', {peer, plaintext})
|
||||
return window.nostr._call('nip04.encrypt', { peer, plaintext })
|
||||
},
|
||||
|
||||
async decrypt(peer, ciphertext) {
|
||||
return window.nostr._call('nip04.decrypt', {peer, ciphertext})
|
||||
return window.nostr._call('nip04.decrypt', { peer, ciphertext })
|
||||
}
|
||||
},
|
||||
|
||||
_call(type, params) {
|
||||
let id = Math.random().toString().slice(-4)
|
||||
const id = Math.random().toString().slice(-4)
|
||||
console.log(
|
||||
'%c[nostrconnect:%c' +
|
||||
id +
|
||||
@@ -46,7 +46,7 @@ window.nostr = {
|
||||
'font-weight:bold;color:#90b12d;font-family:monospace'
|
||||
)
|
||||
return new Promise((resolve, reject) => {
|
||||
this._requests[id] = {resolve, reject}
|
||||
this._requests[id] = { resolve, reject }
|
||||
window.postMessage(
|
||||
{
|
||||
id,
|
||||
@@ -60,7 +60,7 @@ window.nostr = {
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', message => {
|
||||
window.addEventListener('message', (message) => {
|
||||
if (
|
||||
!message.data ||
|
||||
message.data.response === null ||
|
||||
@@ -71,8 +71,8 @@ window.addEventListener('message', message => {
|
||||
return
|
||||
|
||||
if (message.data.response.error) {
|
||||
let error = new Error(
|
||||
`${EXTENSION}: ` + message.data.response.error.message
|
||||
const error = new Error(
|
||||
`${EXTENSION}: ${message.data.response.error.message}`
|
||||
)
|
||||
error.stack = message.data.response.error.stack
|
||||
window.nostr._requests[message.data.id].reject(error)
|
||||
@@ -104,7 +104,9 @@ async function replaceNostrSchemeLink(e) {
|
||||
if (e.target.tagName !== 'A' || !e.target.href.startsWith('nostr:')) return
|
||||
if (replacing === false) return
|
||||
|
||||
let response = await window.nostr._call('replaceURL', {url: e.target.href})
|
||||
const response = await window.nostr._call('replaceURL', {
|
||||
url: e.target.href
|
||||
})
|
||||
if (response === false) {
|
||||
replacing = false
|
||||
return
|
||||
|
||||
@@ -1,42 +1,65 @@
|
||||
import browser from 'webextension-polyfill'
|
||||
import React, {useState, useCallback, useEffect} from 'react'
|
||||
import {render} from 'react-dom'
|
||||
import {generatePrivateKey, nip19} from 'nostr-tools'
|
||||
import { useState, useCallback, useEffect } from 'react'
|
||||
import { render } from 'react-dom'
|
||||
import { generateSecretKey, nip19, utils } from 'nostr-tools'
|
||||
import QRCode from 'react-qr-code'
|
||||
import * as Tabs from '@radix-ui/react-tabs'
|
||||
import {LogoIcon} from './icons'
|
||||
import {removePermissions} from './common'
|
||||
import { LogoIcon } from './icons'
|
||||
import { removePermissions } from './common'
|
||||
import * as Checkbox from '@radix-ui/react-checkbox'
|
||||
|
||||
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 [privKey, setPrivKey] = useState('')
|
||||
const [relays, setRelays] = useState([])
|
||||
const [newRelayURL, setNewRelayURL] = useState('')
|
||||
const [policies, setPermissions] = useState([])
|
||||
const [protocolHandler, setProtocolHandler] = useState(
|
||||
'https://njump.me/{raw}'
|
||||
)
|
||||
const [hidingPrivateKey, hidePrivateKey] = useState(true)
|
||||
const [showNotifications, setNotifications] = useState(false)
|
||||
const [messages, setMessages] = useState([])
|
||||
const [handleNostrLinks, setHandleNostrLinks] = useState(false)
|
||||
const [showProtocolHandlerHelp, setShowProtocolHandlerHelp] = useState(false)
|
||||
const [unsavedChanges, setUnsavedChanges] = useState([])
|
||||
|
||||
const showMessage = useCallback(msg => {
|
||||
const showMessage = useCallback((msg) => {
|
||||
messages.push(msg)
|
||||
setMessages(messages)
|
||||
setTimeout(() => setMessages([]), 3000)
|
||||
})
|
||||
|
||||
const loadPermissions = useCallback(async () => {
|
||||
const { policies = {} } = await browser.storage.local.get('policies')
|
||||
const 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)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
browser.storage.local
|
||||
.get(['private_key', 'relays', 'protocol_handler', 'notifications'])
|
||||
.then(results => {
|
||||
.then((results) => {
|
||||
if (results.private_key) {
|
||||
setPrivKey(nip19.nsecEncode(results.private_key))
|
||||
}
|
||||
if (results.relays) {
|
||||
let relaysList = []
|
||||
for (let url in results.relays) {
|
||||
const relaysList = []
|
||||
for (const url in results.relays) {
|
||||
relaysList.push({
|
||||
url,
|
||||
policy: results.relays[url]
|
||||
@@ -57,28 +80,7 @@ function Options() {
|
||||
|
||||
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)
|
||||
}
|
||||
}, [loadPermissions])
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen flex flex-col items-center justify-center">
|
||||
@@ -178,8 +180,8 @@ function Options() {
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="font-semibold text-base">Preferred Relays:</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{relays.map(({url, policy}, i) => (
|
||||
<div key={i} className="flex items-center gap-4">
|
||||
{relays.map(({ url, policy }, i) => (
|
||||
<div key={url} className="flex items-center gap-4">
|
||||
<input
|
||||
value={url}
|
||||
onChange={changeRelayURL.bind(null, i)}
|
||||
@@ -205,6 +207,7 @@ function Options() {
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-4 h-4"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
@@ -240,6 +243,7 @@ function Options() {
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-4 h-4"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
@@ -258,6 +262,7 @@ function Options() {
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={removeRelay.bind(null, i)}
|
||||
className="shrink-0 px-3 w-24 h-9 font-semibold border border-primary shadow-sm rounded-lg inline-flex items-center justify-center disabled:text-muted"
|
||||
>
|
||||
@@ -268,14 +273,15 @@ function Options() {
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
value={newRelayURL}
|
||||
onChange={e => setNewRelayURL(e.target.value)}
|
||||
onKeyDown={e => {
|
||||
onChange={(e) => setNewRelayURL(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') addNewRelay()
|
||||
}}
|
||||
placeholder="wss://"
|
||||
className="flex-1 h-9 bg-transparent border px-3 py-1 border-primary rounded-lg placeholder:text-muted"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!newRelayURL}
|
||||
onClick={addNewRelay}
|
||||
className="shrink-0 px-3 w-24 h-9 font-semibold border border-primary shadow-sm rounded-lg inline-flex items-center justify-center disabled:text-muted"
|
||||
@@ -317,7 +323,7 @@ function Options() {
|
||||
</thead>
|
||||
<tbody>
|
||||
{policies.map(
|
||||
({host, type, accept, conditions, created_at}) => (
|
||||
({ host, type, accept, conditions, created_at }) => (
|
||||
<tr
|
||||
key={
|
||||
host + type + accept + JSON.stringify(conditions)
|
||||
@@ -344,6 +350,7 @@ function Options() {
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleRevoke}
|
||||
data-host={host}
|
||||
data-accept={accept}
|
||||
@@ -358,11 +365,11 @@ function Options() {
|
||||
)}
|
||||
{!policies.length && (
|
||||
<tr>
|
||||
{Array(5)
|
||||
.fill('N/A')
|
||||
.map((v, i) => (
|
||||
<td key={i}>{v}</td>
|
||||
))}
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
@@ -387,6 +394,7 @@ function Options() {
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-4 h-4"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
@@ -413,6 +421,7 @@ function Options() {
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-5 h-5"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
@@ -438,6 +447,7 @@ function Options() {
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-4 h-4"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
@@ -458,7 +468,10 @@ function Options() {
|
||||
onChange={handleChangeProtocolHandler}
|
||||
/>
|
||||
{!showProtocolHandlerHelp && (
|
||||
<button onClick={changeShowProtocolHandlerHelp}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={changeShowProtocolHandlerHelp}
|
||||
>
|
||||
?
|
||||
</button>
|
||||
)}
|
||||
@@ -487,6 +500,7 @@ examples:
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!unsavedChanges.length}
|
||||
onClick={saveChanges}
|
||||
className="w-full h-10 bg-primary rounded-xl font-bold inline-flex items-center justify-center text-white disabled:cursor-not-allowed disabled:opacity-70 transform active:translate-y-1 transition-transform ease-in-out duration-75"
|
||||
@@ -498,13 +512,14 @@ examples:
|
||||
)
|
||||
|
||||
async function handleKeyChange(e) {
|
||||
let key = e.target.value.toLowerCase().trim()
|
||||
const key = e.target.value.toLowerCase().trim()
|
||||
setPrivKey(key)
|
||||
addUnsavedChanges('private_key')
|
||||
}
|
||||
|
||||
async function generate() {
|
||||
setPrivKey(nip19.nsecEncode(generatePrivateKey()))
|
||||
const sk = generateSecretKey()
|
||||
setPrivKey(nip19.nsecEncode(utils.bytesToHex(sk)))
|
||||
addUnsavedChanges('private_key')
|
||||
}
|
||||
|
||||
@@ -517,7 +532,7 @@ examples:
|
||||
let hexOrEmptyKey = privKey
|
||||
|
||||
try {
|
||||
let {type, data} = nip19.decode(privKey)
|
||||
const { type, data } = nip19.decode(privKey)
|
||||
if (type === 'nsec') hexOrEmptyKey = data
|
||||
} catch (_) {}
|
||||
|
||||
@@ -544,7 +559,7 @@ examples:
|
||||
function changeRelayURL(i, ev) {
|
||||
setRelays([
|
||||
...relays.slice(0, i),
|
||||
{url: ev.target.value, policy: relays[i].policy},
|
||||
{ url: ev.target.value, policy: relays[i].policy },
|
||||
...relays.slice(i + 1)
|
||||
])
|
||||
addUnsavedChanges('relays')
|
||||
@@ -555,7 +570,7 @@ examples:
|
||||
...relays.slice(0, i),
|
||||
{
|
||||
url: relays[i].url,
|
||||
policy: {...relays[i].policy, [cat]: !relays[i].policy[cat]}
|
||||
policy: { ...relays[i].policy, [cat]: !relays[i].policy[cat] }
|
||||
},
|
||||
...relays.slice(i + 1)
|
||||
])
|
||||
@@ -572,7 +587,7 @@ examples:
|
||||
if (!newRelayURL.startsWith('wss://')) return
|
||||
relays.push({
|
||||
url: newRelayURL,
|
||||
policy: {read: true, write: true}
|
||||
policy: { read: true, write: true }
|
||||
})
|
||||
setRelays(relays)
|
||||
addUnsavedChanges('relays')
|
||||
@@ -580,7 +595,7 @@ examples:
|
||||
}
|
||||
|
||||
async function handleRevoke(e) {
|
||||
let {host, accept, type} = e.target.dataset
|
||||
const { host, accept, type } = e.target.dataset
|
||||
if (
|
||||
window.confirm(
|
||||
`revoke all ${
|
||||
@@ -601,14 +616,14 @@ examples:
|
||||
}
|
||||
|
||||
async function requestBrowserNotificationPermissions() {
|
||||
let granted = await browser.permissions.request({
|
||||
const granted = await browser.permissions.request({
|
||||
permissions: ['notifications']
|
||||
})
|
||||
if (!granted) setNotifications(false)
|
||||
}
|
||||
|
||||
async function saveNotifications() {
|
||||
await browser.storage.local.set({notifications: showNotifications})
|
||||
await browser.storage.local.set({ notifications: showNotifications })
|
||||
showMessage('saved notifications!')
|
||||
}
|
||||
|
||||
@@ -616,8 +631,8 @@ examples:
|
||||
await browser.storage.local.set({
|
||||
relays: Object.fromEntries(
|
||||
relays
|
||||
.filter(({url}) => url.trim() !== '')
|
||||
.map(({url, policy}) => [url.trim(), policy])
|
||||
.filter(({ url }) => url.trim() !== '')
|
||||
.map(({ url, policy }) => [url.trim(), policy])
|
||||
)
|
||||
})
|
||||
showMessage('saved relays!')
|
||||
@@ -641,19 +656,19 @@ examples:
|
||||
}
|
||||
|
||||
async function saveNostrProtocolHandlerSettings() {
|
||||
await browser.storage.local.set({protocol_handler: protocolHandler})
|
||||
await browser.storage.local.set({ protocol_handler: protocolHandler })
|
||||
showMessage('saved protocol handler!')
|
||||
}
|
||||
|
||||
function addUnsavedChanges(section) {
|
||||
if (!unsavedChanges.find(s => s === section)) {
|
||||
if (!unsavedChanges.find((s) => s === section)) {
|
||||
unsavedChanges.push(section)
|
||||
setUnsavedChanges(unsavedChanges)
|
||||
}
|
||||
}
|
||||
|
||||
async function saveChanges() {
|
||||
for (let section of unsavedChanges) {
|
||||
for (const section of unsavedChanges) {
|
||||
switch (section) {
|
||||
case 'private_key':
|
||||
await saveKey()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,21 +22,18 @@ function matchConditions(conditions, event) {
|
||||
}
|
||||
|
||||
export async function getPermissionStatus(host, type, event) {
|
||||
let {policies} = await browser.storage.local.get('policies')
|
||||
const { policies } = await browser.storage.local.get('policies')
|
||||
|
||||
let answers = [true, false]
|
||||
const answers = [true, false]
|
||||
for (let i = 0; i < answers.length; i++) {
|
||||
let accept = answers[i]
|
||||
let {conditions} = policies?.[host]?.[accept]?.[type] || {}
|
||||
const accept = answers[i]
|
||||
const { conditions } = policies?.[host]?.[accept]?.[type] || {}
|
||||
|
||||
if (conditions) {
|
||||
if (type === 'signEvent') {
|
||||
if (matchConditions(conditions, event)) {
|
||||
return accept // may be true or false
|
||||
} else {
|
||||
// if this doesn't match we just continue so it will either match for the opposite answer (reject)
|
||||
// or it will end up returning undefined at the end
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
return accept // may be true or false
|
||||
@@ -48,17 +45,17 @@ export async function getPermissionStatus(host, type, event) {
|
||||
}
|
||||
|
||||
export async function updatePermission(host, type, accept, conditions) {
|
||||
let {policies = {}} = await browser.storage.local.get('policies')
|
||||
const { policies = {} } = await browser.storage.local.get('policies')
|
||||
|
||||
// if the new conditions is "match everything", override the previous
|
||||
if (Object.keys(conditions).length === 0) {
|
||||
conditions = {}
|
||||
} else {
|
||||
// if we already had a policy for this, merge the conditions
|
||||
let existingConditions = policies[host]?.[accept]?.[type]?.conditions
|
||||
const existingConditions = policies[host]?.[accept]?.[type]?.conditions
|
||||
if (existingConditions) {
|
||||
if (existingConditions.kinds && conditions.kinds) {
|
||||
Object.keys(existingConditions.kinds).forEach(kind => {
|
||||
Object.keys(existingConditions.kinds).forEach((kind) => {
|
||||
conditions.kinds[kind] = true
|
||||
})
|
||||
}
|
||||
@@ -66,8 +63,8 @@ export async function updatePermission(host, type, accept, conditions) {
|
||||
}
|
||||
|
||||
// if we have a reverse policy (accept / reject) that is exactly equal to this, remove it
|
||||
let other = !accept
|
||||
let reverse = policies?.[host]?.[other]?.[type]
|
||||
const other = !accept
|
||||
const reverse = policies?.[host]?.[other]?.[type]
|
||||
if (
|
||||
reverse &&
|
||||
JSON.stringify(reverse.conditions) === JSON.stringify(conditions)
|
||||
@@ -83,19 +80,19 @@ export async function updatePermission(host, type, accept, conditions) {
|
||||
created_at: Math.round(Date.now() / 1000)
|
||||
}
|
||||
|
||||
browser.storage.local.set({policies})
|
||||
browser.storage.local.set({ policies })
|
||||
}
|
||||
|
||||
export async function removePermissions(host, accept, type) {
|
||||
let {policies = {}} = await browser.storage.local.get('policies')
|
||||
const { policies = {} } = await browser.storage.local.get('policies')
|
||||
delete policies[host]?.[accept]?.[type]
|
||||
browser.storage.local.set({policies})
|
||||
browser.storage.local.set({ policies })
|
||||
}
|
||||
|
||||
export async function showNotification(host, answer, type, params) {
|
||||
let ok = await browser.storage.local.get('notifications')
|
||||
const ok = await browser.storage.local.get('notifications')
|
||||
if (ok) {
|
||||
let action = answer ? 'allowed' : 'denied'
|
||||
const action = answer ? 'allowed' : 'denied'
|
||||
browser.notifications.create(undefined, {
|
||||
type: 'basic',
|
||||
title: `${type} ${action} for ${host}`,
|
||||
|
||||
@@ -3,12 +3,7 @@
|
||||
"description": "Nostr Signer Extension",
|
||||
"version": "0.1.2",
|
||||
"homepage_url": "https://github.com/reyamir/nostr-connect",
|
||||
"manifest_version": 2,
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "{e665d138-0e5b-4b7a-ab91-7af834eda7a2}"
|
||||
}
|
||||
},
|
||||
"manifest_version": 3,
|
||||
"icons": {
|
||||
"16": "icons/icon16.png",
|
||||
"32": "icons/icon32.png",
|
||||
@@ -17,19 +12,25 @@
|
||||
},
|
||||
"options_page": "options.html",
|
||||
"background": {
|
||||
"scripts": ["background.build.js"]
|
||||
"service_worker": "background.build.js"
|
||||
},
|
||||
"browser_action": {
|
||||
"action": {
|
||||
"default_title": "Nostr Connect",
|
||||
"default_popup": "popup.html"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["content-script.build.js"]
|
||||
"js": ["content-script.build.js"],
|
||||
"all_frames": true
|
||||
}
|
||||
],
|
||||
"permissions": ["storage"],
|
||||
"optional_permissions": ["notifications"],
|
||||
"web_accessible_resources": ["nostr-provider.js"]
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": ["nostr-provider.js"],
|
||||
"matches": ["https://*/*", "http://localhost:*/*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ window.nostr = {
|
||||
},
|
||||
|
||||
async signEvent(event) {
|
||||
return this._call('signEvent', {event})
|
||||
return this._call('signEvent', { event })
|
||||
},
|
||||
|
||||
async getRelays() {
|
||||
@@ -20,16 +20,16 @@ window.nostr = {
|
||||
|
||||
nip04: {
|
||||
async encrypt(peer, plaintext) {
|
||||
return window.nostr._call('nip04.encrypt', {peer, plaintext})
|
||||
return window.nostr._call('nip04.encrypt', { peer, plaintext })
|
||||
},
|
||||
|
||||
async decrypt(peer, ciphertext) {
|
||||
return window.nostr._call('nip04.decrypt', {peer, ciphertext})
|
||||
return window.nostr._call('nip04.decrypt', { peer, ciphertext })
|
||||
}
|
||||
},
|
||||
|
||||
_call(type, params) {
|
||||
let id = Math.random().toString().slice(-4)
|
||||
const id = Math.random().toString().slice(-4)
|
||||
console.log(
|
||||
'%c[nostrconnect:%c' +
|
||||
id +
|
||||
@@ -46,7 +46,7 @@ window.nostr = {
|
||||
'font-weight:bold;color:#90b12d;font-family:monospace'
|
||||
)
|
||||
return new Promise((resolve, reject) => {
|
||||
this._requests[id] = {resolve, reject}
|
||||
this._requests[id] = { resolve, reject }
|
||||
window.postMessage(
|
||||
{
|
||||
id,
|
||||
@@ -60,7 +60,7 @@ window.nostr = {
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', message => {
|
||||
window.addEventListener('message', (message) => {
|
||||
if (
|
||||
!message.data ||
|
||||
message.data.response === null ||
|
||||
@@ -71,8 +71,8 @@ window.addEventListener('message', message => {
|
||||
return
|
||||
|
||||
if (message.data.response.error) {
|
||||
let error = new Error(
|
||||
`${EXTENSION}: ` + message.data.response.error.message
|
||||
const error = new Error(
|
||||
`${EXTENSION}: ${message.data.response.error.message}`
|
||||
)
|
||||
error.stack = message.data.response.error.stack
|
||||
window.nostr._requests[message.data.id].reject(error)
|
||||
@@ -104,7 +104,9 @@ async function replaceNostrSchemeLink(e) {
|
||||
if (e.target.tagName !== 'A' || !e.target.href.startsWith('nostr:')) return
|
||||
if (replacing === false) return
|
||||
|
||||
let response = await window.nostr._call('replaceURL', {url: e.target.href})
|
||||
const response = await window.nostr._call('replaceURL', {
|
||||
url: e.target.href
|
||||
})
|
||||
if (response === false) {
|
||||
replacing = false
|
||||
return
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,15 +1,15 @@
|
||||
import browser from 'webextension-polyfill'
|
||||
import {render} from 'react-dom'
|
||||
import {getPublicKey, nip19} from 'nostr-tools'
|
||||
import React, {useState, useMemo, useEffect} from 'react'
|
||||
import { render } from 'react-dom'
|
||||
import { getPublicKey, nip19 } from 'nostr-tools'
|
||||
import { useState, useMemo, useEffect } from 'react'
|
||||
import QRCode from 'react-qr-code'
|
||||
import {SettingsIcon} from './icons'
|
||||
import {minidenticon} from 'minidenticons'
|
||||
import { SettingsIcon } from './icons'
|
||||
import { minidenticon } from 'minidenticons'
|
||||
import * as Tabs from '@radix-ui/react-tabs'
|
||||
|
||||
function Popup() {
|
||||
let [keys, setKeys] = useState(null)
|
||||
let avatarURI = useMemo(
|
||||
const [keys, setKeys] = useState(null)
|
||||
const avatarURI = useMemo(
|
||||
() =>
|
||||
keys
|
||||
? 'data:image/svg+xml;utf8,' +
|
||||
@@ -25,27 +25,27 @@ function Popup() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
browser.storage.local.get(['private_key', 'relays']).then(results => {
|
||||
browser.storage.local.get(['private_key', 'relays']).then((results) => {
|
||||
if (results.private_key) {
|
||||
let hexKey = getPublicKey(results.private_key)
|
||||
let npubKey = nip19.npubEncode(hexKey)
|
||||
const hexKey = getPublicKey(results.private_key)
|
||||
const npubKey = nip19.npubEncode(hexKey)
|
||||
|
||||
setKeys({npub: npubKey, hex: hexKey})
|
||||
setKeys({ npub: npubKey, hex: hexKey })
|
||||
|
||||
if (results.relays) {
|
||||
let relaysList = []
|
||||
for (let url in results.relays) {
|
||||
const relaysList = []
|
||||
for (const url in results.relays) {
|
||||
if (results.relays[url].write) {
|
||||
relaysList.push(url)
|
||||
if (relaysList.length >= 3) break
|
||||
}
|
||||
}
|
||||
if (relaysList.length) {
|
||||
let nprofileKey = nip19.nprofileEncode({
|
||||
const nprofileKey = nip19.nprofileEncode({
|
||||
pubkey: hexKey,
|
||||
relays: relaysList
|
||||
})
|
||||
setKeys(prev => ({...prev, nprofile: nprofileKey}))
|
||||
setKeys((prev) => ({ ...prev, nprofile: nprofileKey }))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -71,6 +71,7 @@ function Popup() {
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-6 h-6"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
@@ -95,6 +96,7 @@ function Popup() {
|
||||
<img
|
||||
src={avatarURI}
|
||||
className="w-9 h-9 rounded-full bg-muted"
|
||||
alt="Avatar"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-9 h-9 rounded-full bg-muted" />
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import browser from 'webextension-polyfill'
|
||||
import {render} from 'react-dom'
|
||||
import React, {useState} from 'react'
|
||||
import { render } from 'react-dom'
|
||||
import { useState } from 'react'
|
||||
|
||||
import {PERMISSION_NAMES} from './common'
|
||||
import {LogoIcon} from './icons'
|
||||
import { PERMISSION_NAMES } from './common'
|
||||
import { LogoIcon } from './icons'
|
||||
import * as Checkbox from '@radix-ui/react-checkbox'
|
||||
|
||||
function Prompt() {
|
||||
const [isRemember, setIsRemember] = useState(false)
|
||||
|
||||
let qs = new URLSearchParams(location.search)
|
||||
let id = qs.get('id')
|
||||
let host = qs.get('host')
|
||||
let type = qs.get('type')
|
||||
const qs = new URLSearchParams(location.search)
|
||||
const id = qs.get('id')
|
||||
const host = qs.get('host')
|
||||
const type = qs.get('type')
|
||||
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) {
|
||||
} catch (_err) {
|
||||
params = null
|
||||
}
|
||||
|
||||
function authorizeHandler(accept) {
|
||||
const conditions = isRemember ? {} : null
|
||||
return function (ev) {
|
||||
return (ev) => {
|
||||
ev.preventDefault()
|
||||
browser.runtime.sendMessage({
|
||||
prompt: true,
|
||||
@@ -73,6 +73,7 @@ function Prompt() {
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-4 h-4"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
@@ -88,12 +89,14 @@ function Prompt() {
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={authorizeHandler(false)}
|
||||
className="flex-1 h-10 rounded-lg shadow-sm border border-primary inline-flex items-center justify-center font-semibold"
|
||||
>
|
||||
Reject
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={authorizeHandler(true)}
|
||||
className="flex-1 h-10 rounded-lg shadow-sm border border-secondary bg-primary text-white inline-flex items-center justify-center font-semibold"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user