diff --git a/docs/app/commands.md b/docs/app/commands.md index ef19caf..446beef 100644 --- a/docs/app/commands.md +++ b/docs/app/commands.md @@ -9,11 +9,11 @@ removeRelay(url: string, mode: RelayMode): Promise addRelay(url: string, mode: RelayMode): Promise ``` -## Inbox Relay Management (NIP 17) +## Messaging Relay Management (NIP 17) ```typescript -removeInboxRelay(url: string): Promise -addInboxRelay(url: string): Promise +removeMessagingRelay(url: string): Promise +addMessagingRelay(url: string): Promise ``` ## Profile Management (NIP 01) diff --git a/docs/app/index.md b/docs/app/index.md index d860525..6fb99b6 100644 --- a/docs/app/index.md +++ b/docs/app/index.md @@ -36,7 +36,7 @@ defaultSocketPolicies.push( // This will fetch the user's profile automatically, and return a store that updates // automatically. Several different stores exist that are ready to go, including handles, -// zappers, relaySelections, relays, follows, mutes. +// zappers, relayLists, relays, follows, mutes. const profile = deriveProfile(pubkey.get()) // Publish is done using thunks, which optimistically publish to the local database, deferring diff --git a/docs/app/making-requests.md b/docs/app/making-requests.md index f121c9b..2268145 100644 --- a/docs/app/making-requests.md +++ b/docs/app/making-requests.md @@ -59,8 +59,8 @@ pins → pinsByPubkey → derivePins → loadPins // Relays relays → relaysByUrl → deriveRelay → loadRelay -relaySelections → relaySelectionsByPubkey → deriveRelaySelections → loadRelaySelections -inboxRelaySelections → inboxRelaySelectionsByPubkey → deriveInboxRelaySelections → loadInboxRelaySelections +relayLists → relayListsByPubkey → deriveRelayLists → loadRelayLists +messagingRelayLists → messagingRelayListsByPubkey → deriveMessagingRelayLists → loadMessagingRelayLists // Identity handles → handlesByNip05 → deriveHandle → loadHandle diff --git a/docs/app/publishing-events.md b/docs/app/publishing-events.md index 6feb44b..7f443b7 100644 --- a/docs/app/publishing-events.md +++ b/docs/app/publishing-events.md @@ -57,8 +57,8 @@ Several thunk factories are provided for common or more complicated scenarios li - `removeRelay(url: string, mode: RelayMode)` - `addRelay(url: string, mode: RelayMode)` -- `removeInboxRelay(url: string)` -- `addInboxRelay(url: string)` +- `removeMessagingRelay(url: string)` +- `addMessagingRelay(url: string)` - `setProfile(profile: Profile)` - `unfollow(value: string)` - `follow(tag: string[])` diff --git a/docs/app/user.md b/docs/app/user.md index 4a26888..6c3b372 100644 --- a/docs/app/user.md +++ b/docs/app/user.md @@ -39,10 +39,10 @@ export const userMutes: Store export const userPins: Store // User relay selections -export const userRelaySelections: Store +export const userRelayLists: Store -// User inbox relay selections -export const userInboxRelaySelections: Store +// User messaging relay selections +export const userMessagingRelayLists: Store // User blossom servers export const userBlossomServers: Store @@ -66,10 +66,10 @@ function loadUserMutes(relays?: string[], force?: boolean): Promise function loadUserPins(relays?: string[], force?: boolean): Promise // Load user relay selections -function loadUserRelaySelections(relays?: string[], force?: boolean): Promise +function loadUserRelayLists(relays?: string[], force?: boolean): Promise -// Load user inbox relay selections -function loadUserInboxRelaySelections(relays?: string[], force?: boolean): Promise +// Load user messaging relay selections +function loadUserMessagingRelayLists(relays?: string[], force?: boolean): Promise // Load user blossom servers function loadUserBlossomServers(relays?: string[], force?: boolean): Promise @@ -94,13 +94,13 @@ const follows = userFollows.get() ### Manual Loading ```typescript -import { loadUserMutes, loadUserRelaySelections } from '@welshman/app' +import { loadUserMutes, loadUserRelayLists } from '@welshman/app' // Load user mutes from specific relays await loadUserMutes(['wss://relay1.com', 'wss://relay2.com']) // Force refresh user relay selections -await loadUserRelaySelections([], true) +await loadUserRelayLists([], true) // Load from default relays await loadUserProfile() diff --git a/docs/router/index.md b/docs/router/index.md index 8afc703..cb884e2 100644 --- a/docs/router/index.md +++ b/docs/router/index.md @@ -31,7 +31,7 @@ const router = Router.get() // Get relays for reading events from specific pubkeys const readRelays = router.FromPubkeys(['pubkey1', 'pubkey2']).getUrls() -// Get relays for publishing an event (author's outbox + mentions' inboxes) +// Get relays for publishing an event (author's outbox + mentions' messaginges) const publishRelays = router.PublishEvent(event).getUrls() // Try hard to find a quoted note with maximal fallbacks @@ -65,7 +65,7 @@ The main class for relay selection. Configure it once with your relay discovery **Configuration Options:** - `getUserPubkey()` - Returns the current user's pubkey -- `getPubkeyRelays(pubkey, mode)` - Returns relays for a pubkey ("read", "write", or "inbox") +- `getPubkeyRelays(pubkey, mode)` - Returns relays for a pubkey ("read", "write", or "messaging") - `getDefaultRelays()` - Returns fallback relays - `getIndexerRelays()` - Returns relays that index profiles and relay lists - `getSearchRelays()` - Returns relays that support NIP-50 search @@ -74,8 +74,8 @@ The main class for relay selection. Configure it once with your relay discovery **Scenario Methods:** - `FromRelays(relays)` - Use specific relays -- `ForUser()` / `FromUser()` / `UserInbox()` - User's read/write/inbox relays -- `ForPubkey(pubkey)` / `FromPubkey(pubkey)` / `PubkeyInbox(pubkey)` - Pubkey's relays +- `ForUser()` / `FromUser()` / `UserMessaging()` - User's read/write/messaging relays +- `ForPubkey(pubkey)` / `FromPubkey(pubkey)` / `PubkeyMessaging(pubkey)` - Pubkey's relays - `ForPubkeys(pubkeys)` / `FromPubkeys(pubkeys)` - Multiple pubkeys' relays - `Event(event)` - Relays for an event's author - `PublishEvent(event)` - Relays for publishing (author + mentions) @@ -105,7 +105,7 @@ Functions that determine how many fallback relays to add: `getFilterSelections(filters)` automatically chooses appropriate relays based on filter content: - Search filters → search relays -- Wrap events → user's inbox +- Wrap events → user's messaging - Profile/relay kinds → indexer relays - Author filters → authors' relays - Everything else → user's relays (low weight) diff --git a/docs/util/relay.md b/docs/util/relay.md index 9708076..e119101 100644 --- a/docs/util/relay.md +++ b/docs/util/relay.md @@ -11,7 +11,7 @@ The `Relay` module provides utilities for working with Nostr relays, including U export enum RelayMode { Read = "read", Write = "write", - Inbox = "inbox" + Messaging = "messaging" } // Relay information from NIP-11 diff --git a/packages/app/src/blossom.ts b/packages/app/src/blossom.ts index 7d0a107..9959cc5 100644 --- a/packages/app/src/blossom.ts +++ b/packages/app/src/blossom.ts @@ -2,23 +2,23 @@ import {BLOSSOM_SERVERS, asDecryptedEvent, readList} from "@welshman/util" import {TrustedEvent, PublishedList} from "@welshman/util" import {deriveItemsByKey, deriveItems, makeForceLoadItem, makeLoadItem, makeDeriveItem, getter} from "@welshman/store" import {repository} from "./core.js" -import {makeOutboxLoader} from "./relaySelections.js" +import {makeOutboxLoader} from "./relayLists.js" -export const blossomServersByPubkey = deriveItemsByKey({ +export const blossomServerListsByPubkey = deriveItemsByKey({ repository, eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)), filters: [{kinds: [BLOSSOM_SERVERS]}], - getKey: blossomServers => blossomServers.event.pubkey, + getKey: blossomServerList => blossomServerList.event.pubkey, }) -export const blossomServers = deriveItems(blossomServersByPubkey) +export const blossomServerLists = deriveItems(blossomServerListsByPubkey) -export const getBlossomServersByPubkey = getter(blossomServersByPubkey) +export const getBlossomServerListsByPubkey = getter(blossomServerListsByPubkey) -export const getBlossomServers = (pubkey: string) => getBlossomServersByPubkey().get(pubkey) +export const getBlossomServerList = (pubkey: string) => getBlossomServerListsByPubkey().get(pubkey) -export const forceLoadBlossomServers = makeForceLoadItem(makeOutboxLoader(BLOSSOM_SERVERS), getBlossomServers) +export const forceLoadBlossomServerList = makeForceLoadItem(makeOutboxLoader(BLOSSOM_SERVERS), getBlossomServerList) -export const loadBlossomServers = makeLoadItem(makeOutboxLoader(BLOSSOM_SERVERS), getBlossomServers) +export const loadBlossomServerList = makeLoadItem(makeOutboxLoader(BLOSSOM_SERVERS), getBlossomServerList) -export const deriveBlossomServers = makeDeriveItem(blossomServersByPubkey, loadBlossomServers) +export const deriveBlossomServerList = makeDeriveItem(blossomServerListsByPubkey, loadBlossomServerList) diff --git a/packages/app/src/commands.ts b/packages/app/src/commands.ts index a43c7b5..153feb1 100644 --- a/packages/app/src/commands.ts +++ b/packages/app/src/commands.ts @@ -23,7 +23,7 @@ import { createProfile, editProfile, RelayMode, - INBOX_RELAYS, + MESSAGING_RELAYS, FOLLOWS, RELAYS, MUTES, @@ -32,16 +32,16 @@ import { import type {RoomMeta, Profile} from "@welshman/util" import {Router, addMaximalFallbacks} from "@welshman/router" import { - userRelaySelections, - loadUserRelaySelections, - userInboxRelaySelections, - loadUserInboxRelaySelections, - userFollows, - loadUserFollows, - userMutes, - loadUserMutes, - userPins, - loadUserPins, + userRelayList, + forceLoadUserRelayList, + userMessagingRelayList, + forceLoadUserMessagingRelayList, + userFollowList, + forceLoadUserFollowList, + userMuteList, + forceLoadUserMuteList, + userPinList, + forceLoadUserPinList, } from "./user.js" import {nip44EncryptToSelf, signer} from "./session.js" import {ThunkOptions, MergedThunk, publishThunk} from "./thunk.js" @@ -49,9 +49,9 @@ import {ThunkOptions, MergedThunk, publishThunk} from "./thunk.js" // NIP 65 export const removeRelay = async (url: string, mode: RelayMode) => { - await loadUserRelaySelections([], true) + await forceLoadUserRelayList([]) - const list = get(userRelaySelections) || makeList({kind: RELAYS}) + const list = get(userRelayList) || makeList({kind: RELAYS}) const dup = getRelayTags(getListTags(list)).find(nthEq(1, url)) const alt = mode === RelayMode.Read ? RelayMode.Write : RelayMode.Read const tags = list.publicTags.filter(nthNe(1, url)) @@ -71,9 +71,9 @@ export const removeRelay = async (url: string, mode: RelayMode) => { } export const addRelay = async (url: string, mode: RelayMode) => { - await loadUserRelaySelections([], true) + await forceLoadUserRelayList([]) - const list = get(userRelaySelections) || makeList({kind: RELAYS}) + const list = get(userRelayList) || makeList({kind: RELAYS}) const dup = getRelayTags(getListTags(list)).find(nthEq(1, url)) const tag = removeUndefined(["r", url, dup && dup[2] !== mode ? undefined : mode]) const tags = [...list.publicTags.filter(nthNe(1, url)), tag] @@ -85,20 +85,20 @@ export const addRelay = async (url: string, mode: RelayMode) => { // NIP 17 -export const removeInboxRelay = async (url: string) => { - await loadUserInboxRelaySelections([], true) +export const removeMessagingRelay = async (url: string) => { + await forceLoadUserMessagingRelayList([]) - const list = get(userInboxRelaySelections) || makeList({kind: INBOX_RELAYS}) + const list = get(userMessagingRelayList) || makeList({kind: MESSAGING_RELAYS}) const event = await removeFromList(list, url).reconcile(nip44EncryptToSelf) const relays = Router.get().FromUser().policy(addMaximalFallbacks).getUrls() return publishThunk({event, relays}) } -export const addInboxRelay = async (url: string) => { - await loadUserInboxRelaySelections([], true) +export const addMessagingRelay = async (url: string) => { + await forceLoadUserMessagingRelayList([]) - const list = get(userInboxRelaySelections) || makeList({kind: INBOX_RELAYS}) + const list = get(userMessagingRelayList) || makeList({kind: MESSAGING_RELAYS}) const event = await addToListPublicly(list, ["relay", url]).reconcile(nip44EncryptToSelf) const relays = Router.get().FromUser().policy(addMaximalFallbacks).getUrls() @@ -118,9 +118,9 @@ export const setProfile = (profile: Profile) => { // NIP 02 export const unfollow = async (value: string) => { - await loadUserFollows([], true) + await forceLoadUserFollowList([]) - const list = get(userFollows) || makeList({kind: FOLLOWS}) + const list = get(userFollowList) || makeList({kind: FOLLOWS}) const event = await removeFromList(list, value).reconcile(nip44EncryptToSelf) const relays = Router.get().FromUser().policy(addMaximalFallbacks).getUrls() @@ -128,9 +128,9 @@ export const unfollow = async (value: string) => { } export const follow = async (tag: string[]) => { - await loadUserFollows([], true) + await forceLoadUserFollowList([]) - const list = get(userFollows) || makeList({kind: FOLLOWS}) + const list = get(userFollowList) || makeList({kind: FOLLOWS}) const event = await addToListPublicly(list, tag).reconcile(nip44EncryptToSelf) const relays = Router.get().FromUser().policy(addMaximalFallbacks).getUrls() @@ -138,9 +138,9 @@ export const follow = async (tag: string[]) => { } export const unmute = async (value: string) => { - await loadUserMutes([], true) + await forceLoadUserMuteList([]) - const list = get(userMutes) || makeList({kind: MUTES}) + const list = get(userMuteList) || makeList({kind: MUTES}) const event = await removeFromList(list, value).reconcile(nip44EncryptToSelf) const relays = Router.get().FromUser().policy(addMaximalFallbacks).getUrls() @@ -148,9 +148,9 @@ export const unmute = async (value: string) => { } export const mutePublicly = async (tag: string[]) => { - await loadUserMutes([], true) + await forceLoadUserMuteList([]) - const list = get(userMutes) || makeList({kind: MUTES}) + const list = get(userMuteList) || makeList({kind: MUTES}) const event = await addToListPublicly(list, tag).reconcile(nip44EncryptToSelf) const relays = Router.get().FromUser().policy(addMaximalFallbacks).getUrls() @@ -158,9 +158,9 @@ export const mutePublicly = async (tag: string[]) => { } export const mutePrivately = async (tag: string[]) => { - await loadUserMutes([], true) + await forceLoadUserMuteList([]) - const list = get(userMutes) || makeList({kind: MUTES}) + const list = get(userMuteList) || makeList({kind: MUTES}) const event = await addToListPrivately(list, tag).reconcile(nip44EncryptToSelf) const relays = Router.get().FromUser().policy(addMaximalFallbacks).getUrls() @@ -174,9 +174,9 @@ export const setMutes = async ({ publicTags?: string[][] privateTags?: string[][] }) => { - await loadUserMutes([], true) + await forceLoadUserMuteList([]) - const list = get(userMutes) || makeList({kind: MUTES}) + const list = get(userMuteList) || makeList({kind: MUTES}) const event = await updateList(list, {publicTags, privateTags}).reconcile(nip44EncryptToSelf) const relays = Router.get().FromUser().policy(addMaximalFallbacks).getUrls() @@ -184,9 +184,9 @@ export const setMutes = async ({ } export const unpin = async (value: string) => { - await loadUserPins([], true) + await forceLoadUserPinList([]) - const list = get(userPins) || makeList({kind: PINS}) + const list = get(userPinList) || makeList({kind: PINS}) const event = await removeFromList(list, value).reconcile(nip44EncryptToSelf) const relays = Router.get().FromUser().policy(addMaximalFallbacks).getUrls() @@ -194,9 +194,9 @@ export const unpin = async (value: string) => { } export const pin = async (tag: string[]) => { - await loadUserPins([], true) + await forceLoadUserPinList([]) - const list = get(userPins) || makeList({kind: PINS}) + const list = get(userPinList) || makeList({kind: PINS}) const event = await addToListPublicly(list, tag).reconcile(nip44EncryptToSelf) const relays = Router.get().FromUser().policy(addMaximalFallbacks).getUrls() @@ -213,7 +213,7 @@ export type SendWrappedOptions = Omit & { export const sendWrapped = ({event, recipients, ...options}: SendWrappedOptions) => new MergedThunk( uniq(recipients).map(recipient => { - const relays = Router.get().PubkeyInbox(recipient).getUrls() + const relays = Router.get().MessagesForPubkey(recipient).getUrls() return publishThunk({event, relays, recipient, ...options}) }), diff --git a/packages/app/src/core.ts b/packages/app/src/core.ts index d9b05a0..b968a7f 100644 --- a/packages/app/src/core.ts +++ b/packages/app/src/core.ts @@ -1,55 +1,5 @@ -import {throttle} from "@welshman/lib" import {Repository, Tracker} from "@welshman/net" -import {custom} from "@welshman/store" export const tracker = new Tracker() export const repository = Repository.get() - -// Adapt objects to stores - -export const makeRepositoryStore = ({throttle: t = 300}: {throttle?: number} = {}) => - custom( - setter => { - let onUpdate = () => setter(repository) - - if (t) { - onUpdate = throttle(t, onUpdate) - } - - onUpdate() - repository.on("update", onUpdate) - - return () => repository.off("update", onUpdate) - }, - { - onUpdate: (other: Repository) => repository.load(other.dump()), - }, - ) - -export const makeTrackerStore = ({throttle: t = 300}: {throttle?: number} = {}) => - custom( - setter => { - let onUpdate = () => setter(tracker) - - if (t) { - onUpdate = throttle(t, onUpdate) - } - - onUpdate() - tracker.on("add", onUpdate) - tracker.on("remove", onUpdate) - tracker.on("load", onUpdate) - tracker.on("clear", onUpdate) - - return () => { - tracker.off("add", onUpdate) - tracker.off("remove", onUpdate) - tracker.off("load", onUpdate) - tracker.off("clear", onUpdate) - } - }, - { - onUpdate: (other: Tracker) => tracker.load(other.relaysById), - }, - ) diff --git a/packages/app/src/follows.ts b/packages/app/src/follows.ts index 815c11e..05400a4 100644 --- a/packages/app/src/follows.ts +++ b/packages/app/src/follows.ts @@ -2,23 +2,25 @@ import {FOLLOWS, asDecryptedEvent, readList} from "@welshman/util" import {TrustedEvent, PublishedList} from "@welshman/util" import {deriveItemsByKey, deriveItems, makeForceLoadItem, makeLoadItem, makeDeriveItem, getter} from "@welshman/store" import {repository} from "./core.js" -import {makeOutboxLoader} from "./relaySelections.js" +import {makeOutboxLoader} from "./relayLists.js" -export const followsByPubkey = deriveItemsByKey({ +export const followListsByPubkey = deriveItemsByKey({ repository, - eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)), filters: [{kinds: [FOLLOWS]}], - getKey: follows => follows.event.pubkey, + eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)), + getKey: followList => followList.event.pubkey, }) -export const follows = deriveItems(followsByPubkey) +export const followLists = deriveItems(followListsByPubkey) -export const getFollowsByPubkey = getter(followsByPubkey) +export const getFollowListsByPubkey = getter(followListsByPubkey) -export const getFollows = (pubkey: string) => getFollowsByPubkey().get(pubkey) +export const getFollowLists = getter(followLists) -export const forceLoadFollows = makeForceLoadItem(makeOutboxLoader(FOLLOWS), getFollows) +export const getFollowList = (pubkey: string) => getFollowListsByPubkey().get(pubkey) -export const loadFollows = makeLoadItem(makeOutboxLoader(FOLLOWS), getFollows) +export const forceLoadFollowList = makeForceLoadItem(makeOutboxLoader(FOLLOWS), getFollowList) -export const deriveFollows = makeDeriveItem(followsByPubkey, loadFollows) +export const loadFollowList = makeLoadItem(makeOutboxLoader(FOLLOWS), getFollowList) + +export const deriveFollowList = makeDeriveItem(followListsByPubkey, loadFollowList) diff --git a/packages/app/src/handles.ts b/packages/app/src/handles.ts index bb0698b..7bacc64 100644 --- a/packages/app/src/handles.ts +++ b/packages/app/src/handles.ts @@ -1,7 +1,7 @@ import {writable, derived} from "svelte/store" import {tryCatch, fetchJson, uniq, batcher, postJson, last} from "@welshman/lib" -import {collection} from "@welshman/store" -import {deriveProfile} from "./profiles.js" +import {getter, deriveItems, makeForceLoadItem, makeLoadItem, makeDeriveItem} from "@welshman/store" +import {deriveProfile, loadProfile} from "./profiles.js" import {appContext} from "./context.js" export type Handle = { @@ -40,16 +40,23 @@ export async function queryProfile(nip05: string) { } } -export const handles = writable([]) +export const handlesByNip05 = writable(new Map()) -export const fetchHandles = async (nip05s: string[]) => { - const base = appContext.dufflepudUrl! +export const handles = deriveItems(handlesByNip05) + +export const getHandlesByNip05 = getter(handlesByNip05) + +export const getHandles = getter(handles) + +export const getHandle = (nip05: string) => getHandlesByNip05().get(nip05) + +export const fetchHandle = batcher(800, async (nip05s: string[]) => { const handlesByNip05 = new Map() // Use dufflepud if we it's set up to protect user privacy, otherwise fetch directly - if (base) { + if (appContext.dufflepudUrl) { const res: any = await tryCatch( - async () => await postJson(`${base}/handle/info`, {handles: nip05s}), + async () => await postJson(`${appContext.dufflepudUrl}/handle/info`, {handles: nip05s}), ) for (const {handle: nip05, info} of res?.data || []) { @@ -72,50 +79,40 @@ export const fetchHandles = async (nip05s: string[]) => { } } - return handlesByNip05 -} + return nip05s.map(nip05 => { + const info = handlesByNip05.get(nip05) -export const { - indexStore: handlesByNip05, - deriveItem: deriveHandle, - loadItem: loadHandle, - onItem: onHandle, -} = collection({ - name: "handles", - store: handles, - getKey: (handle: Handle) => handle.nip05, - load: batcher(800, async (nip05s: string[]) => { - const fresh = await fetchHandles(uniq(nip05s)) - const stale = handlesByNip05.get() - - for (const nip05 of nip05s) { - const newHandle = fresh.get(nip05) - - if (newHandle) { - stale.set(nip05, {...newHandle, nip05}) - } + if (info) { + return {...info, nip05} } - - handles.set(Array.from(stale.values())) - - return nip05s - }), + }) }) -export const deriveHandleForPubkey = (pubkey: string, relays: string[] = []) => - derived([handlesByNip05, deriveProfile(pubkey, relays)], ([$handlesByNip05, $profile]) => { - if (!$profile?.nip05) { - return undefined - } +export const forceLoadHandle = makeForceLoadItem(fetchHandle, getHandle) - loadHandle($profile.nip05) +export const loadHandle = makeLoadItem(fetchHandle, getHandle) + +export const deriveHandle = makeDeriveItem(handlesByNip05, loadHandle) + +export const loadHandleForPubkey = async (pubkey: string, relays: string[] = []) => { + const $profile = await loadProfile(pubkey, relays) + + return $profile?.nip05 ? loadHandle($profile.nip05) : undefined +} + +export const deriveHandleForPubkey = (pubkey: string, relays: string[] = []) => { + loadHandleForPubkey(pubkey, relays) + + return derived([handlesByNip05, deriveProfile(pubkey, relays)], ([$handlesByNip05, $profile]) => { + if (!$profile?.nip05) return undefined const handle = $handlesByNip05.get($profile.nip05) - if (handle?.pubkey === pubkey) { - return handle - } + if (handle?.pubkey !== pubkey) return undefined + + return handle }) +} export const displayNip05 = (nip05: string) => nip05?.startsWith("_@") ? last(nip05.split("@")) : nip05 diff --git a/packages/app/src/inboxRelaySelections.ts b/packages/app/src/inboxRelaySelections.ts deleted file mode 100644 index b66346c..0000000 --- a/packages/app/src/inboxRelaySelections.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {INBOX_RELAYS, asDecryptedEvent, readList} from "@welshman/util" -import {TrustedEvent, PublishedList} from "@welshman/util" -import {deriveItemsByKey, deriveItems, makeForceLoadItem, makeLoadItem, makeDeriveItem, getter} from "@welshman/store" -import {repository} from "./core.js" -import {makeOutboxLoader} from "./relaySelections.js" - -export const inboxRelaySelectionsByPubkey = deriveItemsByKey({ - repository, - eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)), - filters: [{kinds: [INBOX_RELAYS]}], - getKey: inboxRelaySelections => inboxRelaySelections.event.pubkey, -}) - -export const inboxRelaySelections = deriveItems(inboxRelaySelectionsByPubkey) - -export const getInboxRelaySelectionsByPubkey = getter(inboxRelaySelectionsByPubkey) - -export const getInboxRelaySelections = (pubkey: string) => getInboxRelaySelectionsByPubkey().get(pubkey) - -export const forceLoadInboxRelaySelections = makeForceLoadItem(makeOutboxLoader(INBOX_RELAYS), getInboxRelaySelections) - -export const loadInboxRelaySelections = makeLoadItem(makeOutboxLoader(INBOX_RELAYS), getInboxRelaySelections) - -export const deriveInboxRelaySelections = makeDeriveItem(inboxRelaySelectionsByPubkey, loadInboxRelaySelections) diff --git a/packages/app/src/index.ts b/packages/app/src/index.ts index 2af885a..88e0ef7 100644 --- a/packages/app/src/index.ts +++ b/packages/app/src/index.ts @@ -11,8 +11,8 @@ export * from "./profiles.js" export * from "./pins.js" export * from "./relays.js" export * from "./relayStats.js" -export * from "./relaySelections.js" -export * from "./inboxRelaySelections.js" +export * from "./relayLists.js" +export * from "./messagingRelayLists.js" export * from "./search.js" export * from "./session.js" export * from "./sync.js" @@ -37,10 +37,10 @@ import {routerContext} from "@welshman/router" import {Pool, SocketEvent, isRelayEvent, netContext} from "@welshman/net" import {pubkey, unwrapAndStore} from "./session.js" import {repository, tracker} from "./core.js" -import {relays, loadRelay} from "./relays.js" +import {getRelays, loadRelay} from "./relays.js" import {trackRelayStats, getRelayQuality} from "./relayStats.js" -import {relaySelectionsByPubkey} from "./relaySelections.js" -import {inboxRelaySelectionsByPubkey} from "./inboxRelaySelections.js" +import {deriveRelayList, getRelayList} from "./relayLists.js" +import {deriveMessagingRelayList, getMessagingRelayList} from "./messagingRelayLists.js" // Sync relays with our database @@ -73,7 +73,7 @@ Pool.get().subscribe(socket => { const _relayGetter = (fn?: (relay: RelayProfile) => any) => throttleWithValue(200, () => { - let _relays = relays.get() + let _relays = getRelays() if (fn) { _relays = _relays.filter(fn) @@ -85,14 +85,14 @@ const _relayGetter = (fn?: (relay: RelayProfile) => any) => }) export const getPubkeyRelays = (pubkey: string, mode?: RelayMode) => - mode === RelayMode.Inbox - ? getRelaysFromList(inboxRelaySelectionsByPubkey.get().get(pubkey)) - : getRelaysFromList(relaySelectionsByPubkey.get().get(pubkey), mode) + mode === RelayMode.Messages + ? getRelaysFromList(getMessagingRelayList(pubkey)) + : getRelaysFromList(getRelayList(pubkey), mode) export const derivePubkeyRelays = (pubkey: string, mode?: RelayMode) => - mode === RelayMode.Inbox - ? derived(inboxRelaySelectionsByPubkey, $m => getRelaysFromList($m.get(pubkey))) - : derived(relaySelectionsByPubkey, $m => getRelaysFromList($m.get(pubkey), mode)) + mode === RelayMode.Messages + ? derived(deriveMessagingRelayList(pubkey), list => getRelaysFromList(list)) + : derived(deriveRelayList(pubkey), list => getRelaysFromList(list, mode)) routerContext.getUserPubkey = () => pubkey.get() routerContext.getPubkeyRelays = getPubkeyRelays diff --git a/packages/app/src/messagingRelayLists.ts b/packages/app/src/messagingRelayLists.ts new file mode 100644 index 0000000..f6f682d --- /dev/null +++ b/packages/app/src/messagingRelayLists.ts @@ -0,0 +1,24 @@ +import {MESSAGING_RELAYS, asDecryptedEvent, readList} from "@welshman/util" +import {TrustedEvent, PublishedList} from "@welshman/util" +import {deriveItemsByKey, deriveItems, makeForceLoadItem, makeLoadItem, makeDeriveItem, getter} from "@welshman/store" +import {repository} from "./core.js" +import {makeOutboxLoader} from "./relayLists.js" + +export const messagingRelayListsByPubkey = deriveItemsByKey({ + repository, + eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)), + filters: [{kinds: [MESSAGING_RELAYS]}], + getKey: messagingRelayLists => messagingRelayLists.event.pubkey, +}) + +export const messagingRelayLists = deriveItems(messagingRelayListsByPubkey) + +export const getMessagingRelayListsByPubkey = getter(messagingRelayListsByPubkey) + +export const getMessagingRelayList = (pubkey: string) => getMessagingRelayListsByPubkey().get(pubkey) + +export const forceLoadMessagingRelayList = makeForceLoadItem(makeOutboxLoader(MESSAGING_RELAYS), getMessagingRelayList) + +export const loadMessagingRelayList = makeLoadItem(makeOutboxLoader(MESSAGING_RELAYS), getMessagingRelayList) + +export const deriveMessagingRelayList = makeDeriveItem(messagingRelayListsByPubkey, loadMessagingRelayList) diff --git a/packages/app/src/mutes.ts b/packages/app/src/mutes.ts index 9bf67b3..6355e94 100644 --- a/packages/app/src/mutes.ts +++ b/packages/app/src/mutes.ts @@ -3,9 +3,9 @@ import {TrustedEvent, PublishedList} from "@welshman/util" import {deriveItemsByKey, deriveItems, makeForceLoadItem, makeLoadItem, makeDeriveItem, getter} from "@welshman/store" import {repository} from "./core.js" import {ensurePlaintext} from "./plaintext.js" -import {makeOutboxLoader} from "./relaySelections.js" +import {makeOutboxLoader} from "./relayLists.js" -export const mutesByPubkey = deriveItemsByKey({ +export const muteListsByPubkey = deriveItemsByKey({ repository, eventToItem: async (event: TrustedEvent) => readList( @@ -17,14 +17,16 @@ export const mutesByPubkey = deriveItemsByKey({ getKey: mute => mute.event.pubkey, }) -export const mutes = deriveItems(mutesByPubkey) +export const muteLists = deriveItems(muteListsByPubkey) -export const getMutesByPubkey = getter(mutesByPubkey) +export const getMuteListsByPubkey = getter(muteListsByPubkey) -export const getMutes = (pubkey: string) => getMutesByPubkey().get(pubkey) +export const getMuteLists = getter(muteLists) -export const forceLoadMutes = makeForceLoadItem(makeOutboxLoader(MUTES), getMutes) +export const getMuteList = (pubkey: string) => getMuteListsByPubkey().get(pubkey) -export const loadMutes = makeLoadItem(makeOutboxLoader(MUTES), getMutes) +export const forceLoadMuteList = makeForceLoadItem(makeOutboxLoader(MUTES), getMuteList) -export const deriveMutes = makeDeriveItem(mutesByPubkey, loadMutes) +export const loadMuteList = makeLoadItem(makeOutboxLoader(MUTES), getMuteList) + +export const deriveMuteList = makeDeriveItem(muteListsByPubkey, loadMuteList) diff --git a/packages/app/src/pins.ts b/packages/app/src/pins.ts index 387e588..cd9bc25 100644 --- a/packages/app/src/pins.ts +++ b/packages/app/src/pins.ts @@ -2,23 +2,25 @@ import {PINS, asDecryptedEvent, readList} from "@welshman/util" import {TrustedEvent, PublishedList} from "@welshman/util" import {deriveItemsByKey, deriveItems, makeForceLoadItem, makeLoadItem, makeDeriveItem, getter} from "@welshman/store" import {repository} from "./core.js" -import {makeOutboxLoader} from "./relaySelections.js" +import {makeOutboxLoader} from "./relayLists.js" -export const pinsByPubkey = deriveItemsByKey({ +export const pinListsByPubkey = deriveItemsByKey({ repository, eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)), filters: [{kinds: [PINS]}], getKey: pins => pins.event.pubkey, }) -export const pins = deriveItems(pinsByPubkey) +export const pinLists = deriveItems(pinListsByPubkey) -export const getPinsByPubkey = getter(pinsByPubkey) +export const getPinListsByPubkey = getter(pinListsByPubkey) -export const getPins = (pubkey: string) => getPinsByPubkey().get(pubkey) +export const getPinLists = getter(pinLists) -export const forceLoadPins = makeForceLoadItem(makeOutboxLoader(PINS), getPins) +export const getPinList = (pubkey: string) => getPinListsByPubkey().get(pubkey) -export const loadPins = makeLoadItem(makeOutboxLoader(PINS), getPins) +export const forceLoadPinList = makeForceLoadItem(makeOutboxLoader(PINS), getPinList) -export const derivePins = makeDeriveItem(pinsByPubkey, loadPins) +export const loadPinList = makeLoadItem(makeOutboxLoader(PINS), getPinList) + +export const derivePinList = makeDeriveItem(pinListsByPubkey, loadPinList) diff --git a/packages/app/src/profiles.ts b/packages/app/src/profiles.ts index 83b6505..c812ad7 100644 --- a/packages/app/src/profiles.ts +++ b/packages/app/src/profiles.ts @@ -2,7 +2,7 @@ import {derived, readable} from "svelte/store" import {readProfile, displayProfile, displayPubkey, PROFILE} from "@welshman/util" import {deriveItemsByKey, deriveItems, makeForceLoadItem, makeLoadItem, makeDeriveItem, getter} from "@welshman/store" import {repository} from "./core.js" -import {makeOutboxLoaderWithIndexers} from "./relaySelections.js" +import {makeOutboxLoaderWithIndexers} from "./relayLists.js" export const profilesByPubkey = deriveItemsByKey({ repository, @@ -15,6 +15,8 @@ export const profiles = deriveItems(profilesByPubkey) export const getProfilesByPubkey = getter(profilesByPubkey) +export const getProfiles = getter(profiles) + export const getProfile = (pubkey: string) => getProfilesByPubkey().get(pubkey) export const forceLoadProfile = makeForceLoadItem(makeOutboxLoaderWithIndexers(PROFILE), getProfile) diff --git a/packages/app/src/relaySelections.ts b/packages/app/src/relayLists.ts similarity index 69% rename from packages/app/src/relaySelections.ts rename to packages/app/src/relayLists.ts index a65b600..d7589e8 100644 --- a/packages/app/src/relaySelections.ts +++ b/packages/app/src/relayLists.ts @@ -41,21 +41,23 @@ export const makeOutboxLoaderWithIndexers = ]) } -export const relaySelectionsByPubkey = deriveItemsByKey({ +export const relayListsByPubkey = deriveItemsByKey({ repository, eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)), filters: [{kinds: [RELAYS]}], - getKey: relaySelections => relaySelections.event.pubkey, + getKey: relayList => relayList.event.pubkey, }) -export const relaySelections = deriveItems(relaySelectionsByPubkey) +export const relayLists = deriveItems(relayListsByPubkey) -export const getRelaySelectionsByPubkey = getter(relaySelectionsByPubkey) +export const getRelayListsByPubkey = getter(relayListsByPubkey) -export const getRelaySelections = (pubkey: string) => getRelaySelectionsByPubkey().get(pubkey) +export const getRelayLists = getter(relayLists) -export const forceLoadRelaySelections = makeForceLoadItem(makeOutboxLoaderWithIndexers(RELAYS), getRelaySelections) +export const getRelayList = (pubkey: string) => getRelayListsByPubkey().get(pubkey) -export const loadRelaySelections = makeLoadItem(makeOutboxLoaderWithIndexers(RELAYS), getRelaySelections) +export const forceLoadRelayList = makeForceLoadItem(makeOutboxLoaderWithIndexers(RELAYS), getRelayList) -export const deriveRelaySelections = makeDeriveItem(relaySelectionsByPubkey, loadRelaySelections) +export const loadRelayList = makeLoadItem(makeOutboxLoaderWithIndexers(RELAYS), getRelayList) + +export const deriveRelayList = makeDeriveItem(relayListsByPubkey, loadRelayList) diff --git a/packages/app/src/relays.ts b/packages/app/src/relays.ts index 229c51a..dd186f3 100644 --- a/packages/app/src/relays.ts +++ b/packages/app/src/relays.ts @@ -8,99 +8,73 @@ import { fetchJson, postJson, Maybe, + noop, } from "@welshman/lib" import {withGetter} from "@welshman/store" import {RelayProfile} from "@welshman/util" import {normalizeRelayUrl, displayRelayUrl, displayRelayProfile, isRelayUrl} from "@welshman/util" -import {collection} from "@welshman/store" +import {getter, deriveItems, makeForceLoadItem, makeLoadItem, makeDeriveItem} from "@welshman/store" import {appContext} from "./context.js" -export const relays = withGetter(writable([])) +export const relaysByUrl = writable(new Map()) -export const fetchRelayProfileDirectly = async (url: string): Promise> => { +export const relays = deriveItems(relaysByUrl) + +export const getRelaysByUrl = getter(relaysByUrl) + +export const getRelays = getter(relays) + +export const getRelay = (url: string) => getRelaysByUrl().get(url) + +export const fetchRelayDirectly = async (url: string): Promise> => { try { - return fetchJson(url.replace(/^ws/, "http"), { + const json = fetchJson(url.replace(/^ws/, "http"), { headers: { Accept: "application/nostr+json", }, }) + + if (json) { + return {...json, url} + } } catch (e) { // pass } } -export const fetchRelayProfilesDirectly = async ( - urls: string[], -): Promise> => - indexBy( - prop("url"), - removeUndefined( - await Promise.all( - urls.map(async url => { - const profile = await fetchRelayProfileDirectly(url) - - if (profile) { - return {...profile, url} - } - }), - ), - ), - ) - -export const fetchRelayProfilesUsingProxy = async ( - proxy: string, - urls: string[], -): Promise> => { - const profilesByUrl = new Map() - const res: any = await postJson(`${proxy}/relay/info`, {urls}) - - for (const {url, info} of res?.data || []) { - profilesByUrl.set(url, info) +export const fetchRelayUsingProxy = batcher(800, async (urls: string[]) => { + // Handle a race condition edge case where dufflepud url changes under us + if (!appContext.dufflepudUrl) { + return urls.map(noop) } - return profilesByUrl -} + const res: any = await postJson(`${appContext.dufflepudUrl}/relay/info`, {urls}) + const relaysByUrl = new Map() -export const fetchRelayProfiles = (urls: string[]) => - appContext.dufflepudUrl - ? fetchRelayProfilesUsingProxy(appContext.dufflepudUrl, urls) - : fetchRelayProfilesDirectly(urls) + for (const {url, info} of res?.data || []) { + relaysByUrl.set(url, info) + } -export const { - indexStore: relaysByUrl, - deriveItem: deriveRelay, - loadItem: loadRelay, - onItem: onRelay, -} = collection({ - name: "relays", - store: relays, - getKey: (relay: RelayProfile) => relay.url, - load: batcher(800, async (rawUrls: string[]) => { - const urls = rawUrls.map(normalizeRelayUrl) - const fresh = await fetchRelayProfiles(uniq(urls)) - const stale = relaysByUrl.get() + return urls.map(url => { + const info = relaysByUrl.get(url) - for (const url of urls) { - const profile = fresh.get(url) - - if (!url || !isRelayUrl(url)) { - console.warn(`Attempted to load invalid relay url: ${url}`) - continue - } - - if (profile) { - stale.set(url, {...profile, url}) - } + if (info) { + return {...info, url} } - - relays.set(Array.from(stale.values())) - - return urls - }), + }) }) +export const fetchRelay = (url: string) => + appContext.dufflepudUrl ? fetchRelayUsingProxy(url) : fetchRelayDirectly(url) + +export const forceLoadRelay = makeForceLoadItem(fetchRelay, getRelay) + +export const loadRelay = makeLoadItem(fetchRelay, getRelay) + +export const deriveRelay = makeDeriveItem(relaysByUrl, loadRelay) + export const displayRelayByPubkey = (url: string) => - displayRelayProfile(relaysByUrl.get().get(url), displayRelayUrl(url)) + displayRelayProfile(getRelay(url), displayRelayUrl(url)) export const deriveRelayDisplay = (url: string) => derived(deriveRelay(url), $relay => displayRelayProfile($relay, displayRelayUrl(url))) diff --git a/packages/app/src/sync.ts b/packages/app/src/sync.ts index 381502a..a6b6e2c 100644 --- a/packages/app/src/sync.ts +++ b/packages/app/src/sync.ts @@ -2,13 +2,13 @@ import type {Filter} from "@welshman/util" import {isSignedEvent, SignedEvent} from "@welshman/util" import {push as basePush, pull as basePull, publishOne, requestOne} from "@welshman/net" import {repository} from "./core.js" -import {relaysByUrl} from "./relays.js" +import {getRelay} from "./relays.js" const query = (filters: Filter[]) => repository.query(filters, {shouldSort: filters.every(f => f.limit === undefined)}) export const hasNegentropy = (url: string) => { - const relay = relaysByUrl.get().get(url) + const relay = getRelay(url) if (relay?.negentropy) return true if (relay?.supported_nips?.includes?.(77)) return true diff --git a/packages/app/src/topics.ts b/packages/app/src/topics.ts index 8926c32..ea72841 100644 --- a/packages/app/src/topics.ts +++ b/packages/app/src/topics.ts @@ -1,5 +1,7 @@ -import {inc, throttle} from "@welshman/lib" -import {custom} from "@welshman/store" +import {readable} from 'svelte/store' +import {on, call} from "@welshman/lib" +import {deriveItems} from "@welshman/store" +import {getTopicTagValues} from "@welshman/util" import {repository} from "./core.js" export type Topic = { @@ -7,25 +9,41 @@ export type Topic = { count: number } -export const topics = custom(setter => { - const getTopics = () => { - const topics = new Map() - for (const tagString of repository.eventsByTag.keys()) { - if (tagString.startsWith("t:")) { - const topic = tagString.slice(2).toLowerCase() +export const topicsByName = call(() => { + const topicsByName = new Map() - topics.set(topic, inc(topics.get(topic))) - } + const addTopic = (name: string) => { + const topic = topicsByName.get(name) + + if (topic) { + topic.count++ + } else { + topicsByName.set(name, {name, count: 0}) } - - return Array.from(topics.entries()).map(([name, count]) => ({name, count})) } - setter(getTopics()) + for (const tagString of repository.eventsByTag.keys()) { + if (tagString.startsWith("t:")) { + addTopic(tagString.slice(2).toLowerCase()) + } + } - const onUpdate = throttle(3000, () => setter(getTopics())) + return readable>(topicsByName, set => { + return on(repository, 'update', ({added}) => { + let dirty = false - repository.on("update", onUpdate) + for (const event of added) { + for (const name of getTopicTagValues(event.tags)) { + addTopic(name) + dirty = true + } + } - return () => repository.off("update", onUpdate) + if (dirty) { + set(topicsByName) + } + }) + }) }) + +export const topics = deriveItems(topicsByName) diff --git a/packages/app/src/user.ts b/packages/app/src/user.ts index cdf9b67..8fb9d6f 100644 --- a/packages/app/src/user.ts +++ b/packages/app/src/user.ts @@ -1,13 +1,13 @@ import {derived, Readable} from "svelte/store" import {withGetter, memoized} from "@welshman/store" import {pubkey} from "./session.js" -import {profilesByPubkey, loadProfile} from "./profiles.js" -import {followsByPubkey, loadFollows} from "./follows.js" -import {loadPins, pinsByPubkey} from "./pins.js" -import {mutesByPubkey, loadMutes} from "./mutes.js" -import {blossomServersByPubkey, loadBlossomServers} from "./blossom.js" -import {relaySelectionsByPubkey, loadRelaySelections} from "./relaySelections.js" -import {inboxRelaySelectionsByPubkey, loadInboxRelaySelections} from "./inboxRelaySelections.js" +import {profilesByPubkey, forceLoadProfile, loadProfile} from "./profiles.js" +import {followListsByPubkey, forceLoadFollowList, loadFollowList} from "./follows.js" +import {pinListsByPubkey, forceLoadPinList, loadPinList} from "./pins.js" +import {muteListsByPubkey, forceLoadMuteList, loadMuteList} from "./mutes.js" +import {blossomServerListsByPubkey, forceLoadBlossomServerList, loadBlossomServerList} from "./blossom.js" +import {relayListsByPubkey, forceLoadRelayList, loadRelayList} from "./relayLists.js" +import {messagingRelayListsByPubkey, forceLoadMessagingRelayList, loadMessagingRelayList} from "./messagingRelayLists.js" import {wotGraph} from "./wot.js" export type UserDataLoader = (pubkey: string, relays?: string[], force?: boolean) => unknown @@ -45,49 +45,56 @@ export const userProfile = makeUserData({ loadItem: loadProfile, }) +export const forceLoadUserProfile = makeUserLoader(forceLoadProfile) export const loadUserProfile = makeUserLoader(loadProfile) -export const userFollows = makeUserData({ - mapStore: followsByPubkey, - loadItem: loadFollows, +export const userFollowList = makeUserData({ + mapStore: followListsByPubkey, + loadItem: loadFollowList, }) -export const loadUserFollows = makeUserLoader(loadFollows) +export const forceLoadUserFollowList = makeUserLoader(forceLoadFollowList) +export const loadUserFollowList = makeUserLoader(loadFollowList) -export const userMutes = makeUserData({ - mapStore: mutesByPubkey, - loadItem: loadMutes, +export const userMuteList = makeUserData({ + mapStore: muteListsByPubkey, + loadItem: loadMuteList, }) -export const loadUserMutes = makeUserLoader(loadMutes) +export const forceLoadUserMuteList = makeUserLoader(forceLoadMuteList) +export const loadUserMuteList = makeUserLoader(loadMuteList) -export const userPins = makeUserData({ - mapStore: pinsByPubkey, - loadItem: loadPins, +export const userPinList = makeUserData({ + mapStore: pinListsByPubkey, + loadItem: loadPinList, }) -export const loadUserPins = makeUserLoader(loadPins) +export const forceLoadUserPinList = makeUserLoader(forceLoadPinList) +export const loadUserPinList = makeUserLoader(loadPinList) -export const userRelaySelections = makeUserData({ - mapStore: relaySelectionsByPubkey, - loadItem: loadRelaySelections, +export const userRelayList = makeUserData({ + mapStore: relayListsByPubkey, + loadItem: loadRelayList, }) -export const loadUserRelaySelections = makeUserLoader(loadRelaySelections) +export const forceLoadUserRelayList = makeUserLoader(forceLoadRelayList) +export const loadUserRelayList = makeUserLoader(loadRelayList) -export const userInboxRelaySelections = makeUserData({ - mapStore: inboxRelaySelectionsByPubkey, - loadItem: loadInboxRelaySelections, +export const userMessagingRelayList = makeUserData({ + mapStore: messagingRelayListsByPubkey, + loadItem: loadMessagingRelayList, }) -export const loadUserInboxRelaySelections = makeUserLoader(loadInboxRelaySelections) +export const forceLoadUserMessagingRelayList = makeUserLoader(forceLoadMessagingRelayList) +export const loadUserMessagingRelayList = makeUserLoader(loadMessagingRelayList) -export const userBlossomServers = makeUserData({ - mapStore: blossomServersByPubkey, - loadItem: loadBlossomServers, +export const userBlossomServerList = makeUserData({ + mapStore: blossomServerListsByPubkey, + loadItem: loadBlossomServerList, }) -export const loadUserBlossomServers = makeUserLoader(loadBlossomServers) +export const forceLoadUserBlossomServerList = makeUserLoader(forceLoadBlossomServerList) +export const loadUserBlossomServerList = makeUserLoader(loadBlossomServerList) export const getUserWotScore = (tpk: string) => wotGraph.get().get(tpk) || 0 diff --git a/packages/app/src/wot.ts b/packages/app/src/wot.ts index b502642..dd4f892 100644 --- a/packages/app/src/wot.ts +++ b/packages/app/src/wot.ts @@ -3,14 +3,14 @@ import {max, throttle, addToMapKey, inc, dec} from "@welshman/lib" import {getListTags, getPubkeyTagValues} from "@welshman/util" import {throttled, withGetter} from "@welshman/store" import {pubkey} from "./session.js" -import {follows, followsByPubkey} from "./follows.js" -import {mutes, mutesByPubkey} from "./mutes.js" +import {followLists, getFollowListsByPubkey, getFollowList} from "./follows.js" +import {muteLists, getMuteList} from "./mutes.js" export const getFollows = (pubkey: string) => - getPubkeyTagValues(getListTags(followsByPubkey.get().get(pubkey))) + getPubkeyTagValues(getListTags(getFollowList(pubkey))) export const getMutes = (pubkey: string) => - getPubkeyTagValues(getListTags(mutesByPubkey.get().get(pubkey))) + getPubkeyTagValues(getListTags(getMuteList(pubkey))) export const getNetwork = (pubkey: string) => { const pubkeys = new Set(getFollows(pubkey)) @@ -28,7 +28,7 @@ export const getNetwork = (pubkey: string) => { } export const followersByPubkey = withGetter( - derived(throttled(1000, follows), lists => { + derived(throttled(1000, followLists), lists => { const $followersByPubkey = new Map>() for (const list of lists) { @@ -42,7 +42,7 @@ export const followersByPubkey = withGetter( ) export const mutersByPubkey = withGetter( - derived(throttled(1000, mutes), lists => { + derived(throttled(1000, muteLists), lists => { const $mutersByPubkey = new Map>() for (const list of lists) { @@ -73,7 +73,7 @@ export const maxWot = withGetter(derived(wotGraph, $g => max(Array.from($g.value const buildGraph = throttle(1000, () => { const $pubkey = pubkey.get() const $graph = new Map() - const $follows = $pubkey ? getFollows($pubkey) : followsByPubkey.get().keys() + const $follows = $pubkey ? getFollows($pubkey) : getFollowListsByPubkey().keys() for (const follow of $follows) { for (const pubkey of getFollows(follow)) { @@ -89,8 +89,8 @@ const buildGraph = throttle(1000, () => { }) pubkey.subscribe(buildGraph) -follows.subscribe(buildGraph) -mutes.subscribe(buildGraph) +followLists.subscribe(buildGraph) +muteLists.subscribe(buildGraph) export const getWotScore = (pubkey: string, target: string) => { const follows = pubkey ? getFollowsWhoFollow(pubkey, target) : getFollowers(target) diff --git a/packages/app/src/zappers.ts b/packages/app/src/zappers.ts index 3f5a5b4..430e3bb 100644 --- a/packages/app/src/zappers.ts +++ b/packages/app/src/zappers.ts @@ -10,15 +10,21 @@ import { batcher, postJson, } from "@welshman/lib" -import {collection} from "@welshman/store" +import {getter, deriveItems, makeForceLoadItem, makeLoadItem, makeDeriveItem} from "@welshman/store" import {deriveProfile, loadProfile} from "./profiles.js" import {appContext} from "./context.js" -export const zappers = writable([]) +export const zappersByLnurl = writable(new Map()) -export const fetchZappers = async (lnurls: string[]) => { +export const zappers = deriveItems(zappersByLnurl) + +export const getZappersByLnurl = getter(zappersByLnurl) + +export const getZapper = (lnurl: string) => getZappersByLnurl().get(lnurl) + +export const fetchZapper = batcher(800, async (lnurls: string[]) => { const base = appContext.dufflepudUrl - const zappersByLnurl = new Map() + const result = new Map() // Use dufflepud if we it's set up to protect user privacy, otherwise fetch directly if (base) { @@ -30,7 +36,7 @@ export const fetchZappers = async (lnurls: string[]) => { ) for (const {lnurl, info} of res?.data || []) { - tryCatch(() => zappersByLnurl.set(hexToBech32("lnurl", lnurl), info)) + tryCatch(() => result.set(hexToBech32("lnurl", lnurl), info)) } } } else { @@ -45,61 +51,39 @@ export const fetchZappers = async (lnurls: string[]) => { for (const {lnurl, info} of results) { if (info) { - zappersByLnurl.set(lnurl, info) + result.set(lnurl, info) } } } - return zappersByLnurl -} + return lnurls.map(lnurl => { + const info = result.get(lnurl) -export const { - indexStore: zappersByLnurl, - deriveItem: deriveZapper, - loadItem: loadZapper, - onItem: onZapper, -} = collection({ - name: "zappers", - store: zappers, - getKey: (zapper: Zapper) => zapper.lnurl, - load: batcher(800, async (lnurls: string[]) => { - const fresh = await fetchZappers(uniq(lnurls)) - const stale = zappersByLnurl.get() - - for (const lnurl of lnurls) { - const newZapper = fresh.get(lnurl) - - if (newZapper) { - stale.set(lnurl, {...newZapper, lnurl}) - } + if (info) { + return {...info, lnurl} } - - zappers.set(Array.from(stale.values())) - - return lnurls - }), + }) }) +export const forceLoadZapper = makeForceLoadItem(fetchZapper, getZapper) + +export const loadZapper = makeLoadItem(fetchZapper, getZapper) + +export const deriveZapper = makeDeriveItem(zappersByLnurl, loadZapper) + export const loadZapperForPubkey = async (pubkey: string, relays: string[] = []) => { const $profile = await loadProfile(pubkey, relays) - if (!$profile?.lnurl) { - return undefined - } - - return loadZapper($profile.lnurl) + return $profile?.lnurl ? loadZapper($profile.lnurl) : undefined } -export const deriveZapperForPubkey = (pubkey: string, relays: string[] = []) => - derived([zappersByLnurl, deriveProfile(pubkey, relays)], ([$zappersByLnurl, $profile]) => { - if (!$profile?.lnurl) { - return undefined - } +export const deriveZapperForPubkey = (pubkey: string, relays: string[] = []) => { + loadZapperForPubkey(pubkey, relays) - loadZapper($profile.lnurl) - - return $zappersByLnurl.get($profile.lnurl) + return derived([zappersByLnurl, deriveProfile(pubkey, relays)], ([$zappersByLnurl, $profile]) => { + return $profile?.lnurl ? $zappersByLnurl.get($profile.lnurl) : undefined }) +} export const getLnUrlsForEvent = async (event: TrustedEvent) => { const lnurls = removeUndefined(getTagValues("zap", event.tags).map(getLnUrl)) diff --git a/packages/router/src/index.ts b/packages/router/src/index.ts index 1c6e8c7..b2d3c60 100644 --- a/packages/router/src/index.ts +++ b/packages/router/src/index.ts @@ -21,7 +21,7 @@ import { isShareableRelayUrl, PROFILE, RELAYS, - INBOX_RELAYS, + MESSAGING_RELAYS, FOLLOWS, WRAP, getPubkeyTagValues, @@ -37,7 +37,7 @@ import { } from "@welshman/util" import {Repository} from "@welshman/net" -export const INDEXED_KINDS = [PROFILE, RELAYS, INBOX_RELAYS, FOLLOWS] +export const INDEXED_KINDS = [PROFILE, RELAYS, MESSAGING_RELAYS, FOLLOWS] export type RelaysAndFilters = { relays: string[] @@ -54,7 +54,7 @@ export type RouterOptions = { /** * Retrieves relays for the specified public key and mode. * @param pubkey - The public key to retrieve relays for. - * @param mode - The relay mode (optional). May be "read", "write", or "inbox". + * @param mode - The relay mode (optional). May be "read", "write", or "messaging". * @returns An array of relay URLs as strings. */ getPubkeyRelays?: (pubkey: string, mode?: RelayMode) => string[] @@ -174,20 +174,20 @@ export class Router { FromUser = () => this.FromRelays(this.getRelaysForUser(RelayMode.Write)) - UserInbox = () => this.FromRelays(this.getRelaysForUser(RelayMode.Inbox)) + UserMessages = () => this.FromRelays(this.getRelaysForUser(RelayMode.Messages)) ForPubkey = (pubkey: string) => this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Read)) FromPubkey = (pubkey: string) => this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Write)) - PubkeyInbox = (pubkey: string) => - this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Inbox)) + MessagesForPubkey = (pubkey: string) => + this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Messages)) ForPubkeys = (pubkeys: string[]) => this.merge(pubkeys.map(pubkey => this.ForPubkey(pubkey))) FromPubkeys = (pubkeys: string[]) => this.merge(pubkeys.map(pubkey => this.FromPubkey(pubkey))) - PubkeyInboxes = (pubkeys: string[]) => this.merge(pubkeys.map(pubkey => this.PubkeyInbox(pubkey))) + MessagesForPubkeys = (pubkeys: string[]) => this.merge(pubkeys.map(pubkey => this.MessagesForPubkey(pubkey))) Event = (event: TrustedEvent) => this.FromRelays(this.getRelaysForPubkey(event.pubkey, RelayMode.Write)) @@ -371,7 +371,7 @@ export const getFilterSelectionsForWraps = (filter: Filter) => { return [ { filter: {...filter, kinds: [WRAP]}, - scenario: Router.get().UserInbox(), + scenario: Router.get().UserMessages(), }, ] } diff --git a/packages/store/src/repository.ts b/packages/store/src/repository.ts index 2e14dc0..5d06b78 100644 --- a/packages/store/src/repository.ts +++ b/packages/store/src/repository.ts @@ -1,5 +1,5 @@ import {derived, readable, Readable} from "svelte/store" -import {on, now, indexBy, mapPop, Maybe, call, sortBy, first} from "@welshman/lib" +import {on, now, indexBy, mapPop, Maybe, MaybeAsync, call, sortBy, first} from "@welshman/lib" import {matchFilters, getIdFilters, Filter, TrustedEvent} from "@welshman/util" import {Repository, RepositoryUpdate, Tracker} from "@welshman/net" import {deriveDeduplicated} from "./misc.js" @@ -175,7 +175,7 @@ export const deriveEventsByIdForUrl = ( export type ItemsByKey = Map -export type EventToItem = (event: TrustedEvent) => T +export type EventToItem = (event: TrustedEvent) => MaybeAsync> export type GetItem = (key: string, ...args: any[]) => Maybe diff --git a/packages/util/src/Kinds.ts b/packages/util/src/Kinds.ts index 7f9ee37..5b66395 100644 --- a/packages/util/src/Kinds.ts +++ b/packages/util/src/Kinds.ts @@ -135,7 +135,7 @@ export const ROOMS = 10009 export const FEEDS = 10014 export const TOPICS = 10015 export const EMOJIS = 10030 -export const INBOX_RELAYS = 10050 +export const MESSAGING_RELAYS = 10050 export const BLOSSOM_SERVERS = 10063 export const FILE_SERVERS = 10096 export const RELAY_MEMBERS = 13534 diff --git a/packages/util/src/Relay.ts b/packages/util/src/Relay.ts index d676acd..062a845 100644 --- a/packages/util/src/Relay.ts +++ b/packages/util/src/Relay.ts @@ -5,7 +5,7 @@ import {last, normalizeUrl, stripProtocol} from "@welshman/lib" export enum RelayMode { Read = "read", Write = "write", - Inbox = "inbox", + Messages = "messages", } export type RelayProfile = {