Compare commits

...

32 Commits

Author SHA1 Message Date
fiatjaf
6f07c756e5 change nip04 functions interfaces. 2022-01-24 20:21:26 -03:00
fiatjaf
f6bcda8d8d support _ names in nip05. 2022-01-17 17:12:48 -03:00
fiatjaf
4b666e421b update nip05 to well-known version. 2022-01-17 16:37:19 -03:00
fiatjaf
454366f6a2 allow signing events with a custom signing function on pool.publish() 2022-01-12 22:32:45 -03:00
fiatjaf
3d6f9a41e0 prevent blocking waiting times on publish (unless "wait" is set in the pool policy). 2022-01-12 17:39:24 -03:00
fiatjaf
e3631ba806 fix and update nip06. 2022-01-06 21:46:34 -03:00
fiatjaf
89f11e214d fix filter matching for tags. 2022-01-02 19:46:19 -03:00
fiatjaf
bb09e25512 fix tag in matchFilter for kinds and ids. 2022-01-01 21:18:37 -03:00
fiatjaf
1b5c314436 nip-01 update: everything as arrays on filters. 2022-01-01 20:49:05 -03:00
fiatjaf
2230f32d11 use randomBytes from @noble/hashes. 2022-01-01 14:59:12 -03:00
fiatjaf
b271d6c06b fix .kind filter validator. 2022-01-01 10:26:55 -03:00
fiatjaf
76624a0f23 validateEvent() function. 2022-01-01 10:04:36 -03:00
fiatjaf
1f1a6380f0 fix getPublicKey to return the bip340 key. 2022-01-01 10:03:36 -03:00
fiatjaf
a46568d55c fix argument to micro-bip32 2021-12-31 23:09:43 -03:00
fiatjaf
ff4e63ecdf fix param order for verifySignature. 2021-12-31 22:53:27 -03:00
fiatjaf
01dd5b7a3c bring back @noble/secp256k1 along with micro-bip32. 2021-12-31 22:47:45 -03:00
fiatjaf
16536340e5 small fix on pool.removeRelay() 2021-12-31 22:25:33 -03:00
fiatjaf
1037eee335 trim relay url on normalize. 2021-12-31 22:03:02 -03:00
fiatjaf
5ce1b4c9f7 only initiate subscriptions for new relays added with read:true 2021-12-31 20:50:02 -03:00
fiatjaf
7bc9083bc5 randomChoice pool policy. 2021-12-30 21:46:54 -03:00
fiatjaf
ce214ebbab small tweaks on relayConnect. 2021-12-30 15:02:05 -03:00
fiatjaf
800beb37f1 cut out the first byte of pubkeys. 2021-12-29 15:15:53 -03:00
fiatjaf
6d4916e6f7 eslint and minor fixes. 2021-12-29 14:35:28 -03:00
fiatjaf
60fc0d7940 use tiny-secp256k1, updated nip06 and other utils. 2021-12-29 14:29:43 -03:00
fiatjaf
faa308049f always add event.id 2021-12-28 20:44:35 -03:00
fiatjaf
7b0220c1b8 use browserify-cipher for aes.
it seems everybody was including this by default before, but now webpack and others are not.
2021-12-18 20:30:58 -03:00
fiatjaf
d8eee25e3a another typo: null != undefined. 2021-12-14 22:06:31 -03:00
fiatjaf
d5e93e0c30 fix a typo in matchFilter function. 2021-12-14 22:02:56 -03:00
fiatjaf
fff31b5ff4 automatically run received events through the filters they should pass (double-check the work made by the relay). 2021-12-14 22:00:42 -03:00
fiatjaf
cd7ffb8911 add local event filter functions. 2021-12-14 21:56:07 -03:00
fiatjaf
4f0cae0eb8 add missing id arguments. 2021-12-13 21:22:23 -03:00
fiatjaf
06e867b675 stop sending repeated REQs. 2021-12-13 20:58:49 -03:00
13 changed files with 297 additions and 158 deletions

View File

@@ -1,4 +1,5 @@
{ {
"root": true,
"parserOptions": { "parserOptions": {
"ecmaVersion": 9, "ecmaVersion": 9,
"ecmaFeatures": { "ecmaFeatures": {

View File

@@ -67,4 +67,6 @@ pool.addRelay('<url>')
// will automatically subscribe to the all the events called with .sub above // will automatically subscribe to the all the events called with .sub above
``` ```
All functions expect bytearrays as hex strings and output bytearrays as hex strings.
For other utils please read the source (for now). For other utils please read the source (for now).

View File

@@ -1,8 +1,7 @@
import {Buffer} from 'buffer' import {Buffer} from 'buffer'
import createHash from 'create-hash'
import * as secp256k1 from '@noble/secp256k1' import * as secp256k1 from '@noble/secp256k1'
import {sha256} from './utils'
export function getBlankEvent() { export function getBlankEvent() {
return { return {
kind: 255, kind: 255,
@@ -19,25 +18,39 @@ export function serializeEvent(evt) {
evt.pubkey, evt.pubkey,
evt.created_at, evt.created_at,
evt.kind, evt.kind,
evt.tags || [], evt.tags,
evt.content evt.content
]) ])
} }
export async function getEventHash(event) { export function getEventHash(event) {
let eventHash = await sha256(Buffer.from(serializeEvent(event))) let eventHash = createHash('sha256')
.update(Buffer.from(serializeEvent(event)))
.digest()
return Buffer.from(eventHash).toString('hex') return Buffer.from(eventHash).toString('hex')
} }
export async function verifySignature(event) { export function validateEvent(event) {
return await secp256k1.schnorr.verify( if (event.id !== getEventHash(event)) return false
event.sig, if (typeof event.content !== 'string') return false
await getEventHash(event), if (typeof event.created_at !== 'number') return false
event.pubkey
) if (!Array.isArray(event.tags)) return false
for (let i = 0; i < event.tags.length; i++) {
let tag = event.tags[i]
if (!Array.isArray(tag)) return false
for (let j = 0; j < tag.length; j++) {
if (typeof tag[j] === 'object') return false
}
}
return true
}
export function verifySignature(event) {
return secp256k1.schnorr.verify(event.sig, event.id, event.pubkey)
} }
export async function signEvent(event, key) { export async function signEvent(event, key) {
let eventHash = await getEventHash(event) return secp256k1.schnorr.sign(getEventHash(event), key)
return await secp256k1.schnorr.sign(eventHash, key)
} }

30
filter.js Normal file
View File

@@ -0,0 +1,30 @@
export function matchFilter(filter, event) {
if (filter.ids && filter.ids.indexOf(event.id) === -1) return false
if (filter.kinds && filter.kinds.indexOf(event.kind) === -1) return false
if (filter.authors && filter.authors.indexOf(event.pubkey) === -1)
return false
for (let f in filter) {
if (f[0] === '#') {
if (
filter[f] &&
!event.tags.find(
([t, v]) => t === f.slice(1) && filter[f].indexOf(v) !== -1
)
)
return false
}
}
if (filter.since && event.created_at < filter.since) return false
if (filter.until && event.created_at >= filter.until) return false
return true
}
export function matchFilters(filters, event) {
for (let i = 0; i < filters.length; i++) {
if (matchFilter(filters[i], event)) return true
}
return false
}

View File

@@ -1,23 +1,27 @@
import {generatePrivateKey, getPublicKey} from './keys'
import {relayConnect} from './relay' import {relayConnect} from './relay'
import {relayPool} from './pool' import {relayPool} from './pool'
import { import {
getBlankEvent, getBlankEvent,
signEvent, signEvent,
validateEvent,
verifySignature, verifySignature,
serializeEvent, serializeEvent,
getEventHash getEventHash
} from './event' } from './event'
import {makeRandom32, sha256, getPublicKey} from './utils' import {matchFilter, matchFilters} from './filter'
export { export {
generatePrivateKey,
relayConnect, relayConnect,
relayPool, relayPool,
signEvent, signEvent,
validateEvent,
verifySignature, verifySignature,
serializeEvent, serializeEvent,
getEventHash, getEventHash,
makeRandom32,
sha256,
getPublicKey, getPublicKey,
getBlankEvent getBlankEvent,
matchFilter,
matchFilters
} }

9
keys.js Normal file
View File

@@ -0,0 +1,9 @@
import * as secp256k1 from '@noble/secp256k1'
export function generatePrivateKey() {
return Buffer.from(secp256k1.utils.randomPrivateKey()).toString('hex')
}
export function getPublicKey(privateKey) {
return secp256k1.schnorr.getPublicKey(privateKey)
}

View File

@@ -1,5 +1,6 @@
import aes from 'browserify-cipher'
import {Buffer} from 'buffer' import {Buffer} from 'buffer'
import randomBytes from 'randombytes' import {randomBytes} from '@noble/hashes/utils'
import * as secp256k1 from '@noble/secp256k1' import * as secp256k1 from '@noble/secp256k1'
export function encrypt(privkey, pubkey, text) { export function encrypt(privkey, pubkey, text) {
@@ -7,7 +8,7 @@ export function encrypt(privkey, pubkey, text) {
const normalizedKey = getOnlyXFromFullSharedSecret(key) const normalizedKey = getOnlyXFromFullSharedSecret(key)
let iv = Uint8Array.from(randomBytes(16)) let iv = Uint8Array.from(randomBytes(16))
var cipher = crypto.createCipheriv( var cipher = aes.createCipheriv(
'aes-256-cbc', 'aes-256-cbc',
Buffer.from(normalizedKey, 'hex'), Buffer.from(normalizedKey, 'hex'),
iv iv
@@ -15,19 +16,20 @@ export function encrypt(privkey, pubkey, text) {
let encryptedMessage = cipher.update(text, 'utf8', 'base64') let encryptedMessage = cipher.update(text, 'utf8', 'base64')
encryptedMessage += cipher.final('base64') encryptedMessage += cipher.final('base64')
return [encryptedMessage, Buffer.from(iv.buffer).toString('base64')] return `${encryptedMessage}?iv=${Buffer.from(iv.buffer).toString('base64')}`
} }
export function decrypt(privkey, pubkey, ciphertext, iv) { export function decrypt(privkey, pubkey, ciphertext) {
const key = secp256k1.getSharedSecret(privkey, '02' + pubkey) let [cip, iv] = ciphertext.split('?iv=')
const normalizedKey = getOnlyXFromFullSharedSecret(key) let key = secp256k1.getSharedSecret(privkey, '02' + pubkey)
let normalizedKey = getOnlyXFromFullSharedSecret(key)
var decipher = crypto.createDecipheriv( var decipher = aes.createDecipheriv(
'aes-256-cbc', 'aes-256-cbc',
Buffer.from(normalizedKey, 'hex'), Buffer.from(normalizedKey, 'hex'),
Buffer.from(iv, 'base64') Buffer.from(iv, 'base64')
) )
let decryptedMessage = decipher.update(ciphertext, 'base64') let decryptedMessage = decipher.update(cip, 'base64')
decryptedMessage += decipher.final('utf8') decryptedMessage += decipher.final('utf8')
return decryptedMessage return decryptedMessage

View File

@@ -1,52 +1,32 @@
import {Buffer} from 'buffer' import fetch from 'cross-fetch'
import dnsPacket from 'dns-packet'
const dohProviders = [
'cloudflare-dns.com',
'fi.doh.dns.snopyta.org',
'basic.bravedns.com',
'hydra.plan9-ns1.com',
'doh.pl.ahadns.net',
'dns.flatuslifir.is',
'doh.dns.sb',
'doh.li'
]
let counter = 0
export async function keyFromDomain(domain) {
let host = dohProviders[counter % dohProviders.length]
let buf = dnsPacket.encode({
type: 'query',
id: Math.floor(Math.random() * 65534),
flags: dnsPacket.RECURSION_DESIRED,
questions: [
{
type: 'TXT',
name: `_nostrkey.${domain}`
}
]
})
let fetching = fetch(`https://${host}/dns-query`, {
method: 'POST',
headers: {
'Content-Type': 'application/dns-message',
'Content-Length': Buffer.byteLength(buf)
},
body: buf
})
counter++
export async function searchDomain(domain, query = '') {
try { try {
let response = Buffer.from(await (await fetching).arrayBuffer()) let res = await (
let {answers} = dnsPacket.decode(response) await fetch(`https://${domain}/.well-known/nostr.json?name=${query}`)
if (answers.length === 0) return null ).json()
return Buffer.from(answers[0].data[0]).toString()
} catch (err) { return res.names
console.log(`error querying DNS for ${domain} on ${host}`, err) } catch (_) {
return []
}
}
export async function queryName(fullname) {
try {
let [name, domain] = fullname.split('@')
if (!domain) {
domain = name
name = '_'
}
let res = await (
await fetch(`https://${domain}/.well-known/nostr.json?name=${name}`)
).json()
return res.names && res.names[name]
} catch (_) {
return null return null
} }
} }

View File

@@ -1,17 +1,26 @@
import createHmac from 'create-hmac' import {wordlist} from 'micro-bip39/wordlists/english'
import randomBytes from 'randombytes' import {
import * as bip39 from 'bip39' generateMnemonic,
mnemonicToSeedSync,
validateMnemonic
} from 'micro-bip39'
import {HDKey} from 'micro-bip32'
export function privateKeyFromSeed(seed) { export function privateKeyFromSeed(seed) {
let hmac = createHmac('sha512', Buffer.from('Nostr seed', 'utf8')) let root = HDKey.fromMasterSeed(Buffer.from(seed, 'hex'))
hmac.update(seed) return Buffer.from(root.derive(`m/44'/1237'/0'/0/0`).privateKey).toString(
return hmac.digest().slice(0, 32).toString('hex') 'hex'
)
} }
export function seedFromWords(mnemonic) { export function seedFromWords(mnemonic) {
return bip39.mnemonicToSeedSync(mnemonic) return Buffer.from(mnemonicToSeedSync(mnemonic)).toString('hex')
} }
export function generateSeedWords() { export function generateSeedWords() {
return bip39.entropyToMnemonic(randomBytes(16).toString('hex')) return generateMnemonic(wordlist)
}
export function validateWords(words) {
return validateMnemonic(words, wordlist)
} }

View File

@@ -1,18 +1,20 @@
{ {
"name": "nostr-tools", "name": "nostr-tools",
"version": "0.8.1", "version": "0.21.0",
"description": "Tools for making a Nostr client.", "description": "Tools for making a Nostr client.",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/fiatjaf/nostr-tools.git" "url": "https://github.com/fiatjaf/nostr-tools.git"
}, },
"dependencies": { "dependencies": {
"@noble/hashes": "^0.5.7",
"@noble/secp256k1": "^1.3.0", "@noble/secp256k1": "^1.3.0",
"bip39": "^3.0.4", "browserify-cipher": ">=1",
"buffer": "^6.0.3", "buffer": ">=5",
"create-hmac": "^1.1.7", "create-hash": "^1.2.0",
"dns-packet": "^5.2.4", "cross-fetch": "^3.1.4",
"randombytes": "^2.1.0", "micro-bip32": "^0.1.0",
"micro-bip39": "^0.1.3",
"websocket-polyfill": "^0.0.3" "websocket-polyfill": "^0.0.3"
}, },
"keywords": [ "keywords": [
@@ -26,5 +28,9 @@
"censorship", "censorship",
"censorship-resistance", "censorship-resistance",
"client" "client"
] ],
"devDependencies": {
"eslint": "^8.5.0",
"eslint-plugin-babel": "^5.3.1"
}
} }

154
pool.js
View File

@@ -1,9 +1,20 @@
import {getEventHash, signEvent} from './event' import {getEventHash, verifySignature, signEvent} from './event'
import {relayConnect, normalizeRelayURL} from './relay' import {relayConnect, normalizeRelayURL} from './relay'
export function relayPool(globalPrivateKey) { export function relayPool() {
var globalPrivateKey
var globalSigningFunction
const poolPolicy = {
// setting this to a number will cause events to be published to a random
// set of relays only, instead of publishing to all relays all the time
randomChoice: null,
// setting this to true will cause .publish() calls to wait until the event has
// been published -- or at least attempted to be published -- to all relays
wait: false
}
const relays = {} const relays = {}
const globalSub = []
const noticeCallbacks = [] const noticeCallbacks = []
function propagateNotice(notice, relayURL) { function propagateNotice(notice, relayURL) {
@@ -21,36 +32,41 @@ export function relayPool(globalPrivateKey) {
.filter(({policy}) => policy.read) .filter(({policy}) => policy.read)
.map(({relay}) => [ .map(({relay}) => [
relay.url, relay.url,
relay.sub({filter, cb: event => cb(event, relay.url)}) relay.sub({filter, cb: event => cb(event, relay.url)}, id)
]) ])
) )
const activeCallback = cb const activeCallback = cb
const activeFilters = filter const activeFilters = filter
activeSubscriptions[id] = { const unsub = () => {
sub: ({cb = activeCallback, filter = activeFilters}) => { Object.values(subControllers).forEach(sub => sub.unsub())
Object.entries(subControllers).map(([relayURL, sub]) => [ delete activeSubscriptions[id]
relayURL, }
sub.sub({cb, filter}, id) const sub = ({cb = activeCallback, filter = activeFilters}) => {
]) Object.entries(subControllers).map(([relayURL, sub]) => [
return activeSubscriptions[id] relayURL,
}, sub.sub({cb, filter}, id)
addRelay: relay => { ])
subControllers[relay.url] = relay.sub({cb, filter}) return activeSubscriptions[id]
return activeSubscriptions[id] }
}, const addRelay = relay => {
removeRelay: relayURL => { subControllers[relay.url] = relay.sub({cb, filter}, id)
if (relayURL in subControllers) { return activeSubscriptions[id]
subControllers[relayURL].unsub() }
if (Object.keys(subControllers).length === 0) unsub() const removeRelay = relayURL => {
} if (relayURL in subControllers) {
return activeSubscriptions[id] subControllers[relayURL].unsub()
}, if (Object.keys(subControllers).length === 0) unsub()
unsub: () => {
Object.values(subControllers).forEach(sub => sub.unsub())
delete activeSubscriptions[id]
} }
return activeSubscriptions[id]
}
activeSubscriptions[id] = {
sub,
unsub,
addRelay,
removeRelay
} }
return activeSubscriptions[id] return activeSubscriptions[id]
@@ -62,25 +78,35 @@ export function relayPool(globalPrivateKey) {
setPrivateKey(privateKey) { setPrivateKey(privateKey) {
globalPrivateKey = privateKey globalPrivateKey = privateKey
}, },
async addRelay(url, policy = {read: true, write: true}) { registerSigningFunction(fn) {
globalSigningFunction = fn
},
setPolicy(key, value) {
poolPolicy[key] = value
},
addRelay(url, policy = {read: true, write: true}) {
let relayURL = normalizeRelayURL(url) let relayURL = normalizeRelayURL(url)
if (relayURL in relays) return if (relayURL in relays) return
let relay = await relayConnect(url, notice => { let relay = relayConnect(url, notice => {
propagateNotice(notice, relayURL) propagateNotice(notice, relayURL)
}) })
relays[relayURL] = {relay, policy} relays[relayURL] = {relay, policy}
Object.values(activeSubscriptions).forEach(subscription => if (policy.read) {
subscription.addRelay(relay) Object.values(activeSubscriptions).forEach(subscription =>
) subscription.addRelay(relay)
)
}
return relay return relay
}, },
removeRelay(url) { removeRelay(url) {
let relayURL = normalizeRelayURL(url) let relayURL = normalizeRelayURL(url)
let {relay} = relays[relayURL] let data = relays[relayURL]
if (!relay) return if (!data) return
let {relay} = data
Object.values(activeSubscriptions).forEach(subscription => Object.values(activeSubscriptions).forEach(subscription =>
subscription.removeRelay(relay) subscription.removeRelay(relay)
) )
@@ -94,31 +120,75 @@ export function relayPool(globalPrivateKey) {
let index = noticeCallbacks.indexOf(cb) let index = noticeCallbacks.indexOf(cb)
if (index !== -1) noticeCallbacks.splice(index, 1) if (index !== -1) noticeCallbacks.splice(index, 1)
}, },
async publish(event, statusCallback = (status, relayURL) => {}) { async publish(event, statusCallback) {
event.id = getEventHash(event)
if (!event.sig) { if (!event.sig) {
event.tags = event.tags || [] event.tags = event.tags || []
if (globalPrivateKey) { if (globalPrivateKey) {
event.id = await getEventHash(event)
event.sig = await signEvent(event, globalPrivateKey) event.sig = await signEvent(event, globalPrivateKey)
} else if (globalSigningFunction) {
event.sig = await globalSigningFunction(event)
if (!event.sig) {
// abort here
return
} else {
// check
if (!(await verifySignature(event)))
throw new Error(
'signature provided by custom signing function is invalid.'
)
}
} else { } else {
throw new Error( throw new Error(
"can't publish unsigned event. either sign this event beforehand or pass a private key while initializing this relay pool so it can be signed automatically." "can't publish unsigned event. either sign this event beforehand, provide a signing function or pass a private key while initializing this relay pool so it can be signed automatically."
) )
} }
} }
Object.values(relays) let writeable = Object.values(relays)
.filter(({policy}) => policy.write) .filter(({policy}) => policy.write)
.map(async ({relay}) => { .sort(() => Math.random() - 0.5) // random
let maxTargets = poolPolicy.randomChoice
? poolPolicy.randomChoice
: writeable.length
let successes = 0
if (poolPolicy.wait) {
for (let i = 0; i < writeable.length; i++) {
let {relay} = writeable[i]
try { try {
await relay.publish(event, status => await new Promise(async (resolve, reject) => {
statusCallback(status, relay.url) try {
) await relay.publish(event, status => {
if (statusCallback) statusCallback(status, relay.url)
resolve()
})
} catch (err) {
if (statusCallback) statusCallback(-1, relay.url)
}
})
successes++
if (successes >= maxTargets) {
break
}
} catch (err) { } catch (err) {
statusCallback(-1, relay.url) /***/
} }
}
} else {
writeable.forEach(async ({relay}) => {
let callback = statusCallback
? status => statusCallback(status, relay.url)
: null
relay.publish(event, callback)
}) })
}
return event return event
} }

View File

@@ -1,19 +1,22 @@
/* global WebSocket */
import 'websocket-polyfill' import 'websocket-polyfill'
import {verifySignature} from './event' import {verifySignature, validateEvent} from './event'
import {matchFilters} from './filter'
export function normalizeRelayURL(url) { export function normalizeRelayURL(url) {
let [host, ...qs] = url.split('?') let [host, ...qs] = url.trim().split('?')
if (host.slice(0, 4) === 'http') host = 'ws' + host.slice(4) if (host.slice(0, 4) === 'http') host = 'ws' + host.slice(4)
if (host.slice(0, 2) !== 'ws') host = 'wss://' + host if (host.slice(0, 2) !== 'ws') host = 'wss://' + host
if (host.length && host[host.length - 1] === '/') host = host.slice(0, -1) if (host.length && host[host.length - 1] === '/') host = host.slice(0, -1)
return [host, ...qs].join('?') return [host, ...qs].join('?')
} }
export function relayConnect(url, onNotice) { export function relayConnect(url, onNotice = () => {}, onError = () => {}) {
url = normalizeRelayURL(url) url = normalizeRelayURL(url)
var ws, resolveOpen, untilOpen var ws, resolveOpen, untilOpen, wasClosed
var openSubs = {} var openSubs = {}
let attemptNumber = 1 let attemptNumber = 1
let nextAttemptSeconds = 1 let nextAttemptSeconds = 1
@@ -34,14 +37,18 @@ export function relayConnect(url, onNotice) {
resolveOpen() resolveOpen()
// restablish old subscriptions // restablish old subscriptions
for (let channel in openSubs) { if (wasClosed) {
let filters = openSubs[channel] wasClosed = false
let cb = channels[channel] for (let channel in openSubs) {
sub({cb, filter: filters}, channel) let filters = openSubs[channel]
let cb = channels[channel]
sub({cb, filter: filters}, channel)
}
} }
} }
ws.onerror = () => { ws.onerror = err => {
console.log('error connecting to relay', url) console.log('error connecting to relay', url)
onError(err)
} }
ws.onclose = () => { ws.onclose = () => {
resetOpenState() resetOpenState()
@@ -58,6 +65,8 @@ export function relayConnect(url, onNotice) {
connect() connect()
} catch (err) {} } catch (err) {}
}, nextAttemptSeconds * 1000) }, nextAttemptSeconds * 1000)
wasClosed = true
} }
ws.onmessage = async e => { ws.onmessage = async e => {
@@ -83,10 +92,13 @@ export function relayConnect(url, onNotice) {
let channel = data[1] let channel = data[1]
let event = data[2] let event = data[2]
if (await verifySignature(event)) { if (
if (channels[channel]) { validateEvent(event) &&
channels[channel](event) verifySignature(event) &&
} channels[channel] &&
matchFilters(openSubs[channel], event)
) {
channels[channel](event)
} }
return return
} }
@@ -136,19 +148,26 @@ export function relayConnect(url, onNotice) {
return { return {
url, url,
sub, sub,
async publish(event, statusCallback = status => {}) { async publish(event, statusCallback) {
try { try {
await trySend(['EVENT', event]) await trySend(['EVENT', event])
statusCallback(0) if (statusCallback) {
let {unsub} = relay.sub({ statusCallback(0)
cb: () => { let {unsub} = sub(
statusCallback(1) {
}, cb: () => {
filter: {id: event.id} statusCallback(1)
}) unsub()
setTimeout(unsub, 5000) clearTimeout(willUnsub)
},
filter: {id: event.id}
},
`monitor-${event.id.slice(0, 5)}`
)
let willUnsub = setTimeout(unsub, 5000)
}
} catch (err) { } catch (err) {
statusCallback(-1) if (statusCallback) statusCallback(-1)
} }
}, },
close() { close() {

View File

@@ -1,6 +0,0 @@
import * as secp256k1 from '@noble/secp256k1'
export const makeRandom32 = () => secp256k1.utils.randomPrivateKey()
export const sha256 = m => secp256k1.utils.sha256(Uint8Array.from(m))
export const getPublicKey = privateKey =>
secp256k1.schnorr.getPublicKey(privateKey)