Re-work relays again

This commit is contained in:
Jonathan Staab 2023-02-16 11:44:04 -06:00
parent 25511fdde4
commit 12506c015b
29 changed files with 303 additions and 205 deletions

View File

@ -1,13 +1,11 @@
# Current
- [ ] Review 10002 usage https://github.com/nostr-protocol/nips/blob/master/65.md
- [ ] Make feeds page customizable. This could potentially use the "lists" NIP
- nevent1qqspjcqw2hu5gfcpkrjhs0aqvxuzjgtp50l375mcqjfpmk48cg5hevgpr3mhxue69uhkummnw3ez6un9d3shjtnhd3m8xtnnwpskxegpzamhxue69uhkummnw3ezuendwsh8w6t69e3xj7spramhxue69uhkummnw3ez6un9d3shjtnwdahxxefwv93kzer9d4usz9rhwden5te0wfjkccte9ejxzmt4wvhxjmcpr9mhxue69uhkummnw3ezuer9d3hjuum0ve68wctjv5n8hwfg
- [ ] Click through on relays page to view a feed for only that relay.
- [ ] Custom views should combine pubkeys, relays, event ids, and topics
- [ ] Remove relays from people, pull from routes only
- [ ] Fix anon/new user experience
- [ ] Clicking stuff that would publish kicks you to the login page, we should open a modal instead.
- [ ] Separate user info and relays so we can still select/figure out relays for anons
- [ ] Separate petnames out as well so anons can follow people
- [ ] Initial user load doesn't have any relays, cache user or wait for people db to be loaded
- [ ] Fix bugs on bugsnag
@ -26,14 +24,21 @@
- nevent1qqsyyxtrhpsqeqaqgucd6uzpyh8eq2hkfgr0yzr7ku7tgyl5cn9jw5qpz3mhxue69uhhyetvv9ujumn0wd68ytnzvuq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7l564wx
- [ ] Search by nip05 alias
- nevent1qqsdt4ux9c0zvd6hzpwnzznjsmd7a337mpxdspu9wd4fq8drvqejdmqpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dsffemjp
- [ ] Show options on note detail for retrieving replies
- Replies from user's network
- All replies from author's + user's read relays, including spam
# Missions
- [ ] Are write relays the only ones that matter? User read relays only matter for global feed, or where there's no relay hints available. But if relays are navigable, this is unnecessary.
- [ ] Make feeds page customizable. This could potentially use the "lists" NIP
- nevent1qqspjcqw2hu5gfcpkrjhs0aqvxuzjgtp50l375mcqjfpmk48cg5hevgpr3mhxue69uhkummnw3ez6un9d3shjtnhd3m8xtnnwpskxegpzamhxue69uhkummnw3ezuendwsh8w6t69e3xj7spramhxue69uhkummnw3ez6un9d3shjtnwdahxxefwv93kzer9d4usz9rhwden5te0wfjkccte9ejxzmt4wvhxjmcpr9mhxue69uhkummnw3ezuer9d3hjuum0ve68wctjv5n8hwfg
- [ ] Click through on relays page to view a feed for only that relay.
- [ ] Custom views should combine pubkeys, relays, event ids, and topics
- [ ] Topics/hashtag views
- [ ] Support paid relays
- atlas.nostr.land
- eden.nostr.land
- [ ] Re-license using https://polyformproject.org/
- [ ] Image uploads
- Default will charge via lightning and have a tos, others can self-host and skip that.
- Add banner field to profile
@ -55,6 +60,7 @@
# Maintenance
- [ ] Keep track of all relays an event was seen on
- [ ] Don't waste space caching rooms, load those lazily
- [ ] Normalize relay urls (lowercase, strip trailing slash)
- [ ] Use nip 56 for reporting

View File

@ -13,7 +13,8 @@
import {displayPerson, isLike} from 'src/util/nostr'
import {timedelta, shuffle, now, sleep} from 'src/util/misc'
import cmd from 'src/agent/cmd'
import {user, getUserRelays} from 'src/agent/helpers'
import {user} from 'src/agent/helpers'
import {getUserRelays} from 'src/agent/relays'
import database from 'src/agent/database'
import keys from 'src/agent/keys'
import network from 'src/agent/network'
@ -100,7 +101,7 @@
const alertSlowConnections = () => {
// Only notify about relays the user is actually subscribed to
const relayUrls = pluck('url', getUserRelays('read'))
const relayUrls = pluck('url', getUserRelays())
// Prune connections we haven't used in a while
pool.getConnections()

View File

@ -4,7 +4,7 @@ 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 {getBestRelay} from 'src/agent/helpers'
import {getPubkeyWriteRelays, getRelayForPersonHint} from 'src/agent/relays'
import database from 'src/agent/database'
import network from 'src/agent/network'
import keys from 'src/agent/keys'
@ -36,7 +36,7 @@ const createDirectMessage = (relays, pubkey, content) =>
const createNote = (relays, content, mentions = [], topics = []) => {
mentions = mentions.map(pubkey => {
const name = displayPerson(database.getPersonWithFallback(pubkey))
const {url} = getBestRelay(pubkey, 'write')
const [{url}] = getPubkeyWriteRelays(pubkey)
return ["p", pubkey, url, name]
})
@ -47,7 +47,7 @@ const createNote = (relays, content, mentions = [], topics = []) => {
}
const createReaction = (relays, note, content) => {
const {url} = getBestRelay(note.pubkey, 'write')
const {url} = getRelayForPersonHint(note.pubkey, note)
const tags = uniqBy(
join(':'),
note.tags
@ -60,10 +60,14 @@ const createReaction = (relays, note, content) => {
}
const createReply = (relays, note, content, mentions = [], topics = []) => {
mentions = mentions.map(pubkey => ["p", pubkey, prop('url', getBestRelay(pubkey))])
topics = topics.map(t => ["t", t])
mentions = mentions.map(pubkey => {
const [{url}] = getRelayForPersonHint(pubkey, note)
const {url} = getBestRelay(note.pubkey, 'write')
return ["p", pubkey, url]
})
const [{url}] = getRelayForPersonHint(note.pubkey, note)
const tags = uniqBy(
join(':'),
note.tags

View File

@ -1,11 +1,9 @@
import type {Person, MyEvent} from 'src/util/types'
import type {Person} from 'src/util/types'
import type {Readable} from 'svelte/store'
import {isEmpty, pick, identity, sortBy, uniq, reject, groupBy, last, propEq, uniqBy, prop} from 'ramda'
import {first, ensurePlural} from 'hurdak/lib/hurdak'
import {uniq, last} from 'ramda'
import {derived, get} from 'svelte/store'
import {Tags} from 'src/util/nostr'
import {now, timedelta} from 'src/util/misc'
import defaults from 'src/agent/defaults'
import database from 'src/agent/database'
import keys from 'src/agent/keys'
@ -33,50 +31,10 @@ export const getMuffle = () => {
}
export const getFollows = pubkey =>
database.getPersonWithFallback(pubkey).petnames || defaults.petnames
Tags.wrap(database.getPersonWithFallback(pubkey).petnames).type("p").values().all()
export const getPersonRelays = (person, mode = 'all') => {
const relays = isEmpty(person?.relays || []) ? defaults.relays : person.relays
return reject(propEq(mode, '!'), relays)
}
export const getUserRelays = (mode = 'all') =>
getPersonRelays(get(user), mode)
export const getPubkeyRelays = (pubkey, mode = 'all') =>
getPersonRelays(database.people.get(pubkey), mode)
export const getTopRelays = (pubkeys, mode = 'all') => {
const routes = database.routes.all({mode, pubkey: pubkeys})
const routesByPubkey = groupBy(prop('pubkey'), routes)
const selectRoute = k => first(sortBy(prop('score'), routesByPubkey[k] || []))
return uniqBy(prop('url'), pubkeys.map(selectRoute).filter(identity)).map(pick(['url']))
}
export const getBestRelay = (pubkey, mode = 'all') =>
first(getTopRelays([pubkey], mode).concat(getPubkeyRelays(pubkey, mode)))
export const getAllEventRelays = events => {
return uniqBy(
prop('url'),
ensurePlural(events)
.flatMap(event =>
getPubkeyRelays(event.pubkey, 'write')
.concat(Tags.from(event).relays())
.concat({url: event.seen_on})
)
)
}
export const getTopEventRelays = (events: Array<MyEvent>, mode = 'all') =>
uniqBy(
prop('url'),
ensurePlural(events)
.flatMap(e => [getBestRelay(e.pubkey, mode), {url: e.seen_on}])
.filter(identity)
)
export const getNetwork = pubkey =>
uniq(getFollows(pubkey).flatMap(getFollows))
export const getStalePubkeys = pubkeys => {
// If we're not reloading, only get pubkeys we don't already know about

View File

@ -1,8 +1,12 @@
import {uniq, uniqBy, prop, map, propEq, indexBy, pluck} from 'ramda'
import {findReply, personKinds, findReplyId, Tags} from 'src/util/nostr'
import {personKinds, findReplyId} from 'src/util/nostr'
import {chunk} from 'hurdak/lib/hurdak'
import {batch} from 'src/util/misc'
import {getStalePubkeys, getTopEventRelays} from 'src/agent/helpers'
import {getStalePubkeys} from 'src/agent/helpers'
import {
getRelaysForEventParent, getAllPubkeyWriteRelays, aggregateScores,
getUserNetworkWriteRelays,
} from 'src/agent/relays'
import pool from 'src/agent/pool'
import keys from 'src/agent/keys'
import sync from 'src/agent/sync'
@ -57,7 +61,7 @@ const listenUntilEose = (relays, filter, onEvents, {shouldProcess = true}: any =
}) as Promise<void>
}
const loadPeople = (relays, pubkeys, {kinds = personKinds, force = false, ...opts} = {}) => {
const loadPeople = (pubkeys, {kinds = personKinds, force = false, ...opts} = {}) => {
pubkeys = uniq(pubkeys)
// If we're not reloading, only get pubkeys we don't already know about
@ -65,25 +69,27 @@ const loadPeople = (relays, pubkeys, {kinds = personKinds, force = false, ...opt
pubkeys = getStalePubkeys(pubkeys)
}
return pubkeys.length > 0
? load(relays, {kinds, authors: pubkeys}, opts)
: Promise.resolve([])
}
const loadParents = (relays, notes) => {
const parentIds = new Set(Tags.wrap(notes.map(findReply)).values().all())
if (parentIds.size === 0) {
return []
}
return load(
relays.concat(getTopEventRelays(notes, 'read')),
{kinds: [1], ids: Array.from(parentIds)}
getAllPubkeyWriteRelays(pubkeys).slice(0, 10),
{kinds, authors: pubkeys},
opts
)
}
const streamContext = ({relays, notes, updateNotes, depth = 0}) => {
const loadParents = notes => {
const notesWithParent = notes.filter(findReplyId)
const relays = aggregateScores(notesWithParent.map(getRelaysForEventParent))
return load(relays, {kinds: [1], ids: notesWithParent.map(findReplyId)})
}
const streamContext = ({notes, updateNotes, depth = 0}) => {
// We could also use getRelaysForEventChildren for a more complete view of replies,
// but it's also more likely to include spam. Checking our user's social graph
// avoids this problem. TODO: review this, maybe add note authors's graphs to this
// as well.
const relays = getUserNetworkWriteRelays()
// Some relays reject very large filters, send multiple
chunk(256, notes).forEach(chunk => {
const authors = getStalePubkeys(pluck('pubkey', chunk))
@ -99,7 +105,7 @@ const streamContext = ({relays, notes, updateNotes, depth = 0}) => {
// Recur if we need to
if (depth > 0) {
streamContext({relays, notes: events, updateNotes, depth: depth - 1})
streamContext({notes: events, updateNotes, depth: depth - 1})
}
const annotate = ({replies = [], reactions = [], children = [], ...note}) => {

152
src/agent/relays.ts Normal file
View File

@ -0,0 +1,152 @@
import {get} from 'svelte/store'
import {sortBy, uniq, uniqBy, prop, pluck} from 'ramda'
import {createMapOf, first} from 'hurdak/lib/hurdak'
import {Tags} from 'src/util/nostr'
import {getFollows} from 'src/agent/helpers'
import database from 'src/agent/database'
import keys from 'src/agent/keys'
// From Mike Dilger:
// 1) Other people's write relays — pull events from people you follow,
// including their contact lists
// 2) Other people's read relays — push events that tag them (replies or just tagging).
// However, these may be authenticated, use with caution
// 3) Your write relays —- write events you post to your microblog feed for the
// world to see. ALSO write your contact list. ALSO read back your own contact list.
// 4) Your read relays —- read events that tag you. ALSO both write and read
// client-private data like client configuration events or anything that the world
// doesn't need to see.
// 5) Advertise relays — write and read back your own relay list
// Pubkey relays
export const getPubkeyRelays = pubkey => {
const person = database.getPersonWithFallback(pubkey)
return scoreRelays(pubkey, person.relays || [])
}
export const getPubkeyReadRelays = pubkey =>
getPubkeyRelays(pubkey).filter(r => r.read !== '!')
export const getPubkeyWriteRelays = pubkey =>
getPubkeyRelays(pubkey).filter(r => r.write !== '!')
// Multiple pubkeys
export const getAllPubkeyRelays = pubkeys =>
aggregateScores(pubkeys.map(getPubkeyRelays))
export const getAllPubkeyReadRelays = pubkeys =>
aggregateScores(pubkeys.map(getPubkeyReadRelays))
export const getAllPubkeyWriteRelays = pubkeys =>
aggregateScores(pubkeys.map(getPubkeyWriteRelays))
// Current user
export const getUserRelays = () => getPubkeyRelays(get(keys.pubkey))
export const getUserReadRelays = () => getPubkeyReadRelays(get(keys.pubkey))
export const getUserWriteRelays = () => getPubkeyWriteRelays(get(keys.pubkey))
// Network relays
export const getNetworkWriteRelays = pubkey => {
const follows = Tags.wrap(getFollows(pubkey)).values().all()
const others = Tags.wrap(follows.flatMap(getFollows)).values().all()
return getAllPubkeyWriteRelays(uniq(follows.concat(others)))
}
// User's network relays
export const getUserNetworkWriteRelays = () => getNetworkWriteRelays(get(keys.pubkey))
// Event-related special cases
// If we're looking for an event's parent, tags are the most reliable hint,
// but we can also look at where other people in the thread write to.
export const getRelaysForEventParent = event => {
const tags = Tags.from(event)
const relays = tags.relays()
const pubkeys = tags.type("p").values().all()
const pubkeyRelays = pubkeys.flatMap(getPubkeyWriteRelays)
return uniqByUrl(relays.concat(pubkeyRelays).concat(event.seen_on))
}
// If we're looking for an event's children, the read relays the author has
// advertised would be the most reliable option, since well-behaved clients
// will write replies there. However, this may include spam, so we may want
// to read from the current user's network's read relays instead.
export const getRelaysForEventChildren = event => {
return uniqByUrl(getPubkeyReadRelays(event.pubkey).concat(event.seen_on))
}
export const getRelayForEventHint = event => event.seen_on
export const getRelayForPersonHint = (pubkey, event) =>
first(getPubkeyWriteRelays(pubkey)) || getRelayForEventHint(event)
// If we're replying or reacting to an event, we want the author to know,
// as well as anyone else who is tagged in the original event or the reply.
// Get everyone's read relays. We also want to advertise our content to
// our followers, so write to our write relays as well.
export const getEventPublishRelays = event => {
const tags = Tags.from(event)
const pubkeys = tags.type("p").values().all()
return uniqByUrl(
pubkeys
.concat(event.pubkey)
.concat(get(keys.pubkey))
.flatMap(getPubkeyReadRelays)
)
}
// Utils
const uniqByUrl = uniqBy(prop('url'))
const sortByScore = sortBy(r => -r.score)
const scoreRelays = (pubkey, relays) => {
const routes = database.routes.all({pubkey, url: pluck('url', relays)})
const scores = createMapOf('url', 'score', routes)
return uniqByUrl(sortByScore(relays.map(r => ({...r, score: scores[r.url] || 0}))))
}
export const aggregateScores = relayGroups => {
const scores = {} as Record<string, {
score: number,
count: number,
weight?: number,
weightedScore?: number
}>
for (const relays of relayGroups) {
for (const relay of relays) {
const {url, score} = relay
if (!scores[url]) {
scores[url] = {score: 0, count: 0}
}
scores[url].score += score
scores[url].count += 1
}
}
// Use the log-sum-exp and a weighted sum
for (const score of Object.values(scores)) {
score.weight = Math.log(relayGroups.length / score.count)
score.weightedScore = score.weight + Math.log1p(Math.exp(score.score - score.count))
}
return sortByScore(
Object.entries(scores)
.map(([url, {weightedScore}]) => ({url, score: weightedScore}))
)
}

View File

@ -5,6 +5,7 @@ import {synced, timedelta, now} from 'src/util/misc'
import {isAlert, findReplyId} from 'src/util/nostr'
import database from 'src/agent/database'
import network from 'src/agent/network'
import {getUserReadRelays} from 'src/agent/relays'
import {asDisplayEvent, mergeParents} from 'src/app'
let listener
@ -12,11 +13,11 @@ let listener
const mostRecentAlert = synced("app/alerts/mostRecentAlert", 0)
const lastCheckedAlerts = synced("app/alerts/lastCheckedAlerts", 0)
const onChunk = async (relays, pubkey, events) => {
const onChunk = async (pubkey, events) => {
events = events.filter(e => isAlert(e, pubkey))
if (events.length > 0) {
const parents = await network.loadParents(relays, events)
const parents = await network.loadParents(events)
const [likes, notes] = partition(propEq('kind', 7), events)
const annotatedNotes = mergeParents(notes.concat(parents).map(asDisplayEvent))
const likesByParent = groupBy(findReplyId, likes)
@ -30,31 +31,29 @@ const onChunk = async (relays, pubkey, events) => {
}
}
const load = async (relays, pubkey) => {
const load = async 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
const events = await network.load(
relays,
getUserReadRelays(),
{kinds: [1, 7], '#p': [pubkey], since, limit: 1000},
{threshold: 0.9}
)
onChunk(relays, pubkey, events)
onChunk(pubkey, events)
}
const listen = async (relays, pubkey) => {
const listen = async pubkey => {
if (listener) {
listener.unsub()
}
listener = await network.listen(
relays,
getUserReadRelays(),
{kinds: [1, 7], '#p': [pubkey], since: now()},
events => {
onChunk(relays, pubkey, events)
}
events => onChunk(pubkey, events)
)
}

View File

@ -5,7 +5,8 @@ import {createMap, ellipsize} from 'hurdak/lib/hurdak'
import {get} from 'svelte/store'
import {renderContent} from 'src/util/html'
import {Tags, displayPerson, findReplyId} from 'src/util/nostr'
import {user, getUserRelays, getFollows} from 'src/agent/helpers'
import {user, getNetwork} from 'src/agent/helpers'
import {getUserWriteRelays} from 'src/agent/relays'
import defaults from 'src/agent/defaults'
import database from 'src/agent/database'
import network from 'src/agent/network'
@ -17,17 +18,13 @@ import {toast, routes, modal, settings, logUsage} from 'src/app/ui'
export {toast, modal, settings, alerts, messages, logUsage}
export const loadAppData = pubkey => {
const relays = getUserRelays('read')
const follows = Tags.wrap(getFollows(pubkey))
return Promise.all([
alerts.load(relays, pubkey),
alerts.listen(relays, pubkey),
messages.listen(relays, pubkey),
network.loadPeople(follows.relays(), follows.values().all()),
export const loadAppData = pubkey =>
Promise.all([
alerts.load(pubkey),
alerts.listen(pubkey),
messages.listen(pubkey),
network.loadPeople(getNetwork(pubkey)),
])
}
export const login = async ({privkey, pubkey}: {privkey?: string, pubkey?: string}) => {
if (privkey) {
@ -38,12 +35,12 @@ export const login = async ({privkey, pubkey}: {privkey?: string, pubkey?: strin
modal.set({type: 'message', message: "Loading your profile data...", spinner: true})
// Load our user so we can populate network and show profile info
await network.loadPeople([pubkey])
// Load network and start listening, but don't wait for it
loadAppData(pubkey)
// Load our user so we can populate network and show profile info
await network.loadPeople(getUserRelays('read'), [pubkey])
// Not ideal, but the network tab depends on the user's social network being
// loaded, so put them on global when they first log in so we're not slowing
// down users' first run experience too much
@ -76,7 +73,7 @@ export const removeRelay = async url => {
defaults.relays = modify(defaults.relays)
if (person) {
await cmd.setRelays(getUserRelays('write'), modify(person.relays || []))
await cmd.setRelays(getUserWriteRelays(), modify(person.relays || []))
}
}
@ -88,7 +85,7 @@ export const setRelayWriteCondition = async (url, write) => {
defaults.relays = modify(defaults.relays)
if (person) {
await cmd.setRelays(getUserRelays('write'), modify(person.relays || []))
await cmd.setRelays(getUserWriteRelays(), modify(person.relays || []))
}
}

View File

@ -2,6 +2,7 @@ import {pluck, reject} from 'ramda'
import {get} from 'svelte/store'
import {synced, now, timedelta} from 'src/util/misc'
import {user} from 'src/agent/helpers'
import {getUserReadRelays} from 'src/agent/relays'
import database from 'src/agent/database'
import network from 'src/agent/network'
@ -11,13 +12,13 @@ const since = now() - timedelta(30, 'days')
const mostRecentByPubkey = synced('app/messages/mostRecentByPubkey', {})
const lastCheckedByPubkey = synced('app/messages/lastCheckedByPubkey', {})
const listen = async (relays, pubkey) => {
const listen = async pubkey => {
if (listener) {
listener.unsub()
}
listener = await network.listen(
relays,
getUserReadRelays(),
[{kinds: [4], authors: [pubkey], since},
{kinds: [4], '#p': [pubkey], since}],
async events => {
@ -27,7 +28,7 @@ const listen = async (relays, pubkey) => {
const messages = reject(e => e.pubkey === e.recipient, await database.messages.all())
if (messages.length > 0) {
await network.loadPeople(relays, pluck('pubkey', messages))
await network.loadPeople(pluck('pubkey', messages))
mostRecentByPubkey.update(o => {
for (const {pubkey, created_at} of messages) {

View File

@ -7,7 +7,7 @@
import {slide} from 'svelte/transition'
import {navigate} from 'svelte-routing'
import {quantify} from 'hurdak/lib/hurdak'
import {Tags, findReply, findRoot, findReplyId, displayPerson, isLike} from "src/util/nostr"
import {Tags, findReply, findRoot, findRootId, findReplyId, displayPerson, isLike} from "src/util/nostr"
import {extractUrls} from "src/util/html"
import ImageCircle from 'src/partials/ImageCircle.svelte'
import Preview from 'src/partials/Preview.svelte'
@ -16,7 +16,8 @@
import {formatTimestamp, stringToColor} from 'src/util/misc'
import Compose from "src/partials/Compose.svelte"
import Card from "src/partials/Card.svelte"
import {user, getTopEventRelays, getAllEventRelays} from 'src/agent/helpers'
import {user} from 'src/agent/helpers'
import {getEventPublishRelays, getRelaysForEventParent} from 'src/agent/relays'
import database from 'src/agent/database'
import cmd from 'src/agent/cmd'
import {routes} from 'src/app/ui'
@ -62,22 +63,20 @@
const target = e.target as HTMLElement
if (interactive && !['I'].includes(target.tagName) && !target.closest('a')) {
modal.set({type: 'note/detail', note, relays: getTopEventRelays(note)})
modal.set({type: 'note/detail', note})
}
}
const goToParent = async () => {
const [id, url] = findReply(note).slice(1)
const relays = getTopEventRelays(note).concat({url})
const relays = getRelaysForEventParent(note)
modal.set({type: 'note/detail', note: {id}, relays})
modal.set({type: 'note/detail', note: {id: findReplyId(note)}, relays})
}
const goToRoot = async () => {
const [id, url] = findRoot(note).slice(1)
const relays = getTopEventRelays(note).concat({url})
const relays = getRelaysForEventParent(note)
modal.set({type: 'note/detail', note: {id}, relays})
modal.set({type: 'note/detail', note: {id: findRootId(note)}, relays})
}
const showActiveRelays = () => {
@ -89,7 +88,7 @@
return navigate('/login')
}
const relays = getTopEventRelays(note)
const relays = getEventPublishRelays(note)
const [event] = cmd.createReaction(relays, note, content)
if (content === '+') {
@ -102,7 +101,7 @@
}
const deleteReaction = e => {
cmd.deleteEvent(getAllEventRelays(note), [e.id])
cmd.deleteEvent(getEventPublishRelays(note), [e.id])
if (e.content === '+') {
likes = reject(propEq('pubkey', $user.pubkey), likes)
@ -136,7 +135,7 @@
if (content) {
mentions = uniq(mentions.concat(replyMentions))
const relays = getTopEventRelays(note)
const relays = getEventPublishRelays(note)
const [event] = cmd.createReply(relays, note, content, mentions, topics)
toast.show("info", {
@ -218,10 +217,7 @@
{/if}
</Anchor>
<Anchor
href={"/" + nip19.neventEncode({
id: note.id,
relays: pluck('url', getTopEventRelays(note).slice(0, 5)),
})}
href={"/" + nip19.neventEncode({id: note.id, relays: [note.seen_on.url]})}
class="text-sm text-light"
type="unstyled">
{formatTimestamp(note.created_at)}

View File

@ -32,13 +32,12 @@
prop('id'),
newNotes
.filter(propEq('kind', 1))
.concat(await network.loadParents(relays, newNotes))
.concat(await network.loadParents(newNotes))
.map(mergeRight({replies: [], reactions: [], children: []}))
)
// Stream in additional data
network.streamContext({
relays,
notes: combined,
updateNotes: cb => {
notes = cb(notes)

View File

@ -5,7 +5,7 @@
import Content from 'src/partials/Content.svelte'
import NoteDetail from 'src/views/NoteDetail.svelte'
import Person from 'src/routes/Person.svelte'
import {getUserRelays} from 'src/agent/helpers'
import {getUserReadRelays} from 'src/agent/relays'
export let entity
@ -14,7 +14,7 @@
onMount(() => {
try {
({type, data} = nip19.decode(entity) as {type: string, data: any})
relays = (data.relays || []).map(objOf('url')).concat(getUserRelays())
relays = (data.relays || []).map(objOf('url')).concat(getUserReadRelays())
} catch (e) {
// pass
}

View File

@ -4,9 +4,10 @@
import {nip19} from 'nostr-tools'
import {navigate} from "svelte-routing"
import {fuzzy} from "src/util/misc"
import {getUserRelays, getTopRelays, user} from 'src/agent/helpers'
import {user} from 'src/agent/helpers'
import network from 'src/agent/network'
import database from 'src/agent/database'
import {getUserReadRelays} from 'src/agent/relays'
import {modal, messages} from 'src/app'
import Room from "src/partials/Room.svelte"
import Input from "src/partials/Input.svelte"
@ -24,7 +25,7 @@
const messages = await database.messages.all()
const pubkeys = without([$user.pubkey], uniq(messages.flatMap(m => [m.pubkey, m.recipient])))
await network.loadPeople(getTopRelays(pubkeys, 'write'), pubkeys)
await network.loadPeople(pubkeys)
return sortBy(k => -(mostRecentByPubkey[k] || 0), pubkeys)
.map(k => ({type: 'npub', id: k, ...database.getPersonWithFallback(k)}))
@ -58,7 +59,7 @@
}
onMount(() => {
const sub = network.listen(getUserRelays('read'), [{kinds: [40, 41]}])
const sub = network.listen(getUserReadRelays(), [{kinds: [40, 41]}])
return () => {
sub.then(s => {

View File

@ -3,7 +3,8 @@
import {nip19} from 'nostr-tools'
import {now} from 'src/util/misc'
import Channel from 'src/partials/Channel.svelte'
import {getTopEventRelays, user} from 'src/agent/helpers'
import {user} from 'src/agent/helpers'
import {getRelaysForEventChildren} from 'src/agent/relays'
import database from 'src/agent/database'
import network from 'src/agent/network'
import {modal} from 'src/app'
@ -15,7 +16,7 @@
const room = database.watch('rooms', rooms => rooms.get(roomId))
const listenForMessages = async cb => {
const relays = getTopEventRelays($room)
const relays = getRelaysForEventChildren($room)
return network.listen(
relays,
@ -33,7 +34,7 @@
}
const loadMessages = async ({until, limit}) => {
const relays = getTopEventRelays($room)
const relays = getRelaysForEventChildren($room)
const events = await network.load(relays, {kinds: [42], '#e': [roomId], until, limit})
if (events.length) {
@ -48,7 +49,7 @@
}
const sendMessage = content =>
cmd.createChatMessage(getTopEventRelays($room), roomId, content)
cmd.createChatMessage(getRelaysForEventChildren($room), roomId, content)
</script>
<Channel

View File

@ -4,13 +4,14 @@
import {personKinds} from 'src/util/nostr'
import {now} from 'src/util/misc'
import Channel from 'src/partials/Channel.svelte'
import {getUserRelays, getPubkeyRelays, user} from 'src/agent/helpers'
import {user} from 'src/agent/helpers'
import {getAllPubkeyRelays} from 'src/agent/relays'
import database from 'src/agent/database'
import network from 'src/agent/network'
import keys from 'src/agent/keys'
import {messages} from 'src/app'
import {routes} from 'src/app/ui'
import cmd from 'src/agent/cmd'
import {routes} from 'src/app/ui'
import {messages} from 'src/app'
export let entity
@ -20,7 +21,7 @@
messages.lastCheckedByPubkey.update($obj => ({...$obj, [pubkey]: now()}))
const getRelays = () => getUserRelays('write').concat(getPubkeyRelays(pubkey, 'write'))
const getRelays = () => getAllPubkeyRelays([pubkey, $user.pubkey]).slice(0, 3)
const decryptMessages = async events => {
// Gotta do it in serial because of extension limitations

View File

@ -16,7 +16,8 @@
import Notes from "src/views/person/Notes.svelte"
import Likes from "src/views/person/Likes.svelte"
import Relays from "src/views/person/Relays.svelte"
import {getPubkeyRelays, getUserRelays, user} from "src/agent/helpers"
import {user} from "src/agent/helpers"
import {getPubkeyWriteRelays, getUserWriteRelays} from "src/agent/relays"
import network from "src/agent/network"
import keys from "src/agent/keys"
import database from "src/agent/database"
@ -43,10 +44,10 @@
log('Person', npub, person)
// Add all the relays we know the person uses
relays = relays.concat(getPubkeyRelays(pubkey))
relays = relays.concat(getPubkeyWriteRelays(pubkey))
// Refresh our person if needed
network.loadPeople(relays, [pubkey]).then(() => {
network.loadPeople([pubkey]).then(() => {
person = database.getPersonWithFallback(pubkey)
loading = false
})
@ -90,13 +91,13 @@
const tag = ["p", pubkey, relays[0].url, person.name || ""]
const petnames = reject(t => t[1] === pubkey, $user.petnames).concat([tag])
cmd.setPetnames(getUserRelays('write'), petnames)
cmd.setPetnames(getUserWriteRelays(), petnames)
}
const unfollow = async () => {
const petnames = reject(t => t[1] === pubkey, $user.petnames)
cmd.setPetnames(getUserRelays('write'), petnames)
cmd.setPetnames(getUserWriteRelays(), petnames)
}
const openAdvanced = () => {

View File

@ -11,7 +11,8 @@
import Button from "src/partials/Button.svelte"
import Content from "src/partials/Content.svelte"
import Heading from "src/partials/Heading.svelte"
import {user, getUserRelays} from "src/agent/helpers"
import {user} from "src/agent/helpers"
import {getUserWriteRelays} from 'src/agent/relays'
import cmd from "src/agent/cmd"
import {toast} from "src/app"
import {routes} from "src/app/ui"
@ -46,7 +47,7 @@
const submit = async event => {
event.preventDefault()
cmd.updateUser(getUserRelays('write'), values)
cmd.updateUser(getUserWriteRelays(), values)
navigate(routes.person($user.pubkey, 'profile'))

View File

@ -7,7 +7,7 @@
import Content from "src/partials/Content.svelte"
import Textarea from "src/partials/Textarea.svelte"
import Button from "src/partials/Button.svelte"
import {getUserRelays} from 'src/agent/helpers'
import {getUserWriteRelays} from 'src/agent/relays'
import database from 'src/agent/database'
import cmd from "src/agent/cmd"
import {toast, modal} from "src/app"
@ -37,8 +37,8 @@
toast.show("error", "Please enter a name for your room.")
} else {
const [event] = room.id
? cmd.updateRoom(getUserRelays('write'), room)
: cmd.createRoom(getUserRelays('write'), room)
? cmd.updateRoom(getUserWriteRelays(), room)
: cmd.createRoom(getUserWriteRelays(), room)
await database.rooms.patch({id: room.id || event.id, joined: true})

View File

@ -13,7 +13,8 @@
import Content from "src/partials/Content.svelte"
import Modal from "src/partials/Modal.svelte"
import Heading from 'src/partials/Heading.svelte'
import {user, getUserRelays} from "src/agent/helpers"
import {user} from "src/agent/helpers"
import {getUserWriteRelays} from 'src/agent/relays'
import database from 'src/agent/database'
import cmd from "src/agent/cmd"
import {toast, modal} from "src/app"
@ -21,7 +22,7 @@
export let pubkey = null
let input = null
let relays = getUserRelays('write')
let relays = getUserWriteRelays()
let showSettings = false
let q = ''
let search

View File

@ -4,7 +4,6 @@
import {fly} from 'svelte/transition'
import {first} from 'hurdak/lib/hurdak'
import {log} from 'src/util/logger'
import {getAllEventRelays} from 'src/agent/helpers'
import network from 'src/agent/network'
import Note from 'src/partials/Note.svelte'
import Content from 'src/partials/Content.svelte'
@ -17,8 +16,6 @@
let loading = true
onMount(async () => {
relays = relays.concat(getAllEventRelays(note))
if (!note.pubkey) {
note = first(await network.load(relays, {ids: [note.id]}))
}
@ -27,7 +24,6 @@
log('NoteDetail', nip19.noteEncode(note.id), note)
network.streamContext({
relays,
depth: 10,
notes: [note],
updateNotes: cb => {

View File

@ -1,13 +1,13 @@
<script type="ts">
import Content from 'src/partials/Content.svelte'
import PersonInfo from 'src/partials/PersonInfo.svelte'
import {getUserRelays, getTopRelays} from 'src/agent/helpers'
import {getAllPubkeyWriteRelays} from 'src/agent/relays'
import database from 'src/agent/database'
import network from 'src/agent/network'
export let pubkeys
const relays = getUserRelays('read').concat(getTopRelays(pubkeys, 'write'))
const relays = getAllPubkeyWriteRelays(pubkeys)
const people = database.watch('people', people => people.all({pubkey: pubkeys}))
network.loadPeople(relays, pubkeys)

View File

@ -5,7 +5,8 @@
import Button from "src/partials/Button.svelte"
import Content from 'src/partials/Content.svelte'
import SelectButton from "src/partials/SelectButton.svelte"
import {user, getUserRelays} from 'src/agent/helpers'
import {user} from 'src/agent/helpers'
import {getUserWriteRelays} from 'src/agent/relays'
import cmd from 'src/agent/cmd'
import {modal} from 'src/app'
@ -28,7 +29,7 @@
.concat([["p", $modal.person.pubkey, muffleValue.toString()]])
.filter(t => last(t) !== "1")
cmd.muffle(getUserRelays('write'), muffleTags)
cmd.muffle(getUserWriteRelays(), muffleTags)
history.back()
}

View File

@ -6,11 +6,11 @@
import {onMount} from 'svelte'
import Content from 'src/partials/Content.svelte'
import Input from 'src/partials/Input.svelte'
import {getBestRelay} from 'src/agent/helpers'
import {getPubkeyWriteRelays} from 'src/agent/relays'
import {modal, toast} from 'src/app'
const {pubkey} = $modal.person
const relays = [prop('url', getBestRelay(pubkey))]
const relays = [prop('url', getPubkeyWriteRelays(pubkey))]
const nprofile = nip19.nprofileEncode({pubkey, relays})
let canvas

View File

@ -3,7 +3,8 @@
import {personKinds} from "src/util/nostr"
import Input from "src/partials/Input.svelte"
import PersonInfo from 'src/partials/PersonInfo.svelte'
import {user, getUserRelays} from 'src/agent/helpers'
import {user} from 'src/agent/helpers'
import {getUserReadRelays} from 'src/agent/relays'
import database from 'src/agent/database'
import network from 'src/agent/network'
@ -18,7 +19,7 @@
})
// Prime our database, in case we don't have any people stored yet
network.listenUntilEose(getUserRelays(), {kinds: personKinds, limit: 300})
network.listenUntilEose(getUserReadRelays(), {kinds: personKinds, limit: 300})
</script>
<Input bind:value={q} placeholder="Search for people">

View File

@ -1,16 +1,15 @@
<script>
import {uniq} from 'ramda'
import Notes from "src/partials/Notes.svelte"
import {shuffle} from 'src/util/misc'
import {Tags} from 'src/util/nostr'
import {user, getTopRelays, getFollows} from 'src/agent/helpers'
import {user, getFollows, getNetwork} from 'src/agent/helpers'
import {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 follows = shuffle(Tags.wrap(getFollows($user?.pubkey)).values().all())
const others = shuffle(Tags.wrap(follows.flatMap(getFollows)).values().all()).slice(0, 50)
const authors = uniq(follows.concat(others)).slice(0, 100)
const relays = getTopRelays(authors, 'write')
const follows = getFollows($user?.pubkey)
const network = getNetwork($user?.pubkey)
const authors = uniq(follows.concat(network)).slice(0, 100)
const relays = getAllPubkeyWriteRelays(authors)
const filter = {kinds: [1, 7], authors}
</script>

View File

@ -1,16 +1,16 @@
<script>
import {uniq} from 'ramda'
import Notes from "src/partials/Notes.svelte"
import {shuffle} from 'src/util/misc'
import {isLike, Tags} from 'src/util/nostr'
import {user, getTopRelays, getFollows} from 'src/agent/helpers'
import {isLike} from 'src/util/nostr'
import {user, getFollows, getNetwork} from 'src/agent/helpers'
import {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 follows = shuffle(Tags.wrap(getFollows($user?.pubkey)).values().all())
const others = shuffle(Tags.wrap(follows.flatMap(getFollows)).values().all()).slice(0, 50)
const authors = uniq(follows.concat(others)).slice(0, 100)
const relays = getTopRelays(authors, 'write')
const follows = getFollows($user?.pubkey)
const network = getNetwork($user?.pubkey)
const authors = uniq(follows.concat(network)).slice(0, 100)
const relays = getAllPubkeyWriteRelays(authors)
const filter = {kinds: [1, 7], authors}
const shouldDisplay = note => {

View File

@ -1,24 +0,0 @@
<script>
import {uniq} from 'ramda'
import Notes from "src/partials/Notes.svelte"
import {shuffle} from 'src/util/misc'
import {isLike, Tags} from 'src/util/nostr'
import {user, getTopRelays, getFollows} from 'src/agent/helpers'
// 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 follows = shuffle(Tags.wrap(getFollows($user?.pubkey)).values().all())
const others = shuffle(Tags.wrap(follows.flatMap(getFollows)).values().all()).slice(0, 50)
const authors = uniq(follows.concat(others)).slice(0, 100)
const relays = getTopRelays(authors, 'write')
const filter = {kinds: [1, 7], authors}
const shouldDisplay = note => {
return (
note.reactions.filter(isLike).length > 2
|| note.replies.length > 2
)
}
</script>
<Notes {relays} {filter} {shouldDisplay} />

View File

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

View File

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