mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-26 17:52:18 +01:00
Add explanations to relay views
This commit is contained in:
parent
a38710e630
commit
ad6e51ed98
5
.changeset/calm-impalas-battle.md
Normal file
5
.changeset/calm-impalas-battle.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": patch
|
||||
---
|
||||
|
||||
Add explanations to relay views
|
12
src/app.tsx
12
src/app.tsx
@ -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 /> },
|
||||
],
|
||||
|
@ -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
19
src/helpers/nip07.ts
Normal 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 };
|
||||
}
|
21
src/helpers/nostr/contacts.ts
Normal file
21
src/helpers/nostr/contacts.ts
Normal 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;
|
||||
}
|
25
src/hooks/use-user-contact-relays.ts
Normal file
25
src/hooks/use-user-contact-relays.ts
Normal 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]);
|
||||
}
|
7
src/hooks/use-user-dns-identity.ts
Normal file
7
src/hooks/use-user-dns-identity.ts
Normal 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);
|
||||
}
|
@ -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>
|
||||
);
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
@ -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() {
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
3
src/views/relays/cache/index.tsx
vendored
3
src/views/relays/cache/index.tsx
vendored
@ -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 />}
|
||||
|
114
src/views/relays/contact-list/index.tsx
Normal file
114
src/views/relays/contact-list/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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">
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
48
src/views/relays/nip05/index.tsx
Normal file
48
src/views/relays/nip05/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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 });
|
||||
|
Loading…
x
Reference in New Issue
Block a user