mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-19 11:43:35 +00:00
Fix alerts, note streaming, initial connect
This commit is contained in:
parent
79d484b0ca
commit
88f7703088
@ -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
|
||||
|
@ -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)]
|
||||
}
|
||||
|
@ -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)
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -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 => {
|
||||
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)))
|
||||
|
||||
// Load authors and reactions in one subscription
|
||||
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})
|
||||
depth -= 1
|
||||
events = await load({relays, filter, onChunk})
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const annotate = ({replies = [], reactions = [], children = [], ...note}) => {
|
||||
if (depth > 0) {
|
||||
children = uniqBy(prop('id'), children.concat(replies))
|
||||
}
|
||||
const applyContext = (notes, context) => {
|
||||
const [replies, reactions] = partition(propEq('kind', 1), context)
|
||||
const repliesByParentId = groupBy(findReplyId, replies)
|
||||
const reactionsByParentId = groupBy(findReplyId, reactions)
|
||||
|
||||
return {
|
||||
const annotate = ({replies = [], reactions = [], ...note}) => ({
|
||||
...note,
|
||||
replies: uniqBy(prop('id'), replies.concat(repliesByParentId[note.id] || [])),
|
||||
replies: uniqBy(prop('id'), replies.concat(repliesByParentId[note.id] || [])).map(annotate),
|
||||
reactions: uniqBy(prop('id'), reactions.concat(reactionsByParentId[note.id] || [])),
|
||||
children: children.map(annotate),
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
updateNotes(map(annotate))
|
||||
},
|
||||
})
|
||||
})
|
||||
return notes.map(annotate)
|
||||
}
|
||||
|
||||
export default {
|
||||
publish, listen, load, loadPeople, personKinds, loadParents, streamContext,
|
||||
publish, listen, load, loadPeople, personKinds, loadParents, streamContext, applyContext,
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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} />
|
||||
|
@ -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">
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -28,5 +28,4 @@ export type MyEvent = Event & {
|
||||
export type DisplayEvent = MyEvent & {
|
||||
replies: Array<MyEvent>
|
||||
reactions: Array<MyEvent>
|
||||
children: Array<DisplayEvent>
|
||||
}
|
||||
|
@ -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.`
|
||||
if (all(isNil, Object.values(currentRelays)) && isNil(customRelayUrl)) {
|
||||
modal = 'failure'
|
||||
customRelayUrl = ''
|
||||
}
|
||||
|
||||
await sleep(3000)
|
||||
|
||||
navigate('/relays')
|
||||
} else {
|
||||
setTimeout(searchForRelays, 300)
|
||||
}
|
||||
|
||||
const addCustomRelay = () => {
|
||||
if (!customRelayUrl.startsWith('ws')) {
|
||||
customRelayUrl = 'wss://' + last(customRelayUrl.split('://'))
|
||||
}
|
||||
|
||||
const skip = () => {
|
||||
navigate('/relays')
|
||||
if (!isRelay(customRelayUrl)) {
|
||||
return toast.show("error", "That isn't a valid relay url")
|
||||
}
|
||||
|
||||
customRelays = [{url: customRelayUrl}].concat(customRelays)
|
||||
customRelayUrl = null
|
||||
modal = null
|
||||
}
|
||||
|
||||
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}
|
||||
|
||||
|
@ -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}
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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} />
|
||||
|
Loading…
Reference in New Issue
Block a user