chore: format and lint

This commit is contained in:
Ren Amamiya
2026-04-08 11:28:39 +07:00
parent 5b7b06ff5d
commit 387796faa3
26 changed files with 28646 additions and 25809 deletions

View File

@@ -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)

View File

@@ -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}`,

View File

@@ -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
)
})

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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}`,

View File

@@ -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:*/*"]
}
]
}

View File

@@ -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

View File

@@ -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" />

View File

@@ -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"
>