add relay context

This commit is contained in:
hzrd149
2023-04-11 22:37:42 -05:00
parent 66ff04254b
commit dc33622e84
14 changed files with 104 additions and 73 deletions

View File

@@ -14,12 +14,14 @@ import { convertTimestampToDate } from "../helpers/date";
import useSubject from "../hooks/use-subject"; import useSubject from "../hooks/use-subject";
import settings from "../services/settings"; import settings from "../services/settings";
import EventVerificationIcon from "./event-verification-icon"; import EventVerificationIcon from "./event-verification-icon";
import { useReadRelayUrls } from "../hooks/use-client-relays";
const EmbeddedNote = ({ note }: { note: NostrEvent }) => { const EmbeddedNote = ({ note }: { note: NostrEvent }) => {
const account = useCurrentAccount(); const account = useCurrentAccount();
const showSignatureVerification = useSubject(settings.showSignatureVerification); const showSignatureVerification = useSubject(settings.showSignatureVerification);
const contacts = useUserContacts(account.pubkey); const readRelays = useReadRelayUrls();
const contacts = useUserContacts(account.pubkey, readRelays);
const following = contacts?.contacts || []; const following = contacts?.contacts || [];
return ( return (

View File

@@ -35,6 +35,7 @@ import EventVerificationIcon from "../event-verification-icon";
import { ReplyButton } from "./buttons/reply-button"; import { ReplyButton } from "./buttons/reply-button";
import { RepostButton } from "./buttons/repost-button"; import { RepostButton } from "./buttons/repost-button";
import { QuoteRepostButton } from "./buttons/quote-repost-button"; import { QuoteRepostButton } from "./buttons/quote-repost-button";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
export type NoteProps = { export type NoteProps = {
event: NostrEvent; event: NostrEvent;
@@ -47,7 +48,8 @@ export const Note = React.memo(({ event, maxHeight, variant = "outline" }: NoteP
const showReactions = useSubject(settings.showReactions); const showReactions = useSubject(settings.showReactions);
const showSignatureVerification = useSubject(settings.showSignatureVerification); const showSignatureVerification = useSubject(settings.showSignatureVerification);
const contacts = useUserContacts(account.pubkey); const readRelays = useReadRelayUrls();
const contacts = useUserContacts(account.pubkey, readRelays);
const following = contacts?.contacts || []; const following = contacts?.contacts || [];
return ( return (

View File

@@ -1,15 +1,11 @@
import { useMemo } from "react"; import { useMemo } from "react";
import { unique } from "../helpers/array";
import userContactsService from "../services/user-contacts"; import userContactsService from "../services/user-contacts";
import { useReadRelayUrls } from "./use-client-relays";
import useSubject from "./use-subject"; import useSubject from "./use-subject";
export function useUserContacts(pubkey: string, additionalRelays: string[] = [], alwaysRequest = false) { export function useUserContacts(pubkey: string, relays: string[], alwaysRequest = false) {
const readRelays = useReadRelayUrls(additionalRelays);
const observable = useMemo( const observable = useMemo(
() => userContactsService.requestContacts(pubkey, readRelays, alwaysRequest), () => userContactsService.requestContacts(pubkey, relays, alwaysRequest),
[pubkey, readRelays, alwaysRequest] [pubkey, relays.join("|"), alwaysRequest]
); );
const contacts = useSubject(observable); const contacts = useSubject(observable);

View File

@@ -2,7 +2,7 @@ import { useMemo } from "react";
import userFollowersService from "../services/user-followers"; import userFollowersService from "../services/user-followers";
import useSubject from "./use-subject"; import useSubject from "./use-subject";
export function useUserFollowers(pubkey: string, relays: string[] = [], alwaysRequest = false) { export function useUserFollowers(pubkey: string, relays: string[], alwaysRequest = false) {
const subject = useMemo( const subject = useMemo(
() => userFollowersService.requestFollowers(pubkey, relays, alwaysRequest), () => userFollowersService.requestFollowers(pubkey, relays, alwaysRequest),
[pubkey, alwaysRequest] [pubkey, alwaysRequest]

View File

@@ -0,0 +1,24 @@
import React, { useContext } from "react";
import { unique } from "../helpers/array";
import { safeRelayUrl } from "../helpers/url";
export const RelayContext = React.createContext<string[]>([]);
export function useAdditionalRelayContext() {
return useContext(RelayContext) ?? [];
}
export function AdditionalRelayProvider({
relays,
children,
extend = true,
}: {
relays: string[];
children: React.ReactNode;
extend?: boolean;
}) {
const parentRelays = useAdditionalRelayContext();
const safeUrls = (extend ? [...parentRelays, ...relays] : relays).map(safeRelayUrl).filter(Boolean) as string[];
return <RelayContext.Provider value={unique(safeUrls)}>{children}</RelayContext.Provider>;
}

View File

@@ -24,10 +24,10 @@ function mergeNext(subject: Subject<string[] | null>, next: string[]) {
subject.next(arr); subject.next(arr);
} }
function requestFollowers(pubkey: string, additionalRelays: string[] = [], alwaysRequest = false) { function requestFollowers(pubkey: string, relays: string[], alwaysRequest = false) {
let subject = subjects.getSubject(pubkey); let subject = subjects.getSubject(pubkey);
if (additionalRelays.length) subjects.addRelays(pubkey, additionalRelays); if (relays.length) subjects.addRelays(pubkey, relays);
db.getAllKeysFromIndex("userFollows", "follows", pubkey).then((cached) => { db.getAllKeysFromIndex("userFollows", "follows", pubkey).then((cached) => {
mergeNext(subject, cached); mergeNext(subject, cached);
@@ -50,9 +50,6 @@ function flushRequests() {
if (pubkeys.size === 0) return; if (pubkeys.size === 0) return;
const clientRelays = clientRelaysService.getReadUrls();
for (const url of clientRelays) relayUrls.add(url);
const query: NostrQuery = { kinds: [3], "#p": Array.from(pubkeys) }; const query: NostrQuery = { kinds: [3], "#p": Array.from(pubkeys) };
subscription.setRelays(Array.from(relayUrls)); subscription.setRelays(Array.from(relayUrls));

View File

@@ -1,5 +1,4 @@
import Subject from "../classes/subject"; import Subject from "../classes/subject";
import { normalizeRelayConfigs } from "../helpers/relay";
import userContactsService from "./user-contacts"; import userContactsService from "./user-contacts";
import userRelaysService, { UserRelays } from "./user-relays"; import userRelaysService, { UserRelays } from "./user-relays";

View File

@@ -14,9 +14,9 @@ import RepostNote from "../../components/repost-note";
export default function FollowingTab() { export default function FollowingTab() {
const account = useCurrentAccount(); const account = useCurrentAccount();
const relays = useReadRelayUrls(); const readRelays = useReadRelayUrls();
const { openModal } = useContext(PostModalContext); const { openModal } = useContext(PostModalContext);
const contacts = useUserContacts(account.pubkey); const contacts = useUserContacts(account.pubkey, readRelays);
const [search, setSearch] = useSearchParams(); const [search, setSearch] = useSearchParams();
const showReplies = search.has("replies"); const showReplies = search.has("replies");
const onToggle = () => { const onToggle = () => {
@@ -26,7 +26,7 @@ export default function FollowingTab() {
const following = contacts?.contacts || []; const following = contacts?.contacts || [];
const { events, loading, loadMore } = useTimelineLoader( const { events, loading, loadMore } = useTimelineLoader(
`${account.pubkey}-following-posts`, `${account.pubkey}-following-posts`,
relays, readRelays,
{ authors: following, kinds: [1, 6], since: moment().subtract(2, "hour").unix() }, { authors: following, kinds: [1, 6], since: moment().subtract(2, "hour").unix() },
{ pageSize: moment.duration(2, "hour").asSeconds(), enabled: following.length > 0 } { pageSize: moment.duration(2, "hour").asSeconds(), enabled: following.length > 0 }
); );

View File

@@ -8,7 +8,7 @@ import { Bech32Prefix, normalizeToBech32 } from "../../../helpers/nip19";
import { UserDnsIdentityIcon } from "../../../components/user-dns-identity"; import { UserDnsIdentityIcon } from "../../../components/user-dns-identity";
export const UserCard = ({ pubkey, relay }: { pubkey: string; relay?: string }) => { export const UserCard = ({ pubkey, relay }: { pubkey: string; relay?: string }) => {
const metadata = useUserMetadata(pubkey); const metadata = useUserMetadata(pubkey, relay ? [relay] : []);
return ( return (
<Box borderWidth="1px" borderRadius="lg" pl="3" pr="3" pt="2" pb="2" overflow="hidden"> <Box borderWidth="1px" borderRadius="lg" pl="3" pr="3" pt="2" pb="2" overflow="hidden">

View File

@@ -5,11 +5,15 @@ import { useUserFollowers } from "../../hooks/use-user-followers";
import { useOutletContext } from "react-router-dom"; import { useOutletContext } from "react-router-dom";
import { usePaginatedList } from "../../hooks/use-paginated-list"; import { usePaginatedList } from "../../hooks/use-paginated-list";
import { PaginationControls } from "../../components/pagination-controls"; import { PaginationControls } from "../../components/pagination-controls";
import { useAdditionalRelayContext } from "../../providers/additional-relay-context";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
const UserFollowersTab = () => { const UserFollowersTab = () => {
const { pubkey } = useOutletContext() as { pubkey: string }; const { pubkey } = useOutletContext() as { pubkey: string };
const { isOpen, onToggle } = useDisclosure(); const { isOpen, onToggle } = useDisclosure();
const followers = useUserFollowers(pubkey, [], isOpen); const contextRelays = useAdditionalRelayContext();
const relays = useReadRelayUrls(contextRelays);
const followers = useUserFollowers(pubkey, relays, isOpen);
const pagination = usePaginatedList(followers ?? [], { pageSize: 3 * 10 }); const pagination = usePaginatedList(followers ?? [], { pageSize: 3 * 10 });

View File

@@ -6,10 +6,12 @@ import { useUserContacts } from "../../hooks/use-user-contacts";
import { useOutletContext } from "react-router-dom"; import { useOutletContext } from "react-router-dom";
import { usePaginatedList } from "../../hooks/use-paginated-list"; import { usePaginatedList } from "../../hooks/use-paginated-list";
import { PaginationControls } from "../../components/pagination-controls"; import { PaginationControls } from "../../components/pagination-controls";
import { useAdditionalRelayContext } from "../../providers/additional-relay-context";
const UserFollowingTab = () => { const UserFollowingTab = () => {
const { pubkey } = useOutletContext() as { pubkey: string }; const { pubkey } = useOutletContext() as { pubkey: string };
const contacts = useUserContacts(pubkey, [], true); const contextRelays = useAdditionalRelayContext();
const contacts = useUserContacts(pubkey, contextRelays, true);
const pagination = usePaginatedList(contacts?.contacts ?? [], { pageSize: 3 * 10 }); const pagination = usePaginatedList(contacts?.contacts ?? [], { pageSize: 3 * 10 });

View File

@@ -1,4 +1,4 @@
import { Flex, Spinner, Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react"; import { Flex, Image, Spinner, Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
import { Outlet, useLoaderData, useMatches, useNavigate } from "react-router-dom"; import { Outlet, useLoaderData, useMatches, useNavigate } from "react-router-dom";
import { useUserMetadata } from "../../hooks/use-user-metadata"; import { useUserMetadata } from "../../hooks/use-user-metadata";
import { getUserDisplayName } from "../../helpers/user-metadata"; import { getUserDisplayName } from "../../helpers/user-metadata";
@@ -7,6 +7,11 @@ import { Bech32Prefix, normalizeToBech32 } from "../../helpers/nip19";
import { useAppTitle } from "../../hooks/use-app-title"; import { useAppTitle } from "../../hooks/use-app-title";
import Header from "./components/header"; import Header from "./components/header";
import { Suspense } from "react"; import { Suspense } from "react";
import useFallbackUserRelays from "../../hooks/use-fallback-user-relays";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
import relayScoreboardService from "../../services/relay-scoreboard";
import { RelayMode } from "../../classes/relay";
import { AdditionalRelayProvider } from "../../providers/additional-relay-context";
const tabs = [ const tabs = [
{ label: "Notes", path: "notes" }, { label: "Notes", path: "notes" },
@@ -17,10 +22,22 @@ const tabs = [
{ label: "Reports", path: "reports" }, { label: "Reports", path: "reports" },
]; ];
function useUserTop4Relays(pubkey: string) {
// get user relays
const userRelays = useFallbackUserRelays(pubkey)
.filter((r) => r.mode & RelayMode.WRITE)
.map((r) => r.url);
// merge the users relays with client relays
const readRelays = useReadRelayUrls();
// find the top 4
return userRelays.length === 0 ? readRelays : relayScoreboardService.getRankedRelays(userRelays).slice(0, 4);
}
const UserView = () => { const UserView = () => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const navigate = useNavigate(); const navigate = useNavigate();
const { pubkey } = useLoaderData() as { pubkey: string }; const { pubkey } = useLoaderData() as { pubkey: string };
const userTopRelays = useUserTop4Relays(pubkey);
const matches = useMatches(); const matches = useMatches();
const lastMatch = matches[matches.length - 1]; const lastMatch = matches[matches.length - 1];
@@ -33,35 +50,37 @@ const UserView = () => {
useAppTitle(getUserDisplayName(metadata, npub ?? pubkey)); useAppTitle(getUserDisplayName(metadata, npub ?? pubkey));
return ( return (
<Flex direction="column" alignItems="stretch" gap="2" overflow={isMobile ? "auto" : "hidden"} height="100%"> <AdditionalRelayProvider relays={userTopRelays}>
{/* {metadata?.banner && <Image src={metadata.banner} />} */} <Flex direction="column" alignItems="stretch" gap="2" overflow={isMobile ? "auto" : "hidden"} height="100%">
<Header pubkey={pubkey} /> {/* {metadata?.banner && <Image src={metadata.banner} mb={-120} />} */}
<Tabs <Header pubkey={pubkey} />
display="flex" <Tabs
flexDirection="column" display="flex"
flexGrow="1" flexDirection="column"
overflow={isMobile ? undefined : "hidden"} flexGrow="1"
isLazy overflow={isMobile ? undefined : "hidden"}
index={activeTab} isLazy
onChange={(v) => navigate(tabs[v].path)} index={activeTab}
> onChange={(v) => navigate(tabs[v].path)}
<TabList overflowX="auto" overflowY="hidden" flexShrink={0}> >
{tabs.map(({ label }) => ( <TabList overflowX="auto" overflowY="hidden" flexShrink={0}>
<Tab key={label}>{label}</Tab> {tabs.map(({ label }) => (
))} <Tab key={label}>{label}</Tab>
</TabList> ))}
</TabList>
<TabPanels overflow={isMobile ? undefined : "auto"} height="100%"> <TabPanels overflow={isMobile ? undefined : "auto"} height="100%">
{tabs.map(({ label }) => ( {tabs.map(({ label }) => (
<TabPanel key={label} pr={0} pl={0}> <TabPanel key={label} pr={0} pl={0}>
<Suspense fallback={<Spinner />}> <Suspense fallback={<Spinner />}>
<Outlet context={{ pubkey }} /> <Outlet context={{ pubkey }} />
</Suspense> </Suspense>
</TabPanel> </TabPanel>
))} ))}
</TabPanels> </TabPanels>
</Tabs> </Tabs>
</Flex> </Flex>
</AdditionalRelayProvider>
); );
}; };

View File

@@ -18,33 +18,23 @@ import {
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import moment from "moment"; import moment from "moment";
import { useOutletContext } from "react-router-dom"; import { useOutletContext } from "react-router-dom";
import { RelayMode } from "../../classes/relay";
import { RelayIcon } from "../../components/icons"; import { RelayIcon } from "../../components/icons";
import { Note } from "../../components/note"; import { Note } from "../../components/note";
import RepostNote from "../../components/repost-note"; import RepostNote from "../../components/repost-note";
import { isReply, isRepost, truncatedId } from "../../helpers/nostr-event"; import { isReply, isRepost, truncatedId } from "../../helpers/nostr-event";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
import useFallbackUserRelays from "../../hooks/use-fallback-user-relays";
import { useTimelineLoader } from "../../hooks/use-timeline-loader"; import { useTimelineLoader } from "../../hooks/use-timeline-loader";
import relayScoreboardService from "../../services/relay-scoreboard"; import { useAdditionalRelayContext } from "../../providers/additional-relay-context";
const UserNotesTab = () => { const UserNotesTab = () => {
const { pubkey } = useOutletContext() as { pubkey: string }; const { pubkey } = useOutletContext() as { pubkey: string };
// get user relays const contextRelays = useAdditionalRelayContext();
const userRelays = useFallbackUserRelays(pubkey)
.filter((r) => r.mode & RelayMode.WRITE)
.map((r) => r.url);
// merge the users relays with client relays
const readRelays = useReadRelayUrls();
// find the top 4
const relays = userRelays.length === 0 ? readRelays : relayScoreboardService.getRankedRelays(userRelays).slice(0, 4);
const { isOpen: showReplies, onToggle: toggleReplies } = useDisclosure(); const { isOpen: showReplies, onToggle: toggleReplies } = useDisclosure();
const { isOpen: hideReposts, onToggle: toggleReposts } = useDisclosure(); const { isOpen: hideReposts, onToggle: toggleReposts } = useDisclosure();
const { events, loading, loadMore } = useTimelineLoader( const { events, loading, loadMore } = useTimelineLoader(
`${truncatedId(pubkey)}-notes`, `${truncatedId(pubkey)}-notes`,
relays, contextRelays,
{ authors: [pubkey], kinds: [1, 6] }, { authors: [pubkey], kinds: [1, 6] },
{ pageSize: moment.duration(2, "day").asSeconds(), startLimit: 20 } { pageSize: moment.duration(2, "day").asSeconds(), startLimit: 20 }
); );
@@ -77,7 +67,7 @@ const UserNotesTab = () => {
<PopoverCloseButton /> <PopoverCloseButton />
<PopoverBody> <PopoverBody>
<UnorderedList> <UnorderedList>
{relays.map((url) => ( {contextRelays.map((url) => (
<ListItem key={url}>{url}</ListItem> <ListItem key={url}>{url}</ListItem>
))} ))}
</UnorderedList> </UnorderedList>

View File

@@ -2,7 +2,6 @@ import { Box, Button, Flex, Select, Spinner, Text, useDisclosure } from "@chakra
import moment from "moment"; import moment from "moment";
import { useState } from "react"; import { useState } from "react";
import { useOutletContext } from "react-router-dom"; import { useOutletContext } from "react-router-dom";
import { RelayMode } from "../../classes/relay";
import { ErrorBoundary, ErrorFallback } from "../../components/error-boundary"; import { ErrorBoundary, ErrorFallback } from "../../components/error-boundary";
import { LightningIcon } from "../../components/icons"; import { LightningIcon } from "../../components/icons";
import { NoteLink } from "../../components/note-link"; import { NoteLink } from "../../components/note-link";
@@ -12,10 +11,10 @@ import { readablizeSats } from "../../helpers/bolt11";
import { convertTimestampToDate } from "../../helpers/date"; import { convertTimestampToDate } from "../../helpers/date";
import { truncatedId } from "../../helpers/nostr-event"; import { truncatedId } from "../../helpers/nostr-event";
import { isProfileZap, isNoteZap, parseZapNote, totalZaps } from "../../helpers/zaps"; import { isProfileZap, isNoteZap, parseZapNote, totalZaps } from "../../helpers/zaps";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
import useFallbackUserRelays from "../../hooks/use-fallback-user-relays";
import { useTimelineLoader } from "../../hooks/use-timeline-loader"; import { useTimelineLoader } from "../../hooks/use-timeline-loader";
import { NostrEvent } from "../../types/nostr-event"; import { NostrEvent } from "../../types/nostr-event";
import { useAdditionalRelayContext } from "../../providers/additional-relay-context";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
const Zap = ({ zapEvent }: { zapEvent: NostrEvent }) => { const Zap = ({ zapEvent }: { zapEvent: NostrEvent }) => {
const { isOpen, onToggle } = useDisclosure(); const { isOpen, onToggle } = useDisclosure();
@@ -66,11 +65,8 @@ const Zap = ({ zapEvent }: { zapEvent: NostrEvent }) => {
const UserZapsTab = () => { const UserZapsTab = () => {
const { pubkey } = useOutletContext() as { pubkey: string }; const { pubkey } = useOutletContext() as { pubkey: string };
const [filter, setFilter] = useState("both"); const [filter, setFilter] = useState("both");
// get user relays const contextRelays = useAdditionalRelayContext();
const userRelays = useFallbackUserRelays(pubkey) const relays = useReadRelayUrls(contextRelays);
.filter((r) => r.mode & RelayMode.WRITE)
.map((r) => r.url);
const relays = useReadRelayUrls(userRelays);
const { events, loading, loadMore } = useTimelineLoader( const { events, loading, loadMore } = useTimelineLoader(
`${truncatedId(pubkey)}-zaps`, `${truncatedId(pubkey)}-zaps`,