mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-26 17:52:18 +01:00
add details to DNS identity
This commit is contained in:
parent
08b5503815
commit
4e3c9070ed
@ -9,7 +9,7 @@ import { Kind0ParsedContent, getDisplayName } from "../../helpers/nostr/user-met
|
||||
import useAppSettings from "../../hooks/use-app-settings";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import { buildImageProxyURL } from "../../helpers/image";
|
||||
import UserDnsIdentityIcon, { useDnsIdentityColor } from "./user-dns-identity-icon";
|
||||
import UserDnsIdentityIcon from "./user-dns-identity-icon";
|
||||
import styled from "@emotion/styled";
|
||||
|
||||
export const UserIdenticon = memo(({ pubkey }: { pubkey: string }) => {
|
||||
|
@ -1,41 +1,19 @@
|
||||
import { forwardRef } from "react";
|
||||
import { IconProps, useColorMode } from "@chakra-ui/react";
|
||||
import { IconProps } from "@chakra-ui/react";
|
||||
|
||||
import useDnsIdentity from "../../hooks/use-dns-identity";
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import { VerificationFailed, VerificationMissing, VerifiedIcon } from "../icons";
|
||||
|
||||
export function useDnsIdentityColor(pubkey: string) {
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const identity = useDnsIdentity(metadata?.nip05);
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
if (!metadata?.nip05) {
|
||||
return colorMode === "light" ? "gray.200" : "gray.800";
|
||||
}
|
||||
|
||||
if (identity === undefined) {
|
||||
return "yellow.500";
|
||||
} else if (identity === null) {
|
||||
return "red.500";
|
||||
} else if (pubkey === identity.pubkey) {
|
||||
return "purple.500";
|
||||
} else {
|
||||
return "red.500";
|
||||
}
|
||||
}
|
||||
|
||||
const UserDnsIdentityIcon = forwardRef<SVGSVGElement, { pubkey: string } & IconProps>(({ pubkey, ...props }, ref) => {
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const identity = useDnsIdentity(metadata?.nip05);
|
||||
|
||||
if (!metadata?.nip05) {
|
||||
return null;
|
||||
}
|
||||
if (!metadata?.nip05) return null;
|
||||
|
||||
if (identity === undefined) {
|
||||
return <VerificationFailed color="yellow.500" {...props} ref={ref} />;
|
||||
} else if (identity === null) {
|
||||
} 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} />;
|
||||
|
@ -12,54 +12,63 @@ export function parseAddress(address: string): { name?: string; domain?: string
|
||||
}
|
||||
|
||||
type IdentityJson = {
|
||||
names: Record<string, string | undefined>;
|
||||
names?: Record<string, string | undefined>;
|
||||
relays?: Record<string, string[]>;
|
||||
nip46?: Record<string, string[]>;
|
||||
};
|
||||
export type DnsIdentity = {
|
||||
name: string;
|
||||
domain: string;
|
||||
pubkey: string;
|
||||
relays: 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 | undefined {
|
||||
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;
|
||||
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 };
|
||||
return { name, domain, pubkey, relays, nip46Relays, hasNip46, exists: true };
|
||||
}
|
||||
|
||||
class DnsIdentityService {
|
||||
identities = new SuperMap<string, Subject<DnsIdentity | null>>(() => new Subject());
|
||||
// undefined === loading
|
||||
identities = new SuperMap<string, Subject<DnsIdentity | undefined>>(() => new Subject());
|
||||
|
||||
async fetchIdentity(address: string) {
|
||||
async fetchIdentity(address: string): Promise<DnsIdentity> {
|
||||
const { name, domain } = parseAddress(address);
|
||||
if (!name || !domain) throw new Error("invalid address " + address);
|
||||
|
||||
const json = await fetchWithProxy(`https://${domain}/.well-known/nostr.json?name=${name}`)
|
||||
.then((res) => 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (json.relays) {
|
||||
for (const [name, pubkey] of Object.entries(json.relays)) {
|
||||
delete json.relays[name];
|
||||
json.relays[name.toLowerCase()] = pubkey;
|
||||
}
|
||||
return json;
|
||||
});
|
||||
}
|
||||
return json;
|
||||
});
|
||||
|
||||
await this.addToCache(domain, json);
|
||||
|
||||
@ -67,12 +76,14 @@ class DnsIdentityService {
|
||||
}
|
||||
|
||||
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) {
|
||||
if (identity && identity.exists && identity.pubkey) {
|
||||
const address = `${name}@${domain}`;
|
||||
|
||||
// add to memory cache
|
||||
@ -80,7 +91,16 @@ class DnsIdentityService {
|
||||
|
||||
// ad to db cache
|
||||
if (transaction.store.put) {
|
||||
await transaction.store.put({ ...identity, updated: now }, address);
|
||||
await transaction.store.put(
|
||||
{
|
||||
name: identity.name,
|
||||
domain: identity.domain,
|
||||
pubkey: identity.pubkey,
|
||||
relays: identity.relays ?? [],
|
||||
updated: now,
|
||||
},
|
||||
address,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -95,14 +115,14 @@ class DnsIdentityService {
|
||||
this.loading.add(address);
|
||||
|
||||
db.get("dnsIdentifiers", address).then((fromDb) => {
|
||||
if (fromDb) sub.next(fromDb);
|
||||
if (fromDb) sub.next({ exists: true, ...fromDb });
|
||||
this.loading.delete(address);
|
||||
});
|
||||
|
||||
if (!sub.value || alwaysFetch) {
|
||||
this.fetchIdentity(address)
|
||||
.then((identity) => {
|
||||
sub.next(identity ?? null);
|
||||
sub.next(identity);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading.delete(address);
|
||||
|
@ -13,7 +13,6 @@ import RelaySet from "../../../classes/relay-set";
|
||||
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";
|
||||
|
@ -69,7 +69,7 @@ export default function RelaysView() {
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{nip05 && (
|
||||
{nip05?.exists && (
|
||||
<Button
|
||||
variant="outline"
|
||||
as={RouterLink}
|
||||
|
@ -42,7 +42,7 @@ export default function NIP05RelaysView() {
|
||||
</Link>
|
||||
</Text>
|
||||
|
||||
{nip05?.relays.map((url) => <RelayItem key={url} url={url} />)}
|
||||
{nip05?.relays?.map((url) => <RelayItem key={url} url={url} />)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ export default function LoginNostrAddressCreate() {
|
||||
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 relays = safeRelayUrls(nip05.nip46Relays || nip05.relays || []);
|
||||
if (relays.length === 0) throw new Error("Cant find providers relays");
|
||||
|
||||
const client = nostrConnectService.createClient("", relays, undefined, nip05.pubkey);
|
||||
|
@ -39,24 +39,26 @@ export default function LoginNostrAddressView() {
|
||||
if (!nip05) return;
|
||||
|
||||
try {
|
||||
if (nip05.hasNip46) {
|
||||
if (nip05.hasNip46 && nip05.pubkey) {
|
||||
setLoading("Connecting...");
|
||||
const relays = safeRelayUrls(nip05.nip46Relays || rootNip05?.nip46Relays || rootNip05?.relays || nip05.relays);
|
||||
const relays = safeRelayUrls(
|
||||
nip05.nip46Relays || rootNip05?.nip46Relays || rootNip05?.relays || nip05.relays || [],
|
||||
);
|
||||
const client = nostrConnectService.fromHostedBunker(nip05.pubkey, relays);
|
||||
await client.connect();
|
||||
|
||||
nostrConnectService.saveClient(client);
|
||||
accountService.addFromNostrConnect(client);
|
||||
accountService.switchAccount(client.pubkey!);
|
||||
} else {
|
||||
} else if (nip05.pubkey) {
|
||||
accountService.addAccount({
|
||||
type: "pubkey",
|
||||
pubkey: nip05.pubkey,
|
||||
relays: [...nip05.relays, COMMON_CONTACT_RELAY],
|
||||
relays: nip05.relays ? [...nip05.relays, COMMON_CONTACT_RELAY] : [COMMON_CONTACT_RELAY],
|
||||
readonly: true,
|
||||
});
|
||||
accountService.switchAccount(nip05.pubkey);
|
||||
}
|
||||
} else throw Error("Cant find address");
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ status: "error", description: e.message });
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ import UserStatsAccordion from "./user-stats-accordion";
|
||||
import UserJoinedChanneled from "./user-joined-channels";
|
||||
import { getTextColor } from "../../../helpers/color";
|
||||
import UserName from "../../../components/user/user-name";
|
||||
import { useUserDNSIdentity } from "../../../hooks/use-user-dns-identity";
|
||||
|
||||
function buildDescriptionContent(description: string) {
|
||||
let content: EmbedableContent = [description.trim()];
|
||||
@ -60,6 +61,42 @@ function buildDescriptionContent(description: string) {
|
||||
return content;
|
||||
}
|
||||
|
||||
function DNSIdentityWarning({ pubkey }: { pubkey: string }) {
|
||||
const metadata = useUserMetadata(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;
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
export default function UserAboutTab() {
|
||||
const expanded = useDisclosure();
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
@ -69,10 +106,13 @@ export default function UserAboutTab() {
|
||||
const metadata = useUserMetadata(pubkey, contextRelays);
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
const nprofile = useSharableProfileId(pubkey);
|
||||
const pubkeyColor = "#" + pubkey.slice(0, 6);
|
||||
|
||||
const aboutContent = metadata?.about && buildDescriptionContent(metadata?.about);
|
||||
const parsedNip05 = metadata?.nip05 ? parseAddress(metadata.nip05) : undefined;
|
||||
const pubkeyColor = "#" + pubkey.slice(0, 6);
|
||||
const nip05URL = parsedNip05
|
||||
? `https://${parsedNip05.domain}/.well-known/nostr.json?name=${parsedNip05.name}`
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@ -162,13 +202,16 @@ export default function UserAboutTab() {
|
||||
</Link>
|
||||
</Flex>
|
||||
)}
|
||||
{parsedNip05 && (
|
||||
<Flex gap="2">
|
||||
<AtIcon />
|
||||
<Link href={`//${parsedNip05.domain}/.well-known/nostr.json?name=${parsedNip05.name}`} isExternal>
|
||||
<UserDnsIdentity pubkey={pubkey} />
|
||||
</Link>
|
||||
</Flex>
|
||||
{nip05URL && (
|
||||
<Box>
|
||||
<Flex gap="2">
|
||||
<AtIcon />
|
||||
<Link href={nip05URL} isExternal>
|
||||
<UserDnsIdentity pubkey={pubkey} />
|
||||
</Link>
|
||||
</Flex>
|
||||
<DNSIdentityWarning pubkey={pubkey} />
|
||||
</Box>
|
||||
)}
|
||||
{metadata?.website && (
|
||||
<Flex gap="2">
|
||||
|
Loading…
x
Reference in New Issue
Block a user