mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-17 13:21:44 +01:00
fix NIP05 caching
This commit is contained in:
parent
258e4b9ff2
commit
7e388bd214
1006
pnpm-lock.yaml
generated
1006
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -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}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
24
src/services/dns-identity-loader.ts
Normal file
24
src/services/dns-identity-loader.ts
Normal 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;
|
@ -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;
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
},
|
||||
})}
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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}>
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user