From b3048086c389a69952309722ae5a673e8049651f Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 12 May 2024 02:10:15 -0500 Subject: [PATCH] Add await registry-informed getTypes method with smart entity tag validation fuck type coloring madness --- src/components/form/LookupInput.tsx | 6 +-- src/hooks/useLookup.tsx | 34 +++++++++----- src/pages/index.tsx | 7 ++- src/rdap.ts | 70 ++++++++++++++++++++++------- 4 files changed, 84 insertions(+), 33 deletions(-) diff --git a/src/components/form/LookupInput.tsx b/src/components/form/LookupInput.tsx index 606f290..7609cfa 100644 --- a/src/components/form/LookupInput.tsx +++ b/src/components/form/LookupInput.tsx @@ -46,7 +46,7 @@ type LookupInputProps = { onChange?: (target: { target: string; targetType: TargetType | null; - }) => void; + }) => Promise; detectedType: Maybe; }; @@ -155,7 +155,7 @@ const LookupInput: FunctionComponent = ({ required: true, onChange: () => { if (onChange != undefined) - onChange({ + void onChange({ target: getValues("target"), // dropdown target will be pulled from state anyways, so no need to provide it here targetType: retrieveTargetType(null), @@ -172,7 +172,7 @@ const LookupInput: FunctionComponent = ({ setSelected(value); if (onChange != undefined) - onChange({ + void onChange({ target: getValues("target"), // we provide the value as the state will not have updated yet for this context targetType: retrieveTargetType(value), diff --git a/src/hooks/useLookup.tsx b/src/hooks/useLookup.tsx index 2282d4a..e086ff4 100644 --- a/src/hooks/useLookup.tsx +++ b/src/hooks/useLookup.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { domainMatchPredicate, getBestURL, getType } from "@/rdap"; import type { AutonomousNumber, @@ -43,19 +43,19 @@ const useLookup = (warningHandler?: WarningHandler) => { const [error, setError] = useState(null); const [target, setTarget] = useState(""); + const [uriType, setUriType] = useState>(Maybe.nothing()); + // Used by a callback on LookupInput to forcibly set the type of the lookup. const [currentType, setTargetType] = useState(null); // Used to allow repeatable lookups when weird errors happen. const repeatableRef = useRef(""); - const uriType = useMemo>( - function () { - if (currentType != null) return Maybe.just(currentType); - return getType(target).mapOr(Maybe.nothing(), (type) => Maybe.just(type)); - }, - [target, currentType] - ); + useCallback(async () => { + if (currentType != null) return Maybe.just(currentType); + const uri: Maybe = (await getTypeEasy(target)).mapOr(Maybe.nothing(), (type) => Maybe.just(type)); + setUriType(uri); + }, [target, currentType]) // Fetch & load a specific registry's data into memory. async function loadBootstrap(type: RootRegistryType, force = false) { @@ -81,6 +81,20 @@ const useLookup = (warningHandler?: WarningHandler) => { }; } + async function getRegistry(type: RootRegistryType): Promise { + if (registryDataRef.current[type] == null) await loadBootstrap(type); + if (registryDataRef.current[type] == null) + throw new Error( + `Could not load bootstrap data for ${type} registry.` + ); + return registryDataRef.current[type]; + } + + async function getTypeEasy(target: string): Promise> { + return getType(target, getRegistry); + } + + function getRegistryURL( type: RootRegistryType, lookupTarget: string @@ -232,7 +246,7 @@ const useLookup = (warningHandler?: WarningHandler) => { new Error("A target must be given in order to execute a lookup.") ); - const targetType = getType(target); + const targetType = await getTypeEasy(target); if (targetType.isErr) { return Result.err( @@ -365,7 +379,7 @@ const useLookup = (warningHandler?: WarningHandler) => { } } - return { error, setTarget, setTargetType, submit, currentType: uriType }; + return { error, setTarget, setTargetType, submit, currentType: uriType, getType: getTypeEasy }; }; export default useLookup; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index e0eac9b..ac5e95f 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -9,10 +9,9 @@ import LookupInput from "@/components/form/LookupInput"; import ErrorCard from "@/components/common/ErrorCard"; import { Maybe } from "true-myth"; import type { TargetType } from "@/types"; -import { getType } from "@/rdap"; const Index: NextPage = () => { - const { error, setTarget, setTargetType, submit } = useLookup(); + const { error, setTarget, setTargetType, submit, getType } = useLookup(); const [detectedType, setDetectedType] = useState>( Maybe.nothing() ); @@ -46,11 +45,11 @@ const Index: NextPage = () => { { + onChange={async ({ target, targetType }) => { setTarget(target); setTargetType(targetType); - const detectResult = getType(target); + const detectResult = await getType(target); if (detectResult.isOk) { setDetectedType(Maybe.just(detectResult.value)); } else { diff --git a/src/rdap.ts b/src/rdap.ts index e8ce73e..f10b8ee 100644 --- a/src/rdap.ts +++ b/src/rdap.ts @@ -1,4 +1,4 @@ -import type { TargetType } from "@/types"; +import type { Register, RootRegistryType, TargetType } from "@/types"; import { Result } from "true-myth"; // const cardTitles = { @@ -757,19 +757,54 @@ export function createRDAPLink(url, title) { } */ -const TypeValidators: Record boolean> = { - autnum: (value) => /^AS\d+$/.test(value), - ip4: (value) => /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/?\d*$/.test(value), - ip6: (value) => /^[0-9a-f:]{2,}\/?\d*$/.test(value), - url: (value) => /^https?:/.test(value), - json: (value) => /^{/.test(value), - tld: (value) => /^\.\w+$/.test(value), - domain: (value) => - /[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?/.test( - value - ), - entity: (value) => false, - registrar: (value) => false, +type ValidatorArgs = { + value: string; + getRegistry: (type: RootRegistryType) => Promise; +}; + +const TypeValidators: Record< + TargetType, + (args: ValidatorArgs) => Promise +> = { + autnum: ({ value }) => Promise.resolve(/^AS\d+$/.test(value)), + ip4: ({ value }) => + Promise.resolve(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/?\d*$/.test(value)), + ip6: ({ value }) => Promise.resolve(/^[0-9a-f:]{2,}\/?\d*$/.test(value)), + url: ({ value }) => Promise.resolve(/^https?:/.test(value)), + json: ({ value }) => Promise.resolve(/^{/.test(value)), + tld: ({ value }) => Promise.resolve(/^\.\w+$/.test(value)), + domain: ({ value }) => { + return Promise.resolve( + /[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?/.test( + value + ) + ); + }, + entity: async ({ value, getRegistry }) => { + // Ensure the entity handle is in the correct format + const result = value.match(/^\w+-(\w+)$/); + if (result === null || result.length <= 1 || result[1] == undefined) return false; + + // Check if the entity object tag is real + try { + const registry = await getRegistry("entity"); + + // Check each service to see if tag starts with inputted value + for (const service of registry.services) { + const tags = service[1]; + console.log({tags, result}); + if (tags.some((tag) => tag.startsWith(result[1] as string))) return true; + } + + return false; + } catch (e) { + console.error(new Error("Failed to fetch entity registry", {cause: e})); + return false; + } + + return true; + }, + registrar: ({ }) => Promise.resolve(false), }; /** @@ -779,9 +814,12 @@ const TypeValidators: Record boolean> = { * @returns A `Result` object containing the determined `TargetType` if a match is found, * otherwise an `Error` object. */ -export function getType(value: string): Result { +export async function getType( + value: string, + getRegistry: (type: RootRegistryType) => Promise +): Promise> { for (const [type, validator] of Object.entries(TypeValidators)) { - if (validator(value)) return Result.ok(type); + if (await validator({ value, getRegistry })) return Result.ok(type); } return Result.err(new Error("No patterns matched the input")); }