mirror of
https://github.com/Xevion/rdap.git
synced 2025-12-06 21:16:06 -06:00
fix: address code quality issues and critical bugs
- Fix getBestURL HTTPS prioritization bug (critical security issue) - Standardize equality operators to === for status code checks - Add JSON parsing error handling with try-catch - Improve IP address validation with explicit error messages - Fix ARIA label accessibility (add id="search" to TextField) - Remove unused callback hook in useLookup - Add type detection debouncing (150ms) using @mantine/hooks - Remove duplicate LookupInput component from src/components/form/
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
"@fontsource/ibm-plex-mono": "^5.2.7",
|
"@fontsource/ibm-plex-mono": "^5.2.7",
|
||||||
|
"@mantine/hooks": "^8.3.5",
|
||||||
"@radix-ui/react-icons": "^1.3.2",
|
"@radix-ui/react-icons": "^1.3.2",
|
||||||
"@radix-ui/themes": "^3.2.1",
|
"@radix-ui/themes": "^3.2.1",
|
||||||
"@swc/helpers": "^0.5.11",
|
"@swc/helpers": "^0.5.11",
|
||||||
|
|||||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@@ -14,6 +14,9 @@ importers:
|
|||||||
'@fontsource/ibm-plex-mono':
|
'@fontsource/ibm-plex-mono':
|
||||||
specifier: ^5.2.7
|
specifier: ^5.2.7
|
||||||
version: 5.2.7
|
version: 5.2.7
|
||||||
|
'@mantine/hooks':
|
||||||
|
specifier: ^8.3.5
|
||||||
|
version: 8.3.5(react@19.2.0)
|
||||||
'@radix-ui/react-icons':
|
'@radix-ui/react-icons':
|
||||||
specifier: ^1.3.2
|
specifier: ^1.3.2
|
||||||
version: 1.3.2(react@19.2.0)
|
version: 1.3.2(react@19.2.0)
|
||||||
@@ -602,6 +605,11 @@ packages:
|
|||||||
'@jridgewell/trace-mapping@0.3.31':
|
'@jridgewell/trace-mapping@0.3.31':
|
||||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||||
|
|
||||||
|
'@mantine/hooks@8.3.5':
|
||||||
|
resolution: {integrity: sha512-0Wf08eWLKi3WkKlxnV1W5vfuN6wcvAV2VbhQlOy0R9nrWorGTtonQF6qqBE3PnJFYF1/ZE+HkYZQ/Dr7DmYSMQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18.x || ^19.x
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@0.2.12':
|
'@napi-rs/wasm-runtime@0.2.12':
|
||||||
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
|
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
|
||||||
|
|
||||||
@@ -4349,6 +4357,10 @@ snapshots:
|
|||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
'@mantine/hooks@8.3.5(react@19.2.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.0
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@0.2.12':
|
'@napi-rs/wasm-runtime@0.2.12':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/core': 1.6.0
|
'@emnapi/core': 1.6.0
|
||||||
|
|||||||
@@ -1,246 +0,0 @@
|
|||||||
import { useForm, Controller } from "react-hook-form";
|
|
||||||
import type { FunctionComponent } from "react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { onPromise, preventDefault } from "@/lib/utils";
|
|
||||||
import type { SimplifiedTargetType, SubmitProps, TargetType } from "@/rdap/schemas";
|
|
||||||
import { TargetTypeEnum } from "@/rdap/schemas";
|
|
||||||
import { MagnifyingGlassIcon, ReloadIcon, LockClosedIcon } from "@radix-ui/react-icons";
|
|
||||||
import { TextField, Select, Flex, Checkbox, Text, IconButton } from "@radix-ui/themes";
|
|
||||||
import type { Maybe } from "true-myth";
|
|
||||||
import { placeholders } from "@/rdap/constants";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Props for the LookupInput component.
|
|
||||||
*/
|
|
||||||
type LookupInputProps = {
|
|
||||||
isLoading?: boolean;
|
|
||||||
/**
|
|
||||||
* Callback function called when a type of registry is detected when a user changes their input.
|
|
||||||
* @param type - The detected type of registry.
|
|
||||||
* @returns A promise.
|
|
||||||
*/
|
|
||||||
onRegistry?: (type: TargetType) => Promise<void>;
|
|
||||||
/**
|
|
||||||
* Callback function called when a user hits submit.
|
|
||||||
* @param props - The submit props.
|
|
||||||
* @returns A promise.
|
|
||||||
*/
|
|
||||||
onSubmit?: (props: SubmitProps) => Promise<void>;
|
|
||||||
/**
|
|
||||||
* Callback function called when a user changes their input (text search) or explicitly changes the type of search.
|
|
||||||
* @param target - The target object containing the search target and target type.
|
|
||||||
* @returns Nothing.
|
|
||||||
*/
|
|
||||||
onChange?: (target: { target: string; targetType: TargetType | null }) => Promise<void>;
|
|
||||||
detectedType: Maybe<TargetType>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const LookupInput: FunctionComponent<LookupInputProps> = ({
|
|
||||||
isLoading,
|
|
||||||
onSubmit,
|
|
||||||
onChange,
|
|
||||||
detectedType,
|
|
||||||
}: LookupInputProps) => {
|
|
||||||
const { register, handleSubmit, getValues, control } = useForm<SubmitProps>({
|
|
||||||
defaultValues: {
|
|
||||||
target: "",
|
|
||||||
// Not used at this time.
|
|
||||||
followReferral: false,
|
|
||||||
requestJSContact: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mapping of available (simple) target types to their long-form human-readable names.
|
|
||||||
*/
|
|
||||||
const objectNames: Record<SimplifiedTargetType | "auto", string> = {
|
|
||||||
auto: "Autodetect",
|
|
||||||
domain: "Domain",
|
|
||||||
ip: "IP/CIDR", // IPv4/IPv6 are combined into this option
|
|
||||||
tld: "TLD",
|
|
||||||
autnum: "AS Number",
|
|
||||||
entity: "Entity Handle",
|
|
||||||
registrar: "Registrar",
|
|
||||||
url: "URL",
|
|
||||||
json: "JSON",
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mapping of precise target types to their simplified short-form names.
|
|
||||||
*/
|
|
||||||
const targetShortNames: Record<TargetType, string> = {
|
|
||||||
domain: "Domain",
|
|
||||||
tld: "TLD",
|
|
||||||
ip4: "IPv4",
|
|
||||||
ip6: "IPv6",
|
|
||||||
autnum: "ASN",
|
|
||||||
entity: "Entity",
|
|
||||||
registrar: "Registrar",
|
|
||||||
url: "URL",
|
|
||||||
json: "JSON",
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the selected value in the LookupInput component.
|
|
||||||
*/
|
|
||||||
const [selected, setSelected] = useState<SimplifiedTargetType | "auto">("auto");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the target type based on the provided value.
|
|
||||||
* @param value - The value to retrieve the target type for.
|
|
||||||
* @returns The target type as ObjectType or null.
|
|
||||||
*/
|
|
||||||
function retrieveTargetType(value?: string | null): TargetType | null {
|
|
||||||
// If the value is null and the selected value is null, return null.
|
|
||||||
if (value == null) value = selected;
|
|
||||||
|
|
||||||
// 'auto' means 'do whatever' so we return null.
|
|
||||||
if (value == "auto") return null;
|
|
||||||
|
|
||||||
// Validate the value is a valid TargetType
|
|
||||||
const result = TargetTypeEnum.safeParse(value);
|
|
||||||
return result.success ? result.data : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form
|
|
||||||
className="pb-2.5"
|
|
||||||
onSubmit={onSubmit != undefined ? onPromise(handleSubmit(onSubmit)) : preventDefault}
|
|
||||||
>
|
|
||||||
<Flex direction="column" gap="3">
|
|
||||||
<label htmlFor="search" className="sr-only">
|
|
||||||
Search
|
|
||||||
</label>
|
|
||||||
<Flex gap="0" style={{ position: "relative" }}>
|
|
||||||
<TextField.Root
|
|
||||||
size="3"
|
|
||||||
placeholder={placeholders[selected]}
|
|
||||||
disabled={isLoading}
|
|
||||||
{...register("target", {
|
|
||||||
required: true,
|
|
||||||
onChange: () => {
|
|
||||||
if (onChange != undefined)
|
|
||||||
void onChange({
|
|
||||||
target: getValues("target"),
|
|
||||||
targetType: retrieveTargetType(null),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
style={{
|
|
||||||
borderTopRightRadius: 0,
|
|
||||||
borderBottomRightRadius: 0,
|
|
||||||
border: "1px solid var(--gray-7)",
|
|
||||||
borderRight: "none",
|
|
||||||
boxShadow: "none",
|
|
||||||
flex: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TextField.Slot side="left">
|
|
||||||
<IconButton
|
|
||||||
size="1"
|
|
||||||
variant="ghost"
|
|
||||||
type="submit"
|
|
||||||
disabled={isLoading}
|
|
||||||
tabIndex={-1}
|
|
||||||
style={{ cursor: isLoading ? "not-allowed" : "pointer" }}
|
|
||||||
>
|
|
||||||
{isLoading ? (
|
|
||||||
<ReloadIcon className="animate-spin" width="16" height="16" />
|
|
||||||
) : (
|
|
||||||
<MagnifyingGlassIcon width="16" height="16" />
|
|
||||||
)}
|
|
||||||
</IconButton>
|
|
||||||
</TextField.Slot>
|
|
||||||
</TextField.Root>
|
|
||||||
|
|
||||||
<Select.Root
|
|
||||||
value={selected}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
setSelected(value as SimplifiedTargetType | "auto");
|
|
||||||
|
|
||||||
if (onChange != undefined)
|
|
||||||
void onChange({
|
|
||||||
target: getValues("target"),
|
|
||||||
targetType: retrieveTargetType(value),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
disabled={isLoading}
|
|
||||||
size="3"
|
|
||||||
>
|
|
||||||
<Select.Trigger
|
|
||||||
style={{
|
|
||||||
borderTopLeftRadius: 0,
|
|
||||||
borderBottomLeftRadius: 0,
|
|
||||||
|
|
||||||
minWidth: "150px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{selected == "auto" ? (
|
|
||||||
detectedType.isJust ? (
|
|
||||||
<Text>Auto ({targetShortNames[detectedType.value]})</Text>
|
|
||||||
) : (
|
|
||||||
objectNames["auto"]
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Flex align="center" gap="2">
|
|
||||||
<LockClosedIcon width="14" height="14" />
|
|
||||||
{objectNames[selected]}
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</Select.Trigger>
|
|
||||||
|
|
||||||
<Select.Content position="popper">
|
|
||||||
{Object.entries(objectNames).map(([key, value]) => (
|
|
||||||
<Select.Item key={key} value={key}>
|
|
||||||
<Flex
|
|
||||||
align="center"
|
|
||||||
justify="between"
|
|
||||||
gap="2"
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</Flex>
|
|
||||||
</Select.Item>
|
|
||||||
))}
|
|
||||||
</Select.Content>
|
|
||||||
</Select.Root>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex pl="3" gapX="5" gapY="2" wrap="wrap">
|
|
||||||
<Flex asChild align="center" gap="2">
|
|
||||||
<Text as="label" size="2">
|
|
||||||
<Controller
|
|
||||||
name="requestJSContact"
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Checkbox
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
Request JSContact
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
<Flex asChild align="center" gap="2">
|
|
||||||
<Text as="label" size="2">
|
|
||||||
<Controller
|
|
||||||
name="followReferral"
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Checkbox
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
Follow referral to registrar's RDAP record
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LookupInput;
|
|
||||||
@@ -8,12 +8,10 @@ import LookupInput from "@/rdap/components/LookupInput";
|
|||||||
import ErrorCard from "@/components/ErrorCard";
|
import ErrorCard from "@/components/ErrorCard";
|
||||||
import { ThemeToggle } from "@/components/ThemeToggle";
|
import { ThemeToggle } from "@/components/ThemeToggle";
|
||||||
import { Maybe } from "true-myth";
|
import { Maybe } from "true-myth";
|
||||||
import type { TargetType } from "@/rdap/schemas";
|
|
||||||
import { Flex, Container, Section, Text, Link } from "@radix-ui/themes";
|
import { Flex, Container, Section, Text, Link } from "@radix-ui/themes";
|
||||||
|
|
||||||
const Index: NextPage = () => {
|
const Index: NextPage = () => {
|
||||||
const { error, setTarget, setTargetType, submit, getType } = useLookup();
|
const { error, setTarget, setTargetType, submit, currentType } = useLookup();
|
||||||
const [detectedType, setDetectedType] = useState<Maybe<TargetType>>(Maybe.nothing());
|
|
||||||
const [response, setResponse] = useState<Maybe<MetaParsedGeneric>>(Maybe.nothing());
|
const [response, setResponse] = useState<Maybe<MetaParsedGeneric>>(Maybe.nothing());
|
||||||
const [isLoading, setLoading] = useState<boolean>(false);
|
const [isLoading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
@@ -65,20 +63,10 @@ const Index: NextPage = () => {
|
|||||||
<Section size="2">
|
<Section size="2">
|
||||||
<LookupInput
|
<LookupInput
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
detectedType={detectedType}
|
detectedType={currentType}
|
||||||
onChange={async ({ target, targetType }) => {
|
onChange={({ target, targetType }) => {
|
||||||
setTarget(target);
|
setTarget(target);
|
||||||
setTargetType(targetType);
|
setTargetType(targetType);
|
||||||
|
|
||||||
// Only run autodetection when in autodetect mode (targetType is null)
|
|
||||||
if (targetType === null) {
|
|
||||||
const detectResult = await getType(target);
|
|
||||||
if (detectResult.isOk) {
|
|
||||||
setDetectedType(Maybe.just(detectResult.value));
|
|
||||||
} else {
|
|
||||||
setDetectedType(Maybe.nothing());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onSubmit={async function (props) {
|
onSubmit={async function (props) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ type LookupInputProps = {
|
|||||||
* @param target - The target object containing the search target and target type.
|
* @param target - The target object containing the search target and target type.
|
||||||
* @returns Nothing.
|
* @returns Nothing.
|
||||||
*/
|
*/
|
||||||
onChange?: (target: { target: string; targetType: TargetType | null }) => Promise<void>;
|
onChange?: (target: { target: string; targetType: TargetType | null }) => void | Promise<void>;
|
||||||
detectedType: Maybe<TargetType>;
|
detectedType: Maybe<TargetType>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -113,6 +113,7 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
|
|||||||
</label>
|
</label>
|
||||||
<Flex gap="0" style={{ position: "relative" }}>
|
<Flex gap="0" style={{ position: "relative" }}>
|
||||||
<TextField.Root
|
<TextField.Root
|
||||||
|
id="search"
|
||||||
size="3"
|
size="3"
|
||||||
placeholder={placeholders[selected]}
|
placeholder={placeholders[selected]}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import { useDebouncedValue } from "@mantine/hooks";
|
||||||
import { getType, validateInputForType } from "@/rdap/utils";
|
import { getType, validateInputForType } from "@/rdap/utils";
|
||||||
import type { AutonomousNumber, Domain, IpNetwork, SubmitProps, TargetType } from "@/rdap/schemas";
|
import type { AutonomousNumber, Domain, IpNetwork, SubmitProps, TargetType } from "@/rdap/schemas";
|
||||||
import {
|
import {
|
||||||
@@ -27,6 +28,7 @@ const schemas = [DomainSchema, AutonomousNumberSchema, IpNetworkSchema];
|
|||||||
const useLookup = (warningHandler?: WarningHandler) => {
|
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 [debouncedTarget] = useDebouncedValue(target, 150);
|
||||||
const [uriType, setUriType] = useState<Maybe<TargetType>>(Maybe.nothing());
|
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.
|
||||||
@@ -39,13 +41,20 @@ const useLookup = (warningHandler?: WarningHandler) => {
|
|||||||
return getType(target, getRegistry);
|
return getType(target, getRegistry);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useCallback(async () => {
|
useEffect(() => {
|
||||||
if (currentType != null) return Maybe.just(currentType);
|
const detectType = async () => {
|
||||||
const uri: Maybe<TargetType> = (await getTypeEasy(target)).mapOr(Maybe.nothing(), (type) =>
|
if (currentType != null || debouncedTarget.length === 0) return;
|
||||||
Maybe.just(type)
|
|
||||||
);
|
const detectedType = await getTypeEasy(debouncedTarget);
|
||||||
setUriType(uri);
|
if (detectedType.isOk) {
|
||||||
}, [target, currentType, getTypeEasy]);
|
setUriType(Maybe.just(detectedType.value));
|
||||||
|
} else {
|
||||||
|
setUriType(Maybe.nothing());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
detectType().catch(console.error);
|
||||||
|
}, [debouncedTarget, currentType, getTypeEasy]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const preload = async () => {
|
const preload = async () => {
|
||||||
@@ -67,7 +76,7 @@ const useLookup = (warningHandler?: WarningHandler) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
preload().catch(console.error);
|
preload().catch(console.error);
|
||||||
}, [target, uriType, warningHandler]);
|
}, [uriType, warningHandler]);
|
||||||
|
|
||||||
async function submitInternal(
|
async function submitInternal(
|
||||||
target: string
|
target: string
|
||||||
@@ -160,7 +169,7 @@ const useLookup = (warningHandler?: WarningHandler) => {
|
|||||||
case "url": {
|
case "url": {
|
||||||
const response = await fetch(target);
|
const response = await fetch(target);
|
||||||
|
|
||||||
if (response.status != 200)
|
if (response.status !== 200)
|
||||||
return Result.err(
|
return Result.err(
|
||||||
new Error(
|
new Error(
|
||||||
`The URL provided returned a non-200 status code: ${response.status}.`
|
`The URL provided returned a non-200 status code: ${response.status}.`
|
||||||
@@ -178,10 +187,14 @@ const useLookup = (warningHandler?: WarningHandler) => {
|
|||||||
return Result.err(new Error("No schema was able to parse the response."));
|
return Result.err(new Error("No schema was able to parse the response."));
|
||||||
}
|
}
|
||||||
case "json": {
|
case "json": {
|
||||||
const data = JSON.parse(target);
|
try {
|
||||||
for (const schema of schemas) {
|
const data = JSON.parse(target);
|
||||||
const result = schema.safeParse(data);
|
for (const schema of schemas) {
|
||||||
if (result.success) return Result.ok({ data: result.data, url: "" });
|
const result = schema.safeParse(data);
|
||||||
|
if (result.success) return Result.ok({ data: result.data, url: "" });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return Result.err(new Error("Invalid JSON format", { cause: e }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "registrar": {
|
case "registrar": {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Result } from "true-myth";
|
|||||||
export async function getAndParse<T>(url: string, schema: ZodSchema<T>): Promise<Result<T, Error>> {
|
export async function getAndParse<T>(url: string, schema: ZodSchema<T>): Promise<Result<T, Error>> {
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
|
|
||||||
if (response.status == 200) {
|
if (response.status === 200) {
|
||||||
const result = schema.safeParse(await response.json());
|
const result = schema.safeParse(await response.json());
|
||||||
|
|
||||||
if (result.success === false) {
|
if (result.success === false) {
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export function getRegistryURL(type: RootRegistryType, lookupTarget: string): st
|
|||||||
throw new Error(`No matching domain found.`);
|
throw new Error(`No matching domain found.`);
|
||||||
case "ip4": {
|
case "ip4": {
|
||||||
// Extract the IP address without CIDR suffix for matching
|
// Extract the IP address without CIDR suffix for matching
|
||||||
const ipAddress = lookupTarget.split("/")[0] ?? lookupTarget;
|
const [ipAddress] = lookupTarget.split("/");
|
||||||
|
if (!ipAddress) throw new Error(`Invalid IPv4 format: ${lookupTarget}`);
|
||||||
for (const bootstrapItem of bootstrap.services) {
|
for (const bootstrapItem of bootstrap.services) {
|
||||||
// bootstrapItem[0] contains CIDR ranges like ["1.0.0.0/8", "2.0.0.0/8"]
|
// bootstrapItem[0] contains CIDR ranges like ["1.0.0.0/8", "2.0.0.0/8"]
|
||||||
if (bootstrapItem[0].some((cidr) => ipv4InCIDR(ipAddress, cidr))) {
|
if (bootstrapItem[0].some((cidr) => ipv4InCIDR(ipAddress, cidr))) {
|
||||||
@@ -37,7 +38,8 @@ export function getRegistryURL(type: RootRegistryType, lookupTarget: string): st
|
|||||||
}
|
}
|
||||||
case "ip6": {
|
case "ip6": {
|
||||||
// Extract the IP address without CIDR suffix for matching
|
// Extract the IP address without CIDR suffix for matching
|
||||||
const ipAddress = lookupTarget.split("/")[0] ?? lookupTarget;
|
const [ipAddress] = lookupTarget.split("/");
|
||||||
|
if (!ipAddress) throw new Error(`Invalid IPv6 format: ${lookupTarget}`);
|
||||||
for (const bootstrapItem of bootstrap.services) {
|
for (const bootstrapItem of bootstrap.services) {
|
||||||
// bootstrapItem[0] contains CIDR ranges like ["2001:0200::/23", "2001:0400::/23"]
|
// bootstrapItem[0] contains CIDR ranges like ["2001:0200::/23", "2001:0400::/23"]
|
||||||
if (bootstrapItem[0].some((cidr) => ipv6InCIDR(ipAddress, cidr))) {
|
if (bootstrapItem[0].some((cidr) => ipv6InCIDR(ipAddress, cidr))) {
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ export function domainMatch(tld: string, domain: string): boolean {
|
|||||||
|
|
||||||
// return the first HTTPS url, or the first URL
|
// return the first HTTPS url, or the first URL
|
||||||
export function getBestURL(urls: [string, ...string[]]): string {
|
export function getBestURL(urls: [string, ...string[]]): string {
|
||||||
urls.forEach((url) => {
|
return urls.find((url) => url.startsWith("https://")) ?? urls[0];
|
||||||
if (url.startsWith("https://")) return url;
|
|
||||||
});
|
|
||||||
return urls[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValidatorArgs = {
|
type ValidatorArgs = {
|
||||||
|
|||||||
Reference in New Issue
Block a user