Fix alerts, note streaming, initial connect

This commit is contained in:
Jonathan Staab 2023-02-21 13:40:52 -06:00
parent 79d484b0ca
commit 88f7703088
22 changed files with 213 additions and 161 deletions

View File

@ -1,9 +1,9 @@
# Current
- [ ] Keep track of relays that fail to connect and don't use them
- [ ] Fix loading routes speed, index by pubkey to avoid filtering
- [ ] Include everyone in person lists, re-fetch missing people
- [ ] Include everyone in person list modal, re-fetch missing people
- [ ] Fix initial relay loading, don't nuke people's relay lists
- [ ] Add a nuclear bypass with a warning, after we fail to find anything
- [ ] Don't waste space caching rooms, load those lazily
# Next

View File

@ -2,7 +2,6 @@ import type {MyEvent} from 'src/util/types'
import {prop, pick, join, uniqBy, last} from 'ramda'
import {get} from 'svelte/store'
import {first} from "hurdak/lib/hurdak"
import {log} from 'src/util/logger'
import {roomAttrs, displayPerson} from 'src/util/nostr'
import {getPubkeyWriteRelays, getRelayForPersonHint, getUserReadRelays} from 'src/agent/relays'
import database from 'src/agent/database'
@ -96,16 +95,10 @@ const deleteEvent = (relays, ids) =>
// Utils
const publishEvent = (relays, kind, {content = '', tags = []} = {}): [MyEvent, Promise<MyEvent>] => {
if (relays.length === 0) {
throw new Error("Unable to publish, no relays specified")
}
const pubkey = get(keys.pubkey)
const createdAt = Math.round(new Date().valueOf() / 1000)
const event = {kind, content, tags, pubkey, created_at: createdAt} as MyEvent
log("Publishing", event, relays)
// Return the event synchronously, separate from the promise
return [event, network.publish(relays, event)]
}

View File

@ -2,7 +2,7 @@ import type {Writable} from 'svelte/store'
import {debounce} from 'throttle-debounce'
import {omit, partition, is, find, without, pluck, all, identity} from 'ramda'
import {writable, derived} from 'svelte/store'
import {createMap, ensurePlural} from 'hurdak/lib/hurdak'
import {createMap, isObject, ensurePlural} from 'hurdak/lib/hurdak'
import {log, error} from 'src/util/logger'
import {where, now, timedelta} from 'src/util/misc'
@ -110,6 +110,10 @@ class Table {
callLocalforage('setItem', this.name, this.data)
})
_setAndNotify(newData) {
if (!isObject(newData)) {
throw new Error(`Invalid data persisted`)
}
// Update our local copy
this.data = newData
@ -191,11 +195,9 @@ const alerts = new Table('alerts', 'id', {
const isValid = alert => typeof alert.isMention === 'boolean'
const [valid, invalid] = partition(isValid, Object.values(await table.dump() || {}))
console.log(valid, invalid)
table.bulkRemove(pluck('id', invalid))
return valid
return createMap('id', valid)
},
})
@ -209,7 +211,7 @@ const routes = new Table('routes', 'id', {
// Delete stale routes asynchronously
table.bulkRemove(pluck('id', invalid))
return valid
return createMap('id', valid)
},
})

View File

@ -1,5 +1,5 @@
import type {MyEvent} from 'src/util/types'
import {uniq, uniqBy, prop, map, propEq, without, indexBy, pluck} from 'ramda'
import {partition, uniq, uniqBy, prop, map, propEq, reject, groupBy, pluck} from 'ramda'
import {personKinds, findReplyId} from 'src/util/nostr'
import {log} from 'src/util/logger'
import {chunk} from 'hurdak/lib/hurdak'
@ -39,25 +39,25 @@ const listen = ({relays, filter, onChunk, shouldProcess = true}) => {
return pool.subscribe({
filter,
relays,
onEvent: batch(300, events => {
onEvent: batch(300, chunk => {
if (shouldProcess) {
sync.processEvents(events)
sync.processEvents(chunk)
}
if (onChunk) {
onChunk(events)
onChunk(chunk)
}
}),
})
}
const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 10_000}) => {
const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 6000}) => {
return new Promise(resolve => {
relays = normalizeRelays(relays)
const now = Date.now()
const done = new Set()
const events = []
const allEvents = []
const attemptToComplete = async () => {
const sub = await subPromise
@ -71,9 +71,12 @@ const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 1
const isTimeout = Date.now() - now >= timeout
if (isTimeout) {
const timedOutRelays = without(Array.from(done), relays)
const timedOutRelays = reject(r => done.has(r.url), relays)
log(`Timing out ${timedOutRelays.length} relays after ${timeout}ms`, timedOutRelays)
log(
`Timing out ${timedOutRelays.length}/${relays.length} relays after ${timeout}ms`,
timedOutRelays
)
timedOutRelays.forEach(url => {
const conn = pool.getConnection(url)
@ -86,7 +89,7 @@ const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 1
if (isDone || isTimeout) {
sub.unsub()
resolve(events)
resolve(allEvents)
}
}
@ -96,16 +99,18 @@ const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 1
const subPromise = pool.subscribe({
relays,
filter,
onEvent: batch(300, event => {
onEvent: batch(300, chunk => {
if (shouldProcess) {
sync.processEvents(events)
sync.processEvents(chunk)
}
if (onChunk) {
onChunk(events)
onChunk(chunk)
}
events.push(event)
for (const event of chunk) {
allEvents.push(event)
}
}),
onEose: url => {
done.add(url)
@ -140,56 +145,53 @@ const loadPeople = async (pubkeys, {relays = null, kinds = personKinds, force =
const loadParents = notes => {
const notesWithParent = notes.filter(findReplyId)
if (notesWithParent.length === 0) {
return []
}
return load({
relays: sampleRelays(aggregateScores(notesWithParent.map(getRelaysForEventParent)), 0.3),
filter: {kinds: [1], ids: notesWithParent.map(findReplyId)}
})
}
const streamContext = ({notes, updateNotes, depth = 0}) => {
const streamContext = ({notes, onChunk, depth = 0}) =>
// Some relays reject very large filters, send multiple subscriptions
chunk(256, notes).forEach(chunk => {
const authors = getStalePubkeys(pluck('pubkey', chunk))
const filter = [{kinds: [1, 7], '#e': pluck('id', chunk)}] as Array<object>
const relays = sampleRelays(aggregateScores(chunk.map(getRelaysForEventChildren)))
Promise.all(
chunk(256, notes).map(async events => {
// Instead of recurring to depth, trampoline so we can batch requests
while (events.length > 0 && depth > 0) {
const chunk = events.splice(0)
const authors = getStalePubkeys(pluck('pubkey', chunk))
const filter = [{kinds: [1, 7], '#e': pluck('id', chunk)}] as Array<object>
const relays = sampleRelays(aggregateScores(chunk.map(getRelaysForEventChildren)))
if (authors.length > 0) {
filter.push({kinds: personKinds, authors})
}
// Load authors and reactions in one subscription
load({
relays,
filter,
onChunk: events => {
const repliesByParentId = indexBy(findReplyId, events.filter(propEq('kind', 1)))
const reactionsByParentId = indexBy(findReplyId, events.filter(propEq('kind', 7)))
// Recur if we need to
if (depth > 0) {
streamContext({notes: events, updateNotes, depth: depth - 1})
// Load authors and reactions in one subscription
if (authors.length > 0) {
filter.push({kinds: personKinds, authors})
}
const annotate = ({replies = [], reactions = [], children = [], ...note}) => {
if (depth > 0) {
children = uniqBy(prop('id'), children.concat(replies))
}
return {
...note,
replies: uniqBy(prop('id'), replies.concat(repliesByParentId[note.id] || [])),
reactions: uniqBy(prop('id'), reactions.concat(reactionsByParentId[note.id] || [])),
children: children.map(annotate),
}
}
updateNotes(map(annotate))
},
depth -= 1
events = await load({relays, filter, onChunk})
}
})
)
const applyContext = (notes, context) => {
const [replies, reactions] = partition(propEq('kind', 1), context)
const repliesByParentId = groupBy(findReplyId, replies)
const reactionsByParentId = groupBy(findReplyId, reactions)
const annotate = ({replies = [], reactions = [], ...note}) => ({
...note,
replies: uniqBy(prop('id'), replies.concat(repliesByParentId[note.id] || [])).map(annotate),
reactions: uniqBy(prop('id'), reactions.concat(reactionsByParentId[note.id] || [])),
})
return notes.map(annotate)
}
export default {
publish, listen, load, loadPeople, personKinds, loadParents, streamContext,
publish, listen, load, loadPeople, personKinds, loadParents, streamContext, applyContext,
}

View File

@ -39,6 +39,12 @@ class Connection {
connections[url] = this
}
hasRecentError() {
return (
this.status === CONNECTION_STATUS.ERROR
&& Date.now() - this.lastConnectionAttempt < 60_000
)
}
async connect() {
const shouldConnect = (
this.status === CONNECTION_STATUS.NEW

View File

@ -5,6 +5,7 @@ import {first, createMap, updateIn} from 'hurdak/lib/hurdak'
import {Tags, normalizeRelayUrl, isRelay, findReplyId} from 'src/util/nostr'
import {shuffle} from 'src/util/misc'
import database from 'src/agent/database'
import pool from 'src/agent/pool'
import user from 'src/agent/user'
// From Mike Dilger:
@ -143,6 +144,9 @@ export const sampleRelays = (relays, scale = 1) => {
limit *= scale
}
// Remove relays that are currently in an error state
relays => relays.filter(r => getConnection(r.url)?.hasRecentError())
// Shuffle and limit target relays
relays = shuffle(relays).slice(0, limit)

View File

@ -1,8 +1,8 @@
import {get} from 'svelte/store'
import {uniq, partition, propEq} from 'ramda'
import {createMap} from 'hurdak/lib/hurdak'
import {synced, timedelta, now} from 'src/util/misc'
import {isAlert, findReplyId} from 'src/util/nostr'
import {synced, timedelta} from 'src/util/misc'
import {isAlert, asDisplayEvent, findReplyId} from 'src/util/nostr'
import database from 'src/agent/database'
import network from 'src/agent/network'
import {getUserReadRelays} from 'src/agent/relays'
@ -12,7 +12,7 @@ let listener
const mostRecentAlert = synced("app/alerts/mostRecentAlert", 0)
const lastCheckedAlerts = synced("app/alerts/lastCheckedAlerts", 0)
const asAlert = e => ({...e, replies: [], likedBy: [], isMention: false})
const asAlert = e => asDisplayEvent({...e, repliesFrom: [], likedBy: [], isMention: false})
const onChunk = async (pubkey, events) => {
events = events.filter(e => isAlert(e, pubkey))
@ -39,7 +39,7 @@ const onChunk = async (pubkey, events) => {
const parent = parents[findReplyId(e)]
const note = database.alerts.get(parent.id) || asAlert(parent)
database.alerts.put({...note, replies: uniq(note.replies.concat(e.pubkey))})
database.alerts.put({...note, repliesFrom: uniq(note.repliesFrom.concat(e.pubkey))})
})
mentions.forEach(e => {
@ -51,28 +51,19 @@ const onChunk = async (pubkey, events) => {
mostRecentAlert.update($t => events.reduce((t, e) => Math.max(t, e.created_at), $t))
}
const load = pubkey => {
// Include an offset so we don't miss alerts on one relay but not another
const since = get(mostRecentAlert) - timedelta(30, 'days')
// Crank the threshold up since we can afford for this to be slow
network.load({
relays: getUserReadRelays(),
filter: {kinds: [1, 7], '#p': [pubkey], since, limit: 1000},
onChunk: events => onChunk(pubkey, events)
})
}
const listen = async pubkey => {
// Include an offset so we don't miss alerts on one relay but not another
const since = get(mostRecentAlert) - timedelta(7, 'days')
if (listener) {
listener.unsub()
}
listener = await network.listen({
relays: getUserReadRelays(),
filter: {kinds: [1, 7], '#p': [pubkey], since: now()},
filter: {kinds: [1, 7], '#p': [pubkey], since},
onChunk: events => onChunk(pubkey, events)
})
}
export default {load, listen, mostRecentAlert, lastCheckedAlerts}
export default {listen, mostRecentAlert, lastCheckedAlerts}

View File

@ -16,7 +16,6 @@ import {routes, modal} from 'src/app/ui'
export const loadAppData = async pubkey => {
if (getUserReadRelays().length > 0) {
await Promise.all([
alerts.load(pubkey),
alerts.listen(pubkey),
messages.listen(pubkey),
network.loadPeople(getUserFollows()),
@ -73,9 +72,6 @@ export const renderNote = (note, {showEntire = false}) => {
return content
}
export const asDisplayEvent = event =>
({children: [], replies: [], reactions: [], ...event}) as DisplayEvent
export const mergeParents = (notes: Array<DisplayEvent>) => {
const notesById = createMap('id', notes) as Record<string, DisplayEvent>
const childIds = []
@ -87,9 +83,9 @@ export const mergeParents = (notes: Array<DisplayEvent>) => {
childIds.push(note.id)
}
// Add the current note to its parents children, but only if we found a parent
// Add the current note to its parents replies, but only if we found a parent
if (notesById[parentId]) {
notesById[parentId].children = notesById[parentId].children.concat([note])
notesById[parentId].replies = notesById[parentId].replies.concat([note])
}
}

View File

@ -11,7 +11,7 @@
export let type
const pubkeys = switcher(type, {
replies: note.replies,
replies: note.repliesFrom,
likes: note.likedBy,
})
@ -41,7 +41,7 @@
on:click={() => modal.set({type: 'note/detail', note})}>
<div class="flex gap-2 items-center justify-between relative">
<button class="cursor-pointer" on:click={openPopover}>
{quantify(note.likedBy.length, 'person', 'people')} {actionText}.
{quantify(pubkeys.length, 'person', 'people')} {actionText}.
</button>
{#if isOpen}
<button in:fly={{y: 20}} class="fixed inset-0 z-10" on:click={closePopover} />

View File

@ -123,7 +123,7 @@
<div class="flex gap-4 h-full">
<div class="relative w-full">
<div class="flex flex-col pt-20 pb-28 h-full">
<div class="flex flex-col py-20 h-full">
<ul class="pb-6 p-4 overflow-auto flex-grow flex flex-col-reverse justify-start channel-messages">
{#each annotatedMessages as m (m.id)}
<li in:fly={{y: 20}} class="py-1 flex flex-col gap-2">

View File

@ -24,13 +24,14 @@
import {routes} from 'src/app/ui'
export let note
export let depth = 0
export let anchorId = null
export let showParent = true
export let invertColors = false
export let shouldDisplay = always(true)
const getDefaultReplyMentions = () =>
without([$profile?.pubkey], uniq(Tags.from(note).type("p").values().all().concat(note.pubkey)))
without([user.getPubkey()], uniq(Tags.from(note).type("p").values().all().concat(note.pubkey)))
let reply = null
let replyMentions = getDefaultReplyMentions()
@ -314,16 +315,22 @@
</div>
{/if}
{#if note.children.length > 0}
{#if note.replies.length > 0 && depth > 0}
<div class="ml-8 note-children" bind:this={childrenContainer}>
{#if !showEntire && note.children.length > 3}
{#if !showEntire && note.replies.length > 3}
<button class="ml-5 py-2 text-light cursor-pointer" on:click={onClick}>
<i class="fa fa-up-down text-sm pr-2" />
Show {quantify(note.children.length - 3, 'other reply', 'more replies')}
Show {quantify(note.replies.length - 3, 'other reply', 'more replies')}
</button>
{/if}
{#each note.children.slice(showEntire ? 0 : -3) as r (r.id)}
<svelte:self showParent={false} note={r} {invertColors} {anchorId} {shouldDisplay} />
{#each note.replies.slice(showEntire ? 0 : -3) as r (r.id)}
<svelte:self
showParent={false}
note={r}
depth={depth - 1}
{invertColors}
{anchorId}
{shouldDisplay} />
{/each}
</div>
{/if}

View File

@ -4,7 +4,7 @@
import {slide} from 'svelte/transition'
import {quantify} from 'hurdak/lib/hurdak'
import {createScroller, now, Cursor} from 'src/util/misc'
import {Tags} from 'src/util/nostr'
import {asDisplayEvent, Tags} from 'src/util/nostr'
import Spinner from 'src/partials/Spinner.svelte'
import Content from 'src/partials/Content.svelte'
import Note from "src/partials/Note.svelte"
@ -38,14 +38,15 @@
newNotes
.filter(propEq('kind', 1))
.concat(await network.loadParents(newNotes))
.map(mergeRight({replies: [], reactions: [], children: []}))
.map(asDisplayEvent)
)
// Stream in additional data
network.streamContext({
depth: 2,
notes: combined,
updateNotes: cb => {
notes = cb(notes)
onChunk: context => {
notes = network.applyContext(notes, context)
},
})
@ -107,7 +108,7 @@
<div>
{#each notes as note (note.id)}
<Note {note} {shouldDisplay} />
<Note depth={2} {note} {shouldDisplay} />
{/each}
</div>

View File

@ -10,7 +10,6 @@
import Alert from 'src/partials/Alert.svelte'
import database from 'src/agent/database'
import alerts from 'src/app/alerts'
import {asDisplayEvent} from 'src/app'
import {modal} from 'src/app/ui'
let limit = 0
@ -24,7 +23,7 @@
const events = await database.alerts.all()
notes = sortBy(e => -e.created_at, events).slice(0, limit).map(asDisplayEvent)
notes = sortBy(e => -e.created_at, events).slice(0, limit)
})
})
</script>
@ -39,14 +38,14 @@
<Alert type="likes" {note} />
{:else}
<button
class="py-2 px-3 flex flex-col gap-2 text-white cursor-pointer transition-all
class="py-2 px-3 flex flex-col gap-2 text-white cursor-pointer transition-all w-full
border border-solid border-black hover:border-medium hover:bg-dark text-left"
on:click={() => modal.set({type: 'note/detail', note})}>
<div class="flex gap-2 items-center justify-between relative">
<p>
<div class="flex gap-2 items-center">
<Badge person={database.getPersonWithFallback(note.pubkey)} />
mentioned you in their note.
</p>
<span>mentioned you in their note.</span>
</div>
<p class="text-sm text-light">{formatTimestamp(note.created_at)}</p>
</div>
<div class="ml-6 text-light">

View File

@ -6,7 +6,7 @@
import {now} from 'src/util/misc'
import Channel from 'src/partials/Channel.svelte'
import user from 'src/agent/user'
import {getAllPubkeyRelays} from 'src/agent/relays'
import {getAllPubkeyRelays, sampleRelays} from 'src/agent/relays'
import database from 'src/agent/database'
import network from 'src/agent/network'
import keys from 'src/agent/keys'
@ -22,7 +22,7 @@
messages.lastCheckedByPubkey.update($obj => ({...$obj, [pubkey]: now()}))
const getRelays = () => getAllPubkeyRelays([pubkey, user.getPubkey()])
const getRelays = () => sampleRelays(getAllPubkeyRelays([pubkey, user.getPubkey()]))
const decryptMessages = async events => {
// Gotta do it in serial because of extension limitations

View File

@ -1,3 +1,4 @@
import type {DisplayEvent} from 'src/util/types'
import {last, identity, objOf, prop, flatten, uniq} from 'ramda'
import {nip19} from 'nostr-tools'
import {ensurePlural, ellipsize, first} from 'hurdak/lib/hurdak'
@ -5,6 +6,7 @@ import {ensurePlural, ellipsize, first} from 'hurdak/lib/hurdak'
export const personKinds = [0, 2, 3, 10001, 10002, 12165]
export class Tags {
tags: Array<any>
constructor(tags) {
this.tags = tags
}
@ -94,6 +96,9 @@ export const isRelay = url => (
&& !url.slice(6).match(/\/npub/)
)
export const normalizeRelayUrl = url => url.replace(/\/+$/, '').toLowerCase()
export const normalizeRelayUrl = url => url.replace(/\/+$/, '').toLowerCase().trim()
export const roomAttrs = ['name', 'about', 'picture']
export const asDisplayEvent = event =>
({replies: [], reactions: [], ...event}) as DisplayEvent

View File

@ -28,5 +28,4 @@ export type MyEvent = Event & {
export type DisplayEvent = MyEvent & {
replies: Array<MyEvent>
reactions: Array<MyEvent>
children: Array<DisplayEvent>
}

View File

@ -1,10 +1,13 @@
<script lang="ts">
import {reject, isNil, find, all, last} from 'ramda'
import {onDestroy} from 'svelte'
import type {Relay} from 'src/util/types'
import {isNil, find, all, last} from 'ramda'
import {onDestroy, onMount} from 'svelte'
import {navigate} from 'svelte-routing'
import {sleep, shuffle} from 'src/util/misc'
import {isRelay} from 'src/util/nostr'
import Content from 'src/partials/Content.svelte'
import Spinner from 'src/partials/Spinner.svelte'
import Input from 'src/partials/Input.svelte'
import Heading from 'src/partials/Heading.svelte'
import RelayCardSimple from 'src/partials/RelayCardSimple.svelte'
import Anchor from 'src/partials/Anchor.svelte'
@ -14,15 +17,21 @@
import network from 'src/agent/network'
import user from 'src/agent/user'
import {loadAppData} from 'src/app'
import {toast} from 'src/app/ui'
let message = null
let mounted = true
let currentRelays = []
let modal = null
let customRelayUrl = null
let searching = true
let currentRelays = {} as Record<number, Relay>
let attemptedRelays = new Set()
let customRelays = []
let knownRelays = database.watch('relays', table => shuffle(table.all()))
let allRelays = []
$: allRelays = $knownRelays.concat(customRelays)
const searchForRelays = async () => {
if (!mounted) {
if (!searching) {
return
}
@ -31,7 +40,7 @@
continue
}
const relay = find(r => !attemptedRelays.has(r.url), $knownRelays)
const relay = find(r => !attemptedRelays.has(r.url), allRelays)
if (!relay) {
break
@ -42,12 +51,14 @@
network.loadPeople([user.getPubkey()], {relays: [relay], force: true})
.then(async () => {
// Wait a bit before removing the relay to smooth out the ui
await sleep(1000)
currentRelays[i] = null
if (mounted && getUserReadRelays().length > 0) {
message = `Success! Just a moment while we get things set up.`
if (searching && getUserReadRelays().length > 0) {
searching = false
modal = 'success'
await Promise.all([
loadAppData(user.getPubkey()),
@ -59,27 +70,34 @@
})
}
if (all(isNil, currentRelays)) {
message = `
No luck finding your profile data - you'll need to select your
relays manually to continue.`
await sleep(3000)
navigate('/relays')
} else {
setTimeout(searchForRelays, 300)
if (all(isNil, Object.values(currentRelays)) && isNil(customRelayUrl)) {
modal = 'failure'
customRelayUrl = ''
}
setTimeout(searchForRelays, 300)
}
const skip = () => {
navigate('/relays')
const addCustomRelay = () => {
if (!customRelayUrl.startsWith('ws')) {
customRelayUrl = 'wss://' + last(customRelayUrl.split('://'))
}
if (!isRelay(customRelayUrl)) {
return toast.show("error", "That isn't a valid relay url")
}
customRelays = [{url: customRelayUrl}].concat(customRelays)
customRelayUrl = null
modal = null
}
searchForRelays()
onMount(() => {
searchForRelays()
})
onDestroy(() => {
mounted = false
searching = false
})
</script>
@ -87,11 +105,12 @@
<Heading class="text-center">Connect to Nostr</Heading>
<p class="text-left">
We're searching for your profile on the network. If you'd like to select your
relays manually instead, click <Anchor on:click={skip}>here</Anchor>.
relays manually instead,
click <Anchor on:click={() => { customRelayUrl = ''; modal = 'custom' }}>here</Anchor>.
</p>
{#if currentRelays.length > 0}
{#if Object.values(currentRelays).length > 0}
<p>Currently searching:</p>
{#each currentRelays as relay}
{#each Object.values(currentRelays) as relay}
<div class="h-12">
{#if relay}
<RelayCardSimple relay={{...relay, description: null}} />
@ -101,11 +120,35 @@
{/if}
</Content>
{#if message}
<Modal nested>
<Content size="lg" class="text-center">
{message}
{#if modal}
<Modal nested onEscape={modal === 'success' ? null : () => { modal = null }}>
<Content>
{#if modal === 'success'}
<div class="text-center my-12">Success! Just a moment while we get things set up.</div>
<Spinner delay={0} />
{:else if modal === 'failure'}
<div class="text-center my-12">
We didn't have any luck finding your profile data - you'll need to select your
relays manually to continue. You can skip this step by clicking
<Anchor href="/relays">here</Anchor>, but be aware that any new relay selections
will replace your old ones.
</div>
{:else if modal === 'custom'}
<div class="text-center my-12">
Enter the url of a relay you've used in the past to store your profile
and we'll check there.
</div>
{/if}
{#if ['failure', 'custom'].includes(modal)}
<form class="flex gap-2" on:submit|preventDefault={addCustomRelay}>
<Input bind:value={customRelayUrl} wrapperClass="flex-grow">
<i slot="before" class="fa fa-search" />
</Input>
<Anchor type="button" on:click={addCustomRelay}>Search relay</Anchor>
</form>
{/if}
</Content>
</Modal>
{/if}

View File

@ -4,23 +4,28 @@
import {fly} from 'svelte/transition'
import {first} from 'hurdak/lib/hurdak'
import {log} from 'src/util/logger'
import network from 'src/agent/network'
import {asDisplayEvent} from 'src/util/nostr'
import Note from 'src/partials/Note.svelte'
import Content from 'src/partials/Content.svelte'
import Spinner from 'src/partials/Spinner.svelte'
import {asDisplayEvent} from 'src/app'
import network from 'src/agent/network'
import {sampleRelays} from 'src/agent/relays'
export let note
export let relays = []
let found = false
let loading = true
onMount(async () => {
if (!note.pubkey) {
if (note.pubkey) {
found = true
} else {
await network.load({
relays,
relays: sampleRelays(relays),
filter: {kinds: [1], ids: [note.id]},
onChunk: events => {
found = true
note = first(events)
},
})
@ -29,11 +34,11 @@
if (note) {
log('NoteDetail', nip19.noteEncode(note.id), note)
network.streamContext({
depth: 10,
await network.streamContext({
depth: 6,
notes: [note],
updateNotes: cb => {
note = first(cb([note]))
onChunk: context => {
note = first(network.applyContext([note], context))
},
})
}
@ -42,7 +47,7 @@
})
</script>
{#if !note}
{#if !loading && !found}
<div in:fly={{y: 20}}>
<Content size="lg" class="text-center">
Sorry, we weren't able to find this note.
@ -50,7 +55,7 @@
</div>
{:else if note.pubkey}
<div in:fly={{y: 20}}>
<Note invertColors anchorId={note.id} note={asDisplayEvent(note)} />
<Note invertColors depth={6} anchorId={note.id} note={asDisplayEvent(note)} />
</div>
{/if}

View File

@ -2,10 +2,10 @@
import {shuffle} from 'src/util/misc'
import Notes from "src/partials/Notes.svelte"
import {getUserFollows} from 'src/agent/social'
import {getAllPubkeyWriteRelays} from 'src/agent/relays'
import {sampleRelays, getAllPubkeyWriteRelays} from 'src/agent/relays'
const authors = shuffle(getUserFollows()).slice(0, 256)
const relays = getAllPubkeyWriteRelays(authors)
const relays = sampleRelays(getAllPubkeyWriteRelays(authors))
const filter = {kinds: [1, 7], authors}
</script>

View File

@ -2,12 +2,12 @@
import {shuffle} from 'src/util/misc'
import Notes from "src/partials/Notes.svelte"
import {getUserNetwork} from 'src/agent/social'
import {getAllPubkeyWriteRelays} from 'src/agent/relays'
import {sampleRelays, getAllPubkeyWriteRelays} from 'src/agent/relays'
// Get first- and second-order follows. shuffle and slice network so we're not
// sending too many pubkeys. This will also result in some variety.
const authors = shuffle(getUserNetwork()).slice(0, 256)
const relays = getAllPubkeyWriteRelays(authors)
const relays = sampleRelays(getAllPubkeyWriteRelays(authors))
const filter = {kinds: [1, 7], authors}
</script>

View File

@ -1,10 +1,10 @@
<script>
<script lang="ts">
import Notes from "src/partials/Notes.svelte"
import {getPubkeyWriteRelays} from 'src/agent/relays'
import {sampleRelays, getPubkeyWriteRelays} from 'src/agent/relays'
export let pubkey
const relays = getPubkeyWriteRelays(pubkey)
const relays = sampleRelays(getPubkeyWriteRelays(pubkey))
const filter = {kinds: [7], authors: [pubkey]}
</script>

View File

@ -1,12 +1,11 @@
<script lang="ts">
import Notes from "src/partials/Notes.svelte"
import {getPubkeyWriteRelays} from 'src/agent/relays'
import {sampleRelays, getPubkeyWriteRelays} from 'src/agent/relays'
export let pubkey
const relays = getPubkeyWriteRelays(pubkey)
const relays = sampleRelays(getPubkeyWriteRelays(pubkey))
const filter = {kinds: [1], authors: [pubkey]}
</script>
<Notes {relays} {filter} />