diff --git a/src/hooks/use-dns-identity.ts b/src/hooks/use-dns-identity.ts index 57f209034..c34810691 100644 --- a/src/hooks/use-dns-identity.ts +++ b/src/hooks/use-dns-identity.ts @@ -6,7 +6,7 @@ import SuperMap from "../classes/super-map"; const parseCache = new SuperMap(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); diff --git a/src/views/messages/index.tsx b/src/views/messages/index.tsx index 936e153a5..d7ec4b4b8 100644 --- a/src/views/messages/index.tsx +++ b/src/views/messages/index.tsx @@ -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); diff --git a/src/views/relays/index.tsx b/src/views/relays/index.tsx index 4f80d9fce..9deec857a 100644 --- a/src/views/relays/index.tsx +++ b/src/views/relays/index.tsx @@ -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() { }> WebRTC Relays - {nip05?.exists && ( + {nip05?.status === IdentityStatus.Found && ( }> NIP-05 Relays diff --git a/src/views/relays/nip05/index.tsx b/src/views/relays/nip05/index.tsx index a77c4012e..92e9558b5 100644 --- a/src/views/relays/nip05/index.tsx +++ b/src/views/relays/nip05/index.tsx @@ -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() { - {nip05?.relays?.map((url) => )} + {nip05?.status === IdentityStatus.Found && nip05?.relays?.map((url) => )} ); } diff --git a/src/views/settings/dns-identity/identity-warning.tsx b/src/views/settings/dns-identity/identity-warning.tsx new file mode 100644 index 000000000..8ea74c8d9 --- /dev/null +++ b/src/views/settings/dns-identity/identity-warning.tsx @@ -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 Unable to find DNS Identity in nostr.json file; + case IdentityStatus.Error: + return ( + + Unable to check DNS identity due to CORS error{" "} + + Test + + + + ); + case IdentityStatus.Found: + if (identity.pubkey !== pubkey) + return ( + + Invalid DNS Identity! + + ); + default: + return null; + } +} diff --git a/src/views/settings/dns-identity/index.tsx b/src/views/settings/dns-identity/index.tsx new file mode 100644 index 000000000..5a230a9c2 --- /dev/null +++ b/src/views/settings/dns-identity/index.tsx @@ -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 ? ( + + } {...getSubmitButtonProps()} aria-label="save" /> + } {...getCancelButtonProps()} aria-label="Cancel" /> + + ) : ( + } {...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 ( + + + {/* Here is the custom input */} + + + + ); +} + +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 Unable to find DNS Identity in nostr.json file; + case IdentityStatus.Error: + return ( + + Unable to check DNS identity due to CORS error{" "} + + Test + + + + ); + case IdentityStatus.Found: + if (identity.pubkey !== pubkey) + return ( + + Invalid DNS Identity! + + ); + else + return ( + + DNS identity matches pubkey + + ); + default: + return null; + } + }; + + return ( + <> + + {renderDetails()} + {loading && } + + + ); +} + +export default function DnsIdentityView() { + const account = useActiveAccount(); + if (!account) return ; + + const profile = useUserProfile(account.pubkey, undefined, true); + + return ( + + {profile?.nip05 ? ( + + ) : ( + <> + No DNS identity setup + + + or read the details on the wiki: NIP-05 + + + )} + + ); +} diff --git a/src/views/settings/index.tsx b/src/views/settings/index.tsx index fff435c99..b2e134708 100644 --- a/src/views/settings/index.tsx +++ b/src/views/settings/index.tsx @@ -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() { }> Search + }> + DNS Identity + )} diff --git a/src/views/settings/relays/index.tsx b/src/views/settings/relays/index.tsx index ac3e12222..cc3d91009 100644 --- a/src/views/settings/relays/index.tsx +++ b/src/views/settings/relays/index.tsx @@ -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) )} - {nip05?.relays && ( + {nip05?.status === IdentityStatus.Found && (