From eed863143d5092747754f7c7f42653fda578b662 Mon Sep 17 00:00:00 2001 From: Jonathan Staab Date: Tue, 14 Mar 2023 16:15:41 -0500 Subject: [PATCH] Disconnect after connecting user, shorted retry timeout for connections --- src/agent/keys.ts | 37 +++++++----- src/agent/pool.ts | 90 +++++++++++++++--------------- src/agent/sync.ts | 37 ++++++------ src/views/login/ConnectUser.svelte | 3 + 4 files changed, 89 insertions(+), 78 deletions(-) diff --git a/src/agent/keys.ts b/src/agent/keys.ts index f27b2db9..99c0f618 100644 --- a/src/agent/keys.ts +++ b/src/agent/keys.ts @@ -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, } diff --git a/src/agent/pool.ts b/src/agent/pool.ts index e6203a1a..541ad6b0 100644 --- a/src/agent/pool.ts +++ b/src/agent/pool.ts @@ -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, } diff --git a/src/agent/sync.ts b/src/agent/sync.ts index 2d5a5578..135a1d7c 100644 --- a/src/agent/sync.ts +++ b/src/agent/sync.ts @@ -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 + return tryJson(() => { + return Object.entries(JSON.parse(e.content)) + .map(([url, conditions]) => { + const {write, read} = conditions as Record - 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)) + }) }) ) diff --git a/src/views/login/ConnectUser.svelte b/src/views/login/ConnectUser.svelte index 82808a6c..2746a782 100644 --- a/src/views/login/ConnectUser.svelte +++ b/src/views/login/ConnectUser.svelte @@ -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() } }) }