import { useEffect, useMemo, useState } from "react"; import { useIntl, FormattedMessage } from "react-intl"; import { useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { ServiceProvider, ServiceConfig, ServiceError, HandleAvailability, ServiceErrorCode, HandleRegisterResponse, CheckRegisterResponse, } from "Nip05/ServiceProvider"; import AsyncButton from "Element/AsyncButton"; import SendSats from "Element/SendSats"; import Copy from "Element/Copy"; import { useUserProfile } from "Feed/ProfileFeed"; import useEventPublisher from "Feed/EventPublisher"; import { debounce, hexToBech32 } from "Util"; import { UserMetadata } from "Nostr"; import messages from "./messages"; type Nip05ServiceProps = { name: string; service: URL | string; about: JSX.Element; link: string; supportLink: string; }; interface ReduxStore { login: { publicKey: string }; } export default function Nip5Service(props: Nip05ServiceProps) { const navigate = useNavigate(); const { formatMessage } = useIntl(); const pubkey = useSelector((s) => s.login.publicKey); const user = useUserProfile(pubkey); const publisher = useEventPublisher(); const svc = useMemo( () => new ServiceProvider(props.service), [props.service] ); const [serviceConfig, setServiceConfig] = useState(); const [error, setError] = useState(); const [handle, setHandle] = useState(""); const [domain, setDomain] = useState(""); const [availabilityResponse, setAvailabilityResponse] = useState(); const [registerResponse, setRegisterResponse] = useState(); const [showInvoice, setShowInvoice] = useState(false); const [registerStatus, setRegisterStatus] = useState(); const domainConfig = useMemo( () => serviceConfig?.domains.find((a) => a.name === domain), [domain, serviceConfig] ); useEffect(() => { svc .GetConfig() .then((a) => { if ("error" in a) { setError(a as ServiceError); } else { const svc = a as ServiceConfig; setServiceConfig(svc); const defaultDomain = svc.domains.find((a) => a.default)?.name || svc.domains[0].name; setDomain(defaultDomain); } }) .catch(console.error); }, [props, svc]); useEffect(() => { setError(undefined); setAvailabilityResponse(undefined); if (handle && domain) { if (handle.length < (domainConfig?.length[0] ?? 2)) { setAvailabilityResponse({ available: false, why: "TOO_SHORT" }); return; } if (handle.length > (domainConfig?.length[1] ?? 20)) { setAvailabilityResponse({ available: false, why: "TOO_LONG" }); return; } const rx = new RegExp( domainConfig?.regex[0] ?? "", domainConfig?.regex[1] ?? "" ); if (!rx.test(handle)) { setAvailabilityResponse({ available: false, why: "REGEX" }); return; } return debounce(500, () => { svc .CheckAvailable(handle, domain) .then((a) => { if ("error" in a) { setError(a as ServiceError); } else { setAvailabilityResponse(a as HandleAvailability); } }) .catch(console.error); }); } }, [handle, domain, domainConfig, svc]); useEffect(() => { if (registerResponse && showInvoice) { const t = setInterval(async () => { const status = await svc.CheckRegistration(registerResponse.token); if ("error" in status) { setError(status); setRegisterResponse(undefined); setShowInvoice(false); } else { const result: CheckRegisterResponse = status; if (result.available && result.paid) { setShowInvoice(false); setRegisterStatus(status); setRegisterResponse(undefined); setError(undefined); } } }, 2_000); return () => clearInterval(t); } }, [registerResponse, showInvoice, svc]); function mapError( e: ServiceErrorCode | undefined, t: string | null ): string | undefined { if (e === undefined) { return undefined; } const whyMap = new Map([ ["TOO_SHORT", formatMessage(messages.TooShort)], ["TOO_LONG", formatMessage(messages.TooLong)], ["REGEX", formatMessage(messages.Regex)], ["REGISTERED", formatMessage(messages.Registered)], ["DISALLOWED_null", formatMessage(messages.Disallowed)], ["DISALLOWED_later", formatMessage(messages.DisalledLater)], ]); return whyMap.get(e === "DISALLOWED" ? `${e}_${t}` : e); } async function startBuy(handle: string, domain: string) { if (registerResponse) { setShowInvoice(true); return; } const rsp = await svc.RegisterHandle(handle, domain, pubkey); if ("error" in rsp) { setError(rsp); } else { setRegisterResponse(rsp); setShowInvoice(true); } } async function updateProfile(handle: string, domain: string) { if (user) { const newProfile = { ...user, nip05: `${handle}@${domain}`, } as UserMetadata; const ev = await publisher.metadata(newProfile); publisher.broadcast(ev); navigate("/settings"); } } return ( <>

{props.name}

{props.about}

{props.link} ), }} />

{error && {error.error}} {!registerStatus && (
setHandle(e.target.value.toLowerCase())} />  @ 
)} {availabilityResponse?.available && !registerStatus && (

{availabilityResponse.quote?.data.type}
startBuy(handle, domain)}>
)} {availabilityResponse?.available === false && !registerStatus && (
{" "} {mapError( availabilityResponse.why, availabilityResponse.reasonTag || null )}
)} setShowInvoice(false)} title={formatMessage(messages.Buying, { item: `${handle}@${domain}` })} /> {registerStatus?.paid && (

{" "} {handle}@{domain}

{" "}

updateProfile(handle, domain)}>
)} ); }