mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-17 13:21:44 +01:00
add DNS Identity settings view
This commit is contained in:
parent
7e388bd214
commit
723668b0fc
@ -6,7 +6,7 @@ import SuperMap from "../classes/super-map";
|
||||
|
||||
const parseCache = new SuperMap<string, { name: string; domain: string } | null>(parseNIP05Address);
|
||||
|
||||
export default function useDnsIdentity(address: string | undefined) {
|
||||
export default function useDnsIdentity(address: string | undefined, force = false) {
|
||||
const parsed = address ? parseCache.get(address) : null;
|
||||
const { value: identity } = useAsync(async () => {
|
||||
if (parsed) return await dnsIdentityLoader.requestIdentity(parsed.name, parsed.domain);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { Button, ButtonGroup, Flex, LinkBox, LinkOverlay, Text } from "@chakra-ui/react";
|
||||
import { Link as RouterLink, useLocation } from "react-router-dom";
|
||||
import { kinds, nip19 } from "nostr-tools";
|
||||
@ -21,20 +21,11 @@ import { useKind4Decrypt } from "../../hooks/use-kind4-decryption";
|
||||
import { truncateId } from "../../helpers/string";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useUserMailboxes from "../../hooks/use-user-mailboxes";
|
||||
import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter";
|
||||
import useUserContacts from "../../hooks/use-user-contacts";
|
||||
import useUserMutes from "../../hooks/use-user-mutes";
|
||||
import SimpleParentView from "../../components/layout/presets/simple-parent-view";
|
||||
|
||||
export function useDirectMessagesTimeline(pubkey?: string) {
|
||||
const userMuteFilter = useClientSideMuteFilter();
|
||||
const eventFilter = useCallback(
|
||||
(event: NostrEvent) => {
|
||||
if (userMuteFilter(event)) return false;
|
||||
return true;
|
||||
},
|
||||
[userMuteFilter],
|
||||
);
|
||||
const mailboxes = useUserMailboxes(pubkey);
|
||||
|
||||
return useTimelineLoader(
|
||||
@ -46,7 +37,6 @@ export function useDirectMessagesTimeline(pubkey?: string) {
|
||||
{ "#p": [pubkey], kinds: [kinds.EncryptedDirectMessage] },
|
||||
]
|
||||
: undefined,
|
||||
{ eventFilter },
|
||||
);
|
||||
}
|
||||
|
||||
@ -115,7 +105,7 @@ function MessagesHomePage() {
|
||||
}
|
||||
|
||||
return filtered.sort((a, b) => b.messages[0].created_at - a.messages[0].created_at);
|
||||
}, [messages, account.pubkey, contacts?.length, filter, mutes?.pubkeys]);
|
||||
}, [messages, account.pubkey, contacts?.length, filter, mutes?.pubkeys.size]);
|
||||
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { useActiveAccount } from "applesauce-react/hooks";
|
||||
import { IdentityStatus } from "applesauce-loaders/helpers/dns-identity";
|
||||
|
||||
import Database01 from "../../components/icons/database-01";
|
||||
import { AtIcon, RelayIcon, SearchIcon } from "../../components/icons";
|
||||
import Mail02 from "../../components/icons/mail-02";
|
||||
@ -37,7 +39,7 @@ export default function RelaysView() {
|
||||
<SimpleNavItem to="/relays/webrtc" leftIcon={<Server05 boxSize={6} />}>
|
||||
WebRTC Relays
|
||||
</SimpleNavItem>
|
||||
{nip05?.exists && (
|
||||
{nip05?.status === IdentityStatus.Found && (
|
||||
<SimpleNavItem to="/relays/nip05" leftIcon={<AtIcon boxSize={6} />}>
|
||||
NIP-05 Relays
|
||||
</SimpleNavItem>
|
||||
|
@ -6,6 +6,7 @@ import { Link as RouterLink } from "react-router-dom";
|
||||
|
||||
import RelayFavicon from "../../../components/relay-favicon";
|
||||
import SimpleView from "../../../components/layout/presets/simple-view";
|
||||
import { IdentityStatus } from "applesauce-loaders/helpers/dns-identity";
|
||||
|
||||
function RelayItem({ url }: { url: string }) {
|
||||
return (
|
||||
@ -38,7 +39,7 @@ export default function NIP05RelaysView() {
|
||||
</Link>
|
||||
</Text>
|
||||
|
||||
{nip05?.relays?.map((url) => <RelayItem key={url} url={url} />)}
|
||||
{nip05?.status === IdentityStatus.Found && nip05?.relays?.map((url) => <RelayItem key={url} url={url} />)}
|
||||
</SimpleView>
|
||||
);
|
||||
}
|
||||
|
33
src/views/settings/dns-identity/identity-warning.tsx
Normal file
33
src/views/settings/dns-identity/identity-warning.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { Link, Text } from "@chakra-ui/react";
|
||||
import { Identity, IdentityStatus } from "applesauce-loaders/helpers/dns-identity";
|
||||
import { ExternalLinkIcon } from "../../../components/icons";
|
||||
|
||||
export default function DNSIdentityWarning({ identity, pubkey }: { pubkey: string; identity: Identity }) {
|
||||
switch (identity?.status) {
|
||||
case IdentityStatus.Missing:
|
||||
return <Text color="red.500">Unable to find DNS Identity in nostr.json file</Text>;
|
||||
case IdentityStatus.Error:
|
||||
return (
|
||||
<Text color="yellow.500">
|
||||
Unable to check DNS identity due to CORS error{" "}
|
||||
<Link
|
||||
color="blue.500"
|
||||
href={`https://cors-test.codehappy.dev/?url=${encodeURIComponent(`https://${identity.domain}/.well-known/nostr.json?name=${identity.name}`)}&method=get`}
|
||||
isExternal
|
||||
>
|
||||
Test
|
||||
<ExternalLinkIcon ml="1" />
|
||||
</Link>
|
||||
</Text>
|
||||
);
|
||||
case IdentityStatus.Found:
|
||||
if (identity.pubkey !== pubkey)
|
||||
return (
|
||||
<Text color="red.500" fontWeight="bold">
|
||||
Invalid DNS Identity!
|
||||
</Text>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
170
src/views/settings/dns-identity/index.tsx
Normal file
170
src/views/settings/dns-identity/index.tsx
Normal file
@ -0,0 +1,170 @@
|
||||
import {
|
||||
ButtonGroup,
|
||||
Editable,
|
||||
EditableInput,
|
||||
EditablePreview,
|
||||
EditableProps,
|
||||
Heading,
|
||||
IconButton,
|
||||
Input,
|
||||
Link,
|
||||
Spinner,
|
||||
Text,
|
||||
useEditableControls,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { getProfileContent, mergeRelaySets, parseNIP05Address, ProfileContent } from "applesauce-core/helpers";
|
||||
import { CheckIcon, CloseIcon, EditIcon } from "@chakra-ui/icons";
|
||||
import { kinds } from "nostr-tools";
|
||||
import { setContent } from "applesauce-factory/operations";
|
||||
import { IdentityStatus } from "applesauce-loaders/helpers/dns-identity";
|
||||
import { useAsync } from "react-use";
|
||||
|
||||
import SimpleView from "../../../components/layout/presets/simple-view";
|
||||
import { useActiveAccount, useEventFactory, useEventStore } from "applesauce-react/hooks";
|
||||
import useUserProfile from "../../../hooks/use-user-profile";
|
||||
import dnsIdentityLoader from "../../../services/dns-identity-loader";
|
||||
import WikiLink from "../../../components/markdown/wiki-link";
|
||||
import RawValue from "../../../components/debug-modal/raw-value";
|
||||
import { ExternalLinkIcon } from "../../../components/icons";
|
||||
import useUserMailboxes from "../../../hooks/use-user-mailboxes";
|
||||
import { useWriteRelays } from "../../../hooks/use-client-relays";
|
||||
import { COMMON_CONTACT_RELAYS } from "../../../const";
|
||||
import { usePublishEvent } from "../../../providers/global/publish-provider";
|
||||
|
||||
function EditableControls() {
|
||||
const { isEditing, getSubmitButtonProps, getCancelButtonProps, getEditButtonProps } = useEditableControls();
|
||||
|
||||
return isEditing ? (
|
||||
<ButtonGroup justifyContent="center" size="sm">
|
||||
<IconButton icon={<CheckIcon />} {...getSubmitButtonProps()} aria-label="save" />
|
||||
<IconButton icon={<CloseIcon />} {...getCancelButtonProps()} aria-label="Cancel" />
|
||||
</ButtonGroup>
|
||||
) : (
|
||||
<IconButton size="sm" icon={<EditIcon />} {...getEditButtonProps()} aria-label="Edit" />
|
||||
);
|
||||
}
|
||||
|
||||
function EditableIdentity() {
|
||||
const factory = useEventFactory();
|
||||
const eventStore = useEventStore();
|
||||
const account = useActiveAccount();
|
||||
const toast = useToast();
|
||||
const publish = usePublishEvent();
|
||||
|
||||
const profile = useUserProfile(account?.pubkey);
|
||||
const mailboxes = useUserMailboxes();
|
||||
const publishRelays = useWriteRelays();
|
||||
|
||||
const onSubmit: EditableProps["onSubmit"] = async (value) => {
|
||||
if (!account) return;
|
||||
try {
|
||||
const metadata = eventStore.getReplaceable(kinds.Metadata, account.pubkey);
|
||||
if (!metadata) throw new Error("Failed to find profile");
|
||||
|
||||
const profile = getProfileContent(metadata);
|
||||
const newProfile = { ...profile, nip05: value };
|
||||
const draft = await factory.modify(metadata, setContent(JSON.stringify(newProfile)));
|
||||
const signed = await account.signEvent(draft);
|
||||
|
||||
await publish("Update NIP-05", signed, mergeRelaySets(publishRelays, mailboxes?.outboxes, COMMON_CONTACT_RELAYS));
|
||||
} catch (error) {
|
||||
if (error instanceof Error) toast({ status: "error", description: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Editable
|
||||
alignItems="center"
|
||||
defaultValue={profile?.nip05}
|
||||
fontSize="2xl"
|
||||
isPreviewFocusable={false}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<EditablePreview mr="2" />
|
||||
{/* Here is the custom input */}
|
||||
<Input as={EditableInput} w="xs" mr="2" />
|
||||
<EditableControls />
|
||||
</Editable>
|
||||
);
|
||||
}
|
||||
|
||||
function IdentityDetails({ pubkey, profile }: { pubkey: string; profile: ProfileContent }) {
|
||||
const { value: identity, loading } = useAsync(async () => {
|
||||
if (!profile?.nip05) return null;
|
||||
const parsed = parseNIP05Address(profile.nip05);
|
||||
if (!parsed) return null;
|
||||
return await dnsIdentityLoader.fetchIdentity(parsed.name, parsed.domain);
|
||||
}, [profile?.nip05]);
|
||||
|
||||
const renderDetails = () => {
|
||||
if (!identity) return null;
|
||||
|
||||
switch (identity.status) {
|
||||
case IdentityStatus.Missing:
|
||||
return <Text color="red.500">Unable to find DNS Identity in nostr.json file</Text>;
|
||||
case IdentityStatus.Error:
|
||||
return (
|
||||
<Text color="yellow.500">
|
||||
Unable to check DNS identity due to CORS error{" "}
|
||||
<Link
|
||||
color="blue.500"
|
||||
href={`https://cors-test.codehappy.dev/?url=${encodeURIComponent(`https://${identity.domain}/.well-known/nostr.json?name=${identity.name}`)}&method=get`}
|
||||
isExternal
|
||||
>
|
||||
Test
|
||||
<ExternalLinkIcon ml="1" />
|
||||
</Link>
|
||||
</Text>
|
||||
);
|
||||
case IdentityStatus.Found:
|
||||
if (identity.pubkey !== pubkey)
|
||||
return (
|
||||
<Text color="red.500" fontWeight="bold">
|
||||
Invalid DNS Identity! <CloseIcon />
|
||||
</Text>
|
||||
);
|
||||
else
|
||||
return (
|
||||
<Text color="green.500" fontWeight="bold">
|
||||
DNS identity matches pubkey <CheckIcon />
|
||||
</Text>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EditableIdentity />
|
||||
{renderDetails()}
|
||||
{loading && <Spinner />}
|
||||
<RawValue heading="Your pubkey" value={pubkey} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DnsIdentityView() {
|
||||
const account = useActiveAccount();
|
||||
if (!account) return <Navigate to="/" />;
|
||||
|
||||
const profile = useUserProfile(account.pubkey, undefined, true);
|
||||
|
||||
return (
|
||||
<SimpleView title="DNS Identity">
|
||||
{profile?.nip05 ? (
|
||||
<IdentityDetails pubkey={account.pubkey} profile={profile} />
|
||||
) : (
|
||||
<>
|
||||
<Heading>No DNS identity setup</Heading>
|
||||
<RawValue heading="Your pubkey" value={account.pubkey} />
|
||||
<Text>
|
||||
or read the details on the wiki: <WikiLink topic="nip-05">NIP-05</WikiLink>
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</SimpleView>
|
||||
);
|
||||
}
|
@ -10,6 +10,7 @@ import {
|
||||
RelayIcon,
|
||||
SearchIcon,
|
||||
SpyIcon,
|
||||
VerifiedIcon,
|
||||
} from "../../components/icons";
|
||||
import { useActiveAccount } from "applesauce-react/hooks";
|
||||
import Image01 from "../../components/icons/image-01";
|
||||
@ -56,6 +57,9 @@ export default function SettingsView() {
|
||||
<SimpleNavItem to="/settings/search-relays" leftIcon={<SearchIcon boxSize={6} />}>
|
||||
Search
|
||||
</SimpleNavItem>
|
||||
<SimpleNavItem to="/settings/identity" leftIcon={<VerifiedIcon boxSize={6} />}>
|
||||
DNS Identity
|
||||
</SimpleNavItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { MouseEventHandler, useCallback, useMemo } from "react";
|
||||
import { Button, Card, CardBody, CardHeader, Flex, Heading, SimpleGrid, Text } from "@chakra-ui/react";
|
||||
import { WarningIcon } from "@chakra-ui/icons";
|
||||
import { IdentityStatus } from "applesauce-loaders/helpers/dns-identity";
|
||||
import { mergeRelaySets } from "applesauce-core/helpers";
|
||||
|
||||
import { RECOMMENDED_READ_RELAYS, RECOMMENDED_WRITE_RELAYS } from "../../../const";
|
||||
import AddRelayForm from "./add-relay-form";
|
||||
@ -10,7 +12,7 @@ import RelayControl from "./relay-control";
|
||||
import { getRelaysFromExt } from "../../../helpers/nip07";
|
||||
import { useUserDNSIdentity } from "../../../hooks/use-user-dns-identity";
|
||||
import useUserContactRelays from "../../../hooks/use-user-contact-relays";
|
||||
import { mergeRelaySets, safeRelayUrls } from "../../../helpers/relay";
|
||||
import { safeRelayUrls } from "../../../helpers/relay";
|
||||
import HoverLinkOverlay from "../../../components/hover-link-overlay";
|
||||
import SimpleView from "../../../components/layout/presets/simple-view";
|
||||
import localSettings from "../../../services/local-settings";
|
||||
@ -108,7 +110,7 @@ export default function AppRelaysView() {
|
||||
NIP-65 (Mailboxes)
|
||||
</Button>
|
||||
)}
|
||||
{nip05?.relays && (
|
||||
{nip05?.status === IdentityStatus.Found && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (!nip05.relays) return;
|
||||
|
@ -15,6 +15,7 @@ import PrivacySettings from "./privacy";
|
||||
import LightningSettings from "./lightning";
|
||||
import PerformanceSettings from "./performance";
|
||||
import AuthenticationSettingsView from "./authentication";
|
||||
import DnsIdentityView from "./dns-identity";
|
||||
|
||||
// bakery settings
|
||||
const BakeryConnectView = lazy(() => import("./bakery/connect"));
|
||||
@ -41,6 +42,7 @@ export default [
|
||||
),
|
||||
},
|
||||
{ path: "mailboxes", Component: MailboxesView },
|
||||
{ path: "identity", Component: DnsIdentityView },
|
||||
{ path: "authentication", Component: AuthenticationSettingsView },
|
||||
{ path: "media-servers", Component: MediaServersView },
|
||||
{ path: "search-relays", Component: SearchRelaysView },
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
import { nip19 } from "nostr-tools";
|
||||
import { ChatIcon } from "@chakra-ui/icons";
|
||||
import { parseLNURLOrAddress, parseNIP05Address } from "applesauce-core/helpers";
|
||||
import { IdentityStatus } from "applesauce-loaders/helpers/dns-identity";
|
||||
|
||||
import { truncatedId } from "../../../helpers/nostr/event";
|
||||
import { useAdditionalRelayContext } from "../../../providers/local/additional-relay-context";
|
||||
@ -51,45 +52,7 @@ import UserAboutContent from "../../../components/user/user-about-content";
|
||||
import UserRecentEvents from "./user-recent-events";
|
||||
import { useUserAppSettings } from "../../../hooks/use-user-app-settings";
|
||||
import UserJoinedGroups from "./user-joined-groups";
|
||||
import { IdentityStatus } from "applesauce-loaders/helpers/dns-identity";
|
||||
|
||||
function DNSIdentityWarning({ pubkey }: { pubkey: string }) {
|
||||
const metadata = useUserProfile(pubkey);
|
||||
const identity = useUserDNSIdentity(pubkey);
|
||||
const parsed = metadata?.nip05 ? parseNIP05Address(metadata.nip05) : undefined;
|
||||
|
||||
const nip05URL = parsed ? `https://${parsed.domain}/.well-known/nostr.json?name=${parsed.name}` : undefined;
|
||||
|
||||
switch (identity?.status) {
|
||||
case IdentityStatus.Missing:
|
||||
return <Text color="red.500">Unable to find DNS Identity in nostr.json file</Text>;
|
||||
case IdentityStatus.Error:
|
||||
return (
|
||||
<Text color="yellow.500">
|
||||
Unable to check DNS identity due to CORS error{" "}
|
||||
{nip05URL && (
|
||||
<Link
|
||||
color="blue.500"
|
||||
href={`https://cors-test.codehappy.dev/?url=${encodeURIComponent(nip05URL)}&method=get`}
|
||||
isExternal
|
||||
>
|
||||
Test
|
||||
<ExternalLinkIcon ml="1" />
|
||||
</Link>
|
||||
)}
|
||||
</Text>
|
||||
);
|
||||
case IdentityStatus.Found:
|
||||
if (identity.pubkey !== pubkey)
|
||||
return (
|
||||
<Text color="red.500" fontWeight="bold">
|
||||
Invalid DNS Identity!
|
||||
</Text>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
import DNSIdentityWarning from "../../settings/dns-identity/identity-warning";
|
||||
|
||||
export default function UserAboutTab() {
|
||||
const expanded = useDisclosure();
|
||||
@ -108,6 +71,8 @@ export default function UserAboutTab() {
|
||||
? `https://${parsedNip05.domain}/.well-known/nostr.json?name=${parsedNip05.name}`
|
||||
: undefined;
|
||||
|
||||
const identity = useUserDNSIdentity(pubkey);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
overflowY="auto"
|
||||
@ -202,7 +167,7 @@ export default function UserAboutTab() {
|
||||
<UserDnsIdentity pubkey={pubkey} />
|
||||
</Link>
|
||||
</Flex>
|
||||
<DNSIdentityWarning pubkey={pubkey} />
|
||||
{identity && <DNSIdentityWarning identity={identity} pubkey={pubkey} />}
|
||||
</Box>
|
||||
)}
|
||||
{metadata?.website && (
|
||||
|
@ -12,6 +12,7 @@ import { EditIcon } from "../../../components/icons";
|
||||
import { getPageTopic } from "../../../helpers/nostr/wiki";
|
||||
import GitBranch02 from "../../../components/icons/git-branch-02";
|
||||
import useShareableEventAddress from "../../../hooks/use-shareable-event-address";
|
||||
import DeleteEventMenuItem from "../../../components/common-menu-items/delete-event";
|
||||
|
||||
export default function WikiPageMenu({ page, ...props }: { page: NostrEvent } & Omit<MenuIconButtonProps, "children">) {
|
||||
const account = useActiveAccount();
|
||||
@ -32,6 +33,8 @@ export default function WikiPageMenu({ page, ...props }: { page: NostrEvent } &
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
<DeleteEventMenuItem event={page} label="Delete Page" />
|
||||
|
||||
<ShareLinkMenuItem event={page} />
|
||||
<CopyEmbedCodeMenuItem event={page} />
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user