From ddcf5622b87a5a205a7caf49ba7d0f1ccfda9551 Mon Sep 17 00:00:00 2001 From: Jonathan Staab Date: Wed, 8 Mar 2023 09:55:56 -0600 Subject: [PATCH] Continue to refine cursor --- ROADMAP.md | 1 + src/agent/network.ts | 4 +- src/agent/relays.ts | 5 +- src/agent/sync.ts | 4 +- src/util/misc.ts | 52 +++++++++++++------- src/views/feed/Feed.svelte | 98 +++++++++++++++++++++----------------- 6 files changed, 96 insertions(+), 68 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 860b9fde..f7453fc7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,6 +2,7 @@ - [ ] Collapse relaycard and relaycardsimple? - [ ] Create my own version of nostr.how and extension explanation +- [ ] Make new notes thing fixed position - [ ] Review sampleRelays, seems like we shouldn't be shuffling - [ ] Go over onboarding process, suggest some good relays for newcomers diff --git a/src/agent/network.ts b/src/agent/network.ts index 21f0c614..14ae6855 100644 --- a/src/agent/network.ts +++ b/src/agent/network.ts @@ -37,7 +37,7 @@ const listen = ({relays, filter, onChunk = null, shouldProcess = true, delay = 5 }) } -const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 10_000}) => { +const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 5000}) => { return new Promise(resolve => { const now = Date.now() const done = new Set() @@ -83,7 +83,7 @@ const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 1 const subPromise = pool.subscribe({ relays, filter, - onEvent: batch(300, chunk => { + onEvent: batch(500, chunk => { if (shouldProcess) { sync.processEvents(chunk) } diff --git a/src/agent/relays.ts b/src/agent/relays.ts index 80fb7ab7..d318ae4a 100644 --- a/src/agent/relays.ts +++ b/src/agent/relays.ts @@ -4,7 +4,7 @@ import {warn} from 'src/util/logger' import {filter, pipe, pick, groupBy, objOf, map, assoc, sortBy, uniqBy, prop} from 'ramda' import {first, createMap} from 'hurdak/lib/hurdak' import {Tags, isRelay, findReplyId} from 'src/util/nostr' -import {shuffle} from 'src/util/misc' +import {shuffle, fetchJson} from 'src/util/misc' import database from 'src/agent/database' import pool from 'src/agent/pool' import user from 'src/agent/user' @@ -40,7 +40,8 @@ export const initializeRelayList = async () => { // Load relays from nostr.watch via dufflepud try { const url = import.meta.env.VITE_DUFFLEPUD_URL + '/relay' - const relays = prop('relays', await fetch(url).then(r => r.json())).filter(isRelay) + const json = await fetchJson(url) + const relays = json.relays.filter(isRelay) await database.relays.bulkPatch(createMap('url', map(objOf('url'), relays))) } catch (e) { diff --git a/src/agent/sync.ts b/src/agent/sync.ts index ac0acc2a..566e972b 100644 --- a/src/agent/sync.ts +++ b/src/agent/sync.ts @@ -2,7 +2,7 @@ import {uniq, pick, identity, isEmpty} from 'ramda' import {nip05} from 'nostr-tools' import {noop, createMap, ensurePlural, chunk, switcherFn} from 'hurdak/lib/hurdak' import {log} from 'src/util/logger' -import {lnurlEncode, lnurlDecode, tryFetch, now, sleep, tryJson, timedelta, shuffle, hash} from 'src/util/misc' +import {lnurlEncode, tryFunc, lnurlDecode, tryFetch, now, sleep, tryJson, timedelta, shuffle, hash} from 'src/util/misc' import {Tags, roomAttrs, personKinds, isRelay, isShareableRelay, normalizeRelayUrl} from 'src/util/nostr' import database from 'src/agent/database' @@ -313,7 +313,7 @@ const verifyZapper = async (pubkey, address) => { // Try to parse it as a lud06 LNURL or as a lud16 address if (address.toLowerCase().startsWith('lnurl1')) { - url = lnurlDecode(address) + url = tryFunc(() => lnurlDecode(address)) } else if (address.includes('@')) { const [name, domain] = address.split('@') diff --git a/src/util/misc.ts b/src/util/misc.ts index 7a07e2de..d0a0a1ac 100644 --- a/src/util/misc.ts +++ b/src/util/misc.ts @@ -1,6 +1,6 @@ import {bech32, utf8} from '@scure/base' import {debounce, throttle} from 'throttle-debounce' -import {aperture, path as getPath, allPass, pipe, isNil, complement, equals, is, pluck, sum, identity, sortBy} from "ramda" +import {gt, aperture, path as getPath, allPass, pipe, isNil, complement, equals, is, pluck, sum, identity, sortBy} from "ramda" import Fuse from "fuse.js/dist/fuse.min.js" import {writable} from 'svelte/store' import {isObject, round} from 'hurdak/lib/hurdak' @@ -149,32 +149,50 @@ export const getLastSync = (k, fallback = 0) => { export class Cursor { until: number limit: number - constructor(limit = 10) { + count: number + constructor(limit = 20) { this.until = now() this.limit = limit + this.count = 0 } getFilter() { return { - // Add a buffer so we can avoid blowing past the most relevant time interval - // (just now) until after a few paginations. - until: this.until + timedelta(3, 'hours'), - // since: this.until - timedelta(8, 'hours'), + until: this.until, limit: this.limit, } } + // Remove events that are significantly older than the average + prune(events) { + const maxDiff = avg(events.map(e => this.until - e.created_at)) * 4 + + return events.filter(e => this.until - e.created_at < maxDiff) + } + // Calculate a reasonable amount to move our window to avoid fetching too much of the + // same stuff we already got without missing certain time periods due to a mismatch + // in event density between various relays update(events) { - // update takes all events in a feed and figures out the best place to set `until` - // in order to find older events without re-fetching events that we've already seen. - // There are various edge cases: - // - When we have zero events, there's nothing we can do, presumably we have everything. - // - Sometimes relays send us extremely old events. Use median to avoid too-large gaps - if (events.length > this.limit) { + if (events.length > 2) { + // Keep track of how many requests we've made + this.count += 1 + + // Find the average gap between events to figure out how regularly people post to this + // feed. Multiply it by the number of events we have but scale down to avoid + // blowing past big gaps due to misbehaving relays skewing the results. Trim off + // outliers and scale based on results/requests to help with that const timestamps = sortBy(identity, pluck('created_at', events)) const gaps = aperture(2, timestamps).map(([a, b]) => b - a) - const gap = quantile(gaps, 0.2) + const high = quantile(gaps, 0.5) + const gap = avg(gaps.filter(gt(high))) + + // If we're just warming up, scale the window down even further to avoid + // blowing past the most relevant time period + const scale = ( + Math.min(1, Math.log10(events.length)) + * Math.min(1, Math.log10(this.count + 1)) + ) // Only paginate part of the way so we can avoid missing stuff - this.until -= Math.round(gap * events.length * 0.5) + this.until -= Math.round(gap * scale * this.limit) } } } @@ -263,13 +281,13 @@ export const stringToColor = (value, {saturation = 100, lightness = 50, opacity return `hsl(${(hash % 360)}, ${saturation}%, ${lightness}%, ${opacity})`; } -export const tryFunc = (f, ignore) => { +export const tryFunc = (f, ignore = null) => { try { const r = f() if (is(Promise, r)) { return r.catch(e => { - if (!e.toString().includes(ignore)) { + if (!ignore || !e.toString().includes(ignore)) { warn(e) } }) @@ -277,7 +295,7 @@ export const tryFunc = (f, ignore) => { return r } } catch (e) { - if (!e.toString().includes(ignore)) { + if (!ignore || !e.toString().includes(ignore)) { warn(e) } } diff --git a/src/views/feed/Feed.svelte b/src/views/feed/Feed.svelte index f8353897..df9ac11a 100644 --- a/src/views/feed/Feed.svelte +++ b/src/views/feed/Feed.svelte @@ -1,6 +1,6 @@