Upgrade nostr-tools, add agent

This commit is contained in:
Jonathan Staab 2023-01-06 14:19:59 -08:00
parent 74eebf8d79
commit e3cf09ce50
8 changed files with 177 additions and 52 deletions

View File

@ -20,6 +20,7 @@ module.exports = {
"rules": {
"a11y-click-events-have-key-events": "off",
"no-unused-vars": ["error", {args: "none"}],
"no-async-promise-executor": "off",
},
"ignorePatterns": ["*.svg"]
}

View File

@ -44,6 +44,12 @@ If you like Coracle and want to support its development, you can donate sats via
# Changelog
## Current
- [ ] Upgrade nostr-tools
- [ ] Close connections that haven't been used in a while
- [ ] Move key management stuff
## 0.2.6
- [x] Add support for at-mentions in note and reply composition

BIN
package-lock.json generated

Binary file not shown.

View File

@ -30,7 +30,7 @@
"extract-urls": "^1.3.2",
"fuse.js": "^6.6.2",
"hurdak": "github:ConsignCloud/hurdak",
"nostr-tools": "github:fiatjaf/nostr-tools#1b798b2",
"nostr-tools": "^1.1.1",
"ramda": "^0.28.0",
"svelte-link-preview": "^0.3.3",
"svelte-loading-spinners": "^0.3.4",

4
src/agent/index.js Normal file
View File

@ -0,0 +1,4 @@
import keys from 'src/agent/keys'
import pool from 'src/agent/pool'
export default {pool, keys}

33
src/agent/keys.js Normal file
View File

@ -0,0 +1,33 @@
import {getPublicKey, getEventHash, signEvent} from 'nostr-tools'
let pubkey
let privkey
let signingFunction
const getPubkey = () => {
return pubkey || getPublicKey(privkey)
}
const setPrivateKey = _privkey => {
privkey = _privkey
}
const setPublicKey = _pubkey => {
signingFunction = async event => {
const {sig} = await window.nostr.signEvent(event)
return sig
}
pubkey = _pubkey
}
const sign = async event => {
event.pubkey = pubkey
event.id = getEventHash(event)
event.sig = signingFunction ? await signingFunction(event) : signEvent(event, privkey)
return event
}
export default {getPubkey, setPrivateKey, setPublicKey, sign}

109
src/agent/pool.js Normal file
View File

@ -0,0 +1,109 @@
import {relayInit} from 'nostr-tools'
import {partial, uniqBy, prop} from 'ramda'
import {ensurePlural} from 'hurdak/lib/hurdak'
const relays = {}
const connect = async url => {
if (!relays[url]) {
relays[url] = relayInit(url)
relays[url].url = url
relays[url].stats = {
count: 0,
timer: 0,
timeouts: 0,
activeCount: 0,
}
relays[url].on('disconnect', () => {
delete relays[url]
})
relays[url].connected = relays[url].connect()
}
await relays[url].connected
return relays[url]
}
const publish = (urls, event) => {
urls.forEach(async url => {
const relay = await connect(url)
relay.publish(event)
})
}
const sub = async (urls, filters) => {
const subs = await Promise.all(
urls.map(async url => {
const relay = await connect(url)
const sub = relay.sub(ensurePlural(filters))
sub.relay = relay
sub.relay.stats.activeCount += 1
if (sub.relay.stats.activeCount > 10) {
console.warning(`Relay ${url} has >10 active subscriptions`)
}
return sub
})
)
return {
unsub: () => {
subs.forEach(sub => {
sub.unsub()
sub.relay.stats.activeCount -= 1
})
},
on: (type, cb) => {
subs.forEach(sub => {
sub.on(type, partial(cb, [sub.relay.url]))
})
},
}
}
const all = (urls, filters) => {
return new Promise(async resolve => {
const subscription = await sub(urls, filters)
const now = Date.now()
const events = []
const eose = []
const done = () => {
resolve(uniqBy(prop('id'), events))
// Keep track of relay timeouts
urls.forEach(url => {
if (!eose.includes(url)) {
relays[url].stats.count += 1
relays[url].stats.timer += Date.now() - now
relays[url].stats.timeouts += 1
}
})
}
subscription.on('event', (url, e) => events.push(e))
subscription.on('eose', url => {
eose.push(url)
// Keep track of relay timing stats
relays[url].stats.count += 1
relays[url].stats.timer += Date.now() - now
if (eose.length === urls.length) {
done()
}
})
// If a relay takes too long, give up
setTimeout(done, 5000)
})
}
export default {relays, connect, publish, sub, all}

View File

@ -1,15 +1,13 @@
import {uniqBy, sortBy, find, propEq, prop, uniq} from 'ramda'
import {get} from 'svelte/store'
import {relayPool, getPublicKey} from 'nostr-tools'
import {noop, range, sleep} from 'hurdak/lib/hurdak'
import {getTagValues, filterTags} from "src/util/nostr"
import agent from 'src/agent'
import {db} from 'src/relay/db'
// ============================================================================
// Utils/config
const pool = relayPool()
class Channel {
constructor(name) {
this.name = name
@ -21,8 +19,8 @@ class Channel {
release() {
this.status = 'idle'
}
sub(filter, onEvent, onEose = noop, opts = {}) {
const relays = Object.keys(pool.relays)
async sub(filter, onEvent, onEose = noop, opts = {}) {
const relays = getRelays()
// If we don't have any relays, we'll wait forever for an eose, but
// we already know we're done. Use a timeout since callers are
@ -36,18 +34,20 @@ class Channel {
// Start our subscription, wait for only our fastest relays to eose before calling it done.
// We were waiting for all before, but that made the slowest relay a bottleneck. Waiting for
// only one meant we might be settling for very incomplete data
const start = new Date().valueOf()
const lastEvent = {}
const eoseRelays = []
// Create our subscription
const sub = await agent.pool.sub(relays, filter)
// Keep track of when we last heard from each relay, and close unresponsive ones
const cb = (e, r) => {
sub.on('event', (r, e) => {
lastEvent[r] = new Date().valueOf()
onEvent(e)
}
})
// If we have lots of relays, ignore the slowest ones
const onRelayEose = r => {
sub.on('eose', r => {
eoseRelays.push(r)
// If we have only a few, wait for all of them, otherwise ignore the slowest 1/5
@ -55,28 +55,14 @@ class Channel {
if (eoseRelays.length >= relays.length - threshold) {
onEose()
}
}
// Create our subscription
const sub = pool.sub({filter, cb}, this.name, onRelayEose)
// Watch for relays that are slow to respond and give up on them
const interval = !opts.timeout ? null : setInterval(() => {
for (const r of relays) {
if ((lastEvent[r] || start) < new Date().valueOf() - opts.timeout) {
onRelayEose(r)
}
}
}, 300)
})
// Clean everything up when we're done
const done = () => {
if (this.status === 'busy') {
sub.unsub()
this.release()
}
clearInterval(interval)
this.release()
}
return {unsub: done}
@ -86,7 +72,7 @@ class Channel {
return new Promise(async resolve => {
const result = []
const sub = this.sub(
const sub = await this.sub(
filter,
e => result.push(e),
r => {
@ -122,39 +108,25 @@ const getChannel = async () => {
const req = async (...args) => (await getChannel()).all(...args)
const sub = async (...args) => (await getChannel()).sub(...args)
const getPubkey = () => {
return pool._pubkey || getPublicKey(pool._privkey)
}
const getRelays = () => {
return Object.keys(pool.relays)
return get(db.connections)
}
const addRelay = url => {
pool.addRelay(url)
agent.pool.connect(url)
}
const removeRelay = url => {
pool.removeRelay(url)
}
const removeRelay = async url => {
const relay = await agent.pool.connect(url)
const setPrivateKey = privkey => {
pool.setPrivateKey(privkey)
pool._privkey = privkey
}
const setPublicKey = pubkey => {
pool.registerSigningFunction(async event => {
const {sig} = await window.nostr.signEvent(event)
return sig
})
pool._pubkey = pubkey
relay.close()
}
const publishEvent = event => {
pool.publish(event)
const relays = getRelays()
event = agent.keys.sign(event)
agent.publish(relays, event)
db.events.process(event)
}
@ -240,6 +212,6 @@ const syncNetwork = async () => {
}
export default {
getPubkey, getRelays, addRelay, removeRelay, setPrivateKey, setPublicKey,
publishEvent, loadEvents, listenForEvents, syncNetwork, loadPeople,
getRelays, addRelay, removeRelay, publishEvent, loadEvents, listenForEvents,
syncNetwork, loadPeople,
}