Guess delta from filter, don't duplicate session across multiple stores

This commit is contained in:
Jonathan Staab 2023-09-14 17:14:07 -07:00
parent 66fcd2cc34
commit c313069bc0
23 changed files with 83 additions and 59 deletions

View File

@ -6,21 +6,21 @@
import Card from "src/partials/Card.svelte" import Card from "src/partials/Card.svelte"
import PersonCircle from "src/app/shared/PersonCircle.svelte" import PersonCircle from "src/app/shared/PersonCircle.svelte"
import {slowConnections} from "src/app/state" import {slowConnections} from "src/app/state"
import {env, session, sessions, logoutPubkey, displayPubkey} from "src/engine2" import {env, pubkey, sessions, logoutPubkey, displayPubkey} from "src/engine2"
const showLogin = () => modal.push({type: "login/advanced"}) const showLogin = () => modal.push({type: "login/advanced"})
</script> </script>
<Popover theme="transparent" placement="top-end" opts={{hideOnClick: true}}> <Popover theme="transparent" placement="top-end" opts={{hideOnClick: true}}>
<div slot="trigger" class="relative flex cursor-pointer items-center"> <div slot="trigger" class="relative flex cursor-pointer items-center">
<PersonCircle size={10} pubkey={$session.pubkey} /> <PersonCircle size={10} pubkey={$pubkey} />
</div> </div>
<div slot="tooltip" class="flex justify-end"> <div slot="tooltip" class="flex justify-end">
<Card class="mt-1 w-48 overflow-hidden shadow-lg"> <Card class="mt-1 w-48 overflow-hidden shadow-lg">
<div class="-mx-3 -mt-1"> <div class="-mx-3 -mt-1">
<Anchor <Anchor
class="block p-3 px-4 transition-all hover:bg-accent hover:text-white" class="block p-3 px-4 transition-all hover:bg-accent hover:text-white"
href={`/${nip19.npubEncode($session.pubkey)}`}> href={`/${nip19.npubEncode($pubkey)}`}>
<i class="fa fa-user mr-2" /> Profile <i class="fa fa-user mr-2" /> Profile
</Anchor> </Anchor>
<Anchor class="block p-3 px-4 transition-all hover:bg-accent hover:text-white" href="/keys"> <Anchor class="block p-3 px-4 transition-all hover:bg-accent hover:text-white" href="/keys">
@ -49,11 +49,11 @@
</Anchor> </Anchor>
<div class="my-2 h-px w-full bg-gray-5" /> <div class="my-2 h-px w-full bg-gray-5" />
{#each Object.values($sessions) as s (s.pubkey)} {#each Object.values($sessions) as s (s.pubkey)}
{#if s.pubkey !== $session.pubkey} {#if s.pubkey !== $pubkey}
<div <div
class="block flex cursor-pointer items-center justify-between gap-2 p-3 px-4 class="block flex cursor-pointer items-center justify-between gap-2 p-3 px-4
transition-all hover:bg-accent hover:text-white" transition-all hover:bg-accent hover:text-white"
on:click={() => session.set(s)}> on:click={() => pubkey.set(s.pubkey)}>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<PersonCircle pubkey={s.pubkey} /> <PersonCircle pubkey={s.pubkey} />
{displayPubkey(s.pubkey)} {displayPubkey(s.pubkey)}

View File

@ -21,7 +21,6 @@
export let hideControls = false export let hideControls = false
export let queryCache = false export let queryCache = false
export let onEvent = null export let onEvent = null
export let delta = null
let scroller, feed, scrollerElement let scroller, feed, scrollerElement
let feedRelay = null let feedRelay = null
@ -90,7 +89,6 @@
filters: [compileFilter(filter)], filters: [compileFilter(filter)],
shouldLoadParents: true, shouldLoadParents: true,
onEvent, onEvent,
delta,
}) })
oldNotes = feed.feed oldNotes = feed.feed

View File

@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import {seconds} from "hurdak"
import Feed from "src/app/shared/Feed.svelte" import Feed from "src/app/shared/Feed.svelte"
export let pubkey export let pubkey
@ -8,4 +7,4 @@
const filter = {kinds: [7], authors: [pubkey]} const filter = {kinds: [7], authors: [pubkey]}
</script> </script>
<Feed hideControls delta={seconds(12, "hour")} {relays} {filter} /> <Feed hideControls {relays} {filter} />

View File

@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import {seconds} from "hurdak"
import {noteKinds} from "src/util/nostr" import {noteKinds} from "src/util/nostr"
import Feed from "src/app/shared/Feed.svelte" import Feed from "src/app/shared/Feed.svelte"
import {sessions} from "src/engine2/state" import {sessions} from "src/engine2/state"
@ -10,9 +9,4 @@
const filter = {kinds: noteKinds, authors: [pubkey]} const filter = {kinds: noteKinds, authors: [pubkey]}
</script> </script>
<Feed <Feed invertColors queryCache={Boolean($sessions[pubkey])} {relays} {filter} />
invertColors
delta={seconds(12, "hour")}
queryCache={Boolean($sessions[pubkey])}
{relays}
{filter} />

View File

@ -12,12 +12,12 @@
import NoteById from "src/app/shared/NoteById.svelte" import NoteById from "src/app/shared/NoteById.svelte"
import PersonBadgeSmall from "src/app/shared/PersonBadgeSmall.svelte" import PersonBadgeSmall from "src/app/shared/PersonBadgeSmall.svelte"
import { import {
session,
getPubkeysWithDefaults,
labels, labels,
getPubkeyHints, session,
follows, follows,
subscribe, subscribe,
getPubkeyHints,
getPubkeysWithDefaults,
} from "src/engine2" } from "src/engine2"
type LabelGroup = { type LabelGroup = {

View File

@ -2,16 +2,14 @@
import Feed from "src/app/shared/Feed.svelte" import Feed from "src/app/shared/Feed.svelte"
import Content from "src/partials/Content.svelte" import Content from "src/partials/Content.svelte"
import Heading from "src/partials/Heading.svelte" import Heading from "src/partials/Heading.svelte"
import {selectHints, getSetting} from "src/engine2" import {selectHints} from "src/engine2"
export let label export let label
export let hints export let hints
export let ids export let ids
const relays = selectHints(getSetting("relay_limit"), hints)
</script> </script>
<Content> <Content>
<Heading>{label}</Heading> <Heading>{label}</Heading>
<Feed invertColors {relays} filter={{ids}} /> <Feed invertColors relays={selectHints(hints)} filter={{ids}} />
</Content> </Content>

View File

@ -3,7 +3,7 @@
import {onMount} from "svelte" import {onMount} from "svelte"
import {fly} from "src/util/transition" import {fly} from "src/util/transition"
import {navigate} from "svelte-routing" import {navigate} from "svelte-routing"
import {now, createScroller, formatTimestampAsLocalISODate} from "src/util/misc" import {now, createScroller, formatTimestampAsDate} from "src/util/misc"
import {noteKinds, reactionKinds} from "src/util/nostr" import {noteKinds, reactionKinds} from "src/util/nostr"
import Tabs from "src/partials/Tabs.svelte" import Tabs from "src/partials/Tabs.svelte"
import Content from "src/partials/Content.svelte" import Content from "src/partials/Content.svelte"
@ -28,11 +28,8 @@
const cur = tabNotifications[i] const cur = tabNotifications[i]
const prev = tabNotifications[i - 1] const prev = tabNotifications[i - 1]
if ( if (!prev || formatTimestampAsDate(prev.timestamp) !== formatTimestampAsDate(cur.timestamp)) {
!prev || return formatTimestampAsDate(cur.timestamp)
formatTimestampAsLocalISODate(prev.timestamp) !== formatTimestampAsLocalISODate(cur.timestamp)
) {
return formatTimestampAsLocalISODate(cur.timestamp)
} }
} }

View File

@ -1,6 +1,5 @@
import {appDataKeys} from "src/util/nostr" import {appDataKeys} from "src/util/nostr"
import {session} from "src/engine2/state" import {session, canSign, nip04} from "src/engine2/queries"
import {canSign, nip04} from "src/engine2/queries"
import {publishEvent} from "./util" import {publishEvent} from "./util"
export const setAppData = async (d: string, data: any) => { export const setAppData = async (d: string, data: any) => {

View File

@ -1,9 +1,9 @@
import EventEmitter from "events" import EventEmitter from "events"
import {omit} from "ramda"
import {defer, union, difference} from "hurdak" import {defer, union, difference} from "hurdak"
import {info} from "src/util/logger" import {info} from "src/util/logger"
import type {Event, NostrEvent} from "src/engine2/model" import type {Event, NostrEvent} from "src/engine2/model"
import {getUrls, getExecutor} from "src/engine2/queries" import {getUrls, getExecutor} from "src/engine2/queries"
import {projections} from "src/engine2/projections"
export type PublisherOpts = { export type PublisherOpts = {
timeout?: number timeout?: number
@ -17,13 +17,16 @@ export type StaticPublisherOpts = PublisherOpts & {
export class Publisher extends EventEmitter { export class Publisher extends EventEmitter {
result = defer() result = defer()
event: NostrEvent
constructor(readonly event: NostrEvent) { constructor(event: NostrEvent) {
super() super()
if ((event as Event).wrap) { if ((event as Event).wrap) {
throw new Error("Can't publish unwrapped events") throw new Error("Can't publish unwrapped events")
} }
this.event = event
} }
static publish({event, relays, ...opts}: StaticPublisherOpts) { static publish({event, relays, ...opts}: StaticPublisherOpts) {
@ -63,6 +66,8 @@ export class Publisher extends EventEmitter {
this.emit("progress", progress) this.emit("progress", progress)
} }
projections.push({...this.event, seen_on: []})
setTimeout(() => { setTimeout(() => {
for (const url of urls) { for (const url of urls) {
if (!succeeded.has(url) && !failed.has(url)) { if (!succeeded.has(url) && !failed.has(url)) {
@ -73,7 +78,7 @@ export class Publisher extends EventEmitter {
attemptToResolve() attemptToResolve()
}, timeout) }, timeout)
const sub = executor.publish(omit(["seen_on"], this.event), { const sub = executor.publish(this.event, {
verb, verb,
onOk: (url: string) => { onOk: (url: string) => {
succeeded.add(url) succeeded.add(url)

View File

@ -1,13 +1,13 @@
import {omit, assoc} from "ramda" import {omit, assoc} from "ramda"
import {generatePrivateKey, getPublicKey} from "nostr-tools" import {generatePrivateKey, getPublicKey} from "nostr-tools"
import type {Session} from "src/engine2/model" import type {Session} from "src/engine2/model"
import {pool, sessions, session} from "src/engine2/state" import {pool, sessions, pubkey} from "src/engine2/state"
import {canSign, signer} from "src/engine2/queries" import {canSign, signer, session} from "src/engine2/queries"
import {buildEvent} from "./util" import {buildEvent} from "./util"
const addSession = (s: Session) => { const addSession = (s: Session) => {
sessions.update(assoc(s.pubkey, s)) sessions.update(assoc(s.pubkey, s))
session.set(s) pubkey.set(s.pubkey)
} }
export const loginWithPrivateKey = privkey => export const loginWithPrivateKey = privkey =>
@ -29,7 +29,7 @@ export const logoutPubkey = pubkey => {
} }
export const logout = () => { export const logout = () => {
session.set(null) pubkey.set(null)
sessions.set({}) sessions.set({})
} }

View File

@ -69,7 +69,6 @@ projections.addHandler(30078, async (e: Event) => {
} }
if (Tags.from(e).getMeta("d") === appDataKeys.NIP28_LAST_CHECKED) { if (Tags.from(e).getMeta("d") === appDataKeys.NIP28_LAST_CHECKED) {
console.log(e)
await tryJson(async () => { await tryJson(async () => {
const payload = JSON.parse(await nip04.get().decryptAsUser(e.content, e.pubkey)) const payload = JSON.parse(await nip04.get().decryptAsUser(e.content, e.pubkey))

View File

@ -2,8 +2,8 @@ import {find, whereEq, nth} from "ramda"
import {derived} from "src/engine2/util" import {derived} from "src/engine2/util"
import type {Event} from "src/engine2/model" import type {Event} from "src/engine2/model"
import {findReplyAndRootIds} from "src/util/nostr" import {findReplyAndRootIds} from "src/util/nostr"
import {session, lists} from "src/engine2/state" import {lists} from "src/engine2/state"
import {user} from "src/engine2/queries/session" import {user, session} from "src/engine2/queries/session"
export const mutes = user.derived($user => ($user?.mutes || []).map(nth(1))) export const mutes = user.derived($user => ($user?.mutes || []).map(nth(1)))

View File

@ -1,5 +1,6 @@
import {prop} from "ramda" import {prop} from "ramda"
import {env, session} from "src/engine2/state" import {env} from "src/engine2/state"
import {session} from "src/engine2/queries"
export const getDefaultSettings = () => ({ export const getDefaultSettings = () => ({
relay_limit: 10, relay_limit: 10,

View File

@ -2,12 +2,16 @@ import {max, sortBy} from "ramda"
import {Tags, findReplyId, findReplyAndRootIds} from "src/util/nostr" import {Tags, findReplyId, findReplyAndRootIds} from "src/util/nostr"
import {formatTimestampAsLocalISODate, tryJson} from "src/util/misc" import {formatTimestampAsLocalISODate, tryJson} from "src/util/misc"
import {derived} from "src/engine2/util/store" import {derived} from "src/engine2/util/store"
import {session, events, notificationsLastChecked} from "src/engine2/state" import {events, notificationsLastChecked} from "src/engine2/state"
import {userEventsById} from "src/engine2/queries/session" import {session, userEventsById} from "src/engine2/queries/session"
export const notifications = derived( export const notifications = derived(
[session, userEventsById.throttle(500), events.throttle(500)], [session, userEventsById.throttle(500), events.throttle(500)],
([$session, $userEventsById, $events]) => { ([$session, $userEventsById, $events]) => {
if (!$session) {
return []
}
return $events.filter(e => { return $events.filter(e => {
const {root, reply} = findReplyAndRootIds(e) const {root, reply} = findReplyAndRootIds(e)

View File

@ -2,7 +2,7 @@ import {nip19} from "nostr-tools"
import {propEq} from "ramda" import {propEq} from "ramda"
import {createMap} from "hurdak" import {createMap} from "hurdak"
import {derived} from "src/engine2/util/store" import {derived} from "src/engine2/util/store"
import {session, events, people} from "src/engine2/state" import {sessions, pubkey, events, people} from "src/engine2/state"
import {prepareNdk, ndkInstances} from "./ndk" import {prepareNdk, ndkInstances} from "./ndk"
import {Signer} from "./signer" import {Signer} from "./signer"
import {Nip04} from "./nip04" import {Nip04} from "./nip04"
@ -21,7 +21,9 @@ export const isKeyValid = (key: string) => {
return true return true
} }
export const stateKey = session.derived($s => $s?.pubkey || "anonymous") export const stateKey = pubkey.derived($pk => $pk || "anonymous")
export const session = derived([pubkey, sessions], ([$pk, $sessions]) => $sessions[$pk])
export const user = derived([session, people.mapStore], ([$s, $p]) => $p.get($s?.pubkey)) export const user = derived([session, people.mapStore], ([$s, $p]) => $p.get($s?.pubkey))

View File

@ -1,16 +1,16 @@
import {mergeRight, identity, sortBy} from "ramda" import {mergeRight, identity, sortBy} from "ramda"
import {seconds, first} from "hurdak" import {first} from "hurdak"
import {now} from "src/util/misc" import {now} from "src/util/misc"
import {EPOCH} from "src/util/nostr" import {EPOCH} from "src/util/nostr"
import type {Filter, Event} from "src/engine2/model" import type {Filter, Event} from "src/engine2/model"
import type {Subscription} from "./subscription" import type {Subscription} from "./subscription"
import {subscribe} from "./subscription" import {subscribe} from "./subscription"
import {guessFilterDelta} from "./filter"
export type CursorOpts = { export type CursorOpts = {
relay: string relay: string
filters: Filter[] filters: Filter[]
onEvent?: (e: Event) => void onEvent?: (e: Event) => void
delta?: number
} }
export class Cursor { export class Cursor {
@ -23,7 +23,7 @@ export class Cursor {
done = false done = false
constructor(readonly opts: CursorOpts) { constructor(readonly opts: CursorOpts) {
this.delta = opts.delta || seconds(10, "minute") this.delta = guessFilterDelta(opts.filters)
this.since = now() - this.delta this.since = now() - this.delta
} }

View File

@ -13,7 +13,6 @@ export type FeedOpts = {
depth: number depth: number
relays: string[] relays: string[]
filters: Filter[] filters: Filter[]
delta?: number
onEvent?: (e: Event) => void onEvent?: (e: Event) => void
shouldLoadParents?: boolean shouldLoadParents?: boolean
} }
@ -61,7 +60,6 @@ export class FeedLoader {
relay => relay =>
new Cursor({ new Cursor({
relay, relay,
delta: opts.delta,
filters: opts.filters, filters: opts.filters,
onEvent: batch(100, (context: Event[]) => { onEvent: batch(100, (context: Event[]) => {
this.context.addContext(context, {shouldLoadParents: true, depth: opts.depth}) this.context.addContext(context, {shouldLoadParents: true, depth: opts.depth})

View File

@ -1,5 +1,5 @@
import {omit, prop, groupBy, uniq} from "ramda" import {omit, find, prop, groupBy, uniq} from "ramda"
import {shuffle} from "hurdak" import {shuffle, seconds, avg} from "hurdak"
import type {DynamicFilter, Filter} from "src/engine2/model" import type {DynamicFilter, Filter} from "src/engine2/model"
import {env} from "src/engine2/state" import {env} from "src/engine2/state"
import {follows, network} from "src/engine2/queries" import {follows, network} from "src/engine2/queries"
@ -57,6 +57,30 @@ export const getIdFilters = values => {
return filters return filters
} }
export const getFilterGenerality = filter => {
if (filter.ids) {
return 1
}
const hasTags = find(k => k.startsWith("#"), Object.keys(filter))
if (filter.pubkeys && hasTags) {
return 0.8
}
if (filter.pubkeys) {
return 1 - Math.max(1, filter.pubkeys.length / 100)
}
return 0
}
export const guessFilterDelta = filters => {
const avgGenerality = avg(filters.map(getFilterGenerality))
return Math.round(seconds(7, "day") * Math.max(0.001, avgGenerality))
}
export const getPubkeysWithDefaults = (pubkeys: string[]) => export const getPubkeysWithDefaults = (pubkeys: string[]) =>
shuffle(pubkeys.length > 0 ? pubkeys : (env.get().DEFAULT_FOLLOWS as string[])).slice(0, 1024) shuffle(pubkeys.length > 0 ? pubkeys : (env.get().DEFAULT_FOLLOWS as string[])).slice(0, 1024)

View File

@ -2,8 +2,8 @@ import {pluck} from "ramda"
import {batch, seconds} from "hurdak" import {batch, seconds} from "hurdak"
import {now} from "src/util/misc" import {now} from "src/util/misc"
import {EventKind} from "src/engine2/model" import {EventKind} from "src/engine2/model"
import {session, nip04ChannelsLastChecked} from "src/engine2/state" import {nip04ChannelsLastChecked} from "src/engine2/state"
import {getInboxHints, getUserRelayUrls} from "src/engine2/queries" import {session, getInboxHints, getUserRelayUrls} from "src/engine2/queries"
import {load} from "./load" import {load} from "./load"
import {loadPubkeys} from "./pubkeys" import {loadPubkeys} from "./pubkeys"
import {subscribe} from "./subscription" import {subscribe} from "./subscription"

View File

@ -1,8 +1,8 @@
import {seconds} from "hurdak" import {seconds} from "hurdak"
import {now} from "src/util/misc" import {now} from "src/util/misc"
import {EventKind} from "src/engine2/model" import {EventKind} from "src/engine2/model"
import {session, nip24ChannelsLastChecked} from "src/engine2/state" import {nip24ChannelsLastChecked} from "src/engine2/state"
import {getUserRelayUrls, nip24Channels, getNip24ChannelPubkeys} from "src/engine2/queries" import {session, getUserRelayUrls, nip24Channels, getNip24ChannelPubkeys} from "src/engine2/queries"
import {load} from "./load" import {load} from "./load"
import {loadPubkeys} from "./pubkeys" import {loadPubkeys} from "./pubkeys"

View File

@ -4,8 +4,8 @@ import type {Event, Delete, Session, Channel, Topic, List, Person, Relay} from "
// Synchronous stores // Synchronous stores
export const pubkey = writable<string | null>(null)
export const sessions = writable<Record<string, Session>>({}) export const sessions = writable<Record<string, Session>>({})
export const session = writable<Session | null>(null)
export const env = writable<Record<string, any>>({}) export const env = writable<Record<string, any>>({})
export const notificationsLastChecked = writable(0) export const notificationsLastChecked = writable(0)
export const nip04ChannelsLastChecked = writable(0) export const nip04ChannelsLastChecked = writable(0)

View File

@ -24,8 +24,8 @@ const sortByPubkeyWhitelist = (fallback: (x: any) => number) => (rows: Record<st
} }
export const storage = new Storage([ export const storage = new Storage([
new LocalStorageAdapter("pubkey", state.pubkey),
new LocalStorageAdapter("sessions", state.sessions), new LocalStorageAdapter("sessions", state.sessions),
new LocalStorageAdapter("session", state.session),
new LocalStorageAdapter("notificationsLastChecked", state.notificationsLastChecked), new LocalStorageAdapter("notificationsLastChecked", state.notificationsLastChecked),
new LocalStorageAdapter("nip04ChannelsLastChecked", state.nip04ChannelsLastChecked), new LocalStorageAdapter("nip04ChannelsLastChecked", state.nip04ChannelsLastChecked),
new LocalStorageAdapter("nip24ChannelsLastChecked", state.nip24ChannelsLastChecked), new LocalStorageAdapter("nip24ChannelsLastChecked", state.nip24ChannelsLastChecked),

View File

@ -16,6 +16,12 @@
"bg-input border border-solid border-gray-3 text-black", "bg-input border border-solid border-gray-3 text-black",
{"pl-10": showBefore, "pr-10": showAfter} {"pl-10": showBefore, "pr-10": showAfter}
) )
$: {
if ($$props.type === "range" && typeof value === "string") {
value = parseInt(value)
}
}
</script> </script>
<div class={cx(wrapperClass, "relative")}> <div class={cx(wrapperClass, "relative")}>