mirror of
https://github.com/Xevion/rdap.git
synced 2025-12-14 02:12:45 -06:00
Add await registry-informed getTypes method with smart entity tag validation
fuck type coloring madness
This commit is contained in:
@@ -46,7 +46,7 @@ type LookupInputProps = {
|
|||||||
onChange?: (target: {
|
onChange?: (target: {
|
||||||
target: string;
|
target: string;
|
||||||
targetType: TargetType | null;
|
targetType: TargetType | null;
|
||||||
}) => void;
|
}) => Promise<void>;
|
||||||
detectedType: Maybe<TargetType>;
|
detectedType: Maybe<TargetType>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
|
|||||||
required: true,
|
required: true,
|
||||||
onChange: () => {
|
onChange: () => {
|
||||||
if (onChange != undefined)
|
if (onChange != undefined)
|
||||||
onChange({
|
void onChange({
|
||||||
target: getValues("target"),
|
target: getValues("target"),
|
||||||
// dropdown target will be pulled from state anyways, so no need to provide it here
|
// dropdown target will be pulled from state anyways, so no need to provide it here
|
||||||
targetType: retrieveTargetType(null),
|
targetType: retrieveTargetType(null),
|
||||||
@@ -172,7 +172,7 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
|
|||||||
setSelected(value);
|
setSelected(value);
|
||||||
|
|
||||||
if (onChange != undefined)
|
if (onChange != undefined)
|
||||||
onChange({
|
void onChange({
|
||||||
target: getValues("target"),
|
target: getValues("target"),
|
||||||
// we provide the value as the state will not have updated yet for this context
|
// we provide the value as the state will not have updated yet for this context
|
||||||
targetType: retrieveTargetType(value),
|
targetType: retrieveTargetType(value),
|
||||||
|
|||||||
@@ -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 { domainMatchPredicate, getBestURL, getType } from "@/rdap";
|
||||||
import type {
|
import type {
|
||||||
AutonomousNumber,
|
AutonomousNumber,
|
||||||
@@ -43,19 +43,19 @@ const useLookup = (warningHandler?: WarningHandler) => {
|
|||||||
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [target, setTarget] = useState<string>("");
|
const [target, setTarget] = useState<string>("");
|
||||||
|
const [uriType, setUriType] = useState<Maybe<TargetType>>(Maybe.nothing());
|
||||||
|
|
||||||
// Used by a callback on LookupInput to forcibly set the type of the lookup.
|
// Used by a callback on LookupInput to forcibly set the type of the lookup.
|
||||||
const [currentType, setTargetType] = useState<TargetType | null>(null);
|
const [currentType, setTargetType] = useState<TargetType | null>(null);
|
||||||
|
|
||||||
// Used to allow repeatable lookups when weird errors happen.
|
// Used to allow repeatable lookups when weird errors happen.
|
||||||
const repeatableRef = useRef<string>("");
|
const repeatableRef = useRef<string>("");
|
||||||
|
|
||||||
const uriType = useMemo<Maybe<TargetType>>(
|
useCallback(async () => {
|
||||||
function () {
|
if (currentType != null) return Maybe.just(currentType);
|
||||||
if (currentType != null) return Maybe.just(currentType);
|
const uri: Maybe<TargetType> = (await getTypeEasy(target)).mapOr(Maybe.nothing(), (type) => Maybe.just(type));
|
||||||
return getType(target).mapOr(Maybe.nothing(), (type) => Maybe.just(type));
|
setUriType(uri);
|
||||||
},
|
}, [target, currentType])
|
||||||
[target, currentType]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fetch & load a specific registry's data into memory.
|
// Fetch & load a specific registry's data into memory.
|
||||||
async function loadBootstrap(type: RootRegistryType, force = false) {
|
async function loadBootstrap(type: RootRegistryType, force = false) {
|
||||||
@@ -81,6 +81,20 @@ const useLookup = (warningHandler?: WarningHandler) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getRegistry(type: RootRegistryType): Promise<Register> {
|
||||||
|
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<Result<TargetType, Error>> {
|
||||||
|
return getType(target, getRegistry);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function getRegistryURL(
|
function getRegistryURL(
|
||||||
type: RootRegistryType,
|
type: RootRegistryType,
|
||||||
lookupTarget: string
|
lookupTarget: string
|
||||||
@@ -232,7 +246,7 @@ const useLookup = (warningHandler?: WarningHandler) => {
|
|||||||
new Error("A target must be given in order to execute a lookup.")
|
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) {
|
if (targetType.isErr) {
|
||||||
return Result.err(
|
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;
|
export default useLookup;
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ import LookupInput from "@/components/form/LookupInput";
|
|||||||
import ErrorCard from "@/components/common/ErrorCard";
|
import ErrorCard from "@/components/common/ErrorCard";
|
||||||
import { Maybe } from "true-myth";
|
import { Maybe } from "true-myth";
|
||||||
import type { TargetType } from "@/types";
|
import type { TargetType } from "@/types";
|
||||||
import { getType } from "@/rdap";
|
|
||||||
|
|
||||||
const Index: NextPage = () => {
|
const Index: NextPage = () => {
|
||||||
const { error, setTarget, setTargetType, submit } = useLookup();
|
const { error, setTarget, setTargetType, submit, getType } = useLookup();
|
||||||
const [detectedType, setDetectedType] = useState<Maybe<TargetType>>(
|
const [detectedType, setDetectedType] = useState<Maybe<TargetType>>(
|
||||||
Maybe.nothing()
|
Maybe.nothing()
|
||||||
);
|
);
|
||||||
@@ -46,11 +45,11 @@ const Index: NextPage = () => {
|
|||||||
<LookupInput
|
<LookupInput
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
detectedType={detectedType}
|
detectedType={detectedType}
|
||||||
onChange={({ target, targetType }) => {
|
onChange={async ({ target, targetType }) => {
|
||||||
setTarget(target);
|
setTarget(target);
|
||||||
setTargetType(targetType);
|
setTargetType(targetType);
|
||||||
|
|
||||||
const detectResult = getType(target);
|
const detectResult = await getType(target);
|
||||||
if (detectResult.isOk) {
|
if (detectResult.isOk) {
|
||||||
setDetectedType(Maybe.just(detectResult.value));
|
setDetectedType(Maybe.just(detectResult.value));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
70
src/rdap.ts
70
src/rdap.ts
@@ -1,4 +1,4 @@
|
|||||||
import type { TargetType } from "@/types";
|
import type { Register, RootRegistryType, TargetType } from "@/types";
|
||||||
import { Result } from "true-myth";
|
import { Result } from "true-myth";
|
||||||
|
|
||||||
// const cardTitles = {
|
// const cardTitles = {
|
||||||
@@ -757,19 +757,54 @@ export function createRDAPLink(url, title) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const TypeValidators: Record<TargetType, (value: string) => boolean> = {
|
type ValidatorArgs = {
|
||||||
autnum: (value) => /^AS\d+$/.test(value),
|
value: string;
|
||||||
ip4: (value) => /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/?\d*$/.test(value),
|
getRegistry: (type: RootRegistryType) => Promise<Register>;
|
||||||
ip6: (value) => /^[0-9a-f:]{2,}\/?\d*$/.test(value),
|
};
|
||||||
url: (value) => /^https?:/.test(value),
|
|
||||||
json: (value) => /^{/.test(value),
|
const TypeValidators: Record<
|
||||||
tld: (value) => /^\.\w+$/.test(value),
|
TargetType,
|
||||||
domain: (value) =>
|
(args: ValidatorArgs) => Promise<boolean>
|
||||||
/[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?/.test(
|
> = {
|
||||||
value
|
autnum: ({ value }) => Promise.resolve(/^AS\d+$/.test(value)),
|
||||||
),
|
ip4: ({ value }) =>
|
||||||
entity: (value) => false,
|
Promise.resolve(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/?\d*$/.test(value)),
|
||||||
registrar: (value) => false,
|
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<TargetType, (value: string) => boolean> = {
|
|||||||
* @returns A `Result` object containing the determined `TargetType` if a match is found,
|
* @returns A `Result` object containing the determined `TargetType` if a match is found,
|
||||||
* otherwise an `Error` object.
|
* otherwise an `Error` object.
|
||||||
*/
|
*/
|
||||||
export function getType(value: string): Result<TargetType, Error> {
|
export async function getType(
|
||||||
|
value: string,
|
||||||
|
getRegistry: (type: RootRegistryType) => Promise<Register>
|
||||||
|
): Promise<Result<TargetType, Error>> {
|
||||||
for (const [type, validator] of Object.entries(TypeValidators)) {
|
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"));
|
return Result.err(new Error("No patterns matched the input"));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user