fix NIP05 caching

This commit is contained in:
hzrd149 2025-02-14 11:59:33 -06:00
parent 258e4b9ff2
commit 7e388bd214
15 changed files with 662 additions and 758 deletions

1006
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -51,7 +51,7 @@ export function ImageGallery({ images, event }: { images: string[]; event?: Nost
const rowMultiplier = useBreakpointValue({ base: 1.5, sm: 2, md: 3, lg: 4, xl: 5 }) ?? 4;
return (
<ExpandableEmbed label="Image Gallery" hideOnDefaultOpen urls={images}>
<ExpandableEmbed label="Image Gallery" urls={images}>
<PhotoGallery
layout="rows"
photos={photos}

View File

@ -1,9 +1,10 @@
import { forwardRef } from "react";
import { IconProps } from "@chakra-ui/react";
import { IdentityStatus } from "applesauce-loaders/helpers/dns-identity";
import useDnsIdentity from "../../hooks/use-dns-identity";
import useUserProfile from "../../hooks/use-user-profile";
import { VerificationFailed, VerificationMissing, VerifiedIcon } from "../icons";
import { ErrorIcon, VerificationFailed, VerificationMissing, VerifiedIcon } from "../icons";
const UserDnsIdentityIcon = forwardRef<SVGSVGElement, { pubkey: string } & IconProps>(({ pubkey, ...props }, ref) => {
const metadata = useUserProfile(pubkey);
@ -11,14 +12,19 @@ const UserDnsIdentityIcon = forwardRef<SVGSVGElement, { pubkey: string } & IconP
if (!metadata?.nip05) return null;
if (identity === undefined) {
return <VerificationFailed color="yellow.500" {...props} ref={ref} />;
} else if (identity.exists === false || identity.pubkey === undefined) {
return <VerificationMissing color="red.500" {...props} ref={ref} />;
} else if (pubkey === identity.pubkey) {
return <VerifiedIcon color="purple.500" {...props} ref={ref} />;
} else {
return <VerificationFailed color="red.500" {...props} ref={ref} />;
switch (identity?.status) {
case IdentityStatus.Missing:
return <VerificationMissing color="red.500" {...props} ref={ref} />;
case IdentityStatus.Error:
return <ErrorIcon color="yellow.500" {...props} ref={ref} />;
case IdentityStatus.Found:
return identity.pubkey === pubkey ? (
<VerifiedIcon color="purple.500" {...props} ref={ref} />
) : (
<VerificationFailed color="red.500" {...props} ref={ref} />
);
default:
return <VerificationMissing color="blue.500" {...props} ref={ref} />;
}
});
export default UserDnsIdentityIcon;

View File

@ -1,12 +1,16 @@
import { useMemo } from "react";
import { useObservable } from "applesauce-react/hooks";
import { useAsync } from "react-use";
import { parseNIP05Address } from "applesauce-core/helpers";
import dnsIdentityService from "../services/dns-identity";
import dnsIdentityLoader from "../services/dns-identity-loader";
import SuperMap from "../classes/super-map";
const parseCache = new SuperMap<string, { name: string; domain: string } | null>(parseNIP05Address);
export default function useDnsIdentity(address: string | undefined) {
const subject = useMemo(() => {
if (address) return dnsIdentityService.getIdentity(address);
}, [address]);
const parsed = address ? parseCache.get(address) : null;
const { value: identity } = useAsync(async () => {
if (parsed) return await dnsIdentityLoader.requestIdentity(parsed.name, parsed.domain);
}, [parsed?.name, parsed?.domain]);
return useObservable(subject);
return identity;
}

View File

@ -3,8 +3,6 @@ import { clearDB, deleteDB as nostrIDBDelete } from "nostr-idb";
import {
SchemaV1,
SchemaV10,
SchemaV11,
SchemaV2,
SchemaV3,
SchemaV4,
@ -12,6 +10,9 @@ import {
SchemaV6,
SchemaV7,
SchemaV9,
SchemaV10,
SchemaV11,
SchemaV12,
} from "./schema";
import { logger } from "../../helpers/debug";
import { localDatabase } from "../cache-relay";
@ -19,8 +20,8 @@ import { localDatabase } from "../cache-relay";
const log = logger.extend("Database");
const dbName = "storage";
const version = 11;
const db = await openDB<SchemaV11>(dbName, version, {
const version = 12;
const db = await openDB<SchemaV12>(dbName, version, {
upgrade(db, oldVersion, newVersion, transaction, event) {
if (oldVersion < 1) {
const v0 = db as unknown as IDBPDatabase<SchemaV1>;
@ -202,6 +203,12 @@ const db = await openDB<SchemaV11>(dbName, version, {
v10.deleteObjectStore("accounts");
db.createObjectStore("accounts", { keyPath: "id" });
}
if (oldVersion < 12) {
const v11 = db as unknown as IDBPDatabase<SchemaV11>;
v11.deleteObjectStore("dnsIdentifiers");
db.createObjectStore("identities");
}
},
});

View File

@ -1,6 +1,8 @@
import { SerializedAccount } from "applesauce-accounts";
import { Identity } from "applesauce-loaders/helpers/dns-identity";
import { NostrEvent } from "../../types/nostr-event";
import { AppSettings } from "../../helpers/app-settings";
import { SerializedAccount } from "applesauce-accounts";
export interface SchemaV1 {
userMetadata: {
@ -160,3 +162,9 @@ export interface SchemaV11 extends Omit<SchemaV10, "accounts"> {
value: SerializedAccount<any, { settings?: AppSettings }>;
};
}
export interface SchemaV12 extends Omit<SchemaV11, "dnsIdentifiers"> {
identities: {
key: string;
value: Identity;
};
}

View File

@ -0,0 +1,24 @@
import db from "./db";
import _throttle from "lodash.throttle";
import { DnsIdentityLoader } from "applesauce-loaders/loaders/dns-identity-loader";
import { fetchWithProxy } from "../helpers/request";
export const dnsIdentityLoader = new DnsIdentityLoader({
save: async (identities) => {
const tx = db.transaction("identities", "readwrite");
for (const [address, identity] of Object.entries(identities)) {
tx.store.put(identity, address);
}
await tx.done;
},
load: async (address) => db.get("identities", address),
});
dnsIdentityLoader.fetch = fetchWithProxy;
if (import.meta.env.DEV) {
// @ts-expect-error debug
window.dnsIdentityLoader = dnsIdentityLoader;
}
export default dnsIdentityLoader;

View File

@ -1,162 +0,0 @@
import dayjs from "dayjs";
import db from "./db";
import _throttle from "lodash.throttle";
import { BehaviorSubject } from "rxjs";
import { fetchWithProxy } from "../helpers/request";
import SuperMap from "../classes/super-map";
export function parseAddress(address: string): { name?: string; domain?: string } {
const parts = address.trim().toLowerCase().split("@");
if (parts.length === 1) return { name: "_", domain: parts[0] };
else return { name: parts[0], domain: parts[1] };
}
type IdentityJson = {
names?: Record<string, string | undefined>;
relays?: Record<string, string[]>;
nip46?: Record<string, string[]>;
};
export type DnsIdentity = {
name: string;
domain: string;
/** If the nostr.json file exists */
exists: boolean;
/** pubkey found for name */
pubkey?: string;
/** relays found for name */
relays?: string[];
hasNip46?: boolean;
nip46Relays?: string[];
};
function getIdentityFromJson(name: string, domain: string, json: IdentityJson): DnsIdentity {
if (!json.names) return { name, domain, exists: true };
const pubkey = json.names[name];
if (!pubkey) return { name, domain, exists: true };
const relays: string[] = json.relays?.[pubkey] ?? [];
const hasNip46 = !!json.nip46;
const nip46Relays = json.nip46?.[pubkey];
return { name, domain, pubkey, relays, nip46Relays, hasNip46, exists: true };
}
class DnsIdentityService {
// undefined === loading
identities = new SuperMap<string, BehaviorSubject<DnsIdentity | undefined>>(
() => new BehaviorSubject<DnsIdentity | undefined>(undefined),
);
async fetchIdentity(address: string): Promise<DnsIdentity> {
const { name, domain } = parseAddress(address);
if (!name || !domain) throw new Error("invalid address " + address);
const res = await fetchWithProxy(`https://${domain}/.well-known/nostr.json?name=${name}`);
// if request was rejected consider identity invalid
if (res.status >= 400 && res.status < 500) return { name, domain, exists: false };
const json = await (res.json() as Promise<IdentityJson>).then((json) => {
// convert all keys in names, and relays to lower case
if (json.names) {
for (const [name, pubkey] of Object.entries(json.names)) {
delete json.names[name];
json.names[name.toLowerCase()] = pubkey;
}
}
if (json.relays) {
for (const [name, pubkey] of Object.entries(json.relays)) {
delete json.relays[name];
json.relays[name.toLowerCase()] = pubkey;
}
}
return json;
});
await this.addToCache(domain, json);
return getIdentityFromJson(name, domain, json);
}
async addToCache(domain: string, json: IdentityJson) {
if (!json.names) return;
const now = dayjs().unix();
const transaction = db.transaction("dnsIdentifiers", "readwrite");
for (const name of Object.keys(json.names)) {
const identity = getIdentityFromJson(name, domain, json);
if (identity && identity.exists && identity.pubkey) {
const address = `${name}@${domain}`;
// add to memory cache
this.identities.get(address).next(identity);
// ad to db cache
if (transaction.store.put) {
await transaction.store.put(
{
name: identity.name,
domain: identity.domain,
pubkey: identity.pubkey,
relays: identity.relays ?? [],
updated: now,
},
address,
);
}
}
}
await transaction.done;
}
loading = new Set<string>();
getIdentity(address: string, alwaysFetch = false) {
const sub = this.identities.get(address);
if (this.loading.has(address)) return sub;
this.loading.add(address);
db.get("dnsIdentifiers", address).then((fromDb) => {
if (fromDb) sub.next({ exists: true, ...fromDb });
this.loading.delete(address);
});
if (!sub.value || alwaysFetch) {
this.fetchIdentity(address)
.then((identity) => {
sub.next(identity);
})
.finally(() => {
this.loading.delete(address);
});
}
return sub;
}
async pruneCache() {
const keys = await db.getAllKeysFromIndex(
"dnsIdentifiers",
"updated",
IDBKeyRange.upperBound(dayjs().subtract(1, "day").unix()),
);
for (const pubkey of keys) {
db.delete("dnsIdentifiers", pubkey);
}
}
}
export const dnsIdentityService = new DnsIdentityService();
setInterval(() => {
dnsIdentityService.pruneCache();
}, 1000 * 60);
if (import.meta.env.DEV) {
// @ts-expect-error debug
window.dnsIdentityService = dnsIdentityService;
}
export default dnsIdentityService;

View File

@ -54,7 +54,7 @@ function ListPage({ list }: { list: NostrEvent }) {
return (
<TrustProvider trust>
<VerticalPageLayout overflow="hidden" h="full">
<VerticalPageLayout h="full">
<Flex gap="2" alignItems="center">
<Button onClick={() => navigate(-1)} leftIcon={<ChevronLeftIcon />}>
Back

View File

@ -107,6 +107,11 @@ function MessagesHomePage() {
case "muted":
filtered = conversations.filter((c) => mutes?.pubkeys?.has(c.correspondent));
break;
case "other":
filtered = conversations.filter(
(c) => !contacts?.includes(c.correspondent) && !mutes?.pubkeys.has(c.correspondent),
);
break;
}
return filtered.sort((a, b) => b.messages[0].created_at - a.messages[0].created_at);

View File

@ -15,20 +15,21 @@ import {
VisuallyHiddenInput,
} from "@chakra-ui/react";
import { useForm } from "react-hook-form";
import { ProfileContent, unixNow } from "applesauce-core/helpers";
import { parseNIP05Address, ProfileContent, unixNow } from "applesauce-core/helpers";
import { ExternalLinkIcon, OutboxIcon } from "../../components/icons";
import { isLNURL } from "../../helpers/lnurl";
import { useReadRelays } from "../../hooks/use-client-relays";
import { useActiveAccount } from "applesauce-react/hooks";
import useUserProfile from "../../hooks/use-user-profile";
import dnsIdentityService from "../../services/dns-identity";
import dnsIdentityLoader from "../../services/dns-identity-loader";
import { DraftNostrEvent } from "../../types/nostr-event";
import lnurlMetadataService from "../../services/lnurl-metadata";
import VerticalPageLayout from "../../components/vertical-page-layout";
import { COMMON_CONTACT_RELAYS } from "../../const";
import { usePublishEvent } from "../../providers/global/publish-provider";
import { useInputUploadFileWithForm } from "../../hooks/use-input-upload-file";
import { IdentityStatus } from "applesauce-loaders/helpers/dns-identity";
const isEmail =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
@ -195,13 +196,20 @@ const MetadataForm = ({ defaultValues, onSubmit }: MetadataFormProps) => {
validate: async (address) => {
if (!address) return true;
if (!address.includes("@")) return "Invalid address";
try {
const id = await dnsIdentityService.fetchIdentity(address);
if (!id) return "Cant find NIP-05 ID";
if (id.pubkey !== account.pubkey) return "Pubkey does not match";
} catch (e) {
return "Failed to fetch ID";
const { name, domain } = parseNIP05Address(address) || {};
if (!name || !domain) return "Failed to parsed address";
const identity = await dnsIdentityLoader.fetchIdentity(name, domain);
switch (identity.status) {
case IdentityStatus.Error:
return "Failed to connect to server";
case IdentityStatus.Missing:
return "Identity missing from server";
case IdentityStatus.Found:
if (identity.pubkey !== account.pubkey) return "Pubkey does not match";
}
return true;
},
})}

View File

@ -2,13 +2,14 @@ import { Button, Flex, FormControl, FormHelperText, FormLabel, Heading, Input, u
import { getPublicKey, nip19 } from "nostr-tools";
import { useForm } from "react-hook-form";
import { Navigate } from "react-router-dom";
import { isHexKey } from "applesauce-core/helpers";
import { isHexKey, parseNIP05Address } from "applesauce-core/helpers";
import { useObservable } from "applesauce-react/hooks";
import { bakery$, controlApi$, setBakeryURL } from "../../../../services/bakery";
import useRouteSearchValue from "../../../../hooks/use-route-search-value";
import dnsIdentityService from "../../../../services/dns-identity";
import dnsIdentityLoader from "../../../../services/dns-identity-loader";
import QRCodeScannerButton from "../../../../components/qr-code/qr-code-scanner-button";
import { IdentityStatus } from "applesauce-loaders/helpers/dns-identity";
export default function BakerySetupView() {
const toast = useToast();
@ -54,9 +55,11 @@ export default function BakerySetupView() {
if (!pubkey && values.owner.includes("@")) {
try {
const id = await dnsIdentityService.fetchIdentity(values.owner);
if (!id || !id.pubkey) throw new Error("Missing pubkey in NIP-05");
pubkey = id.pubkey;
const { name, domain } = parseNIP05Address(values.owner) || {};
if (!name || !domain) throw new Error("Invalid address");
const identity = await dnsIdentityLoader.fetchIdentity(name, domain);
if (identity.status !== IdentityStatus.Found) throw new Error("Missing pubkey in NIP-05");
pubkey = identity.pubkey;
} catch (error) {
throw new Error("Unable to find NIP-05 ID");
}

View File

@ -18,7 +18,7 @@ import {
import { useNavigate } from "react-router-dom";
import { NostrEvent } from "nostr-tools";
import { NostrConnectSigner } from "applesauce-signers/signers/nostr-connect-signer";
import { ProfileContent, safeParse } from "applesauce-core/helpers";
import { parseNIP05Address, ProfileContent, safeParse } from "applesauce-core/helpers";
import { useAccountManager } from "applesauce-react/hooks";
import { NostrConnectAccount } from "applesauce-accounts/accounts";
@ -28,10 +28,11 @@ import HoverLinkOverlay from "../../../components/hover-link-overlay";
import { getEventCoordinate } from "../../../helpers/nostr/event";
import { MetadataAvatar } from "../../../components/user/user-avatar";
import { ErrorBoundary } from "../../../components/error-boundary";
import dnsIdentityService from "../../../services/dns-identity";
import dnsIdentityLoader from "../../../services/dns-identity-loader";
import useUserProfile from "../../../hooks/use-user-profile";
import { safeRelayUrls } from "../../../helpers/relay";
import { createNostrConnectConnection } from "../../../classes/nostr-connect-connection";
import { IdentityStatus } from "applesauce-loaders/helpers/dns-identity";
function ProviderCard({ onClick, provider }: { onClick: () => void; provider: NostrEvent }) {
const metadata = JSON.parse(provider.content) as ProfileContent;
@ -86,20 +87,25 @@ export default function LoginNostrAddressCreate() {
setLoading("Creating...");
const metadata: ProfileContent = { ...userMetadata, ...providerMetadata };
if (!metadata.nip05) throw new Error("Provider missing nip05 address");
const nip05 = await dnsIdentityService.fetchIdentity(metadata.nip05);
if (!nip05 || nip05.pubkey !== selected.pubkey) throw new Error("Invalid provider");
if (nip05.name !== "_") throw new Error("Provider does not own the domain");
if (!nip05.hasNip46) throw new Error("Provider does not support NIP-46");
const relays = safeRelayUrls(nip05.nip46Relays || nip05.relays || []);
const { name, domain } = parseNIP05Address(metadata.nip05) || {};
if (!name || !domain) throw new Error("Invalid DNS identity");
const identity = await dnsIdentityLoader.requestIdentity(name, domain);
if (identity.status === IdentityStatus.Error) throw new Error("Failed to fetch identity");
if (identity.status === IdentityStatus.Missing) throw new Error("Cant find identity");
if (identity.pubkey !== selected.pubkey) throw new Error("Invalid provider");
if (identity.name !== "_") throw new Error("Provider does not own the domain");
if (!identity.hasNip46) throw new Error("Provider does not support NIP-46");
const relays = safeRelayUrls(identity.nip46Relays || identity.relays || []);
if (relays.length === 0) throw new Error("Cant find providers relays");
const signer = new NostrConnectSigner({
relays,
remote: nip05.pubkey,
remote: identity.pubkey,
...createNostrConnectConnection(),
});
const createPromise = signer.createAccount(name, nip05.domain, undefined, NOSTR_CONNECT_PERMISSIONS);
const createPromise = signer.createAccount(name, identity.domain, undefined, NOSTR_CONNECT_PERMISSIONS);
await createPromise;
await signer.connect(undefined, NOSTR_CONNECT_PERMISSIONS);

View File

@ -4,10 +4,11 @@ import { useNavigate, Link as RouterLink } from "react-router-dom";
import { NostrConnectSigner } from "applesauce-signers/signers/nostr-connect-signer";
import { useAccountManager } from "applesauce-react/hooks";
import { NostrConnectAccount, ReadonlyAccount } from "applesauce-accounts/accounts";
import { Identity, IdentityStatus } from "applesauce-loaders/helpers/dns-identity";
import { ReadonlySigner } from "applesauce-signers";
import { useDebounce } from "react-use";
import dnsIdentityService, { DnsIdentity } from "../../../services/dns-identity";
import dnsIdentityLoader from "../../../services/dns-identity-loader";
import { CheckIcon } from "../../../components/icons";
import { NOSTR_CONNECT_PERMISSIONS } from "../../../const";
import { safeRelayUrls } from "../../../helpers/relay";
@ -22,16 +23,16 @@ export default function LoginNostrAddressView() {
const [address, setAddress] = useState("");
const [nip05, setNip05] = useState<DnsIdentity | null>();
const [rootNip05, setRootNip05] = useState<DnsIdentity | null>();
const [nip05, setNip05] = useState<Identity | null>();
const [rootNip05, setRootNip05] = useState<Identity | null>();
useDebounce(
async () => {
if (!address) return setNip05(undefined);
if (!getMatchSimpleEmail().test(address)) return setNip05(undefined);
const [name, domain] = address.split("@");
if (!name || !domain) return setNip05(undefined);
setNip05((await dnsIdentityService.fetchIdentity(address)) ?? null);
setRootNip05((await dnsIdentityService.fetchIdentity(`_@${domain}`)) ?? null);
setNip05((await dnsIdentityLoader.fetchIdentity(name, domain)) ?? null);
setRootNip05((await dnsIdentityLoader.fetchIdentity("_", domain)) ?? null);
},
300,
[address],
@ -40,14 +41,12 @@ export default function LoginNostrAddressView() {
const [loading, setLoading] = useState<string | undefined>();
const connect: React.FormEventHandler<HTMLDivElement> = async (e) => {
e.preventDefault();
if (!nip05) return;
if (nip05?.status !== IdentityStatus.Found) return;
try {
if (nip05.hasNip46 && nip05.pubkey) {
setLoading("Connecting...");
const relays = safeRelayUrls(
nip05.nip46Relays || rootNip05?.nip46Relays || rootNip05?.relays || nip05.relays || [],
);
const relays = safeRelayUrls(nip05.nip46Relays || nip05.relays || []);
const signer = new NostrConnectSigner({
pubkey: nip05.pubkey,
relays,
@ -82,7 +81,7 @@ export default function LoginNostrAddressView() {
flexWrap: "wrap",
};
if (nip05) {
if (nip05?.status === IdentityStatus.Found) {
if (nip05.hasNip46) {
return (
<Card {...cardProps}>

View File

@ -19,10 +19,9 @@ import {
} from "@chakra-ui/react";
import { nip19 } from "nostr-tools";
import { ChatIcon } from "@chakra-ui/icons";
import { parseLNURLOrAddress } from "applesauce-core/helpers";
import { parseLNURLOrAddress, parseNIP05Address } from "applesauce-core/helpers";
import { truncatedId } from "../../../helpers/nostr/event";
import { parseAddress } from "../../../services/dns-identity";
import { useAdditionalRelayContext } from "../../../providers/local/additional-relay-context";
import useUserProfile from "../../../hooks/use-user-profile";
import {
@ -50,43 +49,46 @@ import UserName from "../../../components/user/user-name";
import { useUserDNSIdentity } from "../../../hooks/use-user-dns-identity";
import UserAboutContent from "../../../components/user/user-about-content";
import UserRecentEvents from "./user-recent-events";
import useAppSettings, { useUserAppSettings } from "../../../hooks/use-user-app-settings";
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 dnsIdentity = useUserDNSIdentity(pubkey);
const parsedNip05 = metadata?.nip05 ? parseAddress(metadata.nip05) : undefined;
const nip05URL = parsedNip05
? `https://${parsedNip05.domain}/.well-known/nostr.json?name=${parsedNip05.name}`
: undefined;
const identity = useUserDNSIdentity(pubkey);
const parsed = metadata?.nip05 ? parseNIP05Address(metadata.nip05) : undefined;
if (dnsIdentity === undefined)
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>
);
else if (dnsIdentity.exists === false) return <Text color="red.500">Unable to find nostr.json file</Text>;
else if (dnsIdentity.pubkey === undefined)
return <Text color="red.500">Unable to find DNS Identity in nostr.json file</Text>;
else if (dnsIdentity.pubkey === pubkey) return null;
else
return (
<Text color="red.500" fontWeight="bold">
Invalid DNS Identity!
</Text>
);
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;
}
}
export default function UserAboutTab() {
@ -101,7 +103,7 @@ export default function UserAboutTab() {
const pubkeyColor = "#" + pubkey.slice(0, 6);
const settings = useUserAppSettings(pubkey);
const parsedNip05 = metadata?.nip05 ? parseAddress(metadata.nip05) : undefined;
const parsedNip05 = metadata?.nip05 ? parseNIP05Address(metadata.nip05) : undefined;
const nip05URL = parsedNip05
? `https://${parsedNip05.domain}/.well-known/nostr.json?name=${parsedNip05.name}`
: undefined;