Add explanations to relay views

This commit is contained in:
hzrd149 2024-02-05 11:52:50 +00:00
parent a38710e630
commit ad6e51ed98
20 changed files with 445 additions and 177 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": patch
---
Add explanations to relay views

View File

@ -69,6 +69,12 @@ import CommunityTrendingView from "./views/community/views/trending";
import RelaysView from "./views/relays";
import RelayView from "./views/relays/relay";
import BrowseRelaySetsView from "./views/relays/browse-sets";
import CacheRelayView from "./views/relays/cache";
import RelaySetView from "./views/relays/relay-set";
import AppRelays from "./views/relays/app";
import MailboxesView from "./views/relays/mailboxes";
import NIP05RelaysView from "./views/relays/nip05";
import ContactListRelaysView from "./views/relays/contact-list";
import UserDMsTab from "./views/user/dms";
import DMTimelineView from "./views/tools/dm-timeline";
import LoginNostrConnectView from "./views/signin/nostr-connect";
@ -82,10 +88,6 @@ import LaunchpadView from "./views/launchpad";
import VideosView from "./views/videos";
import VideoDetailsView from "./views/videos/video";
import BookmarksView from "./views/bookmarks";
import CacheRelayView from "./views/relays/cache";
import RelaySetView from "./views/relays/relay-set";
import AppRelays from "./views/relays/app";
import MailboxesView from "./views/relays/mailboxes";
import LoginNostrAddressView from "./views/signin/address";
import LoginNostrAddressCreate from "./views/signin/address/create";
const TracksView = lazy(() => import("./views/tracks"));
@ -267,6 +269,8 @@ const router = createHashRouter([
{ path: "app", element: <AppRelays /> },
{ path: "cache", element: <CacheRelayView /> },
{ path: "mailboxes", element: <MailboxesView /> },
{ path: "nip05", element: <NIP05RelaysView /> },
{ path: "contacts", element: <ContactListRelaysView /> },
{ path: "sets", element: <BrowseRelaySetsView /> },
{ path: ":id", element: <RelaySetView /> },
],

View File

@ -1,4 +1,6 @@
import { relaysFromContactsEvent } from "../helpers/nostr/contacts";
import { getRelaysFromMailbox } from "../helpers/nostr/mailbox";
import { safeJson } from "../helpers/parse";
import { safeRelayUrl } from "../helpers/relay";
import relayPoolService from "../services/relay-pool";
import { NostrEvent } from "../types/nostr-event";
@ -39,4 +41,12 @@ export default class RelaySet extends Set<string> {
.map((r) => r.url),
);
}
static fromContactsEvent(contacts: NostrEvent, mode: RelayMode = RelayMode.ALL) {
return new RelaySet(
relaysFromContactsEvent(contacts)
.filter((r) => r.mode & mode)
.map((r) => r.url),
);
}
}

19
src/helpers/nip07.ts Normal file
View File

@ -0,0 +1,19 @@
import RelaySet from "../classes/relay-set";
import { safeRelayUrls } from "./relay";
export async function getRelaysFromExt() {
if (!window.nostr) throw new Error("Missing extension");
const read = new RelaySet();
const write = new RelaySet();
const extRelays = (await window.nostr.getRelays?.()) ?? [];
if (Array.isArray(extRelays)) {
const safeUrls = safeRelayUrls(extRelays);
read.merge(safeUrls);
write.merge(safeUrls);
} else {
read.merge(safeRelayUrls(Object.keys(extRelays).filter((url) => extRelays[url].read)));
write.merge(safeRelayUrls(Object.keys(extRelays).filter((url) => extRelays[url].write)));
}
return { read, write };
}

View File

@ -0,0 +1,21 @@
import { NostrEvent } from "nostr-tools";
import { RelayMode } from "../../classes/relay";
import { safeJson } from "../parse";
import { safeRelayUrl } from "../relay";
type RelayJson = Record<string, { read: boolean; write: boolean }>;
export function relaysFromContactsEvent(event: NostrEvent) {
const relayJson = safeJson(event.content, {}) as RelayJson;
const relays: { url: string; mode: RelayMode }[] = [];
for (const [url, opts] of Object.entries(relayJson)) {
const safeUrl = safeRelayUrl(url);
if (!safeUrl) continue;
let mode = RelayMode.NONE;
if (opts.write) mode = mode | RelayMode.WRITE;
if (opts.read) mode = mode | RelayMode.READ;
if (mode === RelayMode.NONE) mode = RelayMode.ALL;
relays.push({ url: safeUrl, mode });
}
return relays;
}

View File

@ -0,0 +1,25 @@
import { useMemo } from "react";
import { RequestOptions } from "../services/replaceable-event-requester";
import RelaySet from "../classes/relay-set";
import useUserContactList from "./use-user-contact-list";
import { RelayMode } from "../classes/relay";
import { relaysFromContactsEvent } from "../helpers/nostr/contacts";
export default function useUserContactRelays(
pubkey?: string,
additionalRelays?: Iterable<string>,
opts: RequestOptions = {},
) {
const contacts = useUserContactList(pubkey, additionalRelays, opts);
return useMemo(() => {
if (!contacts) return undefined;
if (contacts.content.length === 0) return null;
const relays = relaysFromContactsEvent(contacts);
const inbox = new RelaySet(relays.filter((r) => r.mode & RelayMode.READ).map((r) => r.url));
const outbox = new RelaySet(relays.filter((r) => r.mode & RelayMode.WRITE).map((r) => r.url));
return { inbox, outbox };
}, [contacts]);
}

View File

@ -0,0 +1,7 @@
import { useDnsIdentity } from "./use-dns-identity";
import { useUserMetadata } from "./use-user-metadata";
export function useUserDNSIdentity(pubkey?: string) {
const metadata = useUserMetadata(pubkey);
return useDnsIdentity(metadata?.nip05);
}

View File

@ -1,5 +1,6 @@
import { MouseEventHandler, PropsWithChildren, useCallback } from "react";
import { Button, Card, CardBody, CardHeader, Flex, Heading } from "@chakra-ui/react";
import { Box, Button, ButtonGroup, Card, CardBody, CardHeader, Flex, Heading, Text } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
import { useReadRelays } from "../../hooks/use-client-relays";
import clientRelaysService, { recommendedReadRelays, recommendedWriteRelays } from "../../services/client-relays";
@ -52,7 +53,14 @@ export default function RequireReadRelays({ children }: PropsWithChildren) {
if (readRelays.size === 0 && !offline && !location.pathname.startsWith("/relays"))
return (
<Flex direction="column" maxW="md" mx="auto" h="full" alignItems="center" justifyContent="center" gap="4">
<Heading size="md">Looks like you don't have any relays setup</Heading>
<Box w="full">
<Heading size="md" textAlign="center">
Setup App Relays
</Heading>
<Text fontStyle="italic">
App Relays are stored locally and are used to fetch your timeline and other users notes
</Text>
</Box>
<RelaySetCard label="Recommended Relays" read={recommendedReadRelays} write={recommendedWriteRelays} />
<RelaySetCard label="Japanese relays" read={JapaneseRelays} write={JapaneseRelays} />
<Card w="full" variant="outline">
@ -63,7 +71,14 @@ export default function RequireReadRelays({ children }: PropsWithChildren) {
<AddRelayForm onSubmit={(url) => clientRelaysService.addRelay(url, RelayMode.ALL)} w="full" />
</CardBody>
</Card>
<Button onClick={() => offlineMode.next(true)}>Offline mode</Button>
<ButtonGroup>
<Button as={RouterLink} to="/relays/app" variant="outline" colorScheme="primary">
Custom Relays
</Button>
<Button onClick={() => offlineMode.next(true)} variant="outline">
Offline mode
</Button>
</ButtonGroup>
</Flex>
);

View File

@ -16,7 +16,7 @@ export const recommendedReadRelays = new RelaySet(
"wss://relay.snort.social/",
"wss://nos.lol/",
"wss://purplerelay.com/",
"wss://eden.nostr.land/",
"wss://nostr.land/",
]),
);
export const recommendedWriteRelays = new RelaySet(
@ -63,6 +63,7 @@ class ClientRelayService {
setRelaysFromRelaySet(event: NostrEvent) {
this.writeRelays.next(RelaySet.fromNIP65Event(event, RelayMode.WRITE));
this.readRelays.next(RelaySet.fromNIP65Event(event, RelayMode.READ));
this.saveRelays();
}
saveRelays() {

View File

@ -1,87 +0,0 @@
import { kinds } from "nostr-tools";
import { isPTag, NostrEvent } from "../types/nostr-event";
import { safeJson } from "../helpers/parse";
import SuperMap from "../classes/super-map";
import Subject from "../classes/subject";
import replaceableEventLoaderService, { RequestOptions } from "./replaceable-event-requester";
import RelaySet from "../classes/relay-set";
export type UserContacts = {
pubkey: string;
relays: RelaySet;
inbox: RelaySet;
outbox: RelaySet;
contacts: string[];
contactRelay: Record<string, string | undefined>;
created_at: number;
};
type RelayJson = Record<string, { read: boolean; write: boolean }>;
function relayJsonToMailboxes(relayJson: RelayJson) {
const relays = new RelaySet();
const inbox = new RelaySet();
const outbox = new RelaySet();
for (const [url, opts] of Object.entries(relayJson)) {
relays.add(url);
if (opts.write) outbox.add(url);
if (opts.read) inbox.add(url);
}
return { relays, inbox, outbox };
}
function parseContacts(event: NostrEvent): UserContacts {
const relayJson = safeJson(event.content, {}) as RelayJson;
const { relays, inbox, outbox } = relayJsonToMailboxes(relayJson);
const pubkeys = event.tags.filter(isPTag).map((tag) => tag[1]);
const contactRelay = event.tags.filter(isPTag).reduce(
(dir, tag) => {
if (tag[2]) {
dir[tag[1]] = tag[2];
}
return dir;
},
{} as Record<string, string>,
);
return {
pubkey: event.pubkey,
relays,
inbox,
outbox,
contacts: pubkeys,
contactRelay,
created_at: event.created_at,
};
}
class UserContactsService {
private subjects = new SuperMap<string, Subject<UserContacts>>(() => new Subject<UserContacts>());
getSubject(pubkey: string) {
return this.subjects.get(pubkey);
}
requestContacts(pubkey: string, relays: Iterable<string>, opts?: RequestOptions) {
const sub = this.subjects.get(pubkey);
const requestSub = replaceableEventLoaderService.requestEvent(relays, kinds.Contacts, pubkey, undefined, opts);
sub.connectWithHandler(requestSub, (event, next) => next(parseContacts(event)));
return sub;
}
/** @deprecated */
receiveEvent(event: NostrEvent) {
replaceableEventLoaderService.handleEvent(event);
}
}
const userContactsService = new UserContactsService();
if (import.meta.env.DEV) {
// @ts-ignore
window.userContactsService = userContactsService;
}
export default userContactsService;

View File

@ -1,10 +1,11 @@
import { kinds } from "nostr-tools";
import { COMMON_CONTACT_RELAY } from "../const";
import { logger } from "../helpers/debug";
import accountService from "./account";
import clientRelaysService from "./client-relays";
import { offlineMode } from "./offline-mode";
import replaceableEventLoaderService from "./replaceable-event-requester";
import userAppSettings from "./settings/user-app-settings";
import userContactsService from "./user-contacts";
import userMailboxesService from "./user-mailboxes";
import userMetadataService from "./user-metadata";
@ -14,9 +15,15 @@ function loadContactsList() {
const account = accountService.current.value!;
log("Loading contacts list");
userContactsService.requestContacts(account.pubkey, [...clientRelaysService.readRelays.value, COMMON_CONTACT_RELAY], {
alwaysRequest: true,
});
replaceableEventLoaderService.requestEvent(
[...clientRelaysService.readRelays.value, COMMON_CONTACT_RELAY],
kinds.Contacts,
account.pubkey,
undefined,
{
alwaysRequest: true,
},
);
}
function downloadEvents() {

View File

@ -3,10 +3,10 @@ import { kinds } from "nostr-tools";
import { NostrEvent } from "../types/nostr-event";
import SuperMap from "../classes/super-map";
import Subject from "../classes/subject";
import userContactsService from "./user-contacts";
import replaceableEventLoaderService, { createCoordinate, RequestOptions } from "./replaceable-event-requester";
import RelaySet from "../classes/relay-set";
import { RelayMode } from "../classes/relay";
import { relaysFromContactsEvent } from "../helpers/nostr/contacts";
export type UserMailboxes = {
pubkey: string;
@ -39,17 +39,18 @@ class UserMailboxesService {
sub.connectWithHandler(requestSub, (event, next) => next(nip65ToUserMailboxes(event)));
// also fetch the relays from the users contacts
const contactsSub = userContactsService.requestContacts(pubkey, relays, opts);
sub.connectWithHandler(contactsSub, (contacts, next, value) => {
const contactsSub = replaceableEventLoaderService.requestEvent(relays, kinds.Contacts, pubkey, undefined, opts);
sub.connectWithHandler(contactsSub, (event, next, value) => {
// NOTE: only use relays from contact list if the user dose not have a NIP-65 relay list
if (contacts.relays.size > 0 && !value) {
const relays = relaysFromContactsEvent(event);
if (relays.length > 0 && !value) {
next({
pubkey: contacts.pubkey,
pubkey: event.pubkey,
event: null,
relays: contacts.relays,
inbox: contacts.inbox,
outbox: contacts.outbox,
created_at: contacts.created_at,
relays: RelaySet.fromContactsEvent(event),
inbox: RelaySet.fromContactsEvent(event, RelayMode.READ),
outbox: RelaySet.fromContactsEvent(event, RelayMode.WRITE),
created_at: event.created_at,
});
}
});

View File

@ -34,7 +34,6 @@ function LaunchpadPage() {
<Flex gap="4" w="full">
<Button colorScheme="primary" size="lg" onClick={() => openModal()} variant="outline">
New Note
<KeyboardShortcut letter="n" ml="auto" onPress={(e) => openModal()} />
</Button>
<SearchForm flex={1} />
</Flex>

View File

@ -1,6 +1,6 @@
import { useMemo } from "react";
import { useCallback, useMemo } from "react";
import { Button, Flex, Heading } from "@chakra-ui/react";
import { Button, ButtonGroup, Flex, Heading, Text } from "@chakra-ui/react";
import useSubject from "../../../hooks/use-subject";
import { offlineMode } from "../../../services/offline-mode";
import WifiOff from "../../../components/icons/wifi-off";
@ -14,12 +14,20 @@ import { useReadRelays, useWriteRelays } from "../../../hooks/use-client-relays"
import useCurrentAccount from "../../../hooks/use-current-account";
import RelayControl from "./relay-control";
import SelectRelaySet from "./select-relay-set";
import useUserMailboxes from "../../../hooks/use-user-mailboxes";
import { getRelaysFromExt } from "../../../helpers/nip07";
import { useUserDNSIdentity } from "../../../hooks/use-user-dns-identity";
import useUserContactRelays from "../../../hooks/use-user-contact-relays";
import { WarningIcon } from "@chakra-ui/icons";
export default function AppRelays() {
const account = useCurrentAccount();
const readRelays = useReadRelays();
const writeRelays = useWriteRelays();
const offline = useSubject(offlineMode);
const { event: nip65 } = useUserMailboxes(account?.pubkey) ?? {};
const nip05 = useUserDNSIdentity(account?.pubkey);
const contactRelays = useUserContactRelays(account?.pubkey);
const sorted = useMemo(() => RelaySet.from(readRelays, writeRelays).urls.sort(), [readRelays, writeRelays]);
@ -27,7 +35,9 @@ export default function AppRelays() {
<Flex gap="2" direction="column" overflow="auto hidden" flex={1}>
<Flex gap="2" alignItems="center">
<BackButton hideFrom="lg" size="sm" />
<Heading size="lg">App Relays</Heading>
<Heading size="lg" px={{ base: 0, lg: "2" }}>
App Relays
</Heading>
<Button
onClick={() => offlineMode.next(!offline)}
leftIcon={offline ? <WifiOff /> : <Wifi />}
@ -38,6 +48,10 @@ export default function AppRelays() {
</Button>
</Flex>
<Text fontStyle="italic" px="2" mt="-2">
These relays are stored locally and are used for everything in the app
</Text>
{sorted.map((url) => (
<RelayControl key={url} url={url} />
))}
@ -47,6 +61,60 @@ export default function AppRelays() {
}}
/>
{writeRelays.size === 0 && (
<Text color="yellow.500">
<WarningIcon /> There are write relays set, any note you create might not be saved
</Text>
)}
<Heading size="md" mt="2">
Import from:
</Heading>
<Flex wrap="wrap" gap="2">
{window.nostr && (
<Button
onClick={async () => {
const { read, write } = await getRelaysFromExt();
clientRelaysService.readRelays.next(read);
clientRelaysService.writeRelays.next(write);
clientRelaysService.saveRelays();
}}
>
Extension
</Button>
)}
{nip65 && (
<Button
onClick={() => {
clientRelaysService.setRelaysFromRelaySet(nip65);
}}
>
NIP-65 (Mailboxes)
</Button>
)}
{nip05 && (
<Button
onClick={() => {
clientRelaysService.readRelays.next(RelaySet.from(nip05.relays));
clientRelaysService.writeRelays.next(RelaySet.from(nip05.relays));
clientRelaysService.saveRelays();
}}
>
NIP-05
</Button>
)}
{contactRelays && (
<Button
onClick={() => {
clientRelaysService.readRelays.next(contactRelays.inbox);
clientRelaysService.writeRelays.next(contactRelays.outbox);
clientRelaysService.saveRelays();
}}
>
Contact List (Legacy)
</Button>
)}
</Flex>
{/* {account && (
<>
<Heading size="md" mt="2">

View File

@ -100,6 +100,9 @@ export default function CacheRelayView() {
<BackButton hideFrom="lg" size="sm" />
<Heading size="lg">Cache Relay</Heading>
</Flex>
<Text fontStyle="italic" mt="-2" px={{ base: "2", lg: 0 }}>
The cache relay is used to cache event locally so they can be loaded quickly
</Text>
<InternalRelay />
<NostrRelayTray />
{window.CACHE_RELAY_ENABLED && <HostedRelay />}

View File

@ -0,0 +1,114 @@
import { Button, Code, Flex, Heading, Link, Spinner, Text } from "@chakra-ui/react";
import BackButton from "../../../components/back-button";
import useCurrentAccount from "../../../hooks/use-current-account";
import { Link as RouterLink } from "react-router-dom";
import { RelayFavicon } from "../../../components/relay-favicon";
import useUserContactRelays from "../../../hooks/use-user-contact-relays";
import { CheckIcon, InboxIcon, OutboxIcon } from "../../../components/icons";
import { useCallback, useState } from "react";
import useCacheForm from "../../../hooks/use-cache-form";
import useUserContactList from "../../../hooks/use-user-contact-list";
import { cloneEvent } from "../../../helpers/nostr/events";
import { EventTemplate } from "nostr-tools";
import dayjs from "dayjs";
import { usePublishEvent } from "../../../providers/global/publish-provider";
function RelayItem({ url }: { url: string }) {
return (
<Flex gap="2" alignItems="center">
<RelayFavicon relay={url} size="sm" />
<Link as={RouterLink} to={`/r/${encodeURIComponent(url)}`} isTruncated>
{url}
</Link>
</Flex>
);
}
export default function ContactListRelaysView() {
const account = useCurrentAccount();
const contacts = useUserContactList(account?.pubkey);
const relays = useUserContactRelays(account?.pubkey);
const publish = usePublishEvent();
const [loading, setLoading] = useState(false);
const clearRelays = useCallback(async () => {
if (!contacts) return;
if (confirm("Are you use you want to remove these relays? Other nostr apps might be effected") !== true) return;
const draft: EventTemplate = {
kind: contacts.kind,
content: "",
tags: contacts.tags,
created_at: dayjs().unix(),
};
setLoading(true);
await publish("Clear Relays", draft);
setLoading(false);
}, [setLoading, contacts, publish]);
if (relays === undefined) return <Spinner />;
return (
<Flex gap="2" direction="column" overflow="auto hidden" flex={1} px={{ base: "2", lg: 0 }}>
<Flex gap="2" alignItems="center">
<BackButton hideFrom="lg" size="sm" />
<Heading size="lg">Contact List Relays</Heading>
{relays && (
<Button
colorScheme="red"
onClick={clearRelays}
isLoading={loading}
ml="auto"
size="sm"
isDisabled={account?.readonly}
>
Clear Relays
</Button>
)}
</Flex>
<Text fontStyle="italic" mt="-2">
Some apps store relays in your contacts list (kind-3)
<br />
noStrudel dose not use these relays, instead it uses your{" "}
<Link as={RouterLink} to="/relays/mailboxes" color="blue.500">
Mailbox Relays
</Link>
</Text>
{relays === null ? (
<Text color="green.500" fontSize="lg" mt="4">
<CheckIcon /> You don't have any relays stored in your contact list
</Text>
) : (
<>
<Heading size="md" mt="2">
Read Relays
</Heading>
{relays.inbox.urls.map((relay) => (
<Flex key={relay} gap="2" alignItems="center" overflow="hidden">
<RelayFavicon relay={relay} size="xs" />
<Link as={RouterLink} to={`/r/${encodeURIComponent(relay)}`} isTruncated>
{relay}
</Link>
</Flex>
))}
<Heading size="md" mt="2">
Write Relays
</Heading>
{relays.outbox.urls.map((relay) => (
<Flex key={relay} gap="2" alignItems="center" overflow="hidden">
<RelayFavicon relay={relay} size="xs" />
<Link as={RouterLink} to={`/r/${encodeURIComponent(relay)}`} isTruncated>
{relay}
</Link>
</Flex>
))}
</>
)}
</Flex>
);
}

View File

@ -8,13 +8,18 @@ import { getListName } from "../../helpers/nostr/lists";
import { getEventCoordinate } from "../../helpers/nostr/events";
import { useBreakpointValue } from "../../providers/global/breakpoint-provider";
import Database01 from "../../components/icons/database-01";
import { RelayIcon } from "../../components/icons";
import { AtIcon, RelayIcon } from "../../components/icons";
import Mail02 from "../../components/icons/mail-02";
import { useUserDNSIdentity } from "../../hooks/use-user-dns-identity";
import useUserContactRelays from "../../hooks/use-user-contact-relays";
import UserSquare from "../../components/icons/user-square";
export default function RelaysView() {
const account = useCurrentAccount();
const relaySets = useUserRelaySets(account?.pubkey, undefined);
const vertical = useBreakpointValue({ base: true, lg: false });
const nip05 = useUserDNSIdentity(account?.pubkey);
const kind3Relays = useUserContactRelays(account?.pubkey);
const location = useLocation();
@ -54,6 +59,26 @@ export default function RelaysView() {
Mailboxes
</Button>
)}
{nip05 && (
<Button
variant="outline"
as={RouterLink}
to="/relays/nip05"
leftIcon={<AtIcon boxSize={6} />}
colorScheme={location.pathname === "/relays/nip05" ? "primary" : undefined}
>
NIP-05 Relays
</Button>
)}
<Button
variant="outline"
as={RouterLink}
to="/relays/contacts"
leftIcon={<UserSquare boxSize={6} />}
colorScheme={location.pathname === "/relays/contacts" ? "primary" : undefined}
>
Contact List Relays
</Button>
{/* {account && (
<>
<Heading size="sm" mt="2">

View File

@ -17,7 +17,7 @@ import VerticalPageLayout from "../../../components/vertical-page-layout";
import RequireCurrentAccount from "../../../providers/route/require-current-account";
import useUserMailboxes from "../../../hooks/use-user-mailboxes";
import useCurrentAccount from "../../../hooks/use-current-account";
import { InboxIcon } from "../../../components/icons";
import { InboxIcon, OutboxIcon } from "../../../components/icons";
import { RelayUrlInput } from "../../../components/relay-url-input";
import { RelayFavicon } from "../../../components/relay-favicon";
import { RelayMode } from "../../../classes/relay";
@ -30,6 +30,7 @@ import { safeRelayUrl } from "../../../helpers/relay";
import { usePublishEvent } from "../../../providers/global/publish-provider";
import { COMMON_CONTACT_RELAY } from "../../../const";
import BackButton from "../../../components/back-button";
import AddRelayForm from "../app/add-relay-form";
function RelayLine({ relay, mode, list }: { relay: string; mode: RelayMode; list?: NostrEvent }) {
const publish = usePublishEvent();
@ -57,30 +58,6 @@ function RelayLine({ relay, mode, list }: { relay: string; mode: RelayMode; list
);
}
function AddRelayForm({ onSubmit }: { onSubmit: (url: string) => void }) {
const { register, handleSubmit, reset } = useForm({
defaultValues: {
url: "",
},
});
const submit = handleSubmit(async (values) => {
const url = safeRelayUrl(values.url);
if (!url) return;
await onSubmit(url);
reset();
});
return (
<Flex as="form" display="flex" gap="2" onSubmit={submit} flex={1}>
<RelayUrlInput {...register("url")} placeholder="wss://relay.example.com" size="sm" borderRadius="md" />
<Button type="submit" colorScheme="primary" size="sm">
Add
</Button>
</Flex>
);
}
function MailboxesPage() {
const account = useCurrentAccount()!;
const publish = usePublishEvent();
@ -95,41 +72,46 @@ function MailboxesPage() {
);
return (
<Flex direction="column" gap="2">
<Flex gap="2" direction="column" overflow="auto hidden" flex={1} px="2">
<Flex gap="2" alignItems="center">
<BackButton hideFrom="lg" size="sm" />
<Heading size="lg">Mailboxes</Heading>
</Flex>
<Card maxW="lg">
<CardHeader p="4" pb="2" display="flex" gap="2" alignItems="center">
<InboxIcon boxSize={5} />
<Heading size="md">Inbox</Heading>
</CardHeader>
<CardBody px="4" py="0" display="flex" flexDirection="column" gap="2">
<Text fontStyle="italic">Other users will send DMs and notes to these relays to notify you</Text>
{inbox?.urls
.sort()
.map((url) => <RelayLine key={url} relay={url} mode={RelayMode.READ} list={event ?? undefined} />)}
</CardBody>
<CardFooter display="flex" gap="2" p="4">
<AddRelayForm onSubmit={(r) => addRelay(r, RelayMode.READ)} />
</CardFooter>
</Card>
<Card maxW="lg">
<CardHeader p="4" pb="2" display="flex" gap="2" alignItems="center">
<InboxIcon boxSize={5} />
<Heading size="md">Outbox</Heading>
</CardHeader>
<CardBody px="4" py="0" display="flex" flexDirection="column" gap="1">
<Text fontStyle="italic">Always publish to these relays so your followers can find your notes</Text>
{outbox?.urls
.sort()
.map((url) => <RelayLine key={url} relay={url} mode={RelayMode.WRITE} list={event ?? undefined} />)}
</CardBody>
<CardFooter display="flex" gap="2" p="4">
<AddRelayForm onSubmit={(r) => addRelay(r, RelayMode.WRITE)} />
</CardFooter>
</Card>
<Text fontStyle="italic" mt="-2">
Mailbox relays are a way for other users to find your events, or send you events. they are defined in{" "}
<Link
color="blue.500"
isExternal
href={`https://github.com/nostr-protocol/nips/blob/master/65.md`}
textDecoration="underline"
>
NIP-65
</Link>
</Text>
<Flex gap="2" mt="2">
<InboxIcon boxSize={5} />
<Heading size="md">Inbox</Heading>
</Flex>
<Text fontStyle="italic" mt="-2">
These relays are used by other users to send DMs and notes to you
</Text>
{inbox?.urls
.sort()
.map((url) => <RelayLine key={url} relay={url} mode={RelayMode.READ} list={event ?? undefined} />)}
<AddRelayForm onSubmit={(r) => addRelay(r, RelayMode.READ)} />
<Flex gap="2" mt="4">
<OutboxIcon boxSize={5} />
<Heading size="md">Outbox</Heading>
</Flex>
<Text fontStyle="italic" mt="-2">
noStrudel will always publish to these relays so other users can find your notes
</Text>
{outbox?.urls
.sort()
.map((url) => <RelayLine key={url} relay={url} mode={RelayMode.WRITE} list={event ?? undefined} />)}
<AddRelayForm onSubmit={(r) => addRelay(r, RelayMode.WRITE)} />
</Flex>
);
}

View File

@ -0,0 +1,48 @@
import { Code, Flex, Heading, Link, Text } from "@chakra-ui/react";
import BackButton from "../../../components/back-button";
import useCurrentAccount from "../../../hooks/use-current-account";
import { useUserDNSIdentity } from "../../../hooks/use-user-dns-identity";
import { Link as RouterLink } from "react-router-dom";
import { RelayFavicon } from "../../../components/relay-favicon";
function RelayItem({ url }: { url: string }) {
return (
<Flex gap="2" alignItems="center">
<RelayFavicon relay={url} size="sm" />
<Link as={RouterLink} to={`/r/${encodeURIComponent(url)}`} isTruncated>
{url}
</Link>
</Flex>
);
}
export default function NIP05RelaysView() {
const account = useCurrentAccount();
const nip05 = useUserDNSIdentity(account?.pubkey);
return (
<Flex gap="2" direction="column" overflow="auto hidden" flex={1} px={{ base: "2", lg: 0 }}>
<Flex gap="2" alignItems="center">
<BackButton hideFrom="lg" size="sm" />
<Heading size="lg">NIP-05 Relays</Heading>
</Flex>
<Text fontStyle="italic" mt="-2">
These relays cant be modified by noStrudel, they must be set manually on your{" "}
<Code>/.well-known/nostr.json</Code> file or by your identity provider
<br />
<Link
href="https://nostr.how/en/guides/get-verified#self-hosted"
isExternal
color="blue.500"
fontStyle="initial"
>
Read more about nip-05
</Link>
</Text>
{nip05?.relays.map((url) => <RelayItem key={url} url={url} />)}
</Flex>
);
}

View File

@ -25,6 +25,8 @@ import accountService from "../../services/account";
import serialPortService from "../../services/serial-port";
import amberSignerService from "../../services/amber-signer";
import { AtIcon } from "../../components/icons";
import { getRelaysFromExt } from "../../helpers/nip07";
import { safeRelayUrls } from "../../helpers/relay";
export default function LoginStartView() {
const location = useLocation();
@ -40,16 +42,15 @@ export default function LoginStartView() {
const pubkey = await window.nostr.getPublicKey();
if (!accountService.hasAccount(pubkey)) {
let relays: string[] = [];
const extRelays = (await window.nostr.getRelays?.()) ?? [];
if (Array.isArray(extRelays)) {
relays = extRelays;
} else {
relays = Object.keys(extRelays).filter((url) => extRelays[url].read);
}
let relays = (await getRelaysFromExt()).read.urls;
if (relays.length === 0) {
relays = ["wss://relay.damus.io", "wss://relay.snort.social", "wss://nostr.wine", COMMON_CONTACT_RELAY];
relays = safeRelayUrls([
"wss://relay.damus.io/",
"wss://relay.snort.social/",
"wss://nostr.wine/",
COMMON_CONTACT_RELAY,
]);
}
accountService.addAccount({ pubkey, relays, type: "extension", readonly: false });