Clean up feeds and listeners

This commit is contained in:
Jon Staab
2024-11-12 17:44:29 -08:00
parent 89a9d37379
commit 19ce6d1bad
8 changed files with 121 additions and 74 deletions

View File

@@ -268,7 +268,7 @@ export const setInboxRelayPolicy = (url: string, enabled: boolean) => {
export const checkRelayAccess = async (url: string, claim = "") => { export const checkRelayAccess = async (url: string, claim = "") => {
const connection = ctx.net.pool.get(url) const connection = ctx.net.pool.get(url)
await connection.auth.attempt() await connection.auth.attempt(5000)
const thunk = publishThunk({ const thunk = publishThunk({
event: createEvent(AUTH_JOIN, {tags: [["claim", claim]]}), event: createEvent(AUTH_JOIN, {tags: [["claim", claim]]}),
@@ -309,9 +309,10 @@ export const checkRelayAuth = async (url: string) => {
const connection = ctx.net.pool.get(url) const connection = ctx.net.pool.get(url)
const okStatuses = [AuthStatus.None, AuthStatus.Ok] const okStatuses = [AuthStatus.None, AuthStatus.Ok]
await connection.auth.attempt() await connection.auth.attempt(5000)
if (!okStatuses.includes(connection.auth.status)) { if (!okStatuses.includes(connection.auth.status)) {
console.log(connection.auth.status, connection)
return `Failed to authenticate: "${connection.auth.message}"` return `Failed to authenticate: "${connection.auth.message}"`
} }
} }

View File

@@ -3,7 +3,7 @@
import type {NativeEmoji} from "emoji-picker-element/shared" import type {NativeEmoji} from "emoji-picker-element/shared"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {REACTION} from "@welshman/util" import {REACTION} from "@welshman/util"
import {pubkey, load, formatTimestamp} from "@welshman/app" import {pubkey, load} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import EmojiButton from "@lib/components/EmojiButton.svelte" import EmojiButton from "@lib/components/EmojiButton.svelte"
import Content from "@app/components/Content.svelte" import Content from "@app/components/Content.svelte"
@@ -40,8 +40,5 @@
<Icon icon="smile-circle" size={4} /> <Icon icon="smile-circle" size={4} />
</EmojiButton> </EmojiButton>
</ReactionSummary> </ReactionSummary>
<p class="whitespace-nowrap text-sm opacity-75">
{formatTimestamp(event.created_at)}
</p>
</div> </div>
</NoteCard> </NoteCard>

View File

@@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte" import {onMount} from "svelte"
import {sortBy, flatten} from "@welshman/lib" import {sortBy, uniqBy} from "@welshman/lib"
import {deriveEvents} from "@welshman/store"
import {feedFromFilter} from "@welshman/feeds" import {feedFromFilter} from "@welshman/feeds"
import {NOTE, getAncestorTags} from "@welshman/util" import {NOTE, getAncestorTags} from "@welshman/util"
import {repository, createFeedController} from "@welshman/app" import type {TrustedEvent} from "@welshman/util"
import {createFeedController} from "@welshman/app"
import {createScroller} from "@lib/html" import {createScroller} from "@lib/html"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import NoteItem from "@app/components/NoteItem.svelte" import NoteItem from "@app/components/NoteItem.svelte"
@@ -12,18 +12,36 @@
export let url export let url
export let pubkey export let pubkey
const filter = {kinds: [NOTE], authors: [pubkey]} const ctrl = createFeedController({
const events = deriveEvents(repository, {filters: [filter]}) useWindowing: true,
feed: feedFromFilter({kinds: [NOTE], authors: [pubkey]}),
onEvent: (event: TrustedEvent) => {
if (getAncestorTags(event.tags).replies.length === 0) {
buffer.push(event)
}
},
})
let element: Element let element: Element
let buffer: TrustedEvent[] = []
let events: TrustedEvent[] = []
onMount(() => { onMount(() => {
const ctrl = createFeedController({feed: feedFromFilter(filter)})
const scroller = createScroller({ const scroller = createScroller({
element, element,
delay: 300, delay: 300,
threshold: 3000, threshold: 3000,
onScroll: () => ctrl.load(5), onScroll: () => {
buffer = uniqBy(
e => e.id,
sortBy(e => -e.created_at, buffer),
)
events = [...events, ...buffer.splice(0, 5)]
if (buffer.length < 50) {
ctrl.load(50)
}
},
}) })
return () => scroller.stop() return () => scroller.stop()
@@ -32,10 +50,8 @@
<div class="col-4" bind:this={element}> <div class="col-4" bind:this={element}>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
{#each sortBy(e => -e.created_at, $events) as event (event.id)} {#each events as event (event.id)}
{#if flatten(Object.values(getAncestorTags(event.tags))).length === 0} <NoteItem {url} {event} />
<NoteItem {url} {event} />
{/if}
{/each} {/each}
<p class="center my-12 flex"> <p class="center my-12 flex">
<Spinner loading /> <Spinner loading />

View File

@@ -3,7 +3,7 @@
import {onMount} from "svelte" import {onMount} from "svelte"
import {get} from "svelte/store" import {get} from "svelte/store"
import {dev} from "$app/environment" import {dev} from "$app/environment"
import {sleep, take, sortBy, ago, now, HOUR} from "@welshman/lib" import {ctx, uniq, sleep, take, sortBy, ago, now, HOUR, WEEK} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import { import {
PROFILE, PROFILE,
@@ -13,6 +13,7 @@
RELAYS, RELAYS,
INBOX_RELAYS, INBOX_RELAYS,
WRAP, WRAP,
DELETE,
getPubkeyTagValues, getPubkeyTagValues,
getListTags, getListTags,
} from "@welshman/util" } from "@welshman/util"
@@ -41,8 +42,17 @@
import {setupTracking} from "@app/tracking" import {setupTracking} from "@app/tracking"
import {setupAnalytics} from "@app/analytics" import {setupAnalytics} from "@app/analytics"
import {theme} from "@app/theme" import {theme} from "@app/theme"
import {INDEXER_RELAYS} from "@app/state" import {
import {loadUserData} from "@app/commands" INDEXER_RELAYS,
getMembershipUrls,
getMembershipRooms,
userMembership,
MEMBERSHIPS,
MESSAGE,
COMMENT,
THREAD,
} from "@app/state"
import {loadUserData, subscribePersistent} from "@app/commands"
import * as state from "@app/state" import * as state from "@app/state"
// Migration: old nostrtalk instance used different sessions // Migration: old nostrtalk instance used different sessions
@@ -130,10 +140,55 @@
tracker: storageAdapters.fromTracker(tracker, {throttle: 1000}), tracker: storageAdapters.fromTracker(tracker, {throttle: 1000}),
}).then(() => sleep(300)) }).then(() => sleep(300))
let unsubRooms: any
userMembership.subscribe($membership => {
unsubRooms?.()
const since = ago(30)
const rooms = uniq(getMembershipRooms($membership).map(m => m.room))
const relays = uniq(getMembershipUrls($membership))
if (relays.length > 0) {
subscribePersistent({
relays,
filters: [
{kinds: [THREAD], since},
{kinds: [MESSAGE], "#~": rooms, since},
{kinds: [COMMENT], "#K": [THREAD, MESSAGE].map(String), since},
{kinds: [DELETE], "#k": [THREAD, COMMENT, MESSAGE].map(String), since},
{kinds: [MEMBERSHIPS], "#r": relays, since},
],
})
}
})
let unsubChats: any
pubkey.subscribe($pubkey => {
unsubChats?.()
if ($pubkey) {
unsubChats = subscribePersistent({
filters: [{kinds: [WRAP], "#p": [$pubkey], since: ago(WEEK)}],
relays: ctx.app.router.UserInbox().getUrls(),
})
}
})
// Unwrap gift wraps as they come in
repository.on("update", ({added}) => {
for (const event of added) {
state.ensureUnwrapped(event)
}
})
// Load relay info
for (const url of INDEXER_RELAYS) { for (const url of INDEXER_RELAYS) {
loadRelay(url) loadRelay(url)
} }
// Load user data
if ($pubkey) { if ($pubkey) {
loadUserData($pubkey) loadUserData($pubkey)
} }

View File

@@ -4,7 +4,7 @@
import {WEEK, ctx, ago} from "@welshman/lib" import {WEEK, ctx, ago} from "@welshman/lib"
import {WRAP} from "@welshman/util" import {WRAP} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {pubkey, repository, subscribe} from "@welshman/app" import {pubkey, repository} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Page from "@lib/components/Page.svelte" import Page from "@lib/components/Page.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@@ -35,15 +35,9 @@
$: chats = $chatSearch.searchOptions(term).filter(c => c.pubkeys.length > 1) $: chats = $chatSearch.searchOptions(term).filter(c => c.pubkeys.length > 1)
onMount(() => { onMount(() => {
const sub = subscribe({
filters: [{kinds: [WRAP], "#p": [$pubkey!], since: ago(WEEK)}],
relays: ctx.app.router.UserInbox().getUrls(),
})
repository.on("update", onUpdate) repository.on("update", onUpdate)
return () => { return () => {
sub.close()
repository.off("update", onUpdate) repository.off("update", onUpdate)
} }
}) })

View File

@@ -1,15 +1,13 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte" import {onMount} from "svelte"
import {page} from "$app/stores" import {page} from "$app/stores"
import {subscribe} from "@welshman/app"
import {DELETE} from "@welshman/util"
import Page from "@lib/components/Page.svelte" import Page from "@lib/components/Page.svelte"
import Delay from "@lib/components/Delay.svelte" import Delay from "@lib/components/Delay.svelte"
import SecondaryNav from "@lib/components/SecondaryNav.svelte" import SecondaryNav from "@lib/components/SecondaryNav.svelte"
import MenuSpace from "@app/components/MenuSpace.svelte" import MenuSpace from "@app/components/MenuSpace.svelte"
import {pushToast} from "@app/toast" import {pushToast} from "@app/toast"
import {checkRelayConnection, checkRelayAuth} from "@app/commands" import {checkRelayConnection, checkRelayAuth} from "@app/commands"
import {decodeRelay, MEMBERSHIPS, THREAD, MESSAGE, COMMENT} from "@app/state" import {decodeRelay} from "@app/state"
$: url = decodeRelay($page.params.relay) $: url = decodeRelay($page.params.relay)
@@ -27,16 +25,6 @@
onMount(() => { onMount(() => {
checkConnection() checkConnection()
const sub = subscribe({
filters: [
{kinds: [DELETE], "#k": [THREAD, COMMENT, MESSAGE].map(String)},
{kinds: [MEMBERSHIPS], "#r": [url]},
],
relays: [url],
})
return () => sub.close()
}) })
</script> </script>

View File

@@ -8,9 +8,8 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte"
import {page} from "$app/stores" import {page} from "$app/stores"
import {sortBy, ago, append} from "@welshman/lib" import {sortBy, append} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util" import type {TrustedEvent, EventContent} from "@welshman/util"
import {createEvent} from "@welshman/util" import {createEvent} from "@welshman/util"
import {formatTimestampAsDate, publishThunk} from "@welshman/app" import {formatTimestampAsDate, publishThunk} from "@welshman/app"
@@ -35,7 +34,7 @@
COMMENT, COMMENT,
getMembershipRoomsByUrl, getMembershipRoomsByUrl,
} from "@app/state" } from "@app/state"
import {subscribePersistent, addRoomMembership, removeRoomMembership} from "@app/commands" import {addRoomMembership, removeRoomMembership} from "@app/commands"
import {pushDrawer} from "@app/modal" import {pushDrawer} from "@app/modal"
import {popKey} from "@app/implicit" import {popKey} from "@app/implicit"
@@ -90,15 +89,6 @@
elements.reverse() elements.reverse()
} }
onMount(() => {
const unsub = subscribePersistent({
filters: [{"#~": [room], since: ago(30)}],
relays: [url],
})
return () => unsub()
})
setTimeout(() => { setTimeout(() => {
loading = false loading = false
}, 5000) }, 5000)

View File

@@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte" import {onMount} from "svelte"
import {page} from "$app/stores" import {page} from "$app/stores"
import {ago, assoc} from "@welshman/lib" import {sortBy, uniqBy} from "@welshman/lib"
import {getListTags, getPubkeyTagValues} from "@welshman/util" import {getListTags, getPubkeyTagValues} from "@welshman/util"
import type {Filter} from "@welshman/util" import type {Filter, TrustedEvent} from "@welshman/util"
import {feedsFromFilters, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds" import {feedsFromFilters, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds"
import {nthEq} from "@welshman/lib" import {nthEq} from "@welshman/lib"
import {createFeedController, userMutes} from "@welshman/app" import {createFeedController, userMutes} from "@welshman/app"
@@ -15,40 +15,43 @@
import MenuSpace from "@app/components/MenuSpace.svelte" import MenuSpace from "@app/components/MenuSpace.svelte"
import ThreadItem from "@app/components/ThreadItem.svelte" import ThreadItem from "@app/components/ThreadItem.svelte"
import ThreadCreate from "@app/components/ThreadCreate.svelte" import ThreadCreate from "@app/components/ThreadCreate.svelte"
import {THREAD, COMMENT, deriveEventsForUrl, decodeRelay} from "@app/state" import {THREAD, COMMENT, decodeRelay} from "@app/state"
import {pushModal, pushDrawer} from "@app/modal" import {pushModal, pushDrawer} from "@app/modal"
import {subscribePersistent} from "@app/commands"
const url = decodeRelay($page.params.relay) const url = decodeRelay($page.params.relay)
const events = deriveEventsForUrl(url, [{kinds: [THREAD]}])
const mutedPubkeys = getPubkeyTagValues(getListTags($userMutes)) const mutedPubkeys = getPubkeyTagValues(getListTags($userMutes))
const filters: Filter[] = [{kinds: [THREAD]}, {kinds: [COMMENT], "#K": [String(THREAD)]}] const filters: Filter[] = [{kinds: [THREAD]}, {kinds: [COMMENT], "#K": [String(THREAD)]}]
const feed = makeIntersectionFeed(makeRelayFeed(url), feedsFromFilters(filters))
const openMenu = () => pushDrawer(MenuSpace, {url}) const openMenu = () => pushDrawer(MenuSpace, {url})
const createThread = () => pushModal(ThreadCreate, {url}) const createThread = () => pushModal(ThreadCreate, {url})
let limit = 5
let loading = true let loading = true
let element: Element let element: Element
let scroller: Scroller let scroller: Scroller
let buffer: TrustedEvent[] = []
let events: TrustedEvent[] = []
onMount(() => { onMount(() => {
let unmounted = false let unmounted = false
const ctrl = createFeedController({ const ctrl = createFeedController({
feed, useWindowing: true,
feed: makeIntersectionFeed(makeRelayFeed(url), feedsFromFilters(filters)),
onEvent: (event: TrustedEvent) => {
if (
event.kind === THREAD &&
!event.tags.some(nthEq(0, "e")) &&
!mutedPubkeys.includes(event.pubkey)
) {
buffer.push(event)
}
},
onExhausted: () => { onExhausted: () => {
loading = false loading = false
}, },
}) })
const unsub = subscribePersistent({
filters: filters.map(assoc("since", ago(30))),
relays: [url],
})
// Element is frequently not defined. I don't know why // Element is frequently not defined. I don't know why
setTimeout(() => { setTimeout(() => {
if (!unmounted) { if (!unmounted) {
@@ -56,10 +59,16 @@
element, element,
delay: 300, delay: 300,
threshold: 3000, threshold: 3000,
onScroll: async () => { onScroll: () => {
limit += 5 buffer = uniqBy(
e => e.id,
sortBy(e => -e.created_at, buffer),
)
events = [...events, ...buffer.splice(0, 5)]
await ctrl.load(5) if (buffer.length < 50) {
ctrl.load(50)
}
}, },
}) })
} }
@@ -67,7 +76,6 @@
return () => { return () => {
unmounted = true unmounted = true
unsub()
scroller?.stop() scroller?.stop()
} }
}) })
@@ -90,16 +98,14 @@
</div> </div>
</PageBar> </PageBar>
<div class="flex flex-grow flex-col gap-2 overflow-auto p-2"> <div class="flex flex-grow flex-col gap-2 overflow-auto p-2">
{#each $events.slice(0, limit) as event (event.id)} {#each events as event (event.id)}
{#if !event.tags.some(nthEq(0, "e")) && !mutedPubkeys.includes(event.pubkey)} <ThreadItem {url} {event} />
<ThreadItem {url} {event} />
{/if}
{/each} {/each}
<p class="flex h-10 items-center justify-center py-20"> <p class="flex h-10 items-center justify-center py-20">
<Spinner {loading}> <Spinner {loading}>
{#if loading} {#if loading}
Looking for threads... Looking for threads...
{:else if $events.length === 0} {:else if events.length === 0}
No threads found. No threads found.
{/if} {/if}
</Spinner> </Spinner>