Disconnect after connecting user, shorted retry timeout for connections

This commit is contained in:
Jonathan Staab 2023-03-14 16:15:41 -05:00
parent 96d8f148c2
commit eed863143d
4 changed files with 89 additions and 78 deletions

View File

@ -1,18 +1,18 @@
import {nip04, getPublicKey, getEventHash, signEvent} from 'nostr-tools'
import {get} from 'svelte/store'
import {error} from 'src/util/logger'
import {synced} from 'src/util/misc'
import {nip04, getPublicKey, getEventHash, signEvent} from "nostr-tools"
import {get} from "svelte/store"
import {error} from "src/util/logger"
import {synced} from "src/util/misc"
const method = synced('agent/keys/method')
const pubkey = synced('agent/keys/pubkey')
const privkey = synced('agent/keys/privkey')
const method = synced("agent/keys/method")
const pubkey = synced("agent/keys/pubkey")
const privkey = synced("agent/keys/privkey")
const getExtension = () => (window as {nostr?: any}).nostr
const canSign = () => ['privkey', 'extension'].includes(get(method))
const canSign = () => ["privkey", "extension"].includes(get(method))
const login = ($method, key) => {
method.set($method)
if ($method === 'privkey') {
if ($method === "privkey") {
privkey.set(key)
pubkey.set(getPublicKey(key))
} else {
@ -33,13 +33,13 @@ const sign = event => {
event.pubkey = get(pubkey)
event.id = getEventHash(event)
if ($method === 'privkey') {
if ($method === "privkey") {
return Object.assign(event, {
sig: signEvent(event, get(privkey)),
})
}
if ($method === 'extension') {
if ($method === "extension") {
return getExtension().signEvent(event)
}
@ -49,7 +49,7 @@ const sign = event => {
const getCrypt = () => {
const $method = get(method)
if ($method === 'privkey') {
if ($method === "privkey") {
const $privkey = get(privkey)
return {
@ -66,7 +66,7 @@ const getCrypt = () => {
}
}
if ($method === 'extension') {
if ($method === "extension") {
return {
encrypt: (pubkey, message) => getExtension().nip04.encrypt(pubkey, message),
decrypt: async (pubkey, message) => {
@ -81,9 +81,16 @@ const getCrypt = () => {
}
}
throw new Error('No encryption method available.')
throw new Error("No encryption method available.")
}
export default {
pubkey, privkey, canSign, login, clear, sign, getCrypt,
method,
pubkey,
privkey,
canSign,
login,
clear,
sign,
getCrypt,
}

View File

@ -1,22 +1,22 @@
import type {Relay, Filter} from 'nostr-tools'
import type {MyEvent} from 'src/util/types'
import {relayInit} from 'nostr-tools'
import {pluck, is} from 'ramda'
import {ensurePlural} from 'hurdak/lib/hurdak'
import {warn, log, error} from 'src/util/logger'
import {union, difference} from 'src/util/misc'
import {isRelay, normalizeRelayUrl} from 'src/util/nostr'
import type {Relay, Filter} from "nostr-tools"
import type {MyEvent} from "src/util/types"
import {relayInit} from "nostr-tools"
import {pluck, is} from "ramda"
import {ensurePlural} from "hurdak/lib/hurdak"
import {warn, log, error} from "src/util/logger"
import {union, difference} from "src/util/misc"
import {isRelay, normalizeRelayUrl} from "src/util/nostr"
// Connection management
const connections = {}
const CONNECTION_STATUS = {
NEW: 'new',
ERROR: 'error',
PENDING: 'pending',
CLOSED: 'closed',
READY: 'ready',
NEW: "new",
ERROR: "error",
PENDING: "pending",
CLOSED: "closed",
READY: "ready",
}
class Connection {
@ -28,7 +28,7 @@ class Connection {
constructor(url) {
this.promise = null
this.nostr = relayInit(url)
this.status = 'new'
this.status = "new"
this.stats = {
timeouts: 0,
subsCount: 0,
@ -42,32 +42,27 @@ class Connection {
}
hasRecentError() {
return (
this.status === CONNECTION_STATUS.ERROR
&& Date.now() - this.lastConnectionAttempt < 60_000
this.status === CONNECTION_STATUS.ERROR && Date.now() - this.lastConnectionAttempt < 10_000
)
}
async connect() {
const shouldConnect = (
this.status === CONNECTION_STATUS.NEW
|| (
this.status === CONNECTION_STATUS.ERROR
&& Date.now() - this.lastConnectionAttempt > 60_000
)
)
const shouldConnect =
this.status === CONNECTION_STATUS.NEW ||
(this.status === CONNECTION_STATUS.ERROR && Date.now() - this.lastConnectionAttempt > 10_000)
if (shouldConnect) {
this.status = CONNECTION_STATUS.PENDING
this.promise = this.nostr.connect()
this.nostr.on('connect', () => {
this.nostr.on("connect", () => {
this.status = CONNECTION_STATUS.READY
})
this.nostr.on('error', () => {
this.nostr.on("error", () => {
this.status = CONNECTION_STATUS.ERROR
})
this.nostr.on('disconnect', () => {
this.nostr.on("disconnect", () => {
this.status = CONNECTION_STATUS.CLOSED
})
}
@ -82,6 +77,11 @@ class Connection {
return this
}
disconnect() {
this.nostr.close()
delete connections[this.nostr.url]
}
getQuality() {
if (this.status === CONNECTION_STATUS.ERROR) {
return [0, "Failed to connect"]
@ -146,7 +146,7 @@ const publish = async ({relays, event, onProgress, timeout = 5000}) => {
log(`Publishing to ${relays.length} relays`, event, relays)
}
const urls = new Set(pluck('url', relays))
const urls = new Set(pluck("url", relays))
if (urls.size !== relays.length) {
warn(`Attempted to publish to non-unique relays`)
@ -200,14 +200,14 @@ const publish = async ({relays, event, onProgress, timeout = 5000}) => {
if (conn.status === CONNECTION_STATUS.READY) {
const pub = conn.nostr.publish(event)
pub.on('ok', () => {
pub.on("ok", () => {
succeeded.add(relay.url)
timeouts.delete(relay.url)
failed.delete(relay.url)
attemptToResolve()
})
pub.on('failed', reason => {
pub.on("failed", reason => {
failed.add(relay.url)
timeouts.delete(relay.url)
attemptToResolve()
@ -230,9 +230,7 @@ type SubscribeOpts = {
onEose?: (url: string) => void
}
const subscribe = async (
{relays, filter, onEvent, onEose, onError}: SubscribeOpts
) => {
const subscribe = async ({relays, filter, onEvent, onEose, onError}: SubscribeOpts) => {
filter = ensurePlural(filter)
const id = createFilterId(filter)
@ -248,14 +246,14 @@ const subscribe = async (
log(`Starting subscription ${id} with ${relays.length} relays`, filter, relays)
}
if (relays.length !== new Set(pluck('url', relays)).size) {
if (relays.length !== new Set(pluck("url", relays)).size) {
error(`Subscribed to non-unique relays`, relays)
}
const promises = relays.map(async relay => {
const conn = await connect(relay.url)
if (conn.status !== 'ready') {
if (conn.status !== "ready") {
if (onError) {
onError(relay.url)
}
@ -265,21 +263,21 @@ const subscribe = async (
const sub = conn.nostr.sub(filter, {
id,
alreadyHaveEvent: (id) => {
alreadyHaveEvent: id => {
conn.stats.eventsCount += 1
let has = false
if (seen.has(id)) has = true
seen.add(id)
return has
}
},
})
sub.on('event', e => {
// Normalize events here, annotate with relay url
onEvent({...e, seen_on: relay.url, content: e.content || ''})
sub.on("event", e => {
// Normalize events here, annotate with relay url
onEvent({...e, seen_on: relay.url, content: e.content || ""})
})
sub.on('eose', () => {
sub.on("eose", () => {
if (onEose) {
onEose(conn.nostr.url)
}
@ -329,12 +327,12 @@ const subscribe = async (
// Utils
const createFilterId = filters =>
[Math.random().toString().slice(2, 6), filters.map(describeFilter).join(':')].join('-')
[Math.random().toString().slice(2, 6), filters.map(describeFilter).join(":")].join("-")
const describeFilter = ({kinds = [], ...filter}) => {
const parts = []
parts.push(kinds.join(','))
parts.push(kinds.join(","))
for (const [key, value] of Object.entries(filter)) {
if (is(Array, value)) {
@ -344,9 +342,13 @@ const describeFilter = ({kinds = [], ...filter}) => {
}
}
return '(' + parts.join(',') + ')'
return "(" + parts.join(",") + ")"
}
export default {
getConnections, getConnection, connect, publish, subscribe,
getConnections,
getConnection,
connect,
publish,
subscribe,
}

View File

@ -165,11 +165,12 @@ const profileHandler = (key, getValue) => e => {
return
}
user.profile.update($p => ({
...$p,
[key]: getValue(e, $p),
[updated_at_key]: e.created_at,
}))
user.profile.update($p => {
const value = getValue(e, $p)
// If we didn't get a value, don't update the key
return value ? {...$p, [key]: value, [updated_at_key]: e.created_at} : $p
})
}
addHandler(
@ -180,21 +181,19 @@ addHandler(
addHandler(
3,
profileHandler("relays", (e, p) => {
return (
tryJson(() => {
return Object.entries(JSON.parse(e.content))
.map(([url, conditions]) => {
const {write, read} = conditions as Record<string, boolean | string>
return tryJson(() => {
return Object.entries(JSON.parse(e.content))
.map(([url, conditions]) => {
const {write, read} = conditions as Record<string, boolean | string>
return {
url: normalizeRelayUrl(url),
write: [false, "!"].includes(write) ? false : true,
read: [false, "!"].includes(read) ? false : true,
}
})
.filter(r => isRelay(r.url))
}) || p.relays
)
return {
url: normalizeRelayUrl(url),
write: [false, "!"].includes(write) ? false : true,
read: [false, "!"].includes(read) ? false : true,
}
})
.filter(r => isRelay(r.url))
})
})
)

View File

@ -15,6 +15,7 @@
import {watch} from "src/agent/storage"
import network from "src/agent/network"
import user from "src/agent/user"
import pool from "src/agent/pool"
import {loadAppData} from "src/app"
import {toast} from "src/app/ui"
@ -65,6 +66,8 @@
await Promise.all([loadAppData(user.getPubkey()), sleep(3000)])
navigate("/notes/follows")
} else {
pool.getConnection(relay.url).disconnect()
}
})
}